I am developing an extension and want to make it interactive with my website. But when I try to call function that is located in the 'content_script.js' file it says that the functions is not defined!
Step 1: Read about isolated context in which Content Scripts operate.
Content scripts execute in a special environment called an isolated world. They have access to the DOM of the page they are injected into, but not to any JavaScript variables or functions created by the page. It looks to each content script as if there is no other JavaScript executing on the page it is running on. The same is true in reverse: JavaScript running on the page cannot call any functions or access any variables defined by content scripts.
Step 2: Since you're interacting with your own website, the preferred method would be to employ messaging and externally_connectable property.
You need to declare in your manifest file that your extension is externally connectable from your site:
"externally_connectable": {
"matches": ["*://*.example.com/*"]
}
In the webpage code, you need to message the extension by its ID:
// The ID of the extension we want to talk to.
var myExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// Make a simple request:
console.log("Sending ping from the page");
chrome.runtime.sendMessage(myExtensionId, {ping: true},
function(response) {
if(response.pong) console.log("Pong received from content script");
}
);
Note: this function will only be exposed to the page if the extension is installed / externally connectable.
And finally, in your content script, react to that message:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (request.ping) {
console.log("Ping received from the page");
sendResponse({pong: true});
}
}
);
In general, read about Messaging to learn more.
Related
I am trying to communicate my web page script with my content script of my web extension with the code below
Web Page Script
const browser = window.browser || window.chrome;
browser.runtime.sendMessage(message,
function (response) {
console.log(response);
}
);
However, I keep getting the error TypeError: browser is undefined. The same goes if I use chrome.runtime.sendMessage() instead.
How am I supposed to use this method?
The issue here is that user/webpage scripts (unprivileged scripts) don't have access to JavaScript API for security purposes and browser, chrome are part of JavaScript APIs which can only be accessed by privileged scripts like web extension's background scripts and content scripts (again content scripts don't have access to all the JavaScript APIs). Basically, if you need to send data from web page script to background script, CustomEvent should be used to send data to a content script which acts as a bridge and from there send that data to background script using browser.runtime.sendMessage. PFB sample code
window.onload = function(){
document.dispatchEvent(new CustomEvent("myEvent",{
detail:["Hello","World"]
}));
}
contentscript.js
document.addEventListener("myEvent", function (event) {
browser.runtime.sendMessage({
data: event.detail
});
background.js
browser.runtime.onMessage.addListener(function (message) {
data = message.data;
// do stuff
});
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'm trying to develop a restartless firefox extension without the sdk, and I would like to be able to manipulate the DOM of the page, but either the document, content.document or unsafeWindow.document are returning undefined.
My bootstrap.js code:
Components.utils.import("resource://gre/modules/Services.jsm");
function startup(data,reason) {
Components.utils.import("chrome://myextension/content/plugin-min.js");
}
function shutdown(data,reason) {
Components.utils.unload("chrome://myextension/content/plugin-min.js");
}
function install(data,reason) { }
function uninstall(data,reason) { }
and my plugin-min.js code:
document.addEventListener('keydown',activate); // document undefined
content.document.addEventListener('keydown',activate); // content undefined
unsafeWindow.document.addEventListener('keydown',activate); // unsafeWindow undefined
And Mozilla published solution only for SDK users, and google searches I've done brought only these SDKs solutions. :/
But in my case, Does anyone know what I'm missing?
Thank you very much.
Main extension script has no direct access to document. You should inject to document own content-script, and exchange with it via async messaging (port object).
Also, you can provide an array of context script -in that case all of them will be imported:
var panel = panels.Panel({
contentURL: self.data.url("page.html"),
contentScriptFile: [self.data.url("script.js"), self.data.url('libs/jss.js')],
contentStyleFile: self.data.url("style.css"),
onHide: handleHide
});
Not sure about loading order, but after documentready evrything accessible.
More reading in official documentation.
my aim is to write an app for chrome which communicates details of the DOM of a web page via xhttprequest to a local web server.
In order to access the dom of a web site one would usually use tabs in combination with a content script like this:
chrome.tabs.create({ url: "www.example.com" });
chrome.tabs.executeScript(null, {file: "content_script.js"});
And then use the messaging system to communicate the stuff to the background script.
Unfortunately when I do that, I need to add "tabs" permission to the manifest which is not allowed for packaged apps, only for extensions. I need to implement a packaged app because in a usual extension, no socket connection is allowed (which I need at some time).
I also tried what is suggested here: How to write content in child window using chrome packaged app?
However,
chrome.app.window.create
Does only work for local html files, not for external websites.
So my question again:
(how) is it possible in a packaged app to access the dom of a website?
You could try using a <webview> for Chrome apps and inject custom scripts via the executeScript method.
Remember to specify the "webview" permission in your manifest file.
Thank you very much! I did it exactly as you said, Joseph Portelli.
The code:
main.js
chrome.app.window.create('webview.html', {},
function (createdWindow) {
var win = createdWindow.contentWindow;
win.onload = function () {
var webview = win.document.querySelector('#webview');
webview.setAttribute("src", "http://www.example.com");
webview.addEventListener("contentload", function () {
webview.executeScript({file: "content_script.js"}, function(result) {});
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if(request.dom) {
plugin.sendDom(request.dom, request.browser);
}
});
});
}
});
webview.html
<!doctype html>
<html>
<body>
<webview id="webview" style="height:100%;width:100%"/>
</body>
</html>
content_script.js
...
var body = document.getElementsByTagName("body")[0];
var dom = {root: domTraverser.recurseDomChildren(body)};
chrome.runtime.sendMessage({dom:dom, browser: getBrowserName()});
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