Chrome Extension — When to use content scripts and injected scripts
Chrome is a well spread platform with extended support for developing extensions. These extensions can be used for building full fledged products like: CRMs (Streak), VPNs (TunnelBear).
According to Google, Chrome Extensions are:
small software programs that customize the browsing experience. They enable users to tailor Chrome functionality and behavior to individual needs or preferences. They are built on web technologies such as HTML, JavaScript, and CSS.
In a nut shell a Chrome Extension is a piece of software that can run inside a Chrome browser. It can enhance Chrome’s functionalities and create custom experiences for users that are outside of Chrome’s scope.
At the core of the business logic there are JS files. JS scripts are divided in:
- background scripts
- content scripts (content scripts have a special subcategory: injected scripts)
One of the main difference between background and content scripts is that background scripts run in the context of the browser and they respond to browser events. (A background script could tally each page that you navigate to and send you a weekly report regarding your navigating behaviour.)
On the other hand content scripts are run in the context of a web page and have access to the embedding page’s DOM.(For instance, a content script can give you the definition of any word present on the page at hover or even translate the entire page in a different language.)
Content scripts
As stated above, content scripts can access the embedding page’s DOM. This gives a lot of power to the script as it can enhance the UX on an existing page. You can add a content scripts by different means:
- programmatically: from a background script as response to an event
- declaratively: by adding entries in manifest.json
Either way you choose you’ll end up with a script that runs on the desired page and has access to the underlying DOM. But this is not the same as running a script on the host page. The main differences are:
- the content scripts runs in a sandbox environment. They have a different security policy than the embedding page. One consequence of this is that content scripts AJAX calls aren’t restricted to the same domains as the embedding page.
- it has a different window object than the embedded page. This means that using existing JS objects from the embedding page is not possible.
This is where injected scripts come into play.
Injected scripts
Injected scripts are JS scripts that have access to the embedded page JS context and run in the same JS scripts are the embedding page. The way to add an injected script is to:
- create a script DOM element, add the content of the script
- add the DOM element to the embedding page DOM
Why injected scripts
Now a question arises: When do I need to use an injected script ?
The obvious answer is: When you need access to the JS context of the embedding page. Here are a few use cases when an injected JS script may come in handy:
- Access JS functionalities that are not available via UI.
- Directly accessing the business model inside the embedded page. Relying on selectors to gather the underlying business model is error prone. If you can access the model directly, which is most likely stored in an JS object, you have a more robust way of dealing with changes.
- Enhance existing functionalities by monkey patching existing JS methods.
How to use injected scripts
Bellow is an example of how to insert an injected script. This should be added to a content script.
var script = document.createElement('script');
script.text = "// your js code here"; (document.head||document.documentElement).appendChild(script);
Although the above methods works, it is hard to work with the code like that. Editing JS code inside a string is not the best way to go about it. A more robust way is to move your JS code to a different file and include it from there.
var script = document.createElement('script');
script.src = chrome.extension.getURL('injected.js');
(document.head||document.documentElement).appendChild(script);
For this to work as expected we need to add a permission to manifest.json
"web_accessible_resources": ["injected.js"]
Now we have an injected script that can access the embedding page DOM an JS context. A few notes about this setup:
- the injected script runs in the same context as the embedding page, for instance this means that AJAX calls are subject to the pages CSP and not the extension’s one
- there is no direct line of communication between the injected script and the content script (they live in distinct JS contexts)
- if you want to communicate between the scripts you can either use window.postMessage or build your own comm bridge using the shared DOM (more about this in a future article)
Conclusion
Both content scripts and injects scripts run in the context of a particular web page. They are actually both content scripts in Chrome Extension terms.
The main difference lays in the fact that injected scripts can access both the DOM and the JS context while content scripts only have access to the DOM. The ability to access the embedding JS context comes at a price: the script runs in the same security sandbox as the embedding page.
There are valid reasons when access to the embedding JS context is needed (accessing the underlaying model that doesn’t have a DOM equivalent is probably the most common one). In this case only injected scripts can help with that, but most likely you’ll need to pair injected scripts with content scripts to get the job done.