I am trying to create a chrome extension that will notify me of host/domain changes when I'm testing a specific website. Often links will be present that point towards developer or live environments and I'd like to be warned if I follow one of these links as the websites are often identical.
Edit for clarity: I want it to alert me when a link takes me away from http(s)://example.staging.something.com and ends up on the live site http(s)://www.example.com or the dev site http(s)://example.dev.something.com
So far I have managed to create a script that identifies when I am on a staging url (our test environment) however I've been unable to reverse this logic to give me a warning when I navigate to a url that doesn't contain 'staging'.
My manifest.json
{
"manifest_version": 2,
"name": "A What URL",
"description": "This extension monitors and warns you of domain changes",
"version": "1.0",
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"background": { "scripts": ["background.js"],
"persistent": false
},
"permissions": [
"activeTab",
"webNavigation"
]
}
my background.js
chrome.webNavigation.onCommitted.addListener(function(e) {
alert ("you are still on staging!");
}, {url: [{hostContains: 'staging'}]});
I'm sure this is simple but it appears my brain is far simpler!
There are multiple ways to solve your problem.
Use the chrome.webRequest.onBeforeSendHeaders (or .onSendHeaders) event to get notified when a request is sent to your production website, and check whether the Referer header is set to your staging site. This only works if the document referrer is set (this won't be the case if you're navigating from https to http, or if the "noreferrer" referrer policy is set).
In the absence of a referer, use the webNavigation, webRequest and/or tabs APIs to track the navigation state of a page, and do whatever you want when you detect that the transition production -> dev occurs. Implementing this correctly is very difficult.
Here is a sample for the first method:
// background.js
chrome.webRequest.onBeforeSendHeaders.addListener(function(details) {
var referer;
for (var i = 0; i < details.requestHeaders.length; ++i) {
var header = details.requestHeaders[i];
if (header.name.toLowerCase() === 'referer' && header.value) {
referer = header.value;
break;
}
}
if (referer && /^https?:\/\/dev\.example\.com(\/|$)/.test(referer)) {
alert('Navigated from dev to production!');
}
}, {
urls: ['*://production.example.com/*', '*://prod.example.com/*'],
types: ['main_frame', 'sub_frame'] // Track navigations, not img/css/etc.
}, ['requestHeaders']);
Example of manifest.json to test the previous logic:
{
"name": "Detect nav from dev to prod",
"version": "1",
"manifest_version": 2,
"background": {
"scripts": ["background.js"],
"persistent": true
},
"permissions": [
"webRequest",
"*://*/*"
]
}
Unfortunately, you can't "invert" the filter, so you'll have to catch all events and filter in the code.
Here come the Chrome Events. Your code suggests you treat them like DOM events (with the e parameter). Instead, they pass arguments depending on the event in question, sometimes several.
If you look at the documentation, you'll see that the expected callback format is:
function(object details) {...};
Where details will contain, among other things, a url property.
So you'll have:
chrome.webNavigation.onCommitted.addListener(function(details) {
// Regexp matches first "//", followed by any number of non-/, followed by "staging"
if(!details.url.match(/[^\/]*\/\/[^\/]*staging/)) {
alert ("This is no longer staging!");
}
});
Note that this is going to be extremely annoying unless you can turn it off - it will match almost any page, after all.
Adding my final solution as an answer as requested in case anyone else is interested in the future.
Many thanks to both Xan and Rob with the help! If I could tick both I would but in the end I ended up ticking the one that led to my implementation.
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
var queryInfo = {
active: true,
currentWindow: true
};
chrome.tabs.query(queryInfo, function(tabs) {
var tab = tabs[0];
var url = tab.url;
if(!url.match(/[^\/]*\/\/[^\/]*staging/) && changeInfo.status=="complete"){
alert ("WTF!!! " +url);
}
});
});
Related
i'm a user of a site (of which I have no control on) which sometimes presents a redirect loop (page A redirects to B, and B to A). Removing a specific cookie fixes the issue. I'd like to automatize the suppression of that cookie when a direct loop is detected.
I was thinking of developing a small chrome extension for that effect (which would be the first plugin I write). Would that work?
I tried using the webRequest API (onBeforeRedirect), but it doesn't seem to catch the redirect.
Here is my manifest.json:
{
"manifest_version": 2,
"name": "name",
"description": "description",
"version": "1.0",
"permissions": [
"<all_urls>","webRequest","webRequestBlocking"
],
"background": {
"scripts": ["test.js"],
"persistent": true
}
}
And here is my test.js:
var callback = function(details) {
console.log("redirect caught!");
};
chrome.webRequest.onBeforeRedirect.addListener(callback);
What am I doing wrong ?
I am just trying to get the MDN example chrome extension working. here is the manifest.json
{
"description": "Altering HTTP responses",
"manifest_version": 2,
"name": "http-response-filter",
"version": "1.0",
"homepage_url":
"https://github.com/mdn/webextensions-examples/tree/master/http-response",
"icons": {
"48": "SA-48x48.png"
},
"background": {
"scripts": ["background.js"]
}
}
and here is the background.js
function listener(details) {
console.log("******listen");
let filter = browser.webRequest.filterResponseData(details.requestId);
let decoder = new TextDecoder("utf-8");
let encoder = new TextEncoder();
filter.ondata = event => {
let str = decoder.decode(event.data, { stream: true });
// Just change any instance of Example in the HTTP response
// to WebExtension Example.
str = str.replace(/Example/g, "WebExtension Example");
filter.write(encoder.encode(str));
filter.disconnect();
};
return {};
}
console.log("******");
browser.webRequest.onBeforeRequest.addListener(
listener,
{ urls: ["https://example.com/*"], types: ["main_frame"] },
["blocking"]
);
So i expect it to
put a couple console.logs out when i load example.com
modify "Example" to "WebExtension Example" as stated by the MDN folks
however, it doesn't work at all for me (I am using Chrome and i added it to my Extensions as an unpacked Extension, i've done other chrome extensions before but this is my first time doing a background script).
Is it possible that something is blocking this background script from running? have i just not configured it in the right way? Please point me in the right direction, thanks very much.
Your Chrome Extension needs to "ask" for permissions to use the webRequest API so that users of the extension will get notified what the extension may be able to do.
Try adding this to your manifest.json:
"permissions": [
"webRequest",
"webRequestBlocking"
]
I'm trying to add a context menu to my firefox add-on using the WebExtensions API. I need the background script to listen to a click on the menu item and send a message to the content script.
This is what I have:
manifest.json
{
"manifest_version": 2,
"name": "MyExt",
"version": "0.0.1",
"description": "Test extension",
"icons": {
"48": "icons/icon-48.png"
},
"applications": {
"gecko": {
"id": "myext#local",
"strict_min_version": "45.0"
}
},
"permissions": ["contextMenus"],
"background": {
"scripts": ["background-scripts.js"]
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content-script.js"]
}
]
}
background-scripts.js
chrome.contextMenus.create({
id: "clickme",
title: "Click me!",
contexts: ["all"]
});
browser.contextMenus.onClicked.addListener(function(info, tab) {
console.log("Hello World!");
sendMessage(info, tab);
});
function sendMessage(info, tab) {
chrome.tabs.query(
{active: true, currentWindow: true },
function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, "Test message from background script.");
}
);
}
content-script.js
browser.runtime.onMessage.addListener(function(msg) {
console.log(msg);
});
The menu item is being created, but the messages are never displayed (I'm checking both the Web and Browser Console). Since the click event is not working, the message is not sent either.
I'm following this example from MDN, which does not work. It also creates the menu items, but they do nothing, which makes me think that something changed in the API and MDN didn't bother to update the documentation.
Any ideas? Thanks.
Your code works as written:
I strongly suspect that your issue is either of:
You are testing using a version of Firefox prior to Firefox 48. Firefox 48 is in Beta. The contextMenus "Browser compatibility" section clearly states that the first version in which it is functional is Firefox 48. The WebExtensions API is still in development. In general, you should be testing against Firefox Developer Edition, or Firefox Nightly. You can use earlier versions if all the APIs you are using are indicated to be working in an earlier version. However, if you experience problems, you should test with Nightly. I suspect that this is your most likely issue as you indicated that the contextMenus example code was not doing anything.
You have not navigated to an actual webpage. Your content-script.js is only loaded into pages that match one of the supported schemes: that is, "http", "https", "file", "ftp", "app". It is not loaded in about:* pages. If this was your issue, you would have had partial functionality from the contextMenus example code. In addition, using your code, the Browser Console would have, after a delay, generated an error message:
Error: Could not establish connection. Receiving end does not exist.
A note on your code:
Note, your sendMessage() function is, potentially, overly complex. You are searching for the active tab when the tabs.Tab object for the tab in which the context menu item was selected was already passed to your function as one of the arguments. A shorter version could be:
function sendMessage(info, tab) {
chrome.tabs.sendMessage(tab.id, "Test message from background script.");
}
I would be interested to know if you have encountered a situation where you needed to search for the active tab rather than use the tabs.Tab object provided by the context menu listener.
Every once in a while my chrome extension's background.js page freezes, i have no idea what is causing it.
When the background.js file has frozen, it no longer responds to messages from the content script, and when I try to open the background page via the extensions manager to inspect it, the window pops up but it stays blank, and no interface appears.
The only things im doing in the background page are message passing and retrieving localstorage variables.
I cant figure out what is causing this, the bug only seems to have happened since i transitioned to the new chrome.runtime api, from the chrome.extension api
Can anyone tell me what is going wrong here? or help me figure it out? Thanks!
Heres the background.js file's code in its entirety
if (!chrome.runtime) {
// Chrome 20-21
chrome.runtime = chrome.extension;
} else if(!chrome.runtime.onMessage) {
// Chrome 22-25
chrome.runtime.onMessage = chrome.extension.onMessage;
chrome.runtime.sendMessage = chrome.extension.sendMessage;
}
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.method == "getLocalStorage")
sendResponse({data: localStorage[request.key]}); // decodeURIComponent
else if (request.method == "setLocalStorage")
sendResponse({data: localStorage[request.key]=request.value});
else
sendResponse({}); // send empty response
});
Is it possible a deadlock situation is occurring that is freezing the page? It doesnt cause the CPU to go mad, so im guessing its not an endless loop.
Update
here is the manifest.json as requested
{
"manifest_version": 2,
"content_scripts": [ {
"exclude_globs": [ "http://*.facebook.com/ajax/*", "https://*.facebook.com/ajax/*" , "http://www.facebook.com/ai.php?*", "https://www.facebook.com/ai.php?*", "http://www.facebook.com/ajax/*", "https://www.facebook.com/ajax/*"],
"include_globs": [ "http://*.facebook.com/*", "https://*.facebook.com/*" ],
"js": [ "script.js" ],
"matches": [ "http://*.facebook.com/*", "https://*.facebook.com/*" ],
"run_at": "document_start"
} ],
"converted_from_user_script": true,
"background": {"scripts": ["background.js"],
"persistent": false},
"icons": {
"128": "ET-128x128.png",
"48": "ET-48x48.png"
},
"key": "xxxxxxxxxxxxxxxxxxxx",
"name": "Extension Test",
"short_name": "ET",
"description": "ET Does this and that, but doesnt phone home",
"version": "999",
"homepage_url": "http://www.etphonehome.com"
}
Only disabling and re-enabling the extension get it to start working again, once the background page has frozen
Below is a screenshot of the frozen background page inspection window:
The localStorage API is problematic because in chrome it is a synchronous API to an inherently asynchronous operation (called from a renderer process, which must then communicate with the browser process that reads from / writes to a backing store in the filesystem and possibly replies back to the renderer process). While it should not in theory be possible to cause deadlocks from webpage or extension code, it's possible there are bugs in chrome's implementation.
One thing you might try is switching from localStorage to chrome.storage.local. It is a little more work to use since it has an asynchronous API, but does not suffer from the same implementation complexity as localStorage.
E.g.
sendResponse({data: localStorage[request.key]});
becomes
chrome.storage.local.get(request.key, function(storageResult) {
sendResponse(storageResult[request.key]);
});
So i'd like to run a script when the tab reloads in a specified URL. It almost works, but actually id doesn't :)
This is my manifest file:
{
"manifest_version": 2,
"name": "Sample Extension",
"description": "Sample Chrome Extension",
"version": "1.0",
"content_scripts":
[
{
"matches": ["http://translate.google.hu/*"],
"js": ["run.js"]
}
],
"permissions":
[
"activeTab",
"tabs"
],
"browser_action":
{
"default_title": "Sample",
"default_icon": "icon.png"
}
}
and this is run.js:
chrome.tabs.onUpdated.addListener(
function ( tabId, changeInfo, tab )
{
if ( changeInfo.status === "complete" )
{
chrome.tabs.executeScript( null, {file: "program.js"} );
}
}
);
The programs.js just alerts some text (yet). When I put an alert to the first line of the run.js, it alerts, but when I put it in the if, it doesn't. I can't find the problem. Did I type something wrong?
Assuming that http://translate.google.hu/* pages are the ones you wish to inject code into on reload, you would have to go about it in a slightly different way. Currently you are always injecting code into those pages (without the permission to do so, no less) and then trying to use the chrome.tabs api inside that content script, which you can't do. Instead, we will put the listener in a background page and inject the code only on a page refresh, like you want. First the manifest:
{
"manifest_version": 2,
"name": "Sample Extension",
"description": "Sample Chrome Extension",
"version": "1.0",
"background": {
"scripts": ["background.js"]
},
"permissions":[
"http://translate.google.hu/*", "tabs"
]
}
background.js
chrome.tabs.onUpdated.addListener(function(tabId,changeInfo,tab){
if (tab.url.indexOf("http://translate.google.hu/") > -1 &&
changeInfo.url === undefined){
chrome.tabs.executeScript(tabId, {file: "program.js"} );
}
});
This will listen for the onUpdated event, checks if it is one of the url's that we want to inject into, and then it checks if the page was reloaded. That last step is accomplished by checking if changeInfo.url exists. If it does, then that means that the url was changed and thus not a refresh. Conversely, if it doesn't exist, then the page must have only been refreshed.
2021
If you want to detect reload from background.js in manifest 3 (maybe also 2), chrome.tabs.onUpdated approach didn't work for me :/ It was invoked too many times.
That what worked for me in the end!
// --- On Reloading or Entering example.com ---
chrome.webNavigation.onCommitted.addListener((details) => {
if (["reload", "link", "typed", "generated"].includes(details.transitionType) &&
details.url === "http://example.com/") {
codeAfterReload();
// If you want to run only when the reload finished (at least the DOM was loaded)
chrome.webNavigation.onCompleted.addListener(function onComplete() {
codeAfterReloadAndFinishSomeLoading();
chrome.webNavigation.onCompleted.removeListener(onComplete);
});
}
});
For more transition types: https://developer.chrome.com/docs/extensions/reference/history/#transition_types
good luck :)
content_scripts are run at every page (re)load, so it's best to just use those to detect it.
This way you also don't risk running any code in the background before your content_script is ready to receive any message.