275 lines
7.6 KiB
JavaScript
275 lines
7.6 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 { isChildLoader } = require('./core');
|
|
if (!isChildLoader)
|
|
throw new Error("Cannot load sdk/remote/child in a main process loader.");
|
|
|
|
const { Ci, Cc } = require('chrome');
|
|
const runtime = require('../system/runtime');
|
|
const { Class } = require('../core/heritage');
|
|
const { Namespace } = require('../core/namespace');
|
|
const { omit } = require('../util/object');
|
|
const { when } = require('../system/unload');
|
|
const { EventTarget } = require('../event/target');
|
|
const { emit } = require('../event/core');
|
|
const { Disposable } = require('../core/disposable');
|
|
const { EventParent } = require('./utils');
|
|
const { addListItem, removeListItem } = require('../util/list');
|
|
|
|
const loaderID = require('@loader/options').loaderID;
|
|
|
|
const MAIN_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
|
|
|
|
const mm = Cc['@mozilla.org/childprocessmessagemanager;1'].
|
|
getService(Ci.nsISyncMessageSender);
|
|
|
|
const ns = Namespace();
|
|
|
|
const process = {
|
|
port: new EventTarget(),
|
|
get id() {
|
|
return runtime.processID;
|
|
},
|
|
get isRemote() {
|
|
return runtime.processType != MAIN_PROCESS;
|
|
}
|
|
};
|
|
exports.process = process;
|
|
|
|
process.port.emit = (...args) => {
|
|
mm.sendAsyncMessage('sdk/remote/process/message', {
|
|
loaderID,
|
|
args
|
|
});
|
|
}
|
|
|
|
function processMessageReceived({ data }) {
|
|
// Ignore messages from other loaders
|
|
if (data.loaderID != loaderID)
|
|
return;
|
|
let [event, ...args] = data.args;
|
|
emit(process.port, event, process, ...args);
|
|
}
|
|
|
|
mm.addMessageListener('sdk/remote/process/message', processMessageReceived);
|
|
|
|
when(() => {
|
|
mm.removeMessageListener('sdk/remote/process/message', processMessageReceived);
|
|
frames = null;
|
|
});
|
|
|
|
process.port.on('sdk/remote/require', (process, uri) => {
|
|
require(uri);
|
|
});
|
|
|
|
function listenerEquals(a, b) {
|
|
for (let prop of ["type", "callback", "isCapturing"]) {
|
|
if (a[prop] != b[prop])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function listenerFor(type, callback, isCapturing = false) {
|
|
return {
|
|
type,
|
|
callback,
|
|
isCapturing,
|
|
registeredCallback: undefined,
|
|
get args() {
|
|
return [
|
|
this.type,
|
|
this.registeredCallback ? this.registeredCallback : this.callback,
|
|
this.isCapturing
|
|
];
|
|
}
|
|
};
|
|
}
|
|
|
|
function removeListenerFromArray(array, listener) {
|
|
let index = array.findIndex(l => listenerEquals(l, listener));
|
|
if (index < 0)
|
|
return;
|
|
array.splice(index, 1);
|
|
}
|
|
|
|
function getListenerFromArray(array, listener) {
|
|
return array.find(l => listenerEquals(l, listener));
|
|
}
|
|
|
|
function arrayContainsListener(array, listener) {
|
|
return !!getListenerFromArray(array, listener);
|
|
}
|
|
|
|
function makeFrameEventListener(frame, callback) {
|
|
return callback.bind(frame);
|
|
}
|
|
|
|
var FRAME_ID = 0;
|
|
var tabMap = new Map();
|
|
|
|
function frameMessageReceived({ data }) {
|
|
if (data.loaderID != loaderID)
|
|
return;
|
|
let [event, ...args] = data.args;
|
|
emit(this.port, event, this, ...args);
|
|
}
|
|
|
|
const Frame = Class({
|
|
implements: [ Disposable ],
|
|
extends: EventTarget,
|
|
setup: function(contentFrame) {
|
|
// This ID should be unique for this loader across all processes
|
|
ns(this).id = runtime.processID + ":" + FRAME_ID++;
|
|
|
|
ns(this).contentFrame = contentFrame;
|
|
ns(this).messageManager = contentFrame;
|
|
ns(this).domListeners = [];
|
|
|
|
tabMap.set(contentFrame.docShell, this);
|
|
|
|
ns(this).messageReceived = frameMessageReceived.bind(this);
|
|
ns(this).messageManager.addMessageListener('sdk/remote/frame/message', ns(this).messageReceived);
|
|
|
|
this.port = new EventTarget();
|
|
this.port.emit = (...args) => {
|
|
ns(this).messageManager.sendAsyncMessage('sdk/remote/frame/message', {
|
|
loaderID,
|
|
args
|
|
});
|
|
};
|
|
|
|
ns(this).messageManager.sendAsyncMessage('sdk/remote/frame/attach', {
|
|
loaderID,
|
|
frameID: ns(this).id,
|
|
processID: runtime.processID
|
|
});
|
|
|
|
frames.attachItem(this);
|
|
},
|
|
|
|
dispose: function() {
|
|
emit(this, 'detach', this);
|
|
|
|
for (let listener of ns(this).domListeners)
|
|
ns(this).contentFrame.removeEventListener(...listener.args);
|
|
|
|
ns(this).messageManager.removeMessageListener('sdk/remote/frame/message', ns(this).messageReceived);
|
|
tabMap.delete(ns(this).contentFrame.docShell);
|
|
ns(this).contentFrame = null;
|
|
},
|
|
|
|
get content() {
|
|
return ns(this).contentFrame.content;
|
|
},
|
|
|
|
get isTab() {
|
|
let docShell = ns(this).contentFrame.docShell;
|
|
if (process.isRemote) {
|
|
// We don't want to roundtrip to the main process to get this property.
|
|
// This hack relies on the host app having defined webBrowserChrome only
|
|
// in frames that are part of the tabs. Since only Firefox has remote
|
|
// processes right now and does this this works.
|
|
let tabchild = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
.getInterface(Ci.nsITabChild);
|
|
return !!tabchild.webBrowserChrome;
|
|
}
|
|
else {
|
|
// This is running in the main process so we can break out to the browser
|
|
// And check we can find a tab for the browser element directly.
|
|
let browser = docShell.chromeEventHandler;
|
|
let tab = require('../tabs/utils').getTabForBrowser(browser);
|
|
return !!tab;
|
|
}
|
|
},
|
|
|
|
addEventListener: function(...args) {
|
|
let listener = listenerFor(...args);
|
|
if (arrayContainsListener(ns(this).domListeners, listener))
|
|
return;
|
|
|
|
listener.registeredCallback = makeFrameEventListener(this, listener.callback);
|
|
|
|
ns(this).domListeners.push(listener);
|
|
ns(this).contentFrame.addEventListener(...listener.args);
|
|
},
|
|
|
|
removeEventListener: function(...args) {
|
|
let listener = getListenerFromArray(ns(this).domListeners, listenerFor(...args));
|
|
if (!listener)
|
|
return;
|
|
|
|
removeListenerFromArray(ns(this).domListeners, listener);
|
|
ns(this).contentFrame.removeEventListener(...listener.args);
|
|
}
|
|
});
|
|
|
|
const FrameList = Class({
|
|
implements: [ EventParent, Disposable ],
|
|
extends: EventTarget,
|
|
setup: function() {
|
|
EventParent.prototype.initialize.call(this);
|
|
|
|
this.port = new EventTarget();
|
|
ns(this).domListeners = [];
|
|
|
|
this.on('attach', frame => {
|
|
for (let listener of ns(this).domListeners)
|
|
frame.addEventListener(...listener.args);
|
|
});
|
|
},
|
|
|
|
dispose: function() {
|
|
// The only case where we get destroyed is when the loader is unloaded in
|
|
// which case each frame will clean up its own event listeners.
|
|
ns(this).domListeners = null;
|
|
},
|
|
|
|
getFrameForWindow: function(window) {
|
|
for (let frame of this) {
|
|
if (frame.content == window)
|
|
return frame;
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
addEventListener: function(...args) {
|
|
let listener = listenerFor(...args);
|
|
if (arrayContainsListener(ns(this).domListeners, listener))
|
|
return;
|
|
|
|
ns(this).domListeners.push(listener);
|
|
for (let frame of this)
|
|
frame.addEventListener(...listener.args);
|
|
},
|
|
|
|
removeEventListener: function(...args) {
|
|
let listener = listenerFor(...args);
|
|
if (!arrayContainsListener(ns(this).domListeners, listener))
|
|
return;
|
|
|
|
removeListenerFromArray(ns(this).domListeners, listener);
|
|
for (let frame of this)
|
|
frame.removeEventListener(...listener.args);
|
|
}
|
|
});
|
|
var frames = exports.frames = new FrameList();
|
|
|
|
function registerContentFrame(contentFrame) {
|
|
let frame = new Frame(contentFrame);
|
|
}
|
|
exports.registerContentFrame = registerContentFrame;
|
|
|
|
function unregisterContentFrame(contentFrame) {
|
|
let frame = tabMap.get(contentFrame.docShell);
|
|
if (!frame)
|
|
return;
|
|
|
|
frame.destroy();
|
|
}
|
|
exports.unregisterContentFrame = unregisterContentFrame;
|