How to access document/DOM in background.js? - google-chrome

I'm trying to access the DOM in my background script to create a canvas element. How do I access the DOM in background.js in manifest v3? I know it's accessible in v2.
I'm creating this element as part of chrome.tabs.captureVisibleTab(), which is an API method specific to background.js
chrome.tabs.get(tabId, function(tab) {
chrome.tabs.captureVisibleTab(tab.windowId, { format: "png" }, function(dataUrl) {
(console.log("chrome.tabs.captureVisibleTab() called"))
if (!canvas) {
canvas = document.createElement("canvas");
document.body.appendChild(canvas);
...
I get an error currently: Error handling response: ReferenceError: document is not defined at chrome-extension://.../background.js
I tried passing the document to background script from content script like this:
chrome.runtime.sendMessage({document: document}, function(response) {})
but the passed document isn't an object and doesn't have normal DOM methods like createElement

Related

Couldn't change the object color in Autodesk Forge Viewer

I've tried to change the object color in Autodesk Forge Viewer using the following link.
https://adndevblog.typepad.com/cloud_and_mobile/2015/12/change-color-of-elements-with-view-and-data-api.html
But, I've got the below issue when trying to load the extension "Autodesk.ADN.Viewing.Extension.Color".
"Uncaught (in promise) Extension failed to .load() : Autodesk.ADN.Viewing.Extension.Color at ExtensionManager.js:390"
That's an old article. Now the load() function of the extension needs to return true on a successful load - as shown here:
https://forge.autodesk.com/en/docs/viewer/v7/developers_guide/viewer_basics/extensions/
So just return true at this place in the code:
_self.unload = function() {
console.log('Autodesk.ADN.Viewing.Extension.Color unloaded');
return true;
};
return true; // <<-- add this here
};

how to send a Json object to a dialog from the parent using dialog API in Office365

I am new to office 365 word JavaScript API. I am trying to send a Json object to a dialog from the parent using the dialog api. But I couldn't find a better solution for that. I have found it is possible to send a Json object from the dialog to the parent using below code snippet.
Office.context.ui.messageParent
can someone give me a good solution with a code snippet to solve this problem?
You can try something like that
In parent web page (the actual add-in) javascript code
Office.context.ui.displayDialogAsync(url, options, function(result) {
var dialog = result.value;
dialog.addEventHandler(Office.EventType.DialogMessageReceived, function(args){
dialog.close();
var json = JSON.parse(args.message);
//do what ever you need to do...
});
});
NOTE: for the sake of simplicity I omitted "error checks" if callback function receive error result. You should take care of that as well.
The web page that is opened at url will have a function for pushing back the json object after representing it as a string
var asString = JSON.stringify(myObj);
Office.context.ui.messageParent(asString);
Of course the webpage opened in the dialog window must also reference Office.js.
Here is the documentation link for this so-called dialogAPI https://dev.office.com/reference/add-ins/shared/officeui
Edit:
the original question is to send data from parent to children
If you need to send info to the page opened in dialogAPI. I suggest your append query parameters to url. You can stringify your Json object and pass it. This is not very clean thought.
Standardized way to serialize JSON to query string?
You can send JSON data or object back to your parent easily.
This code snippet should be in your child page's(Dialog page) JS file.
(function () {
"use strict";
// The Office initialize function must be run each time a new page is loaded
Office.initialize = function (reason) {
$(document).ready(function () {
$('#btnLogin').click(submit);
});
};
function submit() {
// Get and create the data object.
var email = $('#txtEmail').val();
var password = $('#txtPassword').val();
var data = {
email: email,
password: password
}
// Create the JSON and send it to the parent.
var json = JSON.stringify(data);
Office.context.ui.messageParent("json");
}
})();
See here: https://dev.office.com/docs/add-ins/develop/dialog-api-in-office-add-ins
Find section "Passing information to the dialog box".
Two primary ways:
Add query parameters to the URL
Store the information somewhere that is accessible to both the host window and dialog box, e.g. local storage

Chrome Packaged App with SQLite?

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.

access iframe content from a chrome's extension content script

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

How to access google chrome extension from any page's javascript

I am trying to figure out how can I call into my extension from a normal web page.
All documentation that I find seems to be either for communication between extensions, or between content scripts and extensions.
Any pointers are much appreciated!
I think you should make a content script, that injects an object into your page that calls your extension.
Create a content script that injects YourExt.js into every page, which should contain:
var YourExt = {
doThis: function () {
chrome.extension.sendRequest('doThis');
},
doThat: function () {
chrome.extension.sendRequest({
action: 'doThat',
params: ['foo','bar']
});
}
}
While extensions can't access page variables and vice versa, you can communicate between page and extension through events. Here is a quick example of creating custom events:
function fireEvent(name, target) {
//Ready: create a generic event
var evt = document.createEvent("Events")
//Aim: initialize it to be the event we want
evt.initEvent(name, true, true); //true for can bubble, true for cancelable
//FIRE!
target.dispatchEvent(evt);
}
function foobar() {
alert("foobar");
}
function testEvents() {
window.addEventListener("foobar", foobar, false); //false to get it in bubble not capture.
fireEvent("foobar", document);
}
(taken from here)
So if you need to pass information from a page to an extension, you would need to fire a custom event on a page which you will be listening to in your content script.