I've wrote a Chrome Extension. My background.js file is quite large, so I want to split it to smaller parts and load specified methods when required (some kind of lazy-loading).
I've done this with Firefox:
// ( call for load specified lib )
var libPath = redExt.basePath + 'content/redExt/lib/' + redExt.browser + '/' + libName + '.js';
var service = Components.classes["#mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader);
service.loadSubScript("chrome://" + libPath);
// ( executing loaded file )
Is there any possiblity to do it similar way in Webkit-based browsers? I've found solutions for how to inject multiple JS files into matching pages (using manifest.json) but cannot find way to include JS file just for extension.
UPDATE : This solution works only if you are using manifest V2.
You can also do this the extremely easy way that is described here:
https://developer.chrome.com/extensions/background_pages#manifest
{
"name": "My extension",
...
"background": {
"scripts": [
"lib/fileone.js",
"lib/filetwo.js",
"background.js"
]
},
...
}
You won't be doing lazy loading, but it does allow you to break your code up into multiple files and specify the order in which they are loaded onto the background page.
If you want to load a javascript file in the context of your background page and want to avoid using eval, you can just add a script tag to your background page's DOM. For instance, this works if your files are present in the lib folder of your extension:
function loadScript(scriptName, callback) {
var scriptEl = document.createElement('script');
scriptEl.src = chrome.extension.getURL('lib/' + scriptName + '.js');
scriptEl.addEventListener('load', callback, false);
document.head.appendChild(scriptEl);
}
You could attempt to use web workers, perhaps. In your background.js, include:
var worker = new Worker('new-script.js');
Web workers can spawn new workers, and even have an importScript("new-script.js") method.
Web workers can tend to be very limited, however. Check here for a great article on Web workers.
I don't know if they would work, however. The other option is using XMLHTTPRequest (AJAX) to dynamically retrieve the script, and eval it. Over a non-HTTPS connection however, this is probably blocked due to man-in-the-middle attacks.
Alternatively, you can force Chrome to eval script from a non-encrypted connection (BUT DON'T DO IT) by adding this to manifest.json:
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"permissions": ["http://badpractic.es/insecure.js"]
See Load remote webpage in background page: Chrome Extension for a more in-depth discussion of the AJAX method.
So your options seem to be limited.
This of course loads them all at once:
{
"name": "My extension",
...
"background": {
"scripts": ["background.js","background2.js","background3.js"] //and so on
},
...
}
Found possible solution. Theres a simple implementation of RequireJS main method require which uses JavaScript callback trace to find event for loading main extension file and binds executes it with extension context.
https://github.com/salsita/browser-require/blob/master/require.js
It seems to work, but this solution has a few cons:
bug reports are reported in "line 1, column 1" because this solution injects code directly to func.call - debugging is very hard
Loaded JS files does not appear in console / chromebug
If current tab uses HTTPS Chrome will disallow evaling scripts, especially this from local context (file:///), so it sometimes just dont work as expected
What I've done isn't lazy loading but I load some files before the others when my chrome extension icon is clicked. In my case, I wanted my util files before the others that utilize them:
chrome.browserAction.onClicked.addListener(function() {
chrome.tabs.executeScript(null, {file: "src/utils/utilFunction1.js"});
chrome.tabs.executeScript(null, {file: "src/utils/utilFunction2.js"});
chrome.tabs.executeScript(null, {file: "src/main.js"});
chrome.tabs.executeScript(null, {file: "src/otherFeature.js"});
});
although an old post, i thought i try and share my experiences here, especially as manifest v3 has changed a lot, importScripts
importScripts('foo.js');
Related
I am developing a Chrome extension for work, and one of the things it needs to do is to read (only read, not modify) an object that we send back to the website after it makes an asynchronous request to our servers. Basically I need to read the window.<our object name> object and get what's in there.
Now, I know this is possible, because I did this in a Tampermonkey script that I wrote. I was able to console.log(window.<our object name>) and it came in.
Tampermonkey is a Chrome extension, so there's no intrinsic reason why it can access something and another extension can't.
But when I try to access this object, both from content scripts and from injected code, I get nothing. When I get the window object only, it comes up only partially, as if the extension were blind to certain parts of it. But if I'm in the console on the page, and I call window, I get a full window object back. Infuriating.
So if content scripts don't work, and injected scripts don't work, and there's no reason why popup scripts would be any good here, how does one do this?
Many thanks!
UPDATE: As requested, here is the manifest.json (I took the page_redder example and worked off that to make sure I wasn't making any weird mistakes):
{
"name": "Page Redder",
"description": "Make the current page red",
"version": "2.0",
"permissions": [
"activeTab"
],
"background": {
"scripts": ["background.js"],
"persistent": false
},
"browser_action": {
"default_title": "get my object"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
],
"manifest_version": 2
}
And here is content.js:
var getWindow = window.setTimeout(function() { console.log("From content script: " + window.<OBJECT NAME>); }, 5000);
And here is background.js:
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Called when the user clicks on the browser action.
chrome.browserAction.onClicked.addListener(function(tab) {
// No tabs or host permissions needed!
chrome.tabs.executeScript({
code: 'console.log("From injected script:" + window.<OBJECT NAME>);'
});
});
When run, I get:
From content script: undefined
From injected script: undefined
But if I do window. from the console, I get it. I even added a timeout to make sure that the content script wasn't trying to get something that hadn't loaded in yet. But I can retrieve the object manually before the script runs, and it still gives me undefined.
Soo, this is kind of hacky, but I was able to do it and it worked.
To gain access to everything available to the host window, I had to create a script element, put all the code I wanted in there, and add document.body.appendChild(script) for it to work.
Not the sexiest way of doing things, but it will get the job done for small tasks.
Interesting question. Here is a quick and probably incomplete answer :
each tab has its own private separate window object
background.js also has its own
content scripts are a bit tricky in that while they nominally live inside the page, they actually keep a respectful distance : see isolated worlds
I am not familiar with chrome.tabs.executeScript but somehow I would'nt trust it with anything beyond basics
One approach could be as follow :
Open the relevant page from the background script with chrome.tabs.create : hence the backgound will have complete control and dominance over said tab and the window, document and your_object therein. It will also be easier to handle the asynchronous side of thing : you'll learn to love callbacks.
Depending on what is required regarding the UX, another option would be to handle the async request, and fetch your_object, entirely in background.js
One last hint : extensions you download from the store are just zipped files in your Chrome profile. Find tapermonkey.crx or whatever, unzip it and read the sources to figure out what it does.
And oh, relying on timeout to handle asynchronicity is bound to random results.
According to documentation in https://developer.chrome.com/extensions/content_scripts
However, content scripts have some limitations. They cannot:
Use variables or functions defined by web pages or by other content scripts.
So you can access the common window variables from the content script, but not the variables created from the webpage's javascript, another content script or, as in your case, an object you have sent to the website.
I am developing a FF addon recently. My addon will download some configuration files before the contentScriptFile been evaluated. My code is like this:
panel.on("show", function() {
var url = "http://domain/config.js";
request({
url:url,
onComplete: function(response) {
worker = tabs.activeTab.attach({
contentScript:response.text,
contentScriptFile: [data.url("js/do1.js"), data.url("js/do2.js")]
});
...
}
})
}
But as your document has said that contentScriptFile will be run before contentScript, so if I do want to run contentScript first, how should I do?
I have tried to attach twice, first attach contentScript only, then attach contentScriptFile, but not success.
Thank you for your time.
You must not execute remote scripts in a privileged context, or execute insecure remote scripts in a potentially secure context. What you're asking fails both:
Executing remote scripts in a somewhat privileged context, namely a content script, which has some privileged capabilities regular web site scripts have not.
Execute an insecure http:// script (you gave that in the example), in the current tab, which might be https://. This would allow Man-in-the-Middle attacks. (Even if you served your "config" from https, the add-on site would still reject your submission, BTW, because it cannot be reviewed as the active content is not complete).
So, effectively, you got two security vulnerabilities, both of which can and should make your add-on end up on the blocklist.
However, you can retrieve your configuration using a non-active/non-executable format, such as JSON or XML (or plain text, if you like).
Passing these configuration values can be achieved by
using either contentScriptOptions (which is not documented in tabs nor worker, but in page-mod but should work for all, as a cursory test shows). Full example:
var tabs = require("sdk/tabs");
tabs.on('activate', function(tab) {
tab.attach({
contentScript: 'console.log(self.options);',
contentScriptOptions: {"abc": 123}
});
});
Or regular messaging.
You could use data.load from the self SDK module to load the content script in a string and append the values to the contentScript array:
worker = tabs.activeTab.attach({
contentScript: [
response.text,
data.load("js/do1.js"),
data.load("js/do2.js")
]
});
i am trying to write my first firefox add-on. the main problem seem s to be that i am also new to javascript. at the moment i have:
require('sdk/page-mod').PageMod({
include: ["*"],
contentScript: 'window.addEventListener("click", function(e) { alert("blub"); }, false);',
attachTo: ["existing", "top"]
});
(thx to the answer here.)
now i want to use a declared function instead of an anonymous one, but i cant get it to work:
require('sdk/page-mod').PageMod({
include: ["*"],
contentScript: 'window.addEventListener("click", function(e) { alert("blub"); }, false);',
attachTo: ["existing", "top"]
});
getImgData function (e) {
alert("blubber3");
}
the first problem is i get syntax error by just adding the function "missing ; before statement". But cfx doesn't tell me the wrong line. (Is there any useful tool for js editing with good syntax check/ content assist?)
So how to declare a function and use ist somewhere else in the script. At the end the function needs to get the target of click and parse it.
(i read the tutorials but thy all use anonymous functions :-P)
thx in advance
It's important to realize the separation between chrome scripts and content scripts. Chrome scripts are those that run with the same security privileges as Firefox - they have full access to Firefox and your computer. Content scripts are those that run with the same privileges as web pages. They can mess around with that web page, but are severely restricted otherwise. To maintain security, the way these two types of scripts can communicate is limited. You wouldn't want a web page to be able to call any function it wants in your extension's internal code!
Your main JS file (the one that includes require('sdk/page-mod')) is a chrome script. What you're injecting (contentScript) is (obviously) a content script. They can't communicate through a direct function call as you're doing.
If your getImgData function is something that can be done with normal web page privileges, you can move your definition of it to within the content script. If it requires additional privileges, you must have your content script communicate with your chrome script via the emit and on functions as described in the link above.
If you are going to make your content script any longer, I would recommend you separate it into its own file to make your life easier.
While attempting to port the extension from manifest version 1 to version 2, this appeared:
Port error: Could not establish connection. Receiving end does not
exist. chromeHidden.Port.dispatchOnDisconnect
miscellaneous_bindings:232
This appeared in Console in developer tools. I have no idea where to start fixing this cause i don't know what's causing it to begin with..
what can cause this problem? and is there someway to know exactly what's causing it?
Thanks.
The most likely cause of failure is the activation of the default Content security policy when "manifest_version": 2 is active. A consequence of the default CSP is that inline JavaScript will not be executed.
<script>chrome.extension.onConnect.addListener(...);</script>
The previous line is an example of inline code. The solution is to place the script in an external JS file:
<script src="script.js"><!--original contents moved to script.js--></script>
Background pages/scripts
When you were using background pages, do not use:
"background_page": "background.htm", or
"background": {"page": "background.htm"},
but
"background": {"scripts": ["background.js"]}
where background.js contains the script which was initially placed within the <script> tags at background.htm.
Inline event listeners
Browser action popups, app launchers, option pages, etc. often contain inline event listeners. By the CSP, these are also forbidden.
<button onclick="test();"> does not work. The solution is to add the event in an external JS file using addEventListener. Have a look at the documentation or this answer for an example.
Other
JavaScript creation from strings (eval, Function, setTimeout, ...) is forbidden. Rewrite your code to not create code from strings, or use the sandbox manifest option (introduced in Chrome 21). Since Chrome 22, the unsafe-eval CSP policy can be used to lift this restriction.
JSONP does not work, because external (JavaScript) resources cannot be loaded in the extension's context. Use an ordinary XMLHttpRequest instead of JSONP (more information + example).
The only exception is when the resource is fetched over httpsnot http. The CSP can be adjusted to introduce this exception - see documentation:
"content_security_policy": "script-src 'self' https://example.com; object-src 'self'",
Official documentation
The official documentation also provides an excellent explanation on the topic, see "Tutorial: Migrate to Manifest V2".
For me solution was changing:
<script type="text/javascript" src="bgScript.js"></script>
to:
<script src="bgScript.js"></script>
Maybe it's also help others!
I work on a javascript library that customers include on their site to embed a UI widget. I want a way to test dev versions of the library live on the customer's site without requiring them to make any changes to their code. This would make it easy to debug issues and test new versions.
To do this I need to change the script include to point to my dev server, and then override the load() method that's called in the page to add an extra parameter to tell it what server to point to when making remote calls.
It looks like I can add JS to the page using a chrome extension, but I don't see any way to modify the page before it's loaded. Is there something I'm missing, or are chrome extensions not allowed to do this kind of thing?
I've done a fair amount of Chrome extension development, and I don't think there's any way to edit a page source before it's rendered by the browser. The two closest options are:
Content scripts allow you to toss in extra JavaScript and CSS files. You might be able to use these scripts to rewrite existing script tags in the page, but I'm not sure it would work out, since any script tags visible to your script through the DOM are already loaded or are being loaded.
WebRequest allows you to hijack HTTP requests, so you could have an extension reroute a request for library.js to library_dev.js.
Assuming your site is www.mysite.com and you keep your scripts in the /js directory:
chrome.webRequest.onBeforeRequest.addListener(
function(details) {
if( details.url == "http://www.mysite.com/js/library.js" )
return {redirectUrl: "http://www.mysite.com/js/library_dev.js" };
},
{urls: ["*://www.mysite.com/*.js"]},
["blocking"]);
The HTML source will look the same, but the document pulled in by <script src="library.js"></script> will now be a different file. This should achieve what you want.
Here's a way to modify content before it is loaded on the page using the WebRequest API. This requires the content to be loaded into a string variable before the onBeforeRequest listener returns. This example is for javascript, but it should work equally well for other types of content.
chrome.webRequest.onBeforeRequest.addListener(
function (details) {
var javascriptCode = loadSynchronously(details.url);
// modify javascriptCode here
return { redirectUrl: "data:text/javascript,"
+ encodeURIComponent(javascriptCode) };
},
{ urls: ["*://*.example.com/*.js"] },
["blocking"]);
loadSynchronously() can be implemented with a regular XMLHttpRequest. Synchronous loading will block the event loop and is deprecated in XMLHttpRequest, but it is unfortunately hard to avoid with this solution.
You might be interested in the hooks available in the Opera browser. Opera used to have* very powerful hooks, available both to User JavaScript files (single-file things, very easy to write and deploy) and Extensions. Some of these are:
BeforeExternalScript:
This event is fired when a script element with a src attribute is encountered. You may examine the element, including its src attribute, change it, add more specific event listeners to it, or cancel its loading altogether.
One nice trick is to cancel its loading, load the external script in an AJAX call, perform text replacement on it, and then re-inject it into the webpage as a script tag, or using eval.
window.opera.defineMagicVariable:
This method can be used by User JavaScripts to override global variables defined by regular scripts. Any reference to the global name being overridden will call the provided getter and setter functions.
window.opera.defineMagicFunction:
This method can be used by User JavaScripts to override global functions defined by regular scripts. Any invocation of the global name being overridden will call the provided implementation.
*: Opera recently switched over to the Webkit engine, and it seems they have removed some of these hooks. You can still find Opera 12 for download on their website, though.
I had an idea, but I didn't try it, but it worked in theory.
Run content_script that was executed before the document was loaded, and register a ServiceWorker to replace page's requested file content in real time. (ServiceWorker can intercept all requests in the page, including those initiated directly through the dom)
Chrome extension (manifest v3) allow us to add rules for declarativeNetRequest:
chrome.declarativeNetRequest.updateDynamicRules({
addRules: [
{
"id": 1002,
"priority": 1,
"action": {
"type": "redirect",
"redirect": {
"url": "https://example.com/script.js"
}
},
"condition": {
"urlFilter": 'https://www.replaceme.com/js/some_script_to_replace.js',
"resourceTypes": [
'csp_report',
'font',
'image',
'main_frame',
'media',
'object',
'other',
'ping',
'script',
'stylesheet',
'sub_frame',
'webbundle',
'websocket',
'webtransport',
'xmlhttprequest'
]
}
},
],
removeRuleIds: [1002]
});
and debug it by adding listener:
chrome.declarativeNetRequest.onRuleMatchedDebug.addListener(
c => console.log('onRuleMatchedDebug', c)
)
It's not a Chrome extension, but Fiddler can change the script to point to your development server (see this answer for setup instructions from the author of Fiddler). Also, with Fiddler you can setup a search and replace to add that extra parameter that you need.