Access DOM of a web site via google packaged app - javascript

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()});

Related

TypeError: browser is undefined (Web Extension Messaging)

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
});

Import a content script in a firefox extension without sdk

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.

Call a function that is in 'content_script.js' within the webpage

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.

Create XPI package with the Add-on SDK?

I got task to write an add-on for Firefox which will add an div element to existing page. I downloaded Add-on SDK and wrote a main.js file that looks like this:
var data = require("sdk/self").data;
require("sdk/tabs").on("ready", ExecuteAd);
function ExecuteAd(tab) {
if ( tab.url.indexOf("some url checking") > -1 ) {
var image = "http://www.lavasoft.com/img/product_icons/aaw11/free.png";
var link = "http://www.google.me";
tab.attach({
contentScriptFile: data.url("myscript.js"),
contentScript: "appendFunc('"+image+"', '"+link+"');"
//contentScript: "alert('Works');"
});
}
}
When I execute command cfx run it starts Firefox and if I go to specific web pages this script works. But when I create XPI file with cfx xpi and then click on Firefox and open that file it installs my add-on but now when I go to same web pages I gave been before add-on does not work. I have this external Javascript file which is stored in folder 'data'.
appendFunc is in myscript.js file.
How to make my extension work in production environment not just testing environment? I think that main problem is that it does not find this data/myscript.js (does it include in .xpi file?)
Don't mix contentScript and contentScriptFile. Also, you cannot know what of both is loaded first.
Instead load your script, and communicate using port.
main.js
var data = require("sdk/self").data;
require("sdk/tabs").on("ready", ExecuteAd);
function ExecuteAd(tab) {
var image = "http://www.lavasoft.com/img/product_icons/aaw11/free.png";
var link = "http://www.google.me";
var worker = tab.attach({
contentScriptFile: data.url("myscript.js")
});
worker.port.emit("showAd", {image: image, link: link});
}
myscript.js
self.port.on("showAd", function(data) {
console.log("showing ad", data.link, data.image);
});
Also, it sounds like PageMod would be a better choice for what you're doing.
PS: Also consult the Add-on Policies if you're planning to host on the addons.mozilla.org website. The policies e.g. prohibit injecting ads that a) aren't clearly marked as such and b) where the user did not opt-in prior to that.

How to filter out iframes in an Add-on SDK extension?

The main problem is that my extension is loading into every iframes on a target webpage. It puts buttons that appear inside the iframes as well. I want them to disappear. The window and document objects are shown as the parent's window and document objects. So it's impossible to check the document location for example because it shows the parent's location instead of the iframe's location.
You could write a user script which uses the #noframes metadata header key and include the user script in to your Jetpack with this user script package for the addon sdk.
Writing user scripts is much easier than writing Page Mods too.
Edit: now (Add-on SDK version 1.11 released) pagemod supports what you are looking for. See the docs and the new attachTo option.
The following information is outdated (can be used if you build against Add-on SDK 1.10 or previous:
Unfortunately pagemod injects your script in all the frames of the page, and not just once per page on the top frame as what you achieve with Chrome's content scripts. Of course, there are workarounds for stopping the execution of the script (see the other answers).
But if you really want to inject your script only once per page, on the top frame, you have to use tabs. With this solution you script only gets injected when it has to.
Bellow I provide and example for porting from pagemod to tabs, keeping all the message reception system with port working. It can easily be modified if you need to not just receive, but also send messages using that same port object. The script gets injected when we are in the domain youtube.com:
Old pagemod way:
var self = require("self");
var pagemod = require("page-mod");
pagemod.PageMod(
{
include: "*.youtube.com",
contentScriptFile: self.data.url("myContentScript.js"),
onAttach: function(worker)
{
worker.port.on("myMessageId", function(payload)
{
console.log("Message received: " + payload);
});
}
});
New tabs way:
var self = require("self");
var tabs = require("tabs");
tabs.on("ready", function(tab)
{
if (tab != undefined && tab.url != undefined && tab.url.split("/")[2] != undefined)
{
var domain = "youtube.com";
var host = tab.url.split("/")[2];
if (host == domain || host.substr(host.length - domain.length - 1) == "." + domain)
{
var worker = tab.attach({ contentScriptFile: self.data.url("myContentScript.js") });
worker.port.on("myMessageId", function(payload)
{
console.log("Message received: " + payload);
});
}
}
});
One workaround is to put something like this in your content script:
if (window.frameElement === null){
// I'm in the topmost window
// Add buttons and things to the page.
}else{
// I'm in an iFrame... do nothing!
}
The content script will still be added to every page, but it's a relatively simple and lightweight check for iFrames.

Categories