How Do You Override Core Javascript Classes?

I’m writing an extension to help improve browser privacy and I was wondering how I would go about redefining a few core Javascript functions that server up identifiable information about the user. For instance, if I wanted to stop the browser from leaking my list of plugins, how do I get my extension to override the PluginArray class?

I am currently loading my contentScript using PageMod and attempting to override PluginArray using smile:

Object.defineProperty(window, 'PluginArray', { enumerable: false, configurable: false, writable: true, value: PluginArray });

However this code fails with the error: ‘can’t redefine non-configurable property “PluginArray”’. I understand that under normal JavaScript circumstances providing such an override is not allowed. However I’m hoping that there is a lower level API that I can use to bypass these security restrictions.

Javascript doesn’t have classes, only objects :slight_smile: Well actually, javascript does have classes now, but that isn’t what you’re dealing with. Nothing has really changed except some new syntax, and that isn’t even supported until Firefox 45.

Your approach is essentially correct, but certain properties cannot be altered. Or deleted. This setting can be defined when the property is created and there isn’t much you can do about it, short of entirely replacing the object prototype. In particular, once the configurable attribute of a property is set to true then you are stuck with it:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#Modifying_a_property

Thanks for the information and you’ve confirmed my fear. Short of modifying the Firefox code, I guess there’s no way to turn off these data leaks.

You can override/hook them as described here:

https://developer.mozilla.org/en-US/Add-ons/Overlay_Extensions/XUL_School/Appendix_C:Avoid_using_eval_in_Add-ons?redirectlocale=en-US&redirectslug=XUL_School%2FAppendix_C%3A_Avoid_using_eval_in_Add-ons#OverridingExtendingAmending_existing_objects%28or_object_properties%29

I have also seen people override entire XPCOM interfaces with their own javascript replacement. If you’re interested in that I can go dig it up.

Thanks for the information @noitidart. Unfortunately I still am not having success overriding any of the core javascript objects and functions. Do you happen to have any examples of replacing a core object like window.navigator, window.PluginArray, window.navigator.plugins, Date.getTimeoneOffset ?

To clarify what I’m doing, I thought I would include some of my code. When a page loads, I’m loading a contentScript using PageMode:

pageMod.PageMod({
  include: "*",
  contentScriptFile: data.url("test.js")
});

Then in test.js I attempt to override window.navigator.userAgent using the following code:

window.navigator.__defineGetter__('userAgent', function () {
    return 'Mozilla/5.0 (iPad; CPU OS 5_1_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Mobile/9B206';
});
console.log(window.navigator.userAgent);

The logging statement shows the updated userAgent but when I open the console inside of the Inspector, window.navigator.userAgent still returns the correct user agent. Am I not initializing the script correctly? Is my contentScript being sandboxed so that other scripts will not see the core object changes?

Ah replacing window.navigator I’m not sure. I recall someone was trying to do that and I wasn’t able to help them. I’ll link that topic as soon as I find it, you should try to get in touch with him, he might have figured it out .

I don’t think it can be achieved using content scripts. From the content scripts mdn page:

  • content scripts don’t see any JavaScript objects added to the page by page scripts
  • if a page script has redefined the behavior of some DOM object, the content script sees the original behavior.

The same applies in reverse: page scripts can’t see JavaScript objects added by content scripts

@Endyl Thanks for the information as I believe that’s the main reason for my issues. It looks like I can modify window.navigator but my changes remain in a sandbox. Is there another way to inject javascript other then contentScript?

I believe that this document on interacting with page scripts sounds what I’m looking for. For now I’m breaking out of the sandbox by using the unsafeWindow object. I know that will eventually be removed but it will at least let me move forward with my testing.

This add-on here overrides window.navigator per this stackoverflow topic:

https://addons.mozilla.org/en-US/firefox/addon/windscribe/

Their code is open source, see the file uacontentscript.js it is a content script that does the changing. I pasted it here below:

// self.port.emit('log', 'START of uacontentscript');
try{
  (function(){
    'use strict';

    self.port.on('peekUserAgent', function (newUserAgent) {

      if(!newUserAgent){
        // self.port.emit('log', 'no useragent, exiting from peekUserAgent');
        return false;
      }

      var setUserAgent = function (){
        return newUserAgent.trim()
      };

      var actualCode =  '(' + function() {
          'use strict';
          var navigator = window.navigator;
          var modifiedNavigator;
          if (Navigator.prototype.hasOwnProperty('userAgent')) {
            modifiedNavigator = Navigator.prototype;
          } else {
            modifiedNavigator = Object.create(navigator);
            Object.defineProperty(window, 'navigator', {
              value: modifiedNavigator,
              configurable: false,
              enumerable: true,
              writable: false
            });
          }
          Object.defineProperties(modifiedNavigator, {
            userAgent: {
              configurable: true,
              get: function(){ return window.userAgent }
            },
            appVersion: {
              value: navigator.appVersion,
              configurable: false,
              enumerable: true,
              writable: false
            },
            platform: {
              value: navigator.platform,
              configurable: false,
              enumerable: true,
              writable: false
            }
          });
        } + ')();';

      var injectCode = actualCode.replace("window.userAgent", '"' + setUserAgent() + '"');

      var script =  document.createElement('script');
      script.textContent = injectCode;

      (document.head || document.documentElement).appendChild(script);
      script.parentNode.removeChild(script);
    });


// https://stackoverflow.com/questions/1307013/mocking-a-useragent-in-javascript
// setUserAgent('new user agent');
  })();
} catch (e){
  self.port.emit('log', 'error in page script: '+e.message+'\n'+e.stack);
}