I was trying to integrate sql.js(JS based SQLite https://github.com/kripken/sql.js/) into my chrome app but as I launch my app, console shows the following errors:
Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "default-src 'self' chrome-extension-resource:". Note that 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
Uncaught EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "default-src 'self' chrome-extension-resource:".
My manifest file looks like this:
{
"manifest_version": 2,
"name": "Chrome App",
"description": "This is the test app!!!",
"version": "1",
"icons": {
"128": "icon_128.png"
},
"permissions": ["storage"],
"app": {
"background": {
"scripts": ["background.js"]
},
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
},
"minimum_chrome_version": "28"
}
#MarcRochkind I would like to add some knowledge to your book for integrating SQL.js in Chrome Apps.
It is very well possible with very little effort (considered the obedience of policies and rules).
In order to integrate anything that uses eval, you need to sandbox that particular part of the script. In case of SQL.js, it's the entire library.
This can be done with an iframe which needs to be set in the main .html document that's called for creating a (or the main) window, e.g. chrome.app.window.create('index-app.html', { ..
The base of communication between the main document and the iframe will be by using postMessage for sending and receiving messages.
Let's say the source of this iframe is called /iframes/sqljs-sandboxed.html.
In the manifest.json you need to specify sqljs-sandboxed.html as a sandbox. A designated sandbox makes it possible to run eval and eval-like constructs like new Function.
{
"manifest_version": 1,
"name": "SQL.js Test",
..
"sandbox": {
"pages": [
"iframes/sqljs-sandboxed.html",
]
}
}
The sqljs-sandboxed.html uses an event listener to react on an event of type message. Here you can simply add logic (for simplicity sake I used a switch statement) to do anything structured with SQL.js.
The content of sqljs-sandboxed.html as an example:
<script src="/vendor/kripken/sql.js"></script>
<script>
(function(window, undefined) {
// a test database
var db = new SQL.Database();
// create a table with some test values
sqlstr = "CREATE TABLE hello (a int, b char);";
sqlstr += "INSERT INTO hello VALUES (0, 'hello');";
sqlstr += "INSERT INTO hello VALUES (1, 'world');";
// run the query without returning anything
db.run(sqlstr);
// our event listener for message
window.addEventListener('message', function(event) {
var params = event.data.params,
data = event.data.data,
context = {};
try {
switch(params.cmd) {
case '/do/hello':
// process anything with sql.js
var result = db.exec("SELECT * FROM hello");
// set the response context
context = {
message: '/do/hello',
hash: params.hash,
response: result
};
// send a response to the source (parent document)
event.source.postMessage(context, event.origin);
// for simplicity, resend a response to see if event in
// 'index-app.html' gets triggered a second time (which it
// shouldn't)
setTimeout(function() {
event.source.postMessage(context, event.origin);
}, '1000');
break;
}
} catch(err) {
console.log(err);
}
});
})(window);
</script>
A test database is created only once and the event listener mirrors an API using a simple switch. This means in order to use SQL.js you need to write against an API. This might be at, first glance, a little uncomfortable but in plain sense the idea is equivalent when implementing a REST service, which is, in my opinion, very comfortable in the long run.
In order to send requests, the index-app.html is the initiator. It's important to point out that multiple requests can be made to the iframe asynchronously. To prevent cross-fire, a state parameter is send with each request in the form of an unique-identifier (in my example unique-ish). At the same time a listener is attached on the message event which filters out the desired response and triggers its designated callback, and if triggered, removes it from the event stack.
For a fast demo, an object is created which automates attachment and detachment of the message event. Ultimately the listen function should eventually filter on a specific string value, e.g. sandbox === 'sql.js' (not implemented in this example) in order to speed up the filter selection for the many message events that can take place when using multiple iframes that are sandboxed (e.g. handlebars.js for templating).
var sqlRequest = function(request, data, callback) {
// generate unique message id
var hash = Math.random().toString(36).substr(2),
// you can id the iframe as wished
content_window = document.getElementById('sqljs-sandbox').contentWindow,
listen = function(event) {
// attach data to the callback to be used later
this.data = event.data;
// filter the correct response
if(hash === this.data.hash) {
// remove listener
window.removeEventListener('message', listen, false);
// execute callback
callback.call(this);
}
};
// add listener
window.addEventListener('message', listen, false);
// post a request to the sqljs iframe
content_window.postMessage({
params: {
cmd: request,
hash: hash
},
data: data
}, '*');
};
// wait for readiness to catch the iframes element
document.addEventListener('DOMContentLoaded', function() {
// faking sqljs-sandboxed.html to be ready with a timeout
setTimeout(function() {
new sqlRequest('/do/hello', {
allthedata: 'you need to pass'
}, function() {
console.log('response from sql.js');
console.log(this.data);
});
}, '1000');
});
For simplicity, I'm using a timeout to prevent that the request is being send before the iframe was loaded. From my experience, it's best practice to let the iframe post a message to it's parent document that the iframe is loaded, from here on you can start using SQL.js.
Finally, in index-app.html you specify the iframe
<iframe src="/iframes/sqljs-sandboxed.html" id="sqljs-sandbox"></iframe>
Where the content of index-app.html could be
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<iframe src="/iframes/sqljs-sandboxed.html" id="sqljs-sandbox"></iframe>
<h1>Hello, let's code with SQL.js!</h1>
<script src="/assets/js/sqljs-request.js"></script>
</body>
</html>
"content_security_policy" is not a documented manifest property of Chrome Apps.
To my knowledge, sql.js is not compatible with Chrome Apps, as your error message indicates.
A variation of SQLite, Web SQL, is specifically documented as not working with Chrome Apps.
IndexedDB does work with Chrome Apps, but (a) it's not SQL-based, and (b) it's of limited utility because it's sandboxed and data is not visible to other apps, not even other Chrome Apps.
Your reference to "Chrome Packaged Apps" may mean that you're thinking of legacy "packaged apps," which operate under different rules than the newer Chrome Apps. However, packaged apps are no longer supported by Google and should not be developed. Perhaps you were looking at documentation or examples of package apps, not Chrome Apps.
Related
I'm writing a Chrome extension and trying to overlay a <div> over the current webpage as soon as a button is clicked in the popup.html file.
When I access the document.body.insertBefore method from within popup.html it overlays the <div> on the popup, rather than the current webpage.
Do I have to use messaging between background.html and popup.html in order to access the web page's DOM? I would like to do everything in popup.html, and to use jQuery too, if possible.
ManifestV3 service worker doesn't have any DOM/document/window.
ManifestV3/V2 extension pages (and the scripts inside) have their own DOM, document, window, and a chrome-extension:// URL (use devtools for that part of the extension to inspect it).
You need a content script to access DOM of web pages and interact with a tab's contents. Content scripts will execute in the tab as a part of that page, not as a part of the extension, so don't load your content script(s) in the extension page, use the following methods:
Method 1. Declarative
manifest.json:
"content_scripts": [{
"matches": ["*://*.example.com/*"],
"js": ["contentScript.js"]
}],
It will run once when the page loads. After that happens, use messaging but note, it can't send DOM elements, Map, Set, ArrayBuffer, classes, functions, and so on - it can only send JSON-compatible simple objects and types so you'll need to manually extract the required data and pass it as a simple array or object.
Method 2. Programmatic
ManifestV2:
Use chrome.tabs.executeScript in the extension script (like the popup or background) to inject a content script into a tab on demand.
The callback of this method receives results of the last expression in the content script so it can be used to extract data which must be JSON-compatible, see method 1 note above.
Required permissions in manifest.json:
Best case: "activeTab", suitable for a response to a user action (usually a click on the extension icon in the toolbar). Doesn't show a permission warning when installing the extension.
Usual: "*://*.example.com/" plus any other sites you want.
Worst case: "<all_urls>" or "*://*/", "http://*/", "https://*/" - when submitting into Chrome Web Store all of these put your extension in a super slow review queue because of broad host permissions.
ManifestV3 differences to the above:
Use chrome.scripting.executeScript.
Required permissions in manifest.json:
"scripting" - mandatory
"activeTab" - ideal scenario, see notes for ManifestV2 above.
If ideal scenario is impossible add the allowed sites to host_permissions in manifest.json.
Some examples of the extension popup script that use programmatic injection to add that div.
ManifestV3
Don't forget to add the permissions in manifest.json, see the other answer for more info.
Simple call:
(async () => {
const [tab] = await chrome.tabs.query({active: true, currentWindow: true});
await chrome.scripting.executeScript({
target: {tabId: tab.id},
func: inContent1,
});
})();
// executeScript runs this code inside the tab
function inContent1() {
const el = document.createElement('div');
el.style.cssText = 'position:fixed; top:0; left:0; right:0; background:red';
el.textContent = 'DIV';
document.body.appendChild(el);
}
Note: in Chrome 91 or older func: should be function:.
Calling with parameters and receiving a result
Requires Chrome 92 as it implemented args.
Example 1:
res = await chrome.scripting.executeScript({
target: {tabId: tab.id},
func: (a, b) => { return [window[a], window[b]]; },
args: ['foo', 'bar'],
});
Example 2:
(async () => {
const [tab] = await chrome.tabs.query({active: true, currentWindow: true});
let res;
try {
res = await chrome.scripting.executeScript({
target: {tabId: tab.id},
func: inContent2,
args: [{ foo: 'bar' }], // arguments must be JSON-serializable
});
} catch (e) {
console.warn(e.message || e);
return;
}
// res[0] contains results for the main page of the tab
document.body.textContent = JSON.stringify(res[0].result);
})();
// executeScript runs this code inside the tab
function inContent2(params) {
const el = document.createElement('div');
el.style.cssText = 'position:fixed; top:0; left:0; right:0; background:red';
el.textContent = params.foo;
document.body.appendChild(el);
return {
success: true,
html: document.body.innerHTML,
};
}
ManifestV2
Simple call:
// uses inContent1 from ManifestV3 example above
chrome.tabs.executeScript({ code: `(${ inContent1 })()` });
Calling with parameters and receiving a result:
// uses inContent2 from ManifestV3 example above
chrome.tabs.executeScript({
code: `(${ inContent2 })(${ JSON.stringify({ foo: 'bar' }) })`
}, ([result] = []) => {
if (!chrome.runtime.lastError) {
console.log(result); // shown in devtools of the popup window
}
});
This example uses automatic conversion of inContent function's code to string, the benefit here is that IDE can apply syntax highlight and linting. The obvious drawback is that the browser wastes time to parse the code, but usually it's less than 1 millisecond thus negligible.
In background.js, I create a popup like so:
chrome.windows.create({
focused: true,
width: 1170,
url : "settings/index.html",
type: "popup"
}, function(popup) {
tab_app = popup.id;
alert(tab_app);
});
I store the id in tab_app.
how can I pass a value from background.js to my popup?
I'm trying like that:
chrome.tabs.executeScript(tab_app, {code: "alert("+message.add+");"});
but it keeps telling me that this tab id doesnt exist.. im assuming its because its a popup. will appreciate some help.
Since it's your extension page, the method of choice is Messaging.
Note: you can't use the per-tab messaging of chrome.tabs.sendMessage, since this explicitly targets the content script context (that doesn't exist for extension pages). You need to use the "broadcast" chrome.runtime.sendMessage that will send to all other extension pages.
If you can have more than one popup-type window at a time, this may be a problem - you need some identifier to go along. You could pass it as a URL parameter or a URL hash, e.g. "settings/index.html?id=foo" or "settings/index.html#foo". If you don't expect more than one popup-type window (you can always check if one is open before opening a new one), it doesn't matter.
If you really need dynamic code loading or execution, not just passing data (doubtful), you need to be mindful of CSP.
You can dynamically load a script from your extension's package by just creating and adding a <script> tag to the document.
However, you can't, by default, pass a string of code and eval it in the extension context. You could add 'unsafe-eval' to CSP string, but that's a bad idea in general.
Most probably, you only need some commands to be passed along with data. Pure messaging is great for it, just look at the docs.
This old answer of mine may be of use - I'm using opening a new tab and passing data there to print it.
You cannot call executeScript in the your extension pages. If you try to use executeScript in your extension page. It will show error :
Unchecked runtime.lastError while running tabs.executeScript: Cannot
access contents of url
"chrome-extension://extension_id/yourPage.html".
Extension manifest must request permission to access this host
Now you cannot add "chrome-extension://<extension_id>/yourPage.html" under permissions in manifest.json because it is invalid and not allowed.
Instead you can use message passing.
background.js:
function createNewtab(){
var targetId = null;
chrome.tabs.onUpdated.addListener(function listener(tabId, changedProps) {
if (tabId != targetId || changedProps.status != "complete")
return;
chrome.tabs.onUpdated.removeListener(listener);
chrome.tabs.sendMessage(targetId, {message : "loadNewTab"},function(response){
// do nothing yet
});
});
chrome.windows.create({
focused: true,
width: 1170,
url : chrome.extension.getURL("settings/index.html"),
type: "popup"
}, function(popup) {
targetId = popup.tabs[0].id;
});
}
index.js:
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse){
switch (request.message){
case "loadNewTab":
alert("HI")
break;
}
});
I'm trying to block some requests in a Chrome app.
I created a JavaScript listener that does this validation:
chrome.webRequest.onBeforeRequest.addListener(
{
urls: ["*://site.com/test/*"]
},
["blocking"]
);
But the requests are not blocking. Did I miss something in this code?
My manifest:
"background": {
"scripts": ["listener.js"],
"persistent": true
},
"permissions": ["tabs", "http://*/*"],
"manifest_version": 2,
It looks like you misunderstood the meaning of "blocking" here.
https://developer.chrome.com/extensions/webRequest.html#subscription
If the optional opt_extraInfoSpec array contains the string 'blocking'
(only allowed for specific events), the callback function is handled
synchronously. That means that the request is blocked until the
callback function returns. In this case, the callback can return a
BlockingResponse that determines the further life cycle of the
request.
To block a request (cancel it), return {cancel: true} in your event handler.
For example:
chrome.webRequest.onBeforeRequest.addListener(
function() {
return {cancel: true};
},
{
urls: ["*://site.com/test/*"]
},
["blocking"]
);
This will block all URLs matching *://site.com/test/*.
Also remember to declare both webRequest and webRequestBlocking permissions in your manifest.
From Chrome 59 you can block specific requests from Network tab of developer tools itself.
https://developers.google.com/web/updates/2017/04/devtools-release-notes#block-requests
Right-click on the request in the Network panel and select Block Request URL. A new Request blocking tab pops up in the Drawer, which lets you manage blocked requests.
I am learning to develop a Browser Action extension for Google Chrome, and have split up javascript functionality into multiple files. In the popup.html file, script resources are defined like
<script src="js/Identity.js"></script>
<script src="js/View.js"></script>
View.js needs to call into methods of the object exposed from Identity.js, and passes a callback function to be notified when the process is completed. However, it appears Chrome would break execution.
Refused to evaluate script because it violates the following Content
Security Policy directive: "script-src 'self'"
From what i understand, Google states that policy is to prevent arbitrary strings to be evaluated into an executable block of logic. However I am passing actual functions between my objects so i'm not too sure what must be corrected here?
IdentityObj.Process = function (params, callback) {
doSomeWork();
setTimeout(callback(true), 1000); // break here
};
From the View object, an example would be
View.loginClick = function(event) {
event.preventDefault();
this.loggingInState();
var emailAddr = $('#emailAddr').val();
var password = $('#password').val();
IdentityObj.login(emailAddr, password, this.loginCallback.bind(this));
};
View.loginCallback = function(success) {
if (success) { this.usageState(); }
else { this.errorState(); }
};
My colleague sported the problem and explained it, so now I understand what you were referring to.
I was executing the callback function direct in the setTimeout() definition, so setTimeout() receives the result of callback(true) instead of the callback itself. That would then require an eval and thus triggering the Chrome security policy.
The execution of callback() has to be wrapped in a function declaration.
IdentityObj.Process = function (params, callback) {
doSomeWork();
setTimeout(function(){callback(true)}, 1000); // break here
};
I'm doing a plugin to do some transformations to the interface. I keep getting unsafe javascript attempt to access frame with url.... Domains, protocols and ports must match (typical cross site issue)
But being an extension it should have access to the iframe's content http://code.google.com/chrome/extensions/content_scripts.html ...
Doesn anyone know how to access it's contents so they can be capturable?
There's generally no direct way of accessing a different-origin window object. If you want to securely communicate between content scripts in different frames, you have to send a message to the background page which in turn sends the message back to the tab.
Here is an example:
Part of manifest.json:
"background": {"scripts":["bg.js"]},
"content_scripts": [
{"js": ["main.js"], "matches": ["<all_urls>"]},
{"js": ["sub.js"], "matches": ["<all_urls>"], "all_frames":true}
]
main.js:
var isTop = true;
chrome.runtime.onMessage.addListener(function(details) {
alert('Message from frame: ' + details.data);
});
sub.js:
if (!window.isTop) { // true or undefined
// do something...
var data = 'test';
// Send message to top frame, for example:
chrome.runtime.sendMessage({sendBack:true, data:data});
}
Background script 'bg.js':
chrome.runtime.onMessage.addListener(function(message, sender) {
if (message.sendBack) {
chrome.tabs.sendMessage(sender.tab.id, message.data);
}
});
An alternative method is to use chrome.tabs.executeScript in bg.js to trigger a function in the main content script.
Relevant documentation
Message passing c.runtime.sendMessage / c.tabs.sendMessage / c.runtime.onMessage
MessageSender and Tab types.
Content scripts
chrome.tabs.executeScript
I understand that this is an old question but I recently spent half a day in order to solve it.
Usually creating of a iframe looks something like that:
var iframe = document.createElement('iframe');
iframe.src = chrome.extension.getURL('iframe-content-page.html');
This frame will have different origin with a page and you will not be able to obtain its DOM. But if you create iframe just for css isolation you can do this in another way:
var iframe = document.createElement('iframe');
document.getElementById("iframe-parent").appendChild(iframe);
iframe.contentDocument.write(getFrameHtml('html/iframe-content-page.html'));
.......
function getFrameHtml(htmlFileName) {
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", chrome.extension.getURL(html/htmlFileName), false);
xmlhttp.send();
return xmlhttp.responseText;
}
.......
"web_accessible_resources": [
"html/htmlFileName.html",
"styles/*",
"fonts/*"
]
After that you can use iframe.contentDocument to access to iframe's DOM