Search code examples
javascriptgoogle-chrome-extensionscopedefineproperty

How can I define a property on the tab's main window object?


I'm trying to define the property jQuery on the window object from a content script with code in the setter. That way, when the actual jQuery object is defined, I can use it right away. I seem to be unable to get it right, though.

The target website is Outlook.com. That's the webmail version of Outlook.

I tried to put the code in the content script directly, but even if I put "all_frames": true in the content_scripts section of the manifest (so the code gets injected into every frame), it isn't working.

function afterInit(){
  var _jQuery;
  console.log('This gets logged');
  Object.defineProperty(window, 'jQuery', {
    get: function() { return _jQuery; },
    set: function(newValue) {
      _jQuery = $ = newValue;

      console.log('This is never logged!');

      $(document.body).append($('<span>I want to use jQuery here</span>'));
    }
  });
}
afterInit();

I verified that window.jQuery is properly defined afterwards by the actual jQuery function/object, but my setter code is never executed.
I also tried it with message passing: I send a message with the code as a string to a background script, and use executeScript to execute it on the correct tab, but this also doesn't work.

chrome.runtime.sendMessage(
    {action:'jQueryPreInit', value: '('+afterInit.toString()+')();'});

And in my background script:

chrome.runtime.onMessage.addListener(function(message, sender, callback) {
  switch (message.action){
    case "jQueryPreInit": chrome.tabs.executeScript(sender.tab.id, {code: message.value});
  }
});

If I put something else than the Object.defineProperty code in the executeScript code, that works fine. I only have problems defining the property.


Solution

  • Thanks to Xan, I found there are only two ways to do this.

    The first is by adding a <script> element to the DOM containing the appropriate code. This is a pretty extensive StackOverflow answer on how to do that: https://stackoverflow.com/a/9517879/125938.

    The second is using Javascript pseudo-URLs and the window.location object. By assigning window.location a bookmarklet-style URL containing Javascript, you also bypass certain security measures. In the content script, put:

    location = 'javascript:(' + (function(){
      var _jQuery;
      Object.defineProperty(window, 'jQuery', {
        get: function() { return _jQuery; },
        set: function(newValue) {
          _jQuery = $ = newValue;
    
          console.log('totally logged!');
    
          $('<span>jQuery stuff here</span>');
    
        }
      });
    }).toString().replace(/\n/g, ' ')+')();';
    

    The reason I/you were originally failing to define it, was because both methods of code injection we were using, caused our code to be sandboxed into isolated worlds: https://developer.chrome.com/extensions/content_scripts#execution-environment. Meaning, they share the page's DOM and could communicate through it, but they can't access each other's window object directly.