1

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.

Protector one
  • 6,926
  • 5
  • 62
  • 86
  • Simple question: why are you doing this complicated dance? – Xan May 19 '16 at 09:55
  • I want to use the jQuery provided by the page itself. I could try inserting the same jQuery file as Outlook does and hope it gets loaded from cache, but I'd rather just keep my extension as clean as possible, and use what is already available. When all is said and done, I want to use jQuery's `ajaxSuccess` function to execute code every time an e-mail is opened in the read pane. – Protector one May 19 '16 at 10:06

2 Answers2

2

(quotes are from a comment)

I want to use the jQuery provided by the page itself. I could try inserting the same jQuery file as Outlook does and hope it gets loaded from cache, but I'd rather just keep my extension as clean as possible, and use what is already available.

Your attempt at optimizing the extension is not workable / not recommended.

First off, you will not be able to use the page's code anyway because of the isolation between content script and webpage code. You cannot obtain a reference to page's own jQuery/$.

But let's for a moment suppose that you could. And then the site updates jQuery to another version, renames the jQuery object or stops using it entirely, which is outside your control. Result: your extension is broken. This is, partially, the rationale behind the isolation in the first place.

As a result of the context isolation, you are guaranteed there are no conflicts between your copy of jQuery and whatever runs on the site. So you don't need to worry about that: use your copy, and use the standard $ to access it.

Bundling a <100 KB file with your extension as a one-time download that makes sure code is available 100% of the time and with at worst disk-access latency is not making it less "clean", quite the opposite. It's a common practice and is enshrined in the docs.


Looking at your actual code, it executes in the content script context (regardless whether it's through manifest or executeScript), not in the page context. As such, no matter what the page does, $ will not be defined there.

I verified that window.jQuery is properly defined afterwards by the actual jQuery function/object [...]

I assume that you tried to execute window.jQuery in the console; by default, that executes it in the page context, not in your content script context (therefore, not reflecting the state of the content script context and not invoking your getter/setter). If you want to test your content script, you need to change top in the context drop-down above the console to your extension's context.


All that said, however,

When all is said and done, I want to use jQuery's ajaxSuccess function to execute code every time an e-mail is opened in the read pane.

Here we've got a problem. Since the content script code and webpage code are isolated, your code will never know about AJAX executing in the page's copy (not through ajaxSuccess, anyway).

Possible courses of action:

  1. Rely on other methods to detect the event you want. Perhaps monitoring the DOM.
  2. Inject some code into the page itself; the only way to do so is by injecting a <script> tag into the page from the content script. There, you can access the page's copy of jQuery, attach your listener and message your content script when something happens.
  3. Rely on the background page to detect activity you need with webRequest API. This will likely intercept the AJAX calls, but will not give you the reply contents.

Final note: this may not be as simple as AJAX calls; perhaps the page maintains a WebSocket connection to get realtime push updates. Tapping into this is trickier.

Community
  • 1
  • 1
Xan
  • 74,770
  • 16
  • 179
  • 206
  • I appreciate you are trying to solve my _problem_, but you did not answer my _question_. I am aware I could take a different route to solve my problem, but this question isn't about finding just any solution to my problem; it is about why a certain solution isn't working. Also, I'm already injecting code into the page itself, both as a content script, and using executeScript from a background script. But jQuery isn't loaded yet at both times of injection. Hence this complicated dance. – Protector one May 19 '16 at 10:45
  • I see your point. Still, `$` will never become initialized in your content script context (and `executeScript` injects into that, not into the page). – Xan May 19 '16 at 10:47
  • I think now I addressed your _question_ better. – Xan May 19 '16 at 10:53
  • Why do you think `executeScript` injects into the content script?? I'm pretty sure it injects into the page itself—at least it has been doing that for years. Also: https://developer.chrome.com/extensions/tabs#method-executeScript – Protector one May 19 '16 at 11:26
  • 1
    It's a bad phrasing there - it injects into the content script context of the page. Injecting into the page itself is done only through [adding a ` – Xan May 19 '16 at 11:30
  • That's a pretty enviable badge. I tip my hat to you. I think I need to review some information that has become corrupted in my memory. Mea culpa. – Protector one May 19 '16 at 12:01
  • 1
    Even with said badge, there are things to be learned every day. Regarding page-level scripts, while they are a common practice they are not directly explained in the documentation; however, they [do get a mention](https://developer.chrome.com/extensions/contentSecurityPolicy#interactions). – Xan May 19 '16 at 12:31
1

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.

Community
  • 1
  • 1
Protector one
  • 6,926
  • 5
  • 62
  • 86