Chrome extension - retrieving global variable from webpage


I am working on an extension for Chrome. I wish parse the content of the "original" Gmail message (the currently viewed message).

I tried to utilize the jQuery.load() as follows

$(windows).load(function() { alert(GLOBALS); });

and place it at the content script, but it does not work either. I am using Chrome's developer tools, which returns the following error on the invocation of the alert(GLOBALS);

Uncaught ReferenceError: GLOBALS is not defined

Although, when using the developers tools' console, typing into the console GLOBALS it returns an array.

Any clue how to access the GLOBALS from the content script?

Content scripts run in an isolated environment. To get access to the any global properties (of the page's window), you have to either inject a new <script> element, or use event listeners for passing data.

See this answer for example on injecting a <script> element in the context of the page.

Example

contentscript.js ("run_at": "document_end" in manifest):

var s = document.createElement('script');
s.src = chrome.extension.getURL('script.js');
(document.head||document.documentElement).appendChild(s);
s.onload = function() {
    s.remove();
};

// Event listener
document.addEventListener('RW759_connectExtension', function(e) {
    // e.detail contains the transferred data (can be anything, ranging
    // from JavaScript objects to strings).
    // Do something, for example:
    alert(e.detail);
});

script.js - Located in the extension directory, this will be injected into the page itself:

setTimeout(function() {
    /* Example: Send data from the page to your Chrome extension */
    document.dispatchEvent(new CustomEvent('RW759_connectExtension', {
        detail: GLOBALS // Some variable from Gmail.
    }));
}, 0);

Since this file is being loaded via a chrome-extension: URL from within the DOM, "script.js" must be added to the web_accessible_resources section of the manifest file. Otherwise Chrome will refuse to load the script file.

You should run as little logic as possible in the web page, and handle most of your logic in the content script. This has multiple reasons. First and foremost, any script injected in the page runs in the same context as the web page, so the web page can (deliberately or inadvertently) modify JavaScript/DOM methods in such a way that your extension stops working. Secondly, content script have access to extra features, such a limited subset of the chrome.* APIs and cross-origin network requests (provided that the extension has declared permissions for those).


A more modern solution for communicating between a chrome extension content_script and the javascript on the page would be to use the html5 postMessage API. Any messages sent to "window" are visible from both the javascript on the webpage and the extension's content_script.

The extension's content_script.js:

window.addEventListener('message', function(event) {
    console.log('content_script.js got message:', event);
    // check event.type and event.data
});

setTimeout(function () {
    console.log('cs sending message');
    window.postMessage({ type: 'content_script_type',
                         text: 'Hello from content_script.js!'},
                       '*' /* targetOrigin: any */ );
}, 1000);

The javascript running on the webpage:

window.addEventListener('message', function(event) {
    console.log('page javascript got message:', event);
});

setTimeout(function() {
    console.log('page javascript sending message');
    window.postMessage({ type: 'page_js_type',
                         text: "Hello from the page's javascript!"},
                       '*' /* targetOrigin: any */);
}, 2000);

Also see http://developer.chrome.com/extensions/content_scripts.html#host-page-communication


There is a new API for web pages to communicate securely and without any side effects (window.postMessage can have other listeners!) to the content script.

"From the web page, use the runtime.sendMessage or runtime.connect APIs to send a message to a specific app or extension"

// The ID of the extension we want to talk to.
var editorExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";

// Make a simple request:
chrome.runtime.sendMessage(editorExtensionId, {openUrlInEditor: url},
  function(response) {
    if (!response.success)
    handleError(url);
});

"From your app or extension, you may listen to messages from web pages via the runtime.onMessageExternal or runtime.onConnectExternal APIs, similar to cross-extension messaging. Only the web page can initiate a connection. [...]"

(from http://developer.chrome.com/extensions/messaging.html) This is still only available in chrome's dev channel, but seems like it'll be in the next version or so.

Don't ask me how this works, it seems highly confusing. How on earth does chrome.runtime get defined on the web page? What if the script already defined that variable for some reason? I also couldn't find the chromium bug report to see the history of the development of this feature.


I had a slightly different approach based on your extension's accessible html pages.

Add your page to the manifest:

"web_accessible_resources": ["variables.html"]

Create your page (here, variables.html) and extract the content's data (i.e. window.contentVar):

<script type="text/javascript">
  $("#my-var-name").text(window["contentVar"]);
</script>
<div id="my-var-name" style="display: none;"></div>

Access from your extension's JavaScript:

var myVarName = $("#my-var-name").text();


Add to your content script:

function executeOnPageSpace(code){

  // create a script tag
  var script = document.createElement('script')
  script.id = 'tmpScript'
  // place the code inside the script. later replace it with execution result.
  script.textContent = 
  'document.getElementById("tmpScript").textContent = JSON.stringify(' + code + ')'
  // attach the script to page
  document.documentElement.appendChild(script)
  // collect execution results
  let result = document.getElementById("tmpScript").textContent
  // remove script from page
  script.remove()
  return JSON.parse(result)

}

Now you can do:

let window = executeOnPageSpace('window')

or even like this:

executeOnPageSpace('window.location.href = "http://stackoverflow.com"')

NOTE: I have not tested this for long-running code.