Modifying Response Headers in Chrome [duplicate] - google-chrome-extension

In my chrome extension I need to add a line to header of every site browsed. In background.js file I add such code:
var responseListener = function(details){
var rule = {
"name": "Access-Control-Allow-Origin",
"value": "*"
};
details.responseHeaders.push(rule);
return {responseHeaders: details.responseHeaders};
};
chrome.webRequest.onHeadersReceived.addListener(responseListener,
{urls: [ "*://*/*" ] },
["blocking", "responseHeaders"]);
While debugging the handler is called and newly added header successfully passes any filters I have found upper in the stack. But it is not seen on network tab's Response headers section and does not effects any code. I use these permissions:
"tabs","<all_urls>", "http://*/*" ,"webRequest","webRequestBlocking", "webNavigation"
Is there a new policy or API changed which disallow to do such things or there is some bug in my 10 lines of a code?

The Network tab of the Chrome Developer tools does not show the modifications from extensions. See https://crbug.com/258064
If you wish to see whether your extension has successfully modified a request, visit chrome://net-internals/#events, click on a request of type URL_REQUEST and look for URL_REQUEST_DELEGATE entries, e.g. URL_REQUEST_FAKE_RESPONSE_HEADERS_CREATED (this is an example of a log entry generated via the chrome.declarativeWebRequest API) or "delegate_info = "extension [extension name]" (generated by chrome.webRequest).

Related

How do I send a message from background script to content script?

I want my background script in my chrome extension to send a message to the content script and then wait for the response. I tried a couple of different solutions I found but I always get the same error:
_generated_background_page.html:1 Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
_generated_background_page.html:1 Error handling response: TypeError: Cannot read property 'getSelected' of undefined
at chrome-extension://pkolpfkgeblgjiiaklpppfmeomlkbhop/background_script.js:11:25
I also already tried disabling all the other chrome extensions.
All the code that might be important:
//background_script.js
chrome.contextMenus.onClicked.addListener(function(info, tab){
chrome.tabs.sendMessage(tab.id, {
content: "getSelected"
}, function(response) {
console.log(response.getSelected());
});
});
//content_script.js
chrome.runtime.onMessage.addListener(function(message, sender, callback) {
if (message.content == "getSelected") {
callback({getSelected: getSelected()});
}
});
//manifest.json
{
"manifest_version": 2,
"content_scripts":[ {
"matches": ["<all_urls>"],
"js": ["content_script.js"]
}],
"background":{
"scripts": ["background_script.js"]
},
"permissions": [
"*://*/*",
"activeTab",
"declarativeContent",
"storage",
"contextMenus",
"tabs"
]
}
Thanks in advance for the help :)
You need to define getSelected() in the content script, it's not a built-in function. You probably meant getSelection() which is a built-in function.
When the response is sent it's not a function so it can't be invoked: you need to remove () in response.getSelected()
Receiving end does not exist usually means the tab doesn't run the content script:
happens if the tab is not a web page e.g. it's an empty new tab page or a chrome:// page or an extension page
or the tab wasn't reloaded after you reloaded or re-enabled the extension, see content script re-injection after upgrade or install
Apparently you want to get the currently selected text, in which case you don't need a declared content script at all so you can remove content_scripts section and instead use programmatic injection:
chrome.contextMenus.onClicked.addListener((info, tab) => {
chrome.tabs.executeScript(tab.id, {
frameId: info.frameId,
runAt: 'document_start',
code: 'getSelection().toString()',
}, ([sel] = []) => {
if (!chrome.runtime.lastError) {
console.log(sel);
}
})
});
Notes:
getSelection() returns a Selection object which is a complex DOM object so it cannot be transferred, hence we explicitly extract the selection as a string.
Only simple types like strings, numbers, boolean, null, and arrays/objects of such simple types can be transferred.
We're using the frameId where the user invoked the menu
Using runAt: 'document_start' ensures the code runs immediately even if the page is still loading its initial HTML
For this particular task you don't need "*://*/*" or tabs in permissions.
Where to read console messages from background.js in a Chrome extension?
P.S. For more complex data extraction, use file: 'content.js', instead of code: .... and transfer the last expression from content.js like this:
function foo() {
let results;
// .......
return results;
}
foo(); // this line should be the last executed expression in content.js

Is there an easy way to limit my content script to a specific array of matches?

I want my Chrome extension to only work only on news websites, so I have a long list of many URLs I'd like to limit it to.
Is there an easier way to restrict my extension than manually adding the long list in the "matches" field of the manifest.json? Thanks!
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://www.newswebsite.com/*",
"...long long array with urls..."],
...
}
],
...
}
In the extension's manifest
I'll just start by saying that well, the "simplest" way to inject your content scripts in all the sites of your list is to actually just add them in the array of "matches" in the extension's manifest.
If you are worrying about having to insert each site in the array manually then that's no big deal, you can easily paste it inside a text editor and use find and replace to do what you want, for example replacing \n with /*",\n"*:// and then editing the first and last manually:
site1.com -> site1.com/*", -> "*://site1.com/*",
site2.net -> "*://site2.net/*", -> "*://site2.net/*",
site3.org -> "*://site3.org/*", -> "*://site3.org/*"
"*//
Once you've got this you can just copy and paste it inside your "matches": [ ... array
Through the background script with the tabs API
If you don't really want to add them inside your manifest, you can put them in a text file inside your extension's directory, then load it in your background page and add a listener to chrome.tabs.onUpdated to check when the URL changes and inject the script if the new URL matches one of the sites. This IMHO is more complicated than simply adding them in your manifest.
Working example
Your list.txt:
site1.com
site2.net
site3.org
Your background.js script:
// Function to extract the hostname from an URL.
function getHostname(url) {
return url.match(/^(.*:)\/\/([A-Za-z0-9\-\.]+)/)[2];
}
// XHR to get the list.txt
var xhr = new XMLHttpRequest();
xhr.addEventListener('readystatechange', function() {
if (xhr.readyState == 4 && xhr.status == 200) {
// Parse the list and create a set from it:
var list = new Set(xhr.responseText.split('\n'));
// Listen for tabs.onUpdated:
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
// If the tab changed URL:
if (changeInfo.url)
// If the new URL is one of the sites in the list:
if (list.has(getHostname(changeInfo.url)))
// Inject the script:
chrome.tabs.executeScript(tabId, {file: 'yourScript.js'});
});
}
});
xhr.open('GET', 'list.txt', true);
xhr.send();
Don't forget to also add permissions for <all_urls> (since you're gonna inject scripts in sites not listed directly), and the chrome.tabs API in your manifest:
...
"permissions": ["<all_urls>", "tabs"],
...
Through the background script with the declarativeContent API
Similar to the previous one, but simpler: parse the list.txt and create a new rule to be processed by the chrome.declarativeContent API. You will not have to worry about matching the URLs manually, and Chrome will run the matches and optimize the rule for you.
chrome.runtime.onInstalled.addListener(function() {
chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
var xhr = new XMLHttpRequest();
xhr.addEventListener('readystatechange', function() {
if (xhr.readyState == 4 && xhr.status == 200) {
var list = xhr.responseText.split('\n');
// Create a new rule with an array of conditions to match all the sites:
var rule = {
conditions: list.map(site => new chrome.declarativeContent.PageStateMatcher({
pageUrl: { hostEquals: site, schemes: ['http', 'https'] }
})),
// To inject the script when the conditions are met:
actions: [new chrome.declarativeContent.RequestContentScript({js: ['yourScript.js']})]
}
// And register it:
chrome.declarativeContent.onPageChanged.addRules([rule]);
}
});
});
xhr.open('GET', 'list.txt', true);
xhr.send();
});
Note that the above code is all wrapped inside a chrome.runtime.onInstalled listener, since that declarativeContent rules are persistent, and you don't need to add them each time your extension starts.
You'll need permission for both "<all_urls>" and "declarativeContent" in your manifest in this case:
...
"permissions": ["<all_urls>", "declarativeContent"],
...
Now, with this said, I actually think that the easiest way to do what you want is to just simply add the sites in your "matches" field of the extension's manifest, but you're free to do what you think is best. I do actually use the second approach often, however overall the third method is the most efficent if you don't want to manually add the matching rules in your manifest.json.
Take a look at the documentation for the methods and types I used in the examples if you want to know more:
chrome.tabs.onUpdated
chrome.tabs.executeScript
chrome.declarativeContent.PageStateMatcher
chrome.declarativeContent.RequestContentScript
I was searching for an answer that worked with the framework, but came to creating straight Javascript within the Listener and for the active tab. I also wanted to use a defined function, rather than loading a separate Javascript file.
My end goal was to have the extension respond to a hotkey, but only for specific hosts. This is how I did it:
In manifest.json:
...
"commands": {
"some-command": {
"suggested_key": {
"default": "Ctrl+Shift+L"
},
"description": "Tell me something special."
}
},
...
In background.js:
chrome.commands.onCommand.addListener(function(command) {
chrome.tabs.getSelected(null, function(tab) {
var url = new URL(tab.url);
if (url.hostname != 'somedomain.com') return;
if (command == 'some-command') {
custom_function();
}
});
});
function custom_function {}
The end result is that the extension only works on tabs that have the particular domain name, regardless if triggering a hotkey command or through the address bar popup. I didn't include the address bar popup code, as that is right on the "build a Chrome Extension" guide - something I'd expect we have all already completed.

Chrome DevTools Protocol: control new tabs

Need to be done: I need to simulate user interactions (journeys) across a chain of sites.
Question: Do you have any tips how to programatically controll a tab opened as a result of a simulated click?
My experience:
I'm using the chrome-remote-interface npm package.
I'm able to simulate a click with a custom ChromeController class which initializes the chrome-remote-interface and these methods:
async simulateClick(selector) {
return await this.evaluate(function (selector) {
document.querySelector(selector).click()
}, selector);
}
/**
* Shamelessly stolen from simple-headless-browser
*/
async evaluate (fn, ...args) {
const exp = args && args.length > 0 ? `(${String(fn)}).apply(null, ${JSON.stringify(args)})` : `(${String(fn)}).apply(null)`
const result = await this.client.Runtime.evaluate({
expression: exp,
returnByValue: true
})
return result
}
Now I would like to interact with the recently opened tab. I can get the targetId of the new tab with the experimenetal Target Domain (prototyping in node cli):
var targets;
chromeController.client.Target.getTargets().then(t => targets = t);
Which results in:
{ targetInfos:
[ { targetId: '97556479-cdb6-415c-97a1-6efa4e00b281',
type: 'page',
title: 'xxx/preview/239402/',
url: 'xxx/preview/239402/' },
{ targetId: 'bbfe11d5-8e4a-4879-9081-10bb7234209c',
type: 'page',
title: 'Document',
url: 'xxx/preview/239402/teaser/0/' } ] }
I am able to switch between the tabs with:
chromeController.client.Target.activateTarget({targetId:'xxx'})
However I'm not able to get any interaction with this, I can't find the connection, how to load it into the Page and Runtime objects.
I've searched in the docs and also tried googling: 'site:chromedevtools.github.io targetId' which only lead me to
> chromeController.client.Browser.getWindowForTarget({targetId: '97556479-cdb6-415c-97a1-6efa4e00b281'}).catch(e => console.log(e.message));
Promise { <pending> }
> 'Browser.getWindowForTarget' wasn't found
I've also tried to Target.setDiscoverTargets({discover: true}) and to close the original tab.
Thanks for any help!
Recently faced this same issue and in short I had to create a new dev tools protocol client for each new target I wanted control over.
My experience is with dev tools protocol using direct communication with websocket but the api is the same so it should be similar. So here is a summary of what I had to do.
Initially looking at the docs I would have assumed Target.attachToTarget should give us control of the new tab but I found that it didn't work.
My workaround was to create a listener that listened for the Target.targetCreated event which provides a targetInfos just like you found with Target.getTargets but for every new target created like a new tab, page, or iframe. Note: you need to enable Target.setDiscoverTargets in order to receive these events over the protocol.
[ { targetId: '97556479-cdb6-415c-97a1-6efa4e00b281',
type: 'page',
title: 'xxx/preview/239402/',
url: 'xxx/preview/239402/' },
{ targetId: 'bbfe11d5-8e4a-4879-9081-10bb7234209c',
type: 'page',
title: 'Document',
url: 'xxx/preview/239402/teaser/0/' } ] }
With that listener I looked for targets that were of type page, you could filter on a specific url if you know what the page will be. With the targetId in hand I requested available websocket targets following the HTTPEndpoints section near the bottom of the devtools home page.
GET /json or /json/list
A list of all available websocket targets.
[ {
"description": "",
"devtoolsFrontendUrl": "/devtools/inspector.html?ws=localhost:9222/devtools/page/DAB7FB6187B554E10B0BD18821265734",
"id": "DAB7FB6187B554E10B0BD18821265734",
"title": "Yahoo",
"type": "page",
"url": "https://www.yahoo.com/",
"webSocketDebuggerUrl": "ws://localhost:9222/devtools/page/DAB7FB6187B554E10B0BD18821265734"
} ]
I could then launch a new dev tools protocol client using the webSocketDebuggerUrl and have full control over the tab.
I know this is a pretty round about way but, its the only way I was able to make if work.
Although these days it's probably easier to use something like puppeteer to interface with multiple tabs in chrome if you can. Here is the source code to a puppeteer module that follows new tabs that could be good reference for trying to replicate it pageVideoStreamCollector.ts
This is a very late answer but just putting this here if anyone else has the same issue as help on chrome dev tools is very hard to come by. Hope it helps someone out.
I also am getting "Browser.getWindowForTarget wasn't found" on debian, google-chrome-unstable version 61

How to keep the eventlistener real time for chrome extensions?

How would I go about calling my function in a way that it is always running.
I want to get the text selection wherever the user double clicks. So I don't know how to attach the eventlistener that always stays on. not just a one time function when the document is loaded. but more like an ongoing eventlistener as long as the tab is open?
Thanks you so much!
In order to achieve what you describe (and to have it always available on every tab), you can use a content script and have it injected in every page (see code below).
If you want it to be bound to a specific tab only, you can utilize an event page (i.e. a non-persistent background page) and register a listener for the chrome.tabs.onUpdated event and programmatically inject the content script every time a new page is loaded in the specified tab.
In the aforementioned content script, you can attach an event-listener to the document itself to capture all events.
Below is the source code of a sample extension that adds the desired "feature" to every tab:
content.js
var listener = function(evt) {
var selection = window.getSelection();
if (selection.rangeCount > 0) {
var range = selection.getRangeAt(0);
var text = range.cloneContents().textContent;
console.log(text);
}
};
document.addEventListener('dblclick', listener);
manifest.json
{
"manifest_version": 2,
"name": "Test Extension",
"version": "0.0",
"offline_enabled": true,
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_end",
"all_frames": false
}]
}
Ok, so to call the javascript on a persistent manner, you must define a contentScript and call the js from there.
http://net.tutsplus.com/tutorials/javascript-ajax/developing-google-chrome-extensions/

CKeditor in Chrome Extension popup issued: "Refused to load frame from 'about:blank' because of Content-Security-Policy."

CKeditor in Chrome Extension popup issued: "Refused to load frame from 'about:blank' because of Content-Security-Policy."
I am trying to get a chrome extension updated to work with the manifest 2 and new Chrome API's that have currently broken the plugin with the latest version release 18.0.1025.142.
I am using CKEditor in a popup window that is issued by a background page in the extension.
But I get the Refused to load frame from 'about:blank' because of Content-Security-Policy.2 error -- is there a way to get around this?
Update
This could be related: http://code.google.com/p/chromium/issues/detail?id=77947&q=Error%20during%20tabs.executeScript%3A&colspec=ID%20Pri%20Mstone%20ReleaseBlock%20Area%20Feature%20Status%20Owner%20Summary
In Chrome extensions, you cannot access or modify content from protocols other than file:, http:, ftp: and chrome-extension: (data:, blob: and filesystem: when the page created the resource themselves).
I considered four approaches to solve the problem:
Bind a beforeload event to the document, and change the URL when it matches about:blank.
Does not work: The CSP is activated before this event is fired.
Bind a DOMNodeInserted event to the document, and check for about:blank iframes.
Does not work: The CSP is activated before this event is fired.
Use the webRequest API to intercept the request.
Does not work: The strict match patterns do not allow the about: protocol.
Change the src property before inserting the IFRAME in the document:
Either manually (best option), or
Modify document.createElement for IFrames, or
Modify the appendChild, insertBefore and replaceChild methods for Iframes.
For all methods, you have to create a dummy page, say blank.html within your extension, and allow access via web_accessible_resources.
Manifest file example (last four lines are important):
{
"name": "Name",
"version": "1.0",
"background": {"scripts": ["aboutblank.js"]},
"manifest_version": 2,
"content_security_policy": "default-src 'self'",
"web_accessible_resources": ["blank.html"]
}
aboutblank.js
var BLANK_PAGE = chrome.extension.getURL("blank.html");
(function(BLANK_PAGE, createElement, appendChild, insertBefore, replaceChild) {
HTMLDocument.prototype.createElement = function(str) {
var elem = createElement.call(this, str);
if (str.toUpperCase() == 'IFRAME') {
elem.src = BLANK_PAGE;
}
return elem;
};
Element.prototype.appendChild = function(newChild) {
iframe_check(newChild);
return appendChild.call(this, newChild);
};
Element.prototype.insertBefore = function(newChild, refChild) {
iframe_check(newChild);
return insertBefore.call(this, newChild, refChild);
};
Element.prototype.replaceChild = function(newChild, refChild) {
iframe_check(newChild);
return replaceChild.call(this, newChild, refChild);
};
function iframe_check(elem) {
if (elem instanceof HTMLIFrameElement && (elem.src == '' || elem.src.slice(0,11) == 'about:blank')) {
elem.src = BLANK_PAGE;
}
}
})(BLANK_PAGE,
HTMLDocument.prototype.createElement,
Element.prototype.appendChild,
Element.prototype.insertBefore,
Element.prototype.replaceChild);
Note: Option 1 is recommended over modifying these prototype methods. Also, the prototype method does not work for <iframes> injected using .innerHTML.

Resources