Import a content script in a firefox extension without sdk - javascript

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.

Related

How to write a Thunderbird extension (webextension) to modify the message display?

I'd like to write an extension for Thunderbird that modifies the message display (e.g. insert/replace text/markup/image).
Unfortunately, the documentation is lacking (due to recent changes?).
https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Thunderbird_extensions
is outdated
https://developer.thunderbird.net/
does not have useful examples (yet)
https://thunderbird-webextensions.readthedocs.io/
no examples either
Some examples can be found at
https://github.com/thundernest/sample-extensions
Building on https://github.com/thundernest/sample-extensions/tree/master/messageDisplay
I've modified background.js
browser.messageDisplay.onMessageDisplayed.addListener((tabId, message) => {
console.log(`Message displayed in tab ${tabId}: ${message.subject}`);
console.log(message.id);
browser.messages.getFull(message.id).then((messagepart) => {
console.log(messagepart);
body = messagepart['parts'][0]['parts'][0]['body'];
console.log(body);
body += "modified!";
console.log(body);
});
browser.windows.getCurrent().then((window)=>{
console.log(window.type);
});
browser.tabs.getCurrent().then((tab)=>{
console.log("tab",tab);
});
});
which gives me the message body (using magic indexes) but expectedly, the change is not reflected in the message display.
The window type returned is normal, not messageDisplay.
The tab is undefined despite adding permissions
"permissions": [
"messagesRead",
"activeTab",
"tabs",
"tabHide"
],
but I assume that's because the script is running as background.
So I'd need a script running on the content / access to the tab and then some hints on how to modify the displayed message content (I do not want to modify the message).
Where would I find the equivalent documentation to
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/content_scripts
specific to Thunderbird?
Specifying content_scripts in manifest.json causes "Error: Error reloading addon messageDisplay#sample.extensions.thunderbird.net: undefined".
executeScript() from background does not seem to work either, even with tabId specified.
This was not possible to do when you wrote your question, the API for modifying displayed messages was missing.
As of this writing (September 2020), the browser.messageDisplayScripts API landed a few days ago, see bug 1504475 and related patch for examples. It works as follows: You can register your content script (to modify the displayed messages) like this
let myPromise = browser.messageDisplayScripts.register({
css: [{
file: "/style.css",
}],
js: [{
file: "/content_script.js",
}],
});
And you can later unregister with
myPromise.then((script) => { script.unregister(); });
You need to register the script just once for all messages (you do not need a listener that would load it each time a message is displayed).
Note that your manifest.json needs to include the messagesModify permission for this to work.
The new API will be in Thunderbird version 82, so if I understand the release process correctly it should be in stable version 88 (unless it is backported before that). You can try it already (v82 is the current EarlyBird).
Documentation https://thunderbird-webextensions.readthedocs.io/en/68/tabs.html#getcurrent
says:
May be undefined if called from a non-tab context (for example: a background page or popup view).
Since the background.js is not called from a tab context the tab is undefined.

Retrieve html content of a page several seconds after it's loaded

I'm coding a script in nodejs to automatically retrieve data from an online directory.
Knowing that I had never done this, I chose javascript because it is a language I use every day.
I therefore from the few tips I could find on google use request with cheerios to easily access components of dom of the page.
I found and retrieved all the necessary information, the only missing step is to recover the link to the next page except that the one is generated 4 seconds after loading of page and link contains a hash so that this step Is unavoidable.
What I would like to do is to recover dom of page 4-5 seconds after its loading to be able to recover the link
I looked on the internet, and much advice to use PhantomJS for this manipulation, but I can not get it to work after many attempts with node.
This is my code :
#!/usr/bin/env node
require('babel-register');
import request from 'request'
import cheerio from 'cheerio'
import phantom from 'node-phantom'
phantom.create(function(err,ph) {
return ph.createPage(function(err,page) {
return page.open(url, function(err,status) {
console.log("opened site? ", status);
page.includeJs('http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js', function(err) {
//jQuery Loaded.
//Wait for a bit for AJAX content to load on the page. Here, we are waiting 5 seconds.
setTimeout(function() {
return page.evaluate(function() {
var tt = cheerio.load($this.html())
console.log(tt)
}, function(err,result) {
console.log(result);
ph.exit();
});
}, 5000);
});
});
});
});
but i get this error :
return ph.createPage(function (page) {
^
TypeError: ph.createPage is not a function
Is what I am about to do is the best way to do what I want to do? If not what is the simplest way? If so, where does my error come from?
If You dont have to use phantomjs You can use nightmare to do it.
It is pretty neat library to solve problems like yours, it uses electron as web browser and You can run it with or without showing window (You can also open developer tools like in Google Chrome)
It has only one flaw if You want to run it on server without graphical interface that You must install at least framebuffer.
Nightmare has method like wait(cssSelector) that will wait until some element appears on website.
Your code would be something like:
const Nightmare = require('nightmare');
const nightmare = Nightmare({
show: true, // will show browser window
openDevTools: true // will open dev tools in browser window
});
const url = 'http://hakier.pl';
const selector = '#someElementSelectorWitchWillAppearAfterSomeDelay';
nightmare
.goto(url)
.wait(selector)
.evaluate(selector => {
return {
nextPage: document.querySelector(selector).getAttribute('href')
};
}, selector)
.then(extracted => {
console.log(extracted.nextPage); //Your extracted data from evaluate
});
//this variable will be injected into evaluate callback
//it is required to inject required variables like this,
// because You have different - browser scope inside this
// callback and You will not has access to node.js variables not injected
Happy hacking!

ReferenceError while using sdk/tabs in firefox webextension

This is my first time learning to build a firefox addon. I want store all the open tabs in a window and for that I require sdk/tabs.
Here is my js file:
/*
Given the name of a beast, get the URL to the corresponding image.
*/
debugger;
var tabs = require("sdk/tabs");
function beastNameToURL(beastName) {
switch (beastName) {
case "Save Session":
debugger;
for (let tab of tabs)
console.log(tab.url);
return;
case "Load Session":
debugger;
return chrome.extension.getURL("beasts/snake.jpg");
case "Turtle":
return chrome.extension.getURL("beasts/turtle.jpg");
}
}
/*
Listen for clicks in the popup.
If the click is not on one of the beasts, return early.
Otherwise, the text content of the node is the name of the beast we want.
Inject the "beastify.js" content script in the active tab.
Then get the active tab and send "beastify.js" a message
containing the URL to the chosen beast's image.
*/
document.addEventListener("click", function(e) {
if (!e.target.classList.contains("btn")) {
return;
}
var chosenBeast = e.target.textContent;
var chosenBeastURL = beastNameToURL(chosenBeast);
chrome.tabs.executeScript(null, {
file: "/content_scripts/beastify.js"
});
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {beastURL: chosenBeastURL});
});
});
When I reach the var tabs = require("sdk/tabs") line I get a Reference error.
Github : https://github.com/sagar-shah/Session-manifest
Kindly let me know how do I resolve this error. This being my first time with add-ons I am completely lost.
Thanks in advance.
Update:
Tried to declare it globally in the js file. Now I am getting undefined error for tabs.
Update2:
I was mixing up development using sdk and webextensions as pointed out by #matagus. I have decided to go with development using the webextensions. Link to the new repository has been updated.
The error is on package.json line 6: you're telling to the addon sdk that the main file of your addon is manage.json. According to [the docs] the value of main should be:
A string representing the name of a program module that is located in one of the top-level module directories specified by lib. Defaults to "index.js".
So you need to change its value to index.js.
Besides that, I think you're missing a difference between Firefox addon built using the addon-sdk (which do not have a ´manifest.json´ and that you build using jpm tool) and the new WebExtensions which do require you to write a ´manifest.json´ like the one already have.
UPDATE:
Again: you're missing the difference between WebExtensions and SDK-based addons. Now you made a WebExtension but you're trying to use the SDK. It isn't possible. Just use chrome.tabs directly instead of trying to import it from the sdk (var tabs = require("sdk/tabs");).

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.

Chrome app: accessing sandboxed iframe from parent window

I'm using knockoutjs in my google chrome app. To be able to use knockout, I have to define the real application.html as sandox page and include it as an iframe in a dummy container. Application structure is as follows:
- container.html
|
+-- application.html as iframe
|
+-knockout and application.js
Iframe is defined as follows:
<iframe src="application.html" frameborder="0"
sandbox="allow-same-origin allow-scripts" ></iframe>
Running
document.getElementsByTagName("iframe")[0]
in inspect tool on container.html throws following error.
Sandbox access violation: Blocked a frame at "chrome-extension://hllbklabnppjkmnngfanldbllljfeaia"
from accessing a frame at "chrome-extension://hllbklabnppjkmnngfanldbllljfeaia".
The frame being accessed is sandboxed and lacks the "allow-same-origin" flag.
How can i access the iframed document from it's parent?
Do something like this:
manifest.json
"sandbox": {
"pages": ["my_ui.html"]
}
my_ui.html
<script type="text/javascript" src="knockout-1.2.3.4.js"></script>
<script type="text/javascript" src="my_ui.js"></script>
my_ui.js
this.onSomethingChange = function() {
window.top.postMessage(
{ command: 'please-do-something', myArgument: this.myArgument() }, '*');
};
container.html
<script type="text/javascript" src="container.js"></script>
<iframe id="knockoutFrame" src="my_ui.html"></iframe>
container.js
window.addEventListener('message', function(event) {
var kocw = document.getElementById('knockoutFrame').contentWindow;
var anotherContentWindow = // etc.
var dest;
if (event.source == kocw) {
// The knockout iframe sent us a message. So we'll forward it to our
// app code.
dest = anotherContentWindow;
}
if (event.source == anotherContentWindow) {
// Our app code is responding to the knockout message (or initiating
// a conversation with that iframe). Forward it to the knockout code.
dest = kocw;
}
if (dest == null) {
console.log('huh?');
}
// This makes container.js like a gatekeeper, bouncing valid messages between
// the sandboxed page and the other page in your app. You should do
// better validation here, making sure the command is real, the source
// is as expected for the kind of command, etc.
dest.postMessage(event.data, '*');
}
Your statement "I have to define the real application.html as sandbox page and include it as an iframe in a dummy container" is probably not what you wanted. The idea is to sandbox the smallest possible thing, message out to the gatekeeper page that validates the messages, and have the gatekeeper forward the narrow messages to your un-sandboxed application logic. If you just stuff everything into the sandbox, you're defeating the purpose of the sandbox.
Disclaimer: I haven't carefully examined this code from a security perspective. You'll want to assume that hostile messages are coming from the sandbox (or from elsewhere, for that matter), and do what you can to address that threat.
Found out the culprit. This is my proxy.js, which is included by the container.html, used as a bridge to transfer the messages between the application iframe and the background.js. Following part is the one that listens for the messages originated from the iframe.
window.addEventListener("message",
function(evt){
console.log(evt); <= this is the problem
var iframe = document.getElementById("application").contentWindow; <= not this one
if (evt.source == iframe) {
return chrome.runtime.sendMessage(null, evt.data);
}
}
);
I didn't think that console.log would be the causing the problem. Instead i was suspecting from the document.getElem.. line. Because trying to run that code in inspect window of the application was throwing the same error.
But it seems that console.log (console seems to belong to container.html's scope) accesses some internals of event object that are not meant to be accessible out of the iframe's scope(which explains why i get the same error in inspect console). Removing the console.log line solved this problem for me.

Categories