Expose a function to the page when installed via chrome://extensions - google-chrome-extension

I have a UserScript running with #grant none, installed directly in chrome://extensions without Tampermonkey, and I am trying to define a function into the host page global namespace:
window.setRefinedTwitterLiteFeatures = features => {
settings = Object.assign(settings, features)
localStorage.setItem(storageKey, JSON.stringify(settings))
setFeatures()
}
Unfortunately this doesn't seem to be working. I've also tried to use unsafeWindow without luck.

Seems like you install it as described in the repo - that is directly into chrome://extensions - but Chrome doesn't support nonsandboxed environment unlike Tampermonkey. The only way to mimic it is to create a DOM script element, assign the code you want to expose to textContent and append to document.body, for example. Just as shown in the canonic answer “Insert code into the page context using a content script”.
The next problem is twitter's CSP that disallows the code in inline script elements. Luckily, as we can see in devtools network inspector for the main page request, the CSP has a nonce exception, which we can reuse:
function runScriptInPageContext(text) {
const script = document.createElement('script');
script.text = text;
script.nonce = (document.querySelector('script[nonce]') || {}).nonce;
document.documentElement.appendChild(script);
script.remove();
}
Usage examples running code immediately:
runScriptInPageContext("alert('foo')");
runScriptInPageContext(`(${() => {
alert('foo');
})()`);
Usage example exposing a global function:
runScriptInPageContext(function foo() {
alert('foo');
});

Related

Can I prevent node_modules bundled with webpack from using window.postMessage?

For security reasons, I need to disallow 3rd party modules which are included in my bundle by webpack from using window.postMessage to communicate with other processes in my Electron app.
Is that possible?
A standard trick could help here: simply move the method to another variable on window, like this maybe:
window._postMessage = window.postMessage;
window.postMessage = () => {};
Run this first on your render script or with a <script> tag on your html and plugins that would use that can't send events anymore (in fact, they don't crash but never get a response).
Edit
If you want to make sure that only authorized user can use it, something like this could work:
function createSecurePostMessage() {
const _postMessage = window.postMessage;
return {
get() {return function(...args) {
if (isAuthorized(...args) {
_postMessage(...args);
}
}
}
}
window.postMessage = createSecurePostMessage().get();
Now the original postMessage is inside the function and inaccessible and you can implement a method, for example, that checks if a certain message can be sent. If someone calls create again, then the secured postMethod will just be 'secured' again.

Access content script by injected script

I have a content script where I define a small task api, I would like to access this api (namespace) through the script injected by browser.tabs; executeScript (). for example:
//contentScript.js
const api = new (function () {
     this.doSomething()
})();
// injectedScript
console.log (api.doSomething ())
It is possible? If yes, how?
I'm trying to do this, and am getting a RefereceError.
Being the content script and the script injected, considered by the content scripts documentation, why do not they see each other?
Thanks
The symbol api in your content script is a const, which is not visible outside that specific script. The two scripts run in the same global so var api = ... or simply function api() { ... } should work.

Re-inject content scripts after update

I have a chrome extension which injects an iframe into every open tab. I have a chrome.runtime.onInstalled listener in my background.js which manually injects the required scripts as follows (Details of the API here : http://developer.chrome.com/extensions/runtime.html#event-onInstalled ) :
background.js
var injectIframeInAllTabs = function(){
console.log("reinject content scripts into all tabs");
var manifest = chrome.app.getDetails();
chrome.windows.getAll({},function(windows){
for( var win in windows ){
chrome.tabs.getAllInWindow(win.id, function reloadTabs(tabs) {
for (var i in tabs) {
var scripts = manifest.content_scripts[0].js;
console.log("content scripts ", scripts);
var k = 0, s = scripts.length;
for( ; k < s; k++ ) {
chrome.tabs.executeScript(tabs[i].id, {
file: scripts[k]
});
}
}
});
}
});
};
This works fine when I first install the extension. I want to do the same when my extension is updated. If I run the same script on update as well, I do not see a new iframe injected. Not only that, if I try to send a message to my content script AFTER the update, none of the messages go through to the content script. I have seen other people also running into the same issue on SO (Chrome: message content-script on runtime.onInstalled). What is the correct way of removing old content scripts and injecting new ones after chrome extension update?
When the extension is updated Chrome automatically cuts off all the "old" content scripts from talking to the background page and they also throw an exception if the old content script does try to communicate with the runtime. This was the missing piece for me. All I did was, in chrome.runtime.onInstalled in bg.js, I call the same method as posted in the question. That injects another iframe that talks to the correct runtime. At some point in time, the old content scripts tries to talk to the runtime which fails. I catch that exception and just wipeout the old content script. Also note that, each iframe gets injected into its own "isolated world" (Isolated world is explained here: http://www.youtube.com/watch?v=laLudeUmXHM) hence newly injected iframe cannot clear out the old lingering iframe.
Hope this helps someone in future!
There is no way to "remove" old content scripts (Apart from reloading the page in question using window.location.reload, which would be bad)
If you want to be more flexible about what code you execute in your content script, use the "code" parameter in the executeScript function, that lets you pass in a raw string with javascript code. If your content script is just one big function (i.e. content_script_function) which lives in background.js
in background.js:
function content_script_function(relevant_background_script_info) {
// this function will be serialized as a string using .toString()
// and will be called in the context of the content script page
// do your content script stuff here...
}
function execute_script_in_content_page(info) {
chrome.tabs.executeScript(tabid,
{code: "(" + content_script_function.toString() + ")(" +
JSON.stringify(info) + ");"});
}
chrome.tabs.onUpdated.addListener(
execute_script_in_content_page.bind( { reason: 'onUpdated',
otherinfo: chrome.app.getDetails() });
chrome.runtime.onInstalled.addListener(
execute_script_in_content_page.bind( { reason: 'onInstalled',
otherinfo: chrome.app.getDetails() });
)
Where relevant_background_script_info contains information about the background page, i.e. which version it is, whether there was an upgrade event, and why the function is being called. The content script page still maintains all its relevant state. This way you have full control over how to handle an "upgrade" event.

Can js code in chrome extension detect that it's executed as content script?

I have a google chrome extension that shares some code between it's content script and background process / popup. If it some easy and straightforward way for this code to check if it's executed as content script or not? (message passing behavior differs).
I can include additional "marker" javascript in manifest or call some chrome fnction unavailable from content script and check for exceptions - but these methods looks awkward to be. Maybe it's some easy and clean way to make this check?
To check whether or not your script is running as a content script, check if it is not being executed on a chrome-extension scheme.
if (location.protocol == 'chrome-extension:') {
// Running in the extension's process
// Background-specific code (actually, it could also be a popup/options page)
} else {
// Content script code
}
If you further want to know if you're running in a background page, use chrome.extension.getBackgroundPage()=== window. If it's true, the code is running in the background. If not, you're running in the context of a popup / options page / ...
(If you want to detect if the code is running in the context of an extension, ie not in the context of a regular web page, check if chrome.extension exists.)
Explanation of revised answer
Previously, my answer suggested to check whether background-specific APIs such as chrome.tabs were defined. Since Chrome 27 / Opera 15, this approach comes with an unwanted side-effect: Even if you don't use the method, the following error is logged to the console (at most once per page load per API):
chrome.tabs is not available: You do not have permission to access this API. Ensure that the required permission or manifest property is included in your manifest.json.
This doesn't affect your code (!!chrome.tabs will still be false), but users (developers) may get annoyed, and uninstall your extension.
The function chrome.extension.getBackgroundPage is not defined at all in content scripts, so alone it can be used to detect whether the code is running in a content script:
if (chrome.extension.getBackgroundPage) {
// background page, options page, popup, etc
} else {
// content script
}
There are more robust ways to detect each context separately in a module I wrote
function runningScript() {
// This function will return the currently running script of a Chrome extension
if (location.protocol == 'chrome-extension:') {
if (location.pathname == "/_generated_background_page.html")
return "background";
else
return location.pathname; // Will return "/popup.html" if that is the name of your popup
}
else
return "content";
}

How to use the experimental offscreenTab API?

I've been searching for examples and reference and have come up with nothing. I found a note in offscreenTab source code mentioning it cannot be instantiated from a background page (it doesn't have a tab for the offscreenTab to relate to). Elsewhere I found mention that popup also has no tie to a tab.
How do you successfully create an offscreenTab in a Chrome extension?
According to the documentation, offscreenTabs.create won't function in a background page. Although not explicitly mentioned, the API cannot be used in a Content script either. Through a simple test, it seems that the popup has the same limitation as a background page.
The only leftover option is a tab which runs in the context of a Chrome extension. The easiest way to do that is by using the following code in the background/popup:
chrome.tabs.create({url: chrome.extension.getURL('ost.htm'), active:false});
// active:false, so that the window do not jump to the front
ost.htm is a helper page, which creates the tab:
chrome.experimental.offscreenTabs.create({url: '...'}, function(offscreenTab) {
// Do something with offscreenTab.id !
});
To change the URL, use chrome.experimental.offscreenTabs.update.
offscreenTab.id is a tabId, which ought to be used with the chrome.tabs API. However, at least in Chrome 20.0.1130.1, this is not the case. All methods of the tabs API do not recognise the returned tabID.
A work-around is to inject a content script using a manifest file, eg:
{"content_scripts": {"js":["contentscript.js"], "matches":["<all_urls>"]}}
// contentscript.js:
chrome.extension.sendMessage({ .. any request .. }, function(response) {
// Do something with response.
});
Appendum to the background page:
chrome.extension.onMessage.addListener(function(message, sender, sendResponse) {
// Instead of checking for index == -1, you can also see if the ID matches
// the ID of a previously created offscreenTab
if (sender.tab && sender.tab.index === -1) {
// index is negative if the tab is invisible
// ... do something (logic) ...
sendResponse( /* .. some response .. */ );
}
});
With content scripts, you've got full access to a page's DOM. But not to the global object. You'll have to inject scripts (see this answer) if you want to run code in the context of the page.
Another API which might be useful is the chrome.webRequest API. It can be used to modify headers/abort/redirect requests. Note: It cannot be used to read or modify the response.
Currently, the offscreenTabs API is experimental. To play with it, you have to enable the experimental APIs via chrome://flags, and add "permissions":["experimental"] to your manifest file. Once it's not experimental any more, use "permissions":["offscreenTabs"].

Resources