Mozilla extension: How to get a window object in Background script - javascript

I want to use different XPCOM interfaces in Firefox Extension, here is an example taken from their API:
var domWindowUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
But I can't use it in my background script because object Window is undefined:
window.QueryInterface is not a function
Please guide me in right direction.
P.S. What is actually available in a global scope for background scripts? I can't seem to find a single clue in documentation.

The issue is that Firefox extensions do not run in the context of any particular window. As such, they often do not have the window object defined, or it is defined as something which you are not expecting if you are not familiar with writing extension code. This is particularly true if you are approaching this from the point of view of writing JavaScript for use within an HTML page. Extensions operate in a significantly larger context which includes the entire browser and all windows and tabs. Thus, there is no automatically appropriate window to use as the window object. In the context of an extension, each HTML page is just a part of the whole.
You can obtain each primary browser window through the use of nsIWindowMediator. The following function, from MDN, will run the function you pass to it once for each open window:
Components.utils.import("resource://gre/modules/Services.jsm");
function forEachOpenWindow(todo) // Apply a function to all open browser windows
{
var windows = Services.wm.getEnumerator("navigator:browser");
while (windows.hasMoreElements())
todo(windows.getNext().QueryInterface(Components.interfaces.nsIDOMWindow));
}
You will often want to find the window for the most recent browser/tab which was accessed by the user. The following code will define and set the window variable to the most recently used browser/tab. It will work either in the Add-on SDK, or in overlay/bootstrap extensions depending on which portion you un-comment.
For more information about using windows in a Firefox extension, you should see Working with windows in chrome code and perhaps Tabbed browser.
if (window === null || typeof window !== "object") {
//If you do not already have a window reference, you need to obtain one:
// Add a "/" to un-comment the code appropriate for your add-on type.
/* Add-on SDK:
var window = require('sdk/window/utils').getMostRecentBrowserWindow();
//*/
/* Overlay and bootstrap (from almost any context/scope):
var window=Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
//*/
}
Alternately using Services.jsm to access nsIWindowMediator:
/* Overlay and bootstrap:
Components.utils.import("resource://gre/modules/Services.jsm");
//*/
if (window === null || typeof window !== "object") {
//If you do not already have a window reference, you need to obtain one:
// Add a "/" to un-comment the code appropriate for your add-on type.
/* Add-on SDK:
var window = require('sdk/window/utils').getMostRecentBrowserWindow();
//*/
/* Overlay and bootstrap (from almost any context/scope):
var window = Services.wm.getMostRecentWindow("navigator:browser");
//*/
}
Here are some additional variables which might be useful to have available, depending on what you are doing:
if (typeof document === "undefined") {
//If there is no document defined, get it
var document = window.content.document;
}
if (typeof gBrowser === "undefined") {
//If there is no gBrowser defined, get it
var gBrowser = window.gBrowser;
}
var tab = gBrowser.selectedTab;
var browserForTab = gBrowser.getBrowserForTab( tab );
var notificationBox = gBrowser.getNotificationBox( browserForTab );
var ownerDocument = gBrowser.ownerDocument;
NOTE: The content of this answer was primarily taken from my answers here and here, but is based on code from MDN. For me, the code to traverse all open browser windows has its origin in the original article the MDN article How to convert an overlay extension to restartless is based on.

A quick and dirty way for testing, and sometimes real application is Services.wm.getMostRecentWindow('navigator:browser'), or you can use null or any other windowType.

Related

Opening a background window using the Firefox Add-ons SDK

I am writing a Firefox add-on, and I am using the high-level Firefox Add-on SDK API.
My add-on opens a new window, and opens several tabs in that window.
How can I get this new window to open in the background? I do not want its opening to disrupt the user's focus on the active window.
When opening a tab, there is an inBackground option that can be used for this.
I have searched the windows module documentation high and low, but I cannot find a similar option for when creating new windows!
How do I open this new window in the background?
If Mozilla forbids me from doing so, is there a way I can very quickly push the new window to the background just after it opens, so that it is minimally disruptive?
Not disallowed. Perfectly fine. Do it with a features option of alwaysLowered I think.
Full list of features found here: https://developer.mozilla.org/en-US/docs/Web/API/window.open#Position_and_size_features
var sa = Cc["#mozilla.org/supports-array;1"].createInstance(Ci.nsISupportsArray);
var wuri = Cc["#mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
wuri.data = 'about:blank';
sa.AppendElement(wuri);
let features = "chrome,dialog=no,alwaysLowered";
var wantTabs = false;
if (wantTabs) {
features += ',all';
}
/*var sDOMWin = aTab.ownerGlobal; //source DOMWindow*/
if (PrivateBrowsingUtils.permanentPrivateBrowsing/* || PrivateBrowsingUtils.isWindowPrivate(sDOMWin)*/) {
features += ",private";
} else {
features += ",non-private";
}
var XULWindow = Services.ww.openWindow(null, 'chrome://browser/content/browser.xul', null, features, sa);
You can tag this code onto the end to do something after the XULWindow loads:
XULWindow.addEventListener('load', function() {
//can lower or raise the window z-index here
var DOMWindow = XULWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
DOMWindow.gBrowser.selectedTab.linkedBrowser.webNavigation.stop(Ci.nsIWebNavigation.STOP_ALL);
}, false);

How to set 'load' action after unsafeWindow.open

It used to be that I could simply do this:
var newWindow = unsafeWindow.open(someUrlOnTheSameDomain);
newWindow.addEventListener('load', toRunOnLoad);
// or newWindow.onload = toRunOnLoad;
But now Firefox gives the error Permission denied to access property 'addEventListener' when trying to perform this or similar actions. This happens whether window.open or unsafeWindow.open are used.
According to this announcement the new way to interact with an unsafeWindow is to use cloneInto(), exportFunction(), and createObjectIn(), but it's unclear from the announcement or limited documentation how any of these can be used to attach an event handler to the new window.
What's the new solution for this pattern?
Hmm.. Turns out interacting with a window opened from another window via a content script is really a mess.
You can use window.open, but cannot interact with the resulting window, because of the various involved wrappers in conjunction with the sandbox principal (same origin policy).
You can use unsafeWindow.open, but need to make sure to provide a function belongs to unsafeWindow.
So I experimented a bit, and this seems to work in Firefox 31 and Nightly.
main.js
var pageMod = require("sdk/page-mod");
pageMod.PageMod({
include: "*.mozilla.org",
contentScriptFile: require("sdk/self").data.url("script.js")
});
script.js
function doSomething(e) {
var d = e.target.ownerDocument || e.target;
var w = d.defaultView;
console.log("abc", d.location.href, w.document.title, d.body.innerHTML);
}
exportFunction(doSomething, unsafeWindow, {defineAs: "doSomething"});
var w = unsafeWindow.open("http://www.mozilla.org/about");
w.addEventListener('load', unsafeWindow.doSomething);

Controlling a Firefox Extension via Javascript

Is it possible, using javascript, to control an overlay firefox extension? I've extracted the contents of the extension and have identified what functions/methods I need to run, but they are not accessible within the scope of the console.
Thanks in advance for any ideas.
Yes it possible to interact with other add-ons, given the right circumstances.
My test case here will be com.googlecode.sqlitemanager.openInOwnWindow(), which is part of the SqliteManager addon.
In newer builds (I'm using Nightly), there is the Browser Toolbox. With it is is as simple as opening a toolbox and executing com.googlecode.sqlitemanager.openInOwnWindow() in the Console.
You may instead use the Browser Console (or any chrome enabled WebDev Console for that matter, e.g. the Console of "about:newtab"). But you need some boilerplate code to first find the browser window. So here is the code you can execute there: var bwin = Services.wm.getMostRecentWindow("navigator:browser"); bwin.com.googlecode.sqlitemanager.openInOwnWindow()
Again, enable chrome debugging. Then open a Scratchpad and switch to Chrome in the Environment menu. Now executing com.googlecode.sqlitemanager.openInOwnWindow() in our Scratchpad will work.
You may of course write your own overlay add-on.
As a last resort, patch the add-on itself.
Bootstrapped/SDK add-ons: you can load XPIProvider.jsm (which changed location recently) and get to the bootstrapped scope (run environment of bootstrap.js) via XPIProvider.bootstrapScopes[addonID], and take it from there (use whatever is in the bootstrap scope, e.g. the SDK loader).
Now about the right circumstances: If and how you can interact with a certain add-on depends on the add-on. Add-ons may have global symbols in their overlay and hence browser window, such as in the example I used. Or may use (to some extend) JS code modules. Or have their own custom loader stuff (e.g. AdBlock Plus has their own require()-like stuff and SDK add-ons have their own loader, which isn't exactly easy to infiltate)...
Since your question is rather unspecific, I'll leave it at this.
Edit by question asker: This is correct, however I figured I'd add an example of the code I ended up using in the end, which was in fact taken directly from mozilla's developer network website:
In my chrome js:
var myExtension = {
myListener: function(evt) {
IprPreferences.setFreshIpStatus(true); // replace with whatever you want to 'fire' in the extension
}
}
document.addEventListener("MyExtensionEvent", function(e) { myExtension.myListener(e); }, false, true);
// The last value is a Mozilla-specific value to indicate untrusted content is allowed to trigger the event.
In the web content:
var element = document.createElement("MyExtensionDataElement");
element.setAttribute("attribute1", "foobar");
element.setAttribute("attribute2", "hello world");
document.documentElement.appendChild(element);
var evt = document.createEvent("Events");
evt.initEvent("MyExtensionEvent", true, false);
element.dispatchEvent(evt);
Update for Firefox 47 and up
Things changed drastically in Firefox 47. This is the new way to access it.
var XPIScope = Cu.import('resource://gre/modules/addons/XPIProvider.jsm');
var addonid = 'Profilist#jetpack';
var scope = XPIScope.XPIProvider.activeAddons.get(addonid).bootstrapScope
Old way for < Firefox 47
Update for methods of today
Typically you will do so like this:
If i wanted to get into AdBlocks scope, I check AdBlock id, it is {d10d0bf8-f5b5-c8b4-a8b2-2b9879e08c5d} so I would go:
var XPIScope = Cu.import('resource://gre/modules/addons/XPIProvider.jsm');
var adblockScope = XPIScope.XPIProvider.bootstrapScopes['{d10d0bf8-f5b5-c8b4-a8b2-2b9879e08c5d}'];
You can now tap into anything there.
Another example, I have an addon installed with id NativeShot#jetpack
I would tap into it like this:
var XPIScope = Cu.import('resource://gre/modules/addons/XPIProvider.jsm');
var nativeshotScope = XPIScope.XPIProvider.bootstrapScopes['NativeShot#jetpack'];
if you do console.log(nativeshotScope) you will see all that is inside.

Accessing iframe from chrome extension

I'm developing a chrome extension and bumped into a big problem.
I'm using content scripts to inject my javascript code on a web site. The web site has an iframe.
I can change the source code of the iframe but don't seem to get any access to the iframe's contentWindow property. I need it to insert text at the current carret position.
So basically this code works perfectly in the context of the page:
$("#iframe1").contentWindow.document.execCommand("InsertHTML", false, 'test text');
But when I try it to run in the context of my chrome extension I get this error:
TypeError: Cannot read property 'document' of undefined
What's strange is that I can access the html of the iframe. So this code works perfectly from the chrome extension:
$("#iframe1").contents().find('div').html('test')
I tried putting "all_frames": true in the manifest file but no luck :(
To understand why your code does not work, I include a fragment of my previous answer:
Content scripts do not have any access to a page's global window object. For content scripts, the following applies:
The window variable does not refer to the page's global object. Instead, it refers to a new context, a "layer" over the page. The page's DOM is fully accessible. #execution-environment
Given a document consisting of   <iframe id="frameName" src="http://domain/"></iframe>:
Access to the contents of a frame is restricted by the Same origin policy of the page; the permissions of your extension does not relax the policy.
frames[0] and frames['frameName'], (normally referring to the the frame's containing global window object) is undefined.
var iframe = document.getElementById('frameName');
iframe.contentDocument returns a document object of the containing frame, because content scripts have access to the DOM of a page. This property is null when the Same origin policy applies.
iframe.contentDocument.defaultView (refers to the window object associated with the document) is undefined.
iframe.contentWindow is undefined.
Solution for same-origin frames
In your case, either of the following will work:
// jQuery:
$("#iframe1").contents()[0].execCommand( ... );
// VanillaJS
document.getElementById("iframe1").contentDocument.execCommand( ... );
// "Unlock" contentWindow property by injecting code in context of page
var s = document.createElement('script');
s.textContent = 'document.getElementById("iframe1").contentWindow.document.execCommand( ... );';
document.head.appendChild(s);
Generic solution
The generic solution is using "all_frames": true in the manifest file, and use something like this:
if (window != top) {
parent.postMessage({fromExtension:true}, '*');
addEventListener('message', function(event) {
if (event.data && event.data.inserHTML) {
document.execCommand('insertHTML', false, event.data.insertHTML);
}
});
} else {
var test_html = 'test string';
// Explanation of injection at https://stackoverflow.com/a/9517879/938089 :
// Run code in the context of the page, so that the `contentWindow`
// property becomes accessible
var script = document.createElement('script');
script.textContent = '(' + function(s_html) {
addEventListener('message', function(event) {
if (event.data.fromExtension === true) {
var iframe = document.getElementById('iframe1');
if (iframe && (iframe.contentWindow === event.source)) {
// Window recognised, post message back
iframe.contentWindow.postMessage({insertHTML: s_html}, '*');
}
}
});
} + ')(' + JSON.stringify(test_html) + ');';
(document.head||document.documentElement).appendChild(script);
script.parentNode.removeChild(script);
}
This demo is for educational purposes only, do not use this demo in a real extension. Why? Because it uses postMessage to pass messages around. These events can also be generated by the client, which causes a security leak (XSS: arbitrary HTML injection).
The alternative to postMessage is Chrome's message API. For a demo, see this answer. You won't be able to compare the window objects though. What you can do is to rely the window.name property. The window.name property is automatically set to the value of the iframe's name attribute (just once, when the iframe is loaded).

Accessing XUL Elements using Firefox Add-on SDK

I'm trying to manipulate the XUL elements in the Firefox add-on page using the Add-on SDK. I wouldn't mind using lower-level modules. I used DOM inspector to see the structure for the add-on page. It looks like this for the add-on page:
#document
--page (id='addons-page', windowtype='Addons:Manager', etc.)
----...
----hbox
----hbox
----etc.
So I tried this bit of code in exports.main:
let delegate = {
onTrack: function(window) {
console.log('window is being tracked: ' + window); // outputs [object ChromeWindow
let doc = window.document;
var addOnPage = doc.getElementById('addons-page');
console.log(window.document.page); // outputs undefined
console.log(addOnPage); // outputs null
var xulElements = window.document.getElementsByClassName('addon-control');
console.log('our elements: ' + xulElements); // outputs [object HTMLCollection]
console.log('our elements length: ' + xulElements.length); // outputs length of 0
}
};
var tracker = new winUtils.WindowTracker(delegate);
The first problem is that the window tracker only open when Firefox is first started. How can I get it to listen and wait for the add-on page to be opened?
The second problem (probably related to the first) is that getting the elements doesn't seem to be working (xulElements.length is 0).
Any ideas?
Two issues here:
Add-on Manager doesn't usually open as a separate window so using WindowTracker is pointless. It is a page loaded into the browser.
You access the window before it has a chance to load to it isn't surprising that you don't see any elements.
Given that page-mod module doesn't seem to work for this page, listening to the chrome-document-global-created notification is probably the best solution. This code works for me:
var observers = require("observer-service");
observers.add("chrome-document-global-created", function(wnd)
{
if (wnd.location.href == "about:addons")
{
// Wait for the window to load before accessing it
wnd.addEventListener("load", function()
{
console.log(wnd.document.getElementsByClassName('addon-control').length);
}, false);
}
});

Categories

Resources