Skip to content
This repository was archived by the owner on Feb 26, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions lib/sdk/addon/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const { when: unload } = require('../system/unload');
const { loadReason } = require('../self');
const { rootURI } = require("@loader/options");
const globals = require('../system/globals');
const { ready: windowReady } = require('../addon/window');

const NAME2TOPIC = {
'Firefox': 'sessionstore-windows-restored',
Expand Down Expand Up @@ -86,9 +87,11 @@ function startup(reason, options) {
then(function onLocalizationReady(data) {
// Exports data to a pseudo module so that api-utils/l10n/core
// can get access to it
definePseudo(options.loader, '@l10n/data', data ? data : null);
run(options);
});
if (data) definePseudo(options.loader, '@l10n/data', data || null);
windowReady.then(function() {
run(options);
});
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to load the addon/window at load time regardless if it is used or not, am I mistaken?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am concerned about this too. Do we have any information on timing and memory penalties that this incurs?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do use windows regardless of this change, in hidden-window any API doing some ui depends on implicitly. The problem is that all of that APIs need to wait on their own and deal with synchronicity & only on some platforms.

I don't think it's worth it, I'd rather delay once and make all APIs be able to do work synchronously that curry the burden of platfrom specefic inconsistencies into all modules. Not to mention that this that load is still delayed by async read of l10n data etc..

Also note that this is temporary workaround that will go away once platform fix for Bug 565388 will be implemented.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we open the window before we wait for APP_STARTUP so add-ons start as fast as possible on startup?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we open the window before we wait for APP_STARTUP so add-ons start as fast as possible on startup?

We probably could but intention of delaying was to avoid slowing down a startup. We could also start loading l10n stuff before startup, but we decided not to for the same reasons. We can revisit these decisions, but not sure we need to block this patch for that. We can change time we start loading when we want to.

}

function run(options) {
Expand Down Expand Up @@ -128,10 +131,10 @@ function run(options) {

program.main({
loadReason: loadReason,
staticArgs: staticArgs
}, {
staticArgs: staticArgs
}, {
print: function print(_) { dump(_ + '\n') },
quit: exit
quit: exit
});
}
} catch (error) {
Expand Down
43 changes: 43 additions & 0 deletions lib/sdk/addon/window.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

"use strict";

module.metadata = {
"stability": "experimental"
};

const { backgroundify, make, getDOMWindow } = require("../window/utils");
const { defer } = require("../core/promise");
const { when: unload } = require("../system/unload");

// Once Bug 565388 is fixed and shipped we'll be able to make invisible,
// permanent docShells. Meanwhile we create hidden top level window and
// use it's docShell. Also note that we keep a reference to xulWindow since
// otherwise it's unloaded, on the other hand this will require no cleanup
// from our side since once add-on is unloaded window will be removed
// automatically.
const xulWindow = backgroundify(make())
const docShell = xulWindow.docShell;

// Get a reference to the DOM window of the given docShell and load
// such document into that would allow us to create XUL iframes, that
// are necessary for hidden frames etc..
const window = docShell.contentViewer.DOMDocument.defaultView;
window.location = "data:application/xhtml+xml;charset=utf-8,<html/>";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a good url to use? Can things like websockets etc. work from data: urls?
Does this cause the window to be chrome privileged, i.e. does it have a usable Components object in it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's URL that bz suggested us to use for this.


// Create a promise that is delivered once add-on window is interactive,
// used by add-on runner to defer add-on loading until window is ready.
let { promise, resolve } = defer();
window.addEventListener("DOMContentLoaded", function handler(event) {
window.removeEventListener("DOMContentLoaded", handler, false);
resolve();
}, false);

exports.ready = promise;
exports.window = window;

// Still close window on unload to claim memory back early.
unload(function() { window.close() });
74 changes: 13 additions & 61 deletions lib/sdk/frame/hidden-frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,15 @@ module.metadata = {
};

const { Cc, Ci } = require("chrome");
const errors = require("../deprecated/errors");
const { Class } = require("../core/heritage");
const { List, addListItem, removeListItem } = require("../util/list");
const { EventTarget } = require("../event/target");
const { emit } = require("../event/core");
const { create: makeFrame } = require("./utils");
const { defer, resolve } = require("../core/promise");
const { defer } = require("../core/promise");
const { when: unload } = require("../system/unload");
const { validateOptions, getTypeOf } = require("../deprecated/api-utils");


let appShellService = Cc["@mozilla.org/appshell/appShellService;1"].
getService(Ci.nsIAppShellService);

let hiddenWindow = appShellService.hiddenDOMWindow;

if (!hiddenWindow) {
throw new Error([
"The hidden-frame module needs an app that supports a hidden window. ",
"We would like it to support other applications, however. Please see ",
"https://bugzilla.mozilla.org/show_bug.cgi?id=546740 for more information."
].join(""));
}
const { window } = require("../addon/window");

// This cache is used to access friend properties between functions
// without exposing them on the public API.
Expand All @@ -53,33 +39,6 @@ function contentLoaded(target) {
return deferred.promise;
}

function makeHostFrame() {
// Check if we can use the hidden window itself to host our iframes.
// If it's not a suitable host, the hostFrame will be lazily created
// by the first HiddenFrame instance.
if (hiddenWindow.location.protocol == "chrome:" &&
(hiddenWindow.document.contentType == "application/vnd.mozilla.xul+xml" ||
hiddenWindow.document.contentType == "application/xhtml+xml")) {

// Resolve to an object with a shape similar to iframe.
return resolve({ contentDocument: hiddenWindow.document });
}
else {
return contentLoaded(makeFrame(hiddenWindow.document, {
// Ugly ugly hack. This is the most lightweight "chrome:" file I could
// find on the tree.
// This hack should be removed by proper platform support on bug 565388
uri: "chrome://global/content/mozilla.xhtml",
namespaceURI: hiddenWindow.document.documentElement.namespaceURI,
nodeName: "iframe",
allowJavascript: true,
allowPlugins: true,
allowAuth: true
}));
}
}
var hostFrame = makeHostFrame();

function FrameOptions(options) {
options = options || {}
return validateOptions(options, FrameOptions.validator);
Expand Down Expand Up @@ -130,17 +89,16 @@ function addHidenFrame(frame) {
if (isFrameCached(frame)) return frame;
else cache.push(frame);

hostFrame.then(function({ contentDocument }) {
let element = makeFrame(contentDocument, {
nodeName: "iframe",
type: "content",
allowJavascript: true,
allowPlugins: true,
allowAuth: true,
});
elements.set(frame, element);
return contentLoaded(element);
}).then(function onFrameReady(element) {
let element = makeFrame(window.document, {
nodeName: "iframe",
type: "content",
allowJavascript: true,
allowPlugins: true,
allowAuth: true,
});
elements.set(frame, element);

contentLoaded(element).then(function onFrameReady(element) {
emit(frame, "ready");
}, console.exception);

Expand All @@ -162,10 +120,4 @@ function removeHiddenFrame(frame) {
}
exports.remove = removeHiddenFrame;

unload(function () {
cache.splice(0).forEach(removeHiddenFrame);

hostFrame.then(function(host) {
if (hast.parentNode) frame.parentNode.removeChild(frame);
});
});
unload(function() cache.splice(0).forEach(removeHiddenFrame));
7 changes: 6 additions & 1 deletion lib/sdk/test/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@

const { Loader, resolveURI, Require,
unload, override, descriptor } = require('../loader/cuddlefish');
const addonWindow = require('../addon/window');

exports.Loader = function(module, globals, packaging) {
let options = packaging || require("@loader/options");
options = override(options, {
globals: override(require('../system/globals'), globals || {})
globals: override(require('../system/globals'), globals || {}),
modules: override(options.modules || {}, {
'addon-sdk/sdk/addon/window': addonWindow
})
});

let loader = Loader(options);

return Object.create(loader, descriptor({
require: Require(loader, module),
sandbox: function(id) {
Expand Down
52 changes: 39 additions & 13 deletions lib/sdk/window/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1'].
getService(Ci.nsIWindowWatcher);
const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
getService(Ci.nsIAppShellService);
const observers = require('../deprecated/observer-service');
const WM = Cc['@mozilla.org/appshell/window-mediator;1'].
getService(Ci.nsIWindowMediator);
const ioService = Cc['@mozilla.org/network/io-service;1'].
getService(Ci.nsIIOService);

const BROWSER = 'navigator:browser',
URI_BROWSER = 'chrome://browser/content/browser.xul',
Expand All @@ -28,6 +29,11 @@ function getMostRecentBrowserWindow() {
}
exports.getMostRecentBrowserWindow = getMostRecentBrowserWindow;

function getHiddenWindow() {
return appShellService.hiddenDOMWindow;
}
exports.getHiddenWindow = getHiddenWindow;

/**
* Returns the ID of the window's current inner window.
*/
Expand Down Expand Up @@ -58,6 +64,12 @@ function getXULWindow(window) {
};
exports.getXULWindow = getXULWindow;

function getDOMWindow(xulWindow) {
return xulWindow.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDOMWindow);
}
exports.getDOMWindow = getDOMWindow;

/**
* Returns `nsIBaseWindow` for the given `nsIDOMWindow`.
*/
Expand All @@ -81,19 +93,13 @@ function getWindowLoadingContext(window) {
exports.getWindowLoadingContext = getWindowLoadingContext;

/**
* Removes given window from the application's window registry. Unless
* `options.close` is `false` window is automatically closed on application
* quit.
* @params {nsIDOMWindow} window
* @params {Boolean} options.close
* Removes given window from the application's window registry.
* @params {nsIDOMWindow|nsIXULWindow} window
*/
function backgroundify(window, options) {
let base = getBaseWindow(window);
base.visibility = false;
base.enabled = false;
appShellService.unregisterTopLevelWindow(getXULWindow(window));
if (!options || options.close !== false)
observers.add('quit-application-granted', window.close.bind(window));
function backgroundify(window) {
let xulWindow = window instanceof Ci.nsIXULWindow ? window :
getXULWindow(window);
appShellService.unregisterTopLevelWindow(xulWindow);

return window;
}
Expand Down Expand Up @@ -134,6 +140,26 @@ function open(uri, options) {
}
exports.open = open;

function make(options) {
/**
Opens top level window and return it's `nsIXULWindow` representation. Open
window is not shown `unless` optional `options.show` is set to `true`.
**/
options = options || {};
let show = options.show || false;
let loadDefault = options.loadDefault !== false;
let mask = options.mask || null;
let width = options.width || null;
let height = options.height || null;
let appShell = options.shell || null;
let uri = options.uri ? ioService.newURI(options.uri, null, null) : null;
let parent = options.parent || null;

return appShellService.createTopLevelWindow(parent, uri, show, loadDefault,
mask, width, height, appShell);
}
exports.make = make;

/**
* Opens a top level window and returns it's `nsIDOMWindow` representation.
* Same as `open` but with more features
Expand Down
4 changes: 1 addition & 3 deletions test/test-cuddlefish.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ exports['test loader'] = function(assert) {
prints.push(message);
}

let options = JSON.parse(JSON.stringify(packaging));

let loader = Loader(override(options, {
let loader = Loader(override(packaging, {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that let options = JSON.parse(JSON.stringify(packaging)); was testing that packaging was a valid json object. I could be wrong, but this change doesn't seem necessary.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it was done just to copy options from the loader. This no longer works because with this addition:
https://github.com/mozilla/addon-sdk/pull/698/files#L3R14

it no longer JSON since window can not be serialized.

globals: {
print: print,
foo: 1
Expand Down
6 changes: 4 additions & 2 deletions test/test-window-utils2.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

const { Ci } = require('chrome');
const { open, backgroundify, windows,
getXULWindow, getBaseWindow } = require('sdk/window/utils');
getXULWindow, getBaseWindow,
getDOMWindow } = require('sdk/window/utils');

const windowUtils = require('sdk/deprecated/window-utils');

exports['test get nsIBaseWindow from nsIDomWindow'] = function(assert) {
Expand Down Expand Up @@ -50,7 +52,7 @@ exports['test backgroundify'] = function(assert) {
let window = open('data:text/html;charset=utf-8,backgroundy');
assert.ok(~windows().indexOf(window),
'window is in the list of windows');
let backgroundy = backgroundify(window);
let backgroundy = getDOMWindow(backgroundify(window));
assert.equal(backgroundy, window, 'backgroundify returs give window back');
assert.ok(!~windows().indexOf(window),
'backgroundifyied window is in the list of windows');
Expand Down