Chrome extension code vs Content scripts vs Injected scripts - javascript

I am trying to get my Chrome Extension to run the function init() whenever a new page is loaded, but I am having trouble trying to understand how to do this. From what I understand, I need to do the following in background.html:
Use chrome.tabs.onUpdated.addListener() to check when the page is
changed
Use chrome.tabs.executeScript to run a script.
This is the code I have:
//background.html
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
chrome.tabs.executeScript(null, {code:"init();"});
});
//script.js
function init() {
alert("It works!");
}
I am also wondering if the init() function will have access to my other functions located in other JS files?

JavaScript code in Chrome extensions can be divided in the following groups:
Extension code - Full access to all permitted chrome.* APIs.
This includes the background page, and all pages which have direct access to it via chrome.extension.getBackgroundPage(), such as the browser pop-ups.
Content scripts (via the manifest file or chrome.tabs.executeScript) - Partial access to some of the chrome APIs, full access to the page's DOM (not to any of the window objects, including frames).
Content scripts run in a scope between the extension and the page. The global window object of a Content script is distinct from the page/extension's global namespace.
Injected scripts (via this method in a Content script) - Full access to all properties in the page. No access to any of the chrome.* APIs.
Injected scripts behave as if they were included by the page itself, and are not connected to the extension in any way. See this post to learn more information on the various injection methods.
To send a message from the injected script to the content script, events have to be used. See this answer for an example. Note: Message transported within an extension from one context to another are automatically (JSON)-serialised and parsed.
In your case, the code in the background page (chrome.tabs.onUpdated) is likely called before the content script script.js is evaluated. So, you'll get a ReferenceError, because init is not .
Also, when you use chrome.tabs.onUpdated, make sure that you test whether the page is fully loaded, because the event fires twice: Before load, and on finish:
//background.html
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if (changeInfo.status == 'complete') {
// Execute some script when the page is fully (DOM) ready
chrome.tabs.executeScript(null, {code:"init();"});
}
});

Related

Why does browser.runtime.getBackgroundPage() resolve to null in a page action script?

In the following code
browser.runtime.getBackgroundPage().then(bgp=>{
document.querySelector("button").addEventListener("click", e=>{
alert(bgp);
});
});
bgp turns out to be null. I searched around and suggestions are most of the time for Chrome extensions, suggesting adding a "background" permission, which is not valid for Firefox. I also tried adding a background page explicitly, although one should be always created for me but it did not work either.
runtime.getBackgroundPage() provides access to the background script, not an HTML document.
This provides a convenient way for other privileged extension scripts
to get direct access to the background script's scope. This enables
them to access variables or call functions defined in that scope.
"Privileged script" here includes scripts running in options pages, or
scripts running in browser action or page action popups, but does not
include content scripts.
For example, the following code logs <unavailable> to the console.
browser.runtime.getBackgroundPage().then(bg => console.log(bg));
The window object can be seen in the debug console.

Not able to access chrome.runtime in iframe

Trying to access chrome.runtime.sendMessage in an iframe, but it is showing that
chrome.runtime is undefined
chrome.runtime.sendMessage("kbfjlfcddgkokfgifbohnjfpcnkknpbf", { getVersion: true },
function (response) {
console.log(response);
}
);
Based from this thread, if you're inserting JavaScript into a page with a <script> tag, it executes in the page's context.
Sometimes it is desirable: that's the only way to access page-level JavaScript objects.
But in your case it means that the code does not have access to Chrome APIs, as it is "the same" as the page's code.
You need to look into communicating between page-level and context scripts, or between page-level and background (spoiler, in most cases needs a context script proxy anyway).
Also from this page, by adding a breakpoint, or debugger statement, it causes that value to be undefined. Try to refresh the page then open after page is loaded and see if the chrome.runtime works correctly.

Uncaught TypeError: Cannot read property 'colors' of undefined

I am trying to create a chrome extension that allows a user to add a new color to Desmos, a graphing calculator, when the extension button is clicked.
The two basic files:
chrome.browserAction.onClicked.addListener(function (tab) {
chrome.tabs.executeScript(tab.ib, {
file: "add_color.js"
});
});
and
(function() {
if (window.location.href === "https://www.desmos.com/calculator") {
var name = prompt("What would you like the name of the new color to be?");
var hex = prompt("What should the hex code of the new color be?");
window.Calc.colors[name] = hex;
};
})();
But when I try running it, I get Uncaught TypeError: Cannot read property 'colors' of undefined. If I run it using the DevTools console it runs perfectly. Could someone explain why?
You can't access a webpage's evaluated Javascript variables from a content script. Your extension script, content script and THE page's script are running in different contexts. Your content script has access to the page's DOM and not the execution context. Thus, you need your content script to directly inject code into the page's DOM via add script tags.
Summary.
Extension script has access to the chrome tabs.
Content script has access to the page DOM.
Page script is executing in a different context than the other scripts.
Content script must inject code via adding script tags.
Injected code can access page's evaluated javascript variables.
Injected code can communicate back to content script using window.postMessage
Then content script can communicate back to extension script via chrome API.
It's not fun...

Why is chrome.runtime undefined when running JS inside a page from a content script?

I am creating a Google Chrome extension (my first one) and I want to send messages from the extension to the current tab.
I am following the documentation:
https://developer.chrome.com/apps/runtime#event-onMessage
The extension loads a small external JS into the tab's HTML, which contains the following code:
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(request)
}
);
As soon as the JS is loaded I get the following error:
Uncaught TypeError: Cannot read property 'onMessage' of undefined.
Opening console and typing chrome, I can see that the runtime is not a property of chrome.
It looks like I am doing something wrong, but what? Do I need to add something to the manifest.json file?
Chrome Version 39.0.2171.71 m
Thank you.
If you're inserting JavaScript into a page with a <script> tag, it executes in the page's context.
Sometimes it is desirable: that's the only way to access page-level JavaScript objects.
But in your case it means that the code does not have access to Chrome APIs, as it is "the same" as the page's code.
You need to look into communicating between page-level and content scripts, or between page-level and background (spoiler, in most cases needs a context script proxy anyway).
or it could just be a Heisenbug which only appears under certain circumstances. in my case, closing the chrome://extensions tab and refreshing my target caused chrome.runtime to be available again. Why is chrome.runtime undefined in the content script?

Invoking a Google Chrome extension from Javascript

There is an excellent extension called Blipshot which takes page screenshots. I need to invoke the extension with page level javascript, instead of clicking its icon. Is this possible?
You cannot invoke any methods of an extension from within a web page. However, it's possible to inject a content script into the web page, and use sendMessage and onMessage, or onConnect and connect.
To edit an extension: Visit chrome://extensions page, and enable the Developer mode. Unpack an extension and/or visit the extension's directory. Edit the manifest.json file, and add the necessary lines (see here).
Add an event event listener at the background page. Add a poller in the content script, eg:
// Content script
var poller = window.setInterval(function() {
if (document.documentElement.getAttribute('extensionCalled')) {
chrome.extension.sendMessage({"anyname": "anything"}, function() {
/*optional callback function.*/alert("Something happened")
});
clearInterval(poller);
}
}, 200);
// Background
chrome.extension.onMessage.addListener(function(request, sender, callback) {
if (request.anyname == "anything") {
function_logic_here();
//Optionally, callback:
callback();
}
});
See also
Chrome extension - retrieving Gmail's original message - Using DOM events to communicate between a page and extension (recommended)
MDN: postMessage - It can be used to communicate between a page and extension (this method may cause conflicts when the page itself is also using the message events).
References:
Extension messaging
Content scripts
Content scripts in extensions
It would only be possible if the extension provides an interface to do it. Extensions run in an isolated environment, so you don't have direct access to any of their functions.
The closest they get is content scripts, which have access to the DOM. Because of that, you can communicate using events, but obviously the extension would need to set up event handlers for them, so it completely depends on the extension.

Categories