Chrome Extension Code VS Content Scripts VS Injected Scripts

Chrome extension code vs Content scripts vs Injected scripts

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();"});
}
});

What is the most secure way of passing messages between an injected script and Google Chrome extension code/content script?

While code running in your background page / content script is pretty well isolated, as soon as you inject a script into the page context - you're in the Wild West. Any extension, and the page itself, has access to that context and can influence how your code executes.

For example, some extension can override chrome.runtime.sendMessage to send the message AND log it. This needs to be seriously taken into account - probably, you already lost.

That said, method 1 is harder to break into than 2/3 - as explained, the attacker extension would need to directly alter the page context to interfere, while in case of DOM events it can just listen to them from the safety of its content script - events are broadcast to all content script contexts.

Hypothetically, you could employ some sort of asymmetric cryptography for the channel as well - provide the injected script with the encryption key and keep the decryption key in the privileged zone. That safeguards the communication, if that's the only thing intercepted, but at some point the plaintext data exists in the global context - that may be enough for the attacker script to extract (that, you have to assume, executed before your injected script).

What exactly are content script and background script in the Chrome extension?

First of all, you are indeed using a content script. Here you are just controlling the execution of the content script through an event.

background scripts are something that run in background and listen for triggers while the user interacts with the chrome browser (such as listening for a click event on a tab)

While content scripts are the one's that actually interacts with the webpage (essentially DOM elements).

Now, the difference between your method and including them in manifest is that if they are included in manifest, the content scripts will load as soon as the page loads and hence(in this case) will auto-fill data simultaneously, while chrome.tabs.executeScript(tabs[0].id, {file: "xyz.js"}); will load the content script upon a certain triggering event and hence(in this case) auto-fill data on a trigger(such as on button click).

Here are all the methods to inject content scripts.

Difference between background script and content script in chrome extension

I have found the answer to the questions asked.

A. Can we include both content script and background script ?

Yes, we can include both the background scripts and content scripts in manifest.
To do interaction between them you can use Chrome Message Passing API.

I was doing the same way but there was some error in background script which I could not see therefore I posted this question after some searching on google.

B. How can I listen to click event in content script ?

Solution: We can not have browser click event in content script. It has only partial access to chrome object So you have to receive the click handler in background script and send message to content script and do whatever you want.

Use chrome.browserAction.onClicked event in background script and then use message passing to send the information to content script that user clicked on icon.

Chrome extension: Checking if content script has been injected or not

is this safe enough to naively call chrome.tabs.executeScript every
time the extension icon got clicked? In other words, is this
idempotent?

  1. Yes, unless your content script modifies the page's DOM AND the extension is reloaded (either by reloading it via the settings page, via an update, etc.). In this scenario, your old content script will no longer run in the extension's context, so it cannot use extension APIs, nor communicate directly with your extension.

is there a similar method for chrome.tabs.insertCSS?


  1. No, there is no kind of inclusion guard for chrome.tabs.insertCSS. But inserting the same stylesheet again does not change the appearance of the page because all rules have the same CSS specificity, and the last stylesheet takes precedence in this case. But if the stylesheet is tightly coupled with your extension, then you can simply inject the script using executeScript, check whether it was injected for the first time, and if so, insert the stylesheet (see below for an example).

any better method for background script to check the inject status of
content script, so I can just prevent calling
chrome.tabs.executeScript every time when user clicked the icon?


  1. You could send a message to the tab (chrome.tabs.sendMessage), and if you don't get a reply, assume that there was no content script in the tab and insert the content script.

Code sample for 2

In your popup / background script:

chrome.tabs.executeScript(tabId, {
file: 'contentscript.js',
}, function(results) {
if (chrome.runtime.lastError || !results || !results.length) {
return; // Permission error, tab closed, etc.
}
if (results[0] !== true) {
// Not already inserted before, do your thing, e.g. add your CSS:
chrome.tabs.insertCSS(tabId, { file: 'yourstylesheet.css' });
}
});

With contentScript.js you have two solutions:

  1. Using windows directly: not recommended, cause everyone can change that variables and Is there a spec that the id of elements should be made global variable?
  2. Using chrome.storage API: That you can share with other windows the state of the contentScript ( you can see as downside, which is not downside at all, is that you need to request permissions on the Manifest.json. But this is ok, because is the proper way to go.

Option 1: contentscript.js:

// Wrapping in a function to not leak/modify variables if the script
// was already inserted before.
(function() {
if (window.hasRun === true)
return true; // Will ultimately be passed back to executeScript
window.hasRun = true;
// rest of code ...
// No return value here, so the return value is "undefined" (without quotes).
})(); // <-- Invoke function. The return value is passed back to executeScript

Note, it's important to check window.hasRun for the value explicitly (true in the example above), otherwise it can be an auto-created global variable for a DOM element with id="hasRun" attribute, see Is there a spec that the id of elements should be made global variable?

Option 2: contentscript.js (using chrome.storage.sync you could use chrome.storage.local as well)

    // Wrapping in a function to not leak/modify variables if the script
// was already inserted before.
(chrome.storage.sync.get(['hasRun'], (hasRun)=>{
const updatedHasRun = checkHasRun(hasRun); // returns boolean
chrome.storage.sync.set({'hasRun': updatedHasRun});
))()

function checkHasRun(hasRun) {
if (hasRun === true)
return true; // Will ultimately be passed back to executeScript
hasRun = true;
// rest of code ...
// No return value here, so the return value is "undefined" (without quotes).
}; // <-- Invoke function. The return value is passed back to executeScript

Chrome extension's content scripts doesn't affect a particular website

Just added "*://*/*", into your manifest to make working.

manifest.json

{
"name": "test",
"version": "0.0.1",
"manifest_version": 2,
"description": "word",
"background": {
"scripts": [
"background.js"
],
"persistent": true
},
"browser_action": {
"default_title": "chat"
},
"permissions": [
"*://*/*",
"tabs"
]
}

background.js

// listen for our browerAction to be clicked

chrome.browserAction.onClicked.addListener(function (tab) {
// for the current tab, inject the "inject.js" file & execute it
chrome.tabs.executeScript(tab.ib, {
file: 'inject.js'
});
});

inject.js

(function() {
console.log("Inject successfully.");
// just place a div at top right
var div = document.createElement('div');
div.style.position = 'fixed';
div.style.top = 600;
div.style.right = 700;
div.style.zIndex = 9999;
div.textContent = 'Hello!';
document.body.appendChild(div);
})();

Sample Image

Optionally inject Content Script

You cannot run code on a site without the appropriate permissions. Fortunately, you can add the host permissions to optional_permissions in the manifest file to declare them optional and still allow the extension to use them.

In response to a user gesture, you can use chrome.permission.request to request additional permissions. This API can only be used in extension pages (background page, popup page, options page, ...). As of Chrome 36.0.1957.0, the required user gesture also carries over from content scripts, so if you want to, you could add a click event listener from a content script and use chrome.runtime.sendMessage to send the request to the background page, which in turn calls chrome.permissions.request.

Optional code execution in tabs

After obtaining the host permissions (optional or mandatory), you have to somehow inject the content script (or CSS style) in the matching pages. There are a few options, in order of my preference:

  1. Use the chrome.declarativeContent.RequestContentScript action to insert a content script in the page. Read the documentation if you want to learn how to use this API.

  2. Use the webNavigation API (e.g. chrome.webNavigation.onCommitted) to detect when the user has navigated to the page, then use chrome.tabs.executeScript to insert the content script in the tab (or chrome.tabs.insertCSS to insert styles).

  3. Use the tabs API (chrome.tabs.onUpdated) to detect that a page might have changed, and insert a content script in the page using chrome.tabs.executeScript.

I strongly recommend option 1, because it was specifically designed for this use case. Note: This API was added in Chrome 38, but only worked with optional permissions since Chrome 39. Despite the "WARNING: This action is still experimental and is not supported on stable builds of Chrome." in the documentation, the API is actually supported on stable. Initially the idea was to wait for a review before publishing the API on stable, but that review never came and so now this API has been working fine for almost two years.

The second and third options are similar. The difference between the two is that using the webNavigation API adds an additional permission warning ("Read your browsing history"). For this warning, you get an API that can efficiently filter the navigations, so the number of chrome.tabs.executeScript calls can be minimized.

If you don't want to put this extra permission warning in your permission dialog, then you could blindly try to inject on every tab. If your extension has the permission, then the injection will succeed. Otherwise, it fails. This doesn't sound very efficient, and it is not... ...on the bright side, this method does not require any additional permissions.

By using either of the latter two methods, your content script must be designed in such a way that it can handle multiple insertions (e.g. with a guard). Inserting in frames is also supported (allFrames:true), but only if your extension is allowed to access the tab's URL (or the frame's URL if frameId is set).



Related Topics



Leave a reply



Submit