Following up on a previous post, I am now trying to create a version of a Chrome extension that does not specify content_scripts: or matches: in the manifest.json file; instead the content script is to be injected programmatically by a en event triggered from the options page which prompts the user to grant optional permissions for executing the content script. The rationale is to be able to have the extension working on pages from hosts with different top-level domain names (see previous post for details). I have read the documentation on this and tried to connect the dots, but I'm not quite getting there.
Below is a demo version of what I have created so far. I manage to get the optional permissions request processed and the user prompt for granting that request shown (the alert "granted!" is displayed). However, when I try to have the message listener in background.js execute the script content.js (by removing the /* commented-out code */ there), I get the error message
Unchecked runtime.lastError: Cannot access contents of url
"chrome-extension://[blah]/options.html". Extension manifest must
request permission to access this host.
Any guidance as to what I have missed here would be most welcome.
I also have a second question: since I am using jQuery in the content.js script, do I have to execute the jQuery.js file as well in response to the granted permission, and if so, should that be done by adding another separate chrome.tabs.executeScript() command?
manifest.json:
{
"manifest_version": 2,
"name": "My Extension",
"version": "1",
"description": "Demo extension",
"options_page":"options.html",
"background": {
"scripts": ["background.js"],
"persistent": false
},
"optional_permissions":["tabs","https://*/*"],
"permissions": ["activeTab","storage"]
}
options.html:
<html>
<head>
<style>
button#permreq{font-size:2em;}
</style>
</head>
<body>
<button id="permreq">Click this button to enable My Extension</button>
<script src="jQuery.js"></script>
<script src="options.js"></script>
</body>
</html>
options.js:
jQuery(function($){
$(document).ready(function(){
$("button#permreq").click(function(){
chrome.permissions.request({
permissions: ['tabs'],
origins: ["https://*/*"]
}, function(granted) {
if (granted) {
chrome.runtime.sendMessage("granted");
} else {
alert("denied");
}
});
});
});
});
background.js:
chrome.runtime.onMessage.addListener(
function(message, callback) {
if (message == "granted"){
/*chrome.tabs.executeScript({
file: "content.js"
});*/
alert("granted!");//no errors as long as above code is commented out
} else{
alert("denied");
}
});
Related
Directories
----MyExtension
|----popup.html
|----popup.js
|----content.js
|----background.js
|----manifest.json
mainfest.json
{
"manifest_version": 2,
...........
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
],
"browser_action": {
"default_title": "Practice",
"default_popup": "popup.html"
},
"permissions": [
"<all_urls>",
"tabs",
"storage",
"activeTab"
],
"background": {
"scripts": ["background.js"]
}
}
....
popup.html
<html>
<head>
....
<script src="popup.js"></script>
</head>
<body>
<input id="status" type="chckbox">
</body>
</html>
popup.js
$(document).ready(function(){
$on = $("#status");
//sends the settings to background to save it
$on.on("click",function(){
$obj = {"on":$on.prop("checked")}
browser.runtime.sendMessage($obj);
console.log("sending....");
})
})
What im trying to do is simply send a message to background script if the check box in popup.html is checked.
The problem is I cannot access the browser namespace in popup.js because its not content script or background script. And i cannot access the check box from the content scrip as it not linked to popup.html (if its linked i get reference error browser is not defined. I've tried countless google searches and spent hours reading web extension docs still cannot find an answer how to go around it any help appreciated.
I have good news for you - you can access the browser namespace in your browser action poupus, otherwise they would be pretty useless :)
Which means that something else is broken.
First, if you didn't do it yet, open 'browser toolbox' with Ctrl+Shift+Alt+I to see that you probably have a bit different kind of error there.
Then include this in your background.js :
function handleMessage(request, sender, sendResponse) {
console.log("Message from somewhere: ", request);
}
browser.runtime.onMessage.addListener(handleMessage);
To actually read the message.
See:
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/browserAction
JavaScript running in the popup gets access to all the same WebExtension APIs as your background scripts, but its global context is the popup, not the current page displayed in the browser. To affect web pages you need to communicate with them via messages.
Edit, unrelated:
$on = $("#status");
You do realize that by doing so you refer to/create a global variable '$on', right? Did you mean:
var $on = $("#status");
?
I am writing an extension and I encountered a problem: I can not send data from the extension menu to content.js. In the extension menu I have a couple of intuitions, after filling in and clicking on the button, I write down their values and I want to send them to content.js where this data will be used for implementation inhtml But for some reason, the data is not sent.
document.getElementById('btn').onclick = function() {
var first = document.getElementById('first').value;
var second = document.getElementById('second').value;
//send in content
chrome.extension.sendMessage('hello');
}
<head>
<script type="text/javascript" src="content.js"></script>
<script type="text/javascript" src="background.js"></script>
</head>
<input type="text" id="first">
<input type="text" id="second">
<input type="button" id="btn" value="send">
Here is the manifest.json (maybe there's something wrong)
{
"manifest_version": 2,
"version": "1.3",
"description": "name",
"browser_action":{
"default_popup": "content/popup.html"
},
"background": {
"persistent": false,
"scripts": ["content/background.js"]
},
"content_scripts": [
{
"matches": [ "https://google.com/*" ],
"js": ["content/content.js"],
"css": ["content/qq.css"],
"run_at": "document_end"
}
]
}
content.js: get data from background
chrome.extension.onMessage.addListener(function(request){
if(request=='hello'){
console.log('1. Принято: ', request);
}
});
As I can see everything, background.js is the file that is responsible forjs in the extension menu. content.js is the file that is responsible for making changes to the DOM on the sites.
Your files' structure is unclear: what is the content of popup.html? why do you load both content.js and background.js in the same page?
Here is an example that does what I think you try to accomplish.
It works like this:
The popup screen will display the inputs for the user to fill.
When the button is pressed, the value of the inputs is sent to the background script which, in turn, sends them to the content script. The content script then uses those values in the way you want: for instance, to fill an input in the host webpage.
manifest.json
{
"manifest_version": 2,
"version": "1.3",
"description": "name",
"browser_action":{
"default_popup": "content/popup.html"
},
"background": {
"persistent": true,
"scripts": ["content/background.js"]
},
"content_scripts": [
{
"matches": [ "https://google.com/*" ],
"js": ["content/content.js"],
"css": ["content/qq.css"],
"run_at": "document_end"
}
]
}
background.js
var contentTabId;
chrome.runtime.onMessage.addListener(function(msg,sender) {
if (msg.from == "content") { //get content scripts tab id
contentTabId = sender.tab.id;
}
if (msg.from == "popup" && contentTabId) { //got message from popup
chrome.tabs.sendMessage(contentTabId, { //send it to content script
from: "background",
first: msg.first,
second: msg.second
});
}
});
content.js
chrome.runtime.sendMessage({from:"content"}); //first, tell the background page that this is the tab that wants to receive the messages.
chrome.runtime.onMessage.addListener(function(msg) {
if (msg.from == "background") {
var first = msg.first;
var second = msg.second;
//here you use the values as you wish, for example:
//document.getElementById("anInput").value = first;
}
});
popup.html
<html>
<body>
<input type="text" id="first">
<input type="text" id="second">
<button id="send">Send</button>
<script src="popup.js"></script>
</body>
</html>
popup.js (this file must be located in the same directory as popup.html)
document.getElementById("send").onclick = function() {
chrome.runtime.sendMessage({ //send a message to the background script
from: "popup",
first: document.getElementById("first").value,
second: document.getElementById("second").value
});
}
I hope that helps.
I think you are looking for the runtime property of the chrome / browser object.
This would make your send message command chrome.runtime.sendMessage without the use of the extension property.
Likewise the on message event would be chrome.runtime.onMessage.
I'm pulling this info from the following documentation: https://developer.chrome.com/apps/messaging
content.js should not be included into popup.html. content.js is run whenever a site matches the pattern in your manifest.json. Right now, whenever someone visits google.com with your extension installed, the content.js script is run in the background of google.com.
background.js also shouldn't be loaded into the popup. It's a script that's always run in the background of the browser, it's not something that should get loaded by anything. It's where you add stuff like code to change the omnibox behavior.
You should create a new popup.js script that gets included by popup.html, and it should only handle things like onload and onclick events for the actual popup window.
The various files you mention, content.js, background.js and the file you should create popup.js all have different jobs and should not communicate between each other. There's neither a need nor a possibility for it. If you want to e.g. get the value of what's inside some other site put it in content.js, which is run on each site that matches your pattern, and do all the handling in there.
background.js = code that sets up your extension inside the browser, stuff like changing the omnibox behavior.
content.js = code that runs on each website that matches a certain pattern.
popup.js = code that runs when the user opens the popup window of your extension.
So don't have them communicate, they aren't supposed to, they fill entirely different functions.
There's no reason why you should need to communicate between them either, please explain a scenario where you'd need this and I'll explain why you don't need it. :)
To communicate with content.js, you need to use chrome.tabs.sendMessage instead of chrome.extension.sendMessage, because to communicate with the content.js you need to provide the tabId, which is passed as an argument in the former listed API.
I am attempting to make a chrome extension that creates a new tab with a local 'blanksite.html' and injects some javascript code turning it green. Here's what I have so far.
background.js
chrome.browserAction.onClicked.addListener(function(activeTab){
chrome.tabs.create({'url': chrome.extension.getURL("blanksite.html") }, function(tab) {
chrome.tabs.executeScript(tab.id, {
code: 'document.body.style.backgroundColor="green"'
});
});
});
manifest.json
{
"manifest_version": 2,
"name": "Open Green Google Tab",
"description": "This extension opens a Green Google tab.",
"version": "1.0",
"background":{
"scripts": ["background.js"]
},
"browser_action": {
"default_icon": "icon.png"
},
"permissions": [
"tabs",
"activeTab"
]
}
This opens "blanksite.html" (literally an empty html file) in a new tab, but does not turn the tab green.
I've read the other answers at Chrome extension: create tab then inject content script into it, so I know why this doesn't work (not able to directly inject code into chrome://extension pages); but I wasn't able to make the solutions posted on the other answers work for me. Is there a clear, full piece of small code that can make what I want to do work?
I'm afraid I do not understand messaging very well, so for any solution that has that as a piece, a more comprehensive explanation would be greatly appreciated.
Not sure why starting message passing from background page to blanksite.html won't succeed (maybe it's too late to listen to message in blanksite.html when it's created?).
However, starting message passing from blanksite.html and executing corresponding action in the response work well, see following sample code:
blanksite.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script src="blanksite.js"></script>
</body>
</html>
blanksite.js
chrome.runtime.sendMessage({src: "newtab"}, function(response) {
if(response.action === 'changeColor') {
document.body.style.backgroundColor = 'green';
}
});
background.js
chrome.browserAction.onClicked.addListener(function(activeTab) {
chrome.tabs.create({url: chrome.runtime.getURL('newtab.html')});
});
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request.src === 'blanksite') {
sendResponse({action: 'changeColor'});
}
});
i want to display the array data i have in the background when the get data button is clicked but nothing is done when i click the button am very new for chrome ext. thanks.
manifest.json:
{
"name":"modz",
"manifest_version":2,
"version":"1.0",
"description":"this ext. will help you record all the urls you have visited",
"background": {
"scripts": ["background.js"]
},
"browser_action":
{
"default_icon":"icon.png",
"default_popup":"popup.html"
},
"permissions":[
"tabs"
]
}
background.js:
var data=[];
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
var url = tab.url;
if (url!=="undefined" && changeInfo.status=="complete")
{
data.push (tab.url);
alert(data.length);
}
}
);
chrome.runtime.onMessage.addListener(function(message,sender,sendrespose){
//send the array data back
});
popup.js:
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('btn').addEventListener('click',function(){
chrome.runtime.sendMessage("getdata");
});
});
popup.html
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="C:\Users\modz\Desktop\modz_extention\popup.js"></script>
<style type="text/css">
body{
width:440px;
}
</style>
</head>
<body>
<input id="btn" type="button" value="get data" />
<div id="data"></div>
</body>
</html>
The official reference for messaging is here. In your case, you’d want background.js to have
chrome.runtime.onMessage.addListener(function(message,sender,sendResponse){
sendResponse({"dataArray":data});
});
popup.js would have
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('btn').addEventListener('click',function(){
chrome.runtime.sendMessage({},function(response){
document.getElementById("data").textContent = response.dataArray.toString();
});
});
});
This would also work in a content script. But if the content script were running at the default document_end, it wouldn’t need the DOMContentLoaded event, since document_end happens afterward.
This is actually sending an empty message (the empty object {}). If there were different messages you wanted to send, you’d want to change that. This is also why message isn’t used in background.js.
Since you’re not really sending a message, another approach is to use getBackgroundPage. background.js wouldn’t need the listener, and popup.js would have:
chrome.runtime.getBackgroundPage(function(backgroundWindow){
document.getElementById("data").textContent = backgroundWindow.data.toString();
});
Two more things:
popup.html can’t use the absolute path to popup.js. Put both in the extension directory, and use a relative path: src="popup.js".
Google recommends that you transition background pages to event pages. The biggest difference is that you can’t have the global variable data in event pages (you can, but it gets reset when the event page reloads). If you have trouble getting that working, I’d recommend getting your extension working as a background page, and then posting another question.
Edit: Found the error, but can't solve it, see below.
manifest.json
{
...
"offline_enabled": true,
"app": {
"background": {
"persistent": true,
"scripts": [
"/js/background.js"
]
}
},
"permissions": [
"notifications",
"storage",
{"fileSystem": ["directory"]}
]
}
background.js
chrome.app.runtime.onLaunched.addListener(function() {
window.open('/index.html');
});
index.html
<!DOCTYPE html>
<html>
<head>
<title>Achshar Player</title>
<script src="/js/index.js"></script>
</head>
<body>
<input id="input" type="button">
</body>
</html>
index.js
window.addEventListener('load', function() {
document.getElementById('input').addEventListener('click', function() {
chrome.fileSystem.chooseEntry({type: 'openFile'}, function(readOnlyEntry) {
console.log(readOnlyEntry);
});
});
});
The method is called and so is the callback, but the file choosing dialogue never comes and the readOnlyEntry is undefined when the callback is executed. No errors on the dev tools, I am on 35.0.1916.153 m.
I have tried different variations of manifest declarations for fileSystem but since the function is not undefined in the script execution, the manifest is unlikely to be the issue.
When I use the official example extension of fileSystem API the app works, so the chrome setup isn't the problem either. The problem seem to be my code, but I am lost here.
Edit: I added each file's content
Edit 2: Found the error, now how to solve it?
I tried it in canary and realize the errors are shown via chrome.runtime.lastError and not the normal console. And this is the error I get.
Invalid calling page. This function can't be called from a background page.
But this is not in background.js, this is in index.js which is called from index.html.
I just tried this in Chrome, and there doesn't seem to be anything wrong with the code that you've posted. I suspect that there is a problem with the way that you are loading the javascript, or possibly the context that it is running in (foreground page vs. background page)
For instance, if your JavaScript code snippet is actually in main.js, then that will run in the background page, and its window and document elements won't be the ones from your main page.
My test app looks very similar to yours, except that I have left out the main.js file from the manifest, and I have constructed a small index.html file, which loads a foreground.js script instead. This is the complete app:
manifest.json
{
"manifest_version": 2,
"name": "Stack overflow question test app",
"version": "1",
"offline_enabled": true,
"app": {
"background": {
"persistent": true,
"scripts": [
"/js/background.js"
]
}
},
"permissions": [
"notifications",
"storage",
{"fileSystem": ["directory"]}
]
}
js/background.js
chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create("index.html");
});
index.html
<!DOCTYPE html>
<html>
<head>
<title>TEST</title>
<script src="js/foreground.js"></script>
</head>
<body>
<input id="input" />
</body>
</html>
js/foreground.js
window.addEventListener('load', function() {
document.getElementById('input').addEventListener('click', function() {
chrome.fileSystem.chooseEntry({type: 'openFile'}, function(readOnlyEntry) {
console.log(readOnlyEntry);
});
});
});
When I run this, I can click on the input element, and I see a file picker. Choosing an entry returns a FileEntry object, which is logged to the console (of the foreground page, not the background page. Right-click in the app window and select "Inspect Element", rather than "Inspect Background Page", to see the foreground console.):
FileEntry {filesystem: DOMFileSystem, fullPath: "/TestFile.rtf", name: "TestFile.rtf", isDirectory: false, isFile: true…} foreground.js:4
Note:
From your original code, it appeared that you were using a framework like jQuery to search for DOM elements within your page. Chrome Apps work just fine with jQuery, but you have to be aware of when you are using a raw DOM Node object, and when you have a wrapped jQuery object.
Specifically, the line
$('input').addEventListener('click', function() {
would have caused you problems.
Replacing it with
document.querySelector('input').addEventListener('click'), function() {
would correctly find the element on the page, and attached the click handler to it.