fune/addon-sdk/source/lib/sdk/content/page-worker.js
Kris Maglione edebd593de Bug 1239822: Part 2b - Destroy windowless browsers created for add-on SDK page workers.
--HG--
extra : commitid : 3U6icHHS9Cn
extra : rebase_source : 116b75f5beec9fab22ca7aac8542111a830875fa
2016-01-15 13:35:17 -08:00

154 lines
4.5 KiB
JavaScript

/* 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";
const { frames } = require("../remote/child");
const { Class } = require("../core/heritage");
const { Disposable } = require('../core/disposable');
const { data } = require("../self");
const { once } = require("../dom/events");
const { getAttachEventType } = require("./utils");
const { Rules } = require('../util/rules');
const { uuid } = require('../util/uuid');
const { WorkerChild } = require("./worker-child");
const { Cc, Ci, Cu } = require("chrome");
const { observe } = require("../event/chrome");
const { on } = require("../event/core");
const appShell = Cc["@mozilla.org/appshell/appShellService;1"].getService(Ci.nsIAppShellService);
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
const pages = new Map();
const DOC_INSERTED = "document-element-inserted";
function isValidURL(page, url) {
return !page.rules || page.rules.matchesAny(url);
}
const ChildPage = Class({
implements: [ Disposable ],
setup: function(frame, id, options) {
this.id = id;
this.frame = frame;
this.options = options;
this.webNav = appShell.createWindowlessBrowser(false);
this.docShell.allowJavascript = this.options.allow.script;
// Accessing the browser's window forces the initial about:blank document to
// be created before we start listening for notifications
this.contentWindow;
this.webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
pages.set(this.id, this);
this.contentURL = options.contentURL;
if (options.include) {
this.rules = Rules();
this.rules.add.apply(this.rules, [].concat(options.include));
}
},
dispose: function() {
pages.delete(this.id);
this.webProgress.removeProgressListener(this);
this.webNav.close();
this.webNav = null;
},
attachWorker: function() {
if (!isValidURL(this, this.contentWindow.location.href))
return;
this.options.id = uuid().toString();
this.options.window = this.contentWindow;
this.frame.port.emit("sdk/frame/connect", this.id, {
id: this.options.id,
url: this.contentWindow.document.documentURIObject.spec
});
new WorkerChild(this.options);
},
get docShell() {
return this.webNav.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell);
},
get webProgress() {
return this.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
},
get contentWindow() {
return this.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
},
get contentURL() {
return this.options.contentURL;
},
set contentURL(url) {
this.options.contentURL = url;
url = this.options.contentURL ? data.url(this.options.contentURL) : "about:blank";
this.webNav.loadURI(url, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
},
onLocationChange: function(progress, request, location, flags) {
// Ignore inner-frame events
if (progress != this.webProgress)
return;
// Ignore events that don't change the document
if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)
return;
let event = getAttachEventType(this.options);
// Attaching at the start of the load is handled by the
// document-element-inserted listener.
if (event == DOC_INSERTED)
return;
once(this.contentWindow, event, () => {
this.attachWorker();
}, false);
},
QueryInterface: XPCOMUtils.generateQI(["nsIWebProgressListener", "nsISupportsWeakReference"])
});
on(observe(DOC_INSERTED), "data", ({ target }) => {
let page = Array.from(pages.values()).find(p => p.contentWindow.document === target);
if (!page)
return;
if (getAttachEventType(page.options) == DOC_INSERTED)
page.attachWorker();
});
frames.port.on("sdk/frame/create", (frame, id, options) => {
new ChildPage(frame, id, options);
});
frames.port.on("sdk/frame/set", (frame, id, params) => {
let page = pages.get(id);
if (!page)
return;
if ("allowScript" in params)
page.docShell.allowJavascript = params.allowScript;
if ("contentURL" in params)
page.contentURL = params.contentURL;
});
frames.port.on("sdk/frame/destroy", (frame, id) => {
let page = pages.get(id);
if (!page)
return;
page.destroy();
});