59

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?

Kos
  • 4,890
  • 9
  • 38
  • 42
Mr.
  • 9,429
  • 13
  • 58
  • 82

5 Answers5

132

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).

Community
  • 1
  • 1
Rob W
  • 341,306
  • 83
  • 791
  • 678
  • Thank you for your reply. Please correct me if I am wrong, but the source code within the content script is the code to be injected, and which seemlessly being injected (by google definition). otherwise, how may i do that (a reference will be great) – Mr. Mar 09 '12 at 14:53
  • 3
    @MrRoth [Reference](http://code.google.com/chrome/extensions/content_scripts.html#execution-environment): "Content scripts execute in a special environment called an isolated world. They have access to the DOM of the page they are injected into, but **not to any JavaScript variables or functions created by the page.**" – Rob W Mar 09 '12 at 14:55
  • Let me see if I got you correctly. You imply that I need to implement [Communicating with embedded pages](http://code.google.com/chrome/extensions/content_scripts.html#host-page-communication) by injecting a file to the original page (the one that holds ``GLOBALS``), and have a listener on the content script. Correct? – Mr. Mar 09 '12 at 15:06
  • @MrRoth If you don't need any of the chrome's privileges, you'd better use [Method 1 of my linked answer](http://stackoverflow.com/a/9517879/938089?building-a-chrome-extension-with-youtube-events). This method is always necessary. On top of that, you can add event listeners to transfer data between the page and your Content script via event listeners. – Rob W Mar 09 '12 at 15:09
  • Thank you for all of your replies. I understand what you are saying. I do not want to be rude, but please give me a little bit less general answer. Shall I will use the first method in order to inject a code which mimics the native implementation of "communicating with embedded pages"? – Mr. Mar 09 '12 at 15:16
  • Thank you for your time and will to help. Although, it does not work - the event listener never gets invoked. Have you tried it? Does it work for you? – Mr. Mar 09 '12 at 20:57
  • 2
    @MrRoth Updated & tested answer. Previously, the ` – Rob W Mar 09 '12 at 21:07
  • Please accept my apology, it runs beautifully. Amazing skills. thanks. – Mr. Mar 09 '12 at 21:58
  • Thank you Rob. If it is possible to do such hack, a simple permission to access the page scope would be much easier to use. – oldergod Apr 06 '16 at 05:39
  • 1
    is there any specific reason to use `RW759_connectExtension` ? can't find any justification, but I've seen other examples using the same event key – pedrorijo91 May 04 '17 at 21:35
  • 7
    @pedrorijo91 It's an arbitrarily chosen event name. At the time of writing is was unique, but now it's probably not that unique any more. – Rob W May 04 '17 at 22:13
  • @RobW Why is the setTimeout 0 required? – user5155835 Jan 04 '18 at 15:19
  • Thanks for the answer! "Any script injected in the page runs in the same context as the web page" was the piece of information I was looking for. For future readers: even if the ` – cxw Jan 22 '18 at 17:47
  • @user5155835 It is not required. Edited snippet to reflect that. – Falcon Mar 25 '18 at 16:58
  • @RobW- I am fetching emailID from Globals - is it safe how often gmail changes that value - Can you please provide some help – Sandeep Chikhale May 04 '18 at 13:29
  • @pedrorijo91 @Rob-W According to https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage Content scripts should use runtime.sendMessage to communicate with the background script. **Web context scripts can use custom events to communicate with content scripts (with randomly generated event names, if needed, to prevent snooping from the guest page)** So the random event name `RW759_connectExtension` make a lot of sense, and should be regenerated each time we post message. – Thach Lockevn Aug 25 '20 at 16:31
  • 2
    `chrome.extension.getURL()` has been deprecated. It's now `chrome.runtime.getURL()`. – Calvin Huang Jul 01 '22 at 09:49
37

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

Alex
  • 589
  • 4
  • 7
  • 1
    the global `window` object that is on the page is not the same one in the `content_script.js` however, so this won't work right? – vvMINOvv Oct 25 '17 at 16:10
  • Actually, they do share the same window object. Checkout https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage – David D. Jul 10 '18 at 14:32
  • This is confusing to me, because I thought isolated worlds means all env variables, including the `window` object will be different for `webpage.js` and `content_script.js` but this only seems to be partially true. `postMessage` and `eventListeners` seem to share the same `window` object, but other properties do not.. – doublea Sep 11 '19 at 17:36
  • No they are not the same `window`, I tried modifying the `window.open` on `contentScript`, it was not modified on the page itself. @doublea But it doesn't mean that this answer won't work since we're using `postMessage` and `addEventListener`. – Shayan Jan 17 '20 at 11:23
  • At the moment of this comment, I do confirm that MainWorld (non-extension) script `window.postMessage` and ExtensionWorld-ContentScript `window.addEventListener("message")` can talk to each others, and vice versa. See the official paragraph in this https://developer.chrome.com/extensions/content_scripts#host-page-communication – Thach Lockevn Aug 25 '20 at 16:38
  • That entire link: https://developer.chrome.com/docs/extensions/content_scripts.html no longer works. – Raleigh L. Jan 20 '21 at 06:56
3

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.

kzahel
  • 2,765
  • 1
  • 22
  • 31
  • 1
    Re your last paragraph, see https://code.google.com/p/chromium/issues/detail?id=249080 – Rob W Aug 17 '13 at 13:13
  • 1
    You should mention that (unfortunately) `The URL pattern must contain at least a second-level domain` See https://developer.chrome.com/extensions/messaging#external-webpage – manuell Feb 09 '16 at 14:15
  • @RobW Hello are you saying that this won't work? Because I don't understand this answer, I have read all your answers regarding executing a script in a page and I have no problems, I'm asking this out of curiosity, Thanks. – Shayan Jan 17 '20 at 11:33
  • 1
    @Shayan The last paragraph asks how to detect the availability of `chrome.runtime.sendMessage`. Short of repeatedly calling `chrome.runtime.sendMessage` or using another way to notify the page (e.g. via a content script), there is no method to do so. https://crbug.com/249080 was about offering a way to let websites discover the API availability, but it seems to have been closed by now. – Rob W Jan 18 '20 at 17:09
  • 1
    The year is 2020 and I would recommend against doing this. The [documentation](https://developer.chrome.com/extensions/content_scripts#host-page-communication) recommends using `postMessage`. Note the docs use a "*" targetOrigin, this is [bad](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage). Additionally using `runtime.sendMessage` to communicate with embedded scripts means you have to use a background script to relay all your [communication](https://developer.chrome.com/extensions/runtime#method-sendMessage). A carefully implemented `postMesage` should do it. – Christoph Hansen Nov 24 '20 at 01:02
0

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();
0

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.

Naveen
  • 623
  • 9
  • 20