I try to write a Google Chrome extension that simply opens a new tab when I click left-right in a short interval. The JavaScript is no problem but I implemented this as a "content_scripts" script.
In some other threads I read that I can't access the chrome.* APIs from content_scripts (except the chrome.extension API).
Even if it's not necessary to access the chrome.tabs API to open a new window (window.open should do the job) it seems I need it though for opening a new tab with the new tab page which obviously isn't possible via window.open.
So I can't really figure out what is the best way to do that. I could use a background page which I could call from the content_script but I think there should be a much more simple way to do that, I just don't get it.
Anyone have an idea?
I think your content script will have to send a message to your background page to invoke chrome.tabs.create - content scripts cannot use the chrome api, nor can they directly communicate with the background page.
Here's a reference about message passing inside Chrome extensions for further detail, but here's the example code ( modified from the example in said reference )
// in background
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse) {
switch ( request.action) {
case 'newTab' : {
//note: passing an empty object opens a new blank tab,
//but an object must be passed
chrome.tabs.create({/*options*/});
// run callback / send response
} break;
}
return true; //required if you want your callback to run, IIRC
});
// in content script:
chrome.extension.sendMessage({action: "newTab"}, function(response) {
//optional callback code here.
});
simple and easy
document.body.onclick = function openNewWindow( ) {
window.location.href = 'javascript:void window.open( "chrome://newtab" )';
}
manifest:
,"permissions":[
"http://*/*"
,"https://*/*"
]
,"manifest_version": 2
,"content_scripts":[{
"matches":[
"http://*/*"
,"https://*/*"
]
,"js":[
"js/openWindow.js"
]
}]
alright i miss understanding the question... modified
Related
I'd like to write an extension for Thunderbird that modifies the message display (e.g. insert/replace text/markup/image).
Unfortunately, the documentation is lacking (due to recent changes?).
https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Thunderbird_extensions
is outdated
https://developer.thunderbird.net/
does not have useful examples (yet)
https://thunderbird-webextensions.readthedocs.io/
no examples either
Some examples can be found at
https://github.com/thundernest/sample-extensions
Building on https://github.com/thundernest/sample-extensions/tree/master/messageDisplay
I've modified background.js
browser.messageDisplay.onMessageDisplayed.addListener((tabId, message) => {
console.log(`Message displayed in tab ${tabId}: ${message.subject}`);
console.log(message.id);
browser.messages.getFull(message.id).then((messagepart) => {
console.log(messagepart);
body = messagepart['parts'][0]['parts'][0]['body'];
console.log(body);
body += "modified!";
console.log(body);
});
browser.windows.getCurrent().then((window)=>{
console.log(window.type);
});
browser.tabs.getCurrent().then((tab)=>{
console.log("tab",tab);
});
});
which gives me the message body (using magic indexes) but expectedly, the change is not reflected in the message display.
The window type returned is normal, not messageDisplay.
The tab is undefined despite adding permissions
"permissions": [
"messagesRead",
"activeTab",
"tabs",
"tabHide"
],
but I assume that's because the script is running as background.
So I'd need a script running on the content / access to the tab and then some hints on how to modify the displayed message content (I do not want to modify the message).
Where would I find the equivalent documentation to
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/content_scripts
specific to Thunderbird?
Specifying content_scripts in manifest.json causes "Error: Error reloading addon messageDisplay#sample.extensions.thunderbird.net: undefined".
executeScript() from background does not seem to work either, even with tabId specified.
This was not possible to do when you wrote your question, the API for modifying displayed messages was missing.
As of this writing (September 2020), the browser.messageDisplayScripts API landed a few days ago, see bug 1504475 and related patch for examples. It works as follows: You can register your content script (to modify the displayed messages) like this
let myPromise = browser.messageDisplayScripts.register({
css: [{
file: "/style.css",
}],
js: [{
file: "/content_script.js",
}],
});
And you can later unregister with
myPromise.then((script) => { script.unregister(); });
You need to register the script just once for all messages (you do not need a listener that would load it each time a message is displayed).
Note that your manifest.json needs to include the messagesModify permission for this to work.
The new API will be in Thunderbird version 82, so if I understand the release process correctly it should be in stable version 88 (unless it is backported before that). You can try it already (v82 is the current EarlyBird).
Documentation https://thunderbird-webextensions.readthedocs.io/en/68/tabs.html#getcurrent
says:
May be undefined if called from a non-tab context (for example: a background page or popup view).
Since the background.js is not called from a tab context the tab is undefined.
I found several approaches, but they seem outdated or simply won't work for some reasons for me. Maybe tunnelvision:
First things first:
I have the correct permissions in my manifest.json, I think:
"permissions": [
"tabs",
"activeTab"
]
I have 2 simple scripts, background.js and content.js (which are recognized correctly, the error can't be here).
In my background.js I tried several approaches:
chrome.browserAction.onClicked.addListener(buttonClicked);
var sharedUrl;
function buttonClicked(tab) {
chrome.tabs.getCurrent(function(tab) {
// please read further, this was my last resort, I tried other stuff as well
sharedUrl = console.log(window.location.href);
});
let msg = {
txt: "Hello",
url: sharedUrl
}
chrome.tabs.sendMessage(tab.id, msg);
}
I tried it with getCurrent() and then tab.url, but that didn't work (neither with tab[0].url
I tried it also with getSelected() as well as with something like this:
chrome.tabs.query({active: true, currentWindow: true}, function(arrayOfTabs) {
var activeTab = arrayOfTabs[0];
});
and my content.js is simply this here:
chrome.runtime.onMessage.addListener(gotMessage);
function gotMessage(message, sender, sendResponse) {
console.log(message.txt);
console.log(message.url);
}
It displays "Hello", but not the URL I'm looking for.
Edit:
It might of importance, that I want to retrieve the url after a button-click in my extension.
Thanks for the feedback and help.
Ok, based on the documentation you are not able to grab the tab object while you are not in the tab context. The tab context includes only content scripts. So you can't access to tab because you are calling it from your backend page. You can only do it, if your extension has generated the tab.
Gets the tab that this script call is being made from. May be
undefined if called from a non-tab context (for example: a background
page or popup view).
So, the only possible way is to change your extension data flow.
Overview of the problem
I cannot access the DOM of a window that I open programmatically in my chrome extension. I believe this may be due to origin/cross-site scripting restrictions. I have tried to give it the maximal permissions I can, including "<all_urls>", "activeTab" and "tabs"
I also played around with "content_security_policy_" settings but from the documentation I was hoping rather than expecting that to help.
I have a simple extension that runs code when the button is clicked, I want to open a few different tabs (but from different domains) and then access the DOM of each. However, it only seems to work if I'm on a tab of a domain and then I open another tab of the same of domain. Then I can access, for example, window onload of the new tab. I have no luck when it is a different domain.
e.g. if I press the button with activeTab "foo.com" then if it window.opens a new tab "foo.com/something" then I can access the document of that opened tab. But if it was "bar.com" then I wouldn't be able to access "foo.com/something"'s DOM
p.s. please note that executeScripts is used instead of manifest content scripts because it is necessary for my code to work. I must inject at least some of the files there this way, otherwise my code will not work (for reasons that are not completely apparent in the example)
my Question
What's way(s) can I get around this - I mean be able to access the DOM of any tab that I open, regardless of what site is in the active tab when the extension button is pressed in the toolbar?
Should I inject content scripts into a a tab that has been opened with window.open and somehow pass its document to Code.js? If so, how could I go about doing that? Can I somehow pass the document to the background.js? and somehow pass it to the injected Code.js?
If this will work (get around security restrictions) then can these content scripts be injected programatically (I don't know exactly what sites they will be until runtime, so I can't really specify them in the manifest)
or is there some way I can just relax the security restrictions and be able to directly access window.open's returned window's document? (which as I mentioned above currently only works on same-domain sites)
background.js
// this is the background code...
// listen for our browerAction to be clicked
chrome.browserAction.onClicked.addListener(function (tab) {
executeScripts(null, [ // jquery can be inserted here: https://stackoverflow.com/questions/21317476/how-to-use-jquery-in-chrome-extension
{ file: "lib/jquery-3.2.1.js" },
{ file: "lib/kotlin.js" },
{ file: "Code.js" } // don't include it in manifest "content_scripts" because it gives an error about kotlin.js not present that way
])
});
/*
* https://stackoverflow.com/questions/21535233/injecting-multiple-scripts-through-executescript-in-google-chrome
*/
function executeScripts(tabId, injectDetailsArray)
{
function createCallback(tabId, injectDetails, innerCallback) {
return function () {
chrome.tabs.executeScript(tabId, injectDetails, innerCallback);
};
}
var callback = null;
for (var i = injectDetailsArray.length - 1; i >= 0; --i)
callback = createCallback(tabId, injectDetailsArray[i], callback);
if (callback !== null)
callback(); // execute outermost function
}
code flow
background.js
Code.jscalls a function that starts with var w = window.open(url)
then w.addEventListener("load", ...), which is the problem. It only fires if the url belongs to the same domain as the active tab. (onLoad can't be used any-which-way, it never fires that's why I use addEventListener)
notes
I have "manifest_version": 2
I need to inject this way rather than include content scripts in the manifest (I guess I could put the jquery library in the manifest but the others I can't and I prefer to keep all injections together in the "code")
This question already has answers here:
Optionally inject Content Script
(3 answers)
Closed 7 years ago.
I am writing an extension that has options which let users decide which sites they want the extension to run on.
Say the user has this site in the options
site action
stackoverflow.com/* change background css to blue
google.com/* change background css to green
I store these string in the options. When the content_script runs should I retrieve these strings from options, loop through each one, parse with a urlParser into parts, turn each part into a regex (escaping everything but *), and compare it with document.URL? I recently read that this kind of user options validation for Urls should be done through a background script too so I'm not sure which way to go or if there's a more obvious way to do it.
I think extensions like Adblocker and Vimium seem to have this functionality but for deciding which sites not to run on. I want to figure out how to decide which sites to run on.
Update to the Question: Since my content_script needs to run at document_start (before the page is loaded since it deals with editting the page appearance) as a content_script, will the background page be able to execute the content_script before the webpage is loaded at all?
Validated a web page's url should be "validated" via a background page because the user's options will be hosted in local storage in the context of the background page. Here is what I would do... (although it's more of a suggestion that an answer).
I am not sure how the actions on the right column of your list factor into your question, sorry.
(Also note, you would need to incorporate a library (external or self-written) that can parse globs into regex.)
manifest.json
permissions: ["tabs", "storage", "webRequest", "<all_urls>"]
background.js
//allow the webrequest to run on all urls
var filter = { urls: "<all_urls>" };
//receives url information from webrequest listener
function listen(details) {
getUserOpts()
.then(function(arrayOfWhitelistUrls) {
//you can't use globs here, need to use more powerful filtering mechanisms
if (arrayOfWhitelistUrls.indexOf(details.url) > -1) {
message();
}
});
}
//returns a promise containing user defined whitelist urls from background local storage
function getUserOpts() {
return new Promise(function(res, rej) {
chrome.storage.get("whitelist", function(data) {
//you are saving all localhost data as a string, so you need to parse it first
res(JSON.parse(data));
});
});
}
//messages content script and allows execution
function message() {
chrome.tabs.query({active: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {permission: true});
});
}
chrome.webRequest.onBeforeRequest.addListener(listen, filter)
contentscript.js
function listen(message) {
if (message.permission) {
if (message.permission === true) {
init();
}
}
}
//listen for message
chrome.runtime.onMessage.addEventListener(listen);
So the order in which things run is like:
background page listens to each web request
on each web request, the background page asynchronously fetches user options from local storage
if the url of the current tab passes your filter, message your content script
the content script receives the message and then runs
There might be an easier way to do this; the downside to this method is that you need to include the permission gateway in every content script you have.
I want to create an extension that redirects the user to another website if he clicks on the extension button. So far I have only seen extensions which create a new tab for each click.
Is it possible to redirect the user to another website using the active tab?
I tried something like this:
chrome.browserAction.onClicked.addListener(function(tab) {
var url = "https://www.mipanga.com/Content/Submit?url="
+ encodeURIComponent(tab.url)
+ "&title=" + encodeURIComponent(tab.title);
document.location.href = url; // <-- this does not work
});
Attention: If you develop cross-browser extensions (I hope you do!), I recommend that you use chrome.tabs.query(). Please see Jean-Marc Amon's answer for more information. This answer still works in both Firefox and Chrome, but query() is more commonly used, has more options, and works in background pages and popup views.
From the chrome.tabs API, you can use getCurrent(), query(), or update().
Right now, I prefer update() as this allows you to update the current tab without needing to do much else.
NB: You cannot use update() from content scripts.
If updating the url from a content script is required then you should look to use query instead. Jean-Marc Amon's answer provides a wonderful example of how to get the active tab in this case (don't forget to upvote him!).
update()
let myNewUrl = `https://www.mipanga.com/Content/Submit?url=${encodeURIComponent(tab.url)}&title=${encodeURIComponent(tab.title)}`;
chrome.tabs.update(undefined, { url: myNewUrl });
Here, we have set the first argument of update to undefined. This is the tab id that you're wanting to update. If it's undefined then Chrome will update the current tab in the current window.
Please see Domino's answer for more information on update and also note that undefined is not needed. Again, please don't forget to upvote their answer as wellif you find it useful.
getCurrent()
getCurrent also cannot be called from a non-tab context (eg a background page or popup view).
Once you have the current tab, simply pass update().
chrome.tabs.getCurrent(function (tab) {
//Your code below...
let myNewUrl = `https://www.mipanga.com/Content/Submit?url=${encodeURIComponent(tab.url)}&title=${encodeURIComponent(tab.title)}`;
//Update the url here.
chrome.tabs.update(tab.id, { url: myNewUrl });
});
NB: In order to use this this functionality, you must ensure that you have the tabs permission enabled in your manifest.json file:
"permissions": [
"tabs"
],
You can use chrome.tabs.query too
chrome.tabs.query({currentWindow: true, active: true}, function (tab) {
chrome.tabs.update(tab.id, {url: your_new_url});
});
The chrome.tabs.update method will automatically run on the current active tab if no tab id is passed.
This has the added advantage of not requiring the tabs permission. Extensions with this permission warn the user that they can read the browsing history, so you should avoid asking for it if you don't need to.
Changing the current tab's URL is as simple as writing this:
chrome.tabs.update(undefined, {url: 'http://example.com'});
Or as mentionned by farwayer in the comments, you don't need to put two arguments at all.
chrome.tabs.update({url: 'http://example.com'});
The answers given here no longer work: the Chrome Tabs API can no longer be used by content scripts, only by service workers and extension pages.
Instead, you can send a message to a service worker to get it to update the location of the current tab: see https://stackoverflow.com/a/62461987.
See this for a simple working example.