Chrome Extension - Retrieving Global Variable from Webpage

Chrome extension - retrieving global variable from webpage

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

Access global js variables from js injected by a chrome extension

Since content scripts run in an "isolated world" the JS variables of the page cannot be directly accessed from an extension, you need to run code in page's main world.

WARNING! DOM element cannot be extracted as an element so just send its innerHTML or another attribute. Only JSON-compatible data types can be extracted (string, number, boolean, null, and arrays/objects of these types), no circular references.

1. ManifestV3 in modern Chrome 95 or newer

This is the entire code in your extension popup/background script:

async function getPageVar(name, tabId) {
const [{result}] = await chrome.scripting.executeScript({
func: name => window[name],
args: [name],
target: {
tabId: tabId ??
(await chrome.tabs.query({active: true, currentWindow: true}))[0].id
},
world: 'MAIN',
});
return result;
}

Usage:

(async () => {
const v = await getPageVar('foo');
console.log(v);
})();

See also how to open correct devtools console.

2. ManifestV3 in old Chrome and ManifestV2

We'll extract the variable and send it into the content script via DOM messaging. Then the content script can relay the message to the extension script in iframe or popup/background pages.

  • ManifestV3 for Chrome 94 or older needs two separate files

    content script:

    const evtToPage = chrome.runtime.id;
    const evtFromPage = chrome.runtime.id + '-response';

    chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
    if (msg === 'getConfig') {
    // DOM messaging is synchronous so we don't need `return true` in onMessage
    addEventListener(evtFromPage, e => {
    sendResponse(JSON.parse(e.detail));
    }, {once: true});
    dispatchEvent(new Event(evtToPage));
    }
    });

    // Run the script in page context and pass event names
    const script = document.createElement('script');
    script.src = chrome.runtime.getURL('page-context.js');
    script.dataset.args = JSON.stringify({evtToPage, evtFromPage});
    document.documentElement.appendChild(script);

    page-context.js should be exposed in manifest.json's web_accessible_resources, example.

    // This script runs in page context and registers a listener.
    // Note that the page may override/hook things like addEventListener...
    (() => {
    const el = document.currentScript;
    const {evtToPage, evtFromPage} = JSON.parse(el.dataset.args);
    el.remove();
    addEventListener(evtToPage, () => {
    dispatchEvent(new CustomEvent(evtFromPage, {
    // stringifying strips nontranferable things like functions or DOM elements
    detail: JSON.stringify(window.config),
    }));
    });
    })();
  • ManifestV2 content script:

    const evtToPage = chrome.runtime.id;
    const evtFromPage = chrome.runtime.id + '-response';

    // this creates a script element with the function's code and passes event names
    const script = document.createElement('script');
    script.textContent = `(${inPageContext})("${evtToPage}", "${evtFromPage}")`;
    document.documentElement.appendChild(script);
    script.remove();

    // this function runs in page context and registers a listener
    function inPageContext(listenTo, respondWith) {
    addEventListener(listenTo, () => {
    dispatchEvent(new CustomEvent(respondWith, {
    detail: window.config,
    }));
    });
    }

    chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
    if (msg === 'getConfig') {
    // DOM messaging is synchronous so we don't need `return true` in onMessage
    addEventListener(evtFromPage, e => sendResponse(e.detail), {once: true});
    dispatchEvent(new Event(evtToPage));
    }
    });
  • usage example for extension iframe script in the same tab:

    function handler() {
    chrome.tabs.getCurrent(tab => {
    chrome.tabs.sendMessage(tab.id, 'getConfig', config => {
    console.log(config);
    // do something with config
    });
    });
    }
  • usage example for popup script or background script:

    function handler() {
    chrome.tabs.query({active: true, currentWindow: true}, tabs => {
    chrome.tabs.sendMessage(tabs[0].id, 'getConfig', config => {
    console.log(config);
    // do something with config
    });
    });
    }

So, basically:

  1. the iframe script gets its own tab id (or the popup/background script gets the active tab id) and sends a message to the content script
  2. the content script sends a DOM message to a previously inserted page script
  3. the page script listens to that DOM message and sends another DOM message back to the content script
  4. the content script sends it in a response back to the extension script.

Global Variables in Chrome Extensions

I managed to complete it. Thanks for the help. I used simple message passing to retrieve the value from the extension script to the content script. The place where I had missed was, the listener at the extension script needs to be at the background page (I think so). Once I changed that, it worked.

Chrome Extension: Get Page Variables in Content Script

If you really need to, you can insert a <script> element into the page's DOM; the code inside your <script> element will be executed and that code will have access to JavaScript variables at the scope of the window. You can then communicate them back to the content script using data- attributes and firing custom events.

Sound awkward? Why yes, it is, and intentionally so for all the reasons in the documentation that serg has cited. But if you really, really need to do it, it can be done. See here and here for more info. And good luck!

Accessing global object from content script in chrome extension

There is no way to get direct access to the background page's global object from a Content script or injected script.

To use a background page's method from an injected script , follow these steps:

  1. Content Script: Bind an event listener to the page example 1.

    Whenever you want to notify the method from the background page:
  2. Injected script: Create and fire this specific event example 1.

    → The event listener from 1) gets triggered.
  3. In this event listener, use chrome.runtime.sendMessage to request the functionality from the background example 2.
  4. In the background page, handle this request using chrome.runtime.onMessage.
  5. Optionally, inject code in the original tab using chrome.tabs.executeScript(tab.id, func).

To use a background page's method from a Content script, only steps 3 and 4 are needed.

Here's an example, in which the content script communicates with the background page:

// Example background page
function some_method(arg_name) {
return localStorage.getItem(arg_name);
}
chrome.runtime.onMessage.addListener(function(request, sender, callback) {
if (request.type == 'localStorage - step 4') {
callback( some_method(request.name) );
} else if (request.type == 'localStorage - step 5') {
localStorage.setItem(request.name, request.value);
}
});

// Example contentscript.js
chrome.runtime.sendMessage({
type: 'localStorage - step 4',
name: 'preference'
}, function(value) {
if (value === null) {
// Example: If no preference is set, set one:
chrome.runtime.sendMessage({
type: 'localStorage - step 5',
name: 'preference',
value: 'default'
});
}
});

See also

  • SO: What you should know about background scripts vs Content Scripts vs Injected scripts.
  • SO: Example code: Communicating between an Injected script and a Content script.


Related Topics



Leave a reply



Submit