Backed out changeset f55ff90f2b6f (bug 1044736)

This commit is contained in:
Carsten "Tomcat" Book 2014-11-12 08:39:40 +01:00
parent 00d6e9f501
commit b2fad0e589
3 changed files with 275 additions and 128 deletions

View File

@ -8,10 +8,122 @@ const {utils: Cu, interfaces: Ci} = Components;
Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/BrowserElementParent.jsm");
const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
const BROWSER_FRAMES_ENABLED_PREF = "dom.mozBrowserFramesEnabled";
XPCOMUtils.defineLazyModuleGetter(this, "BrowserElementParentBuilder",
"resource://gre/modules/BrowserElementParent.jsm",
"BrowserElementParentBuilder");
function debug(msg) {
//dump("BrowserElementParent.js - " + msg + "\n");
}
/** /**
* BrowserElementParent implements one half of <iframe mozbrowser>. (The other * BrowserElementParent implements one half of <iframe mozbrowser>. (The other
* half is, unsurprisingly, BrowserElementChild.) * half is, unsurprisingly, BrowserElementChild.)
*
* BrowserElementParentFactory detects when we create a windows or docshell
* contained inside a <iframe mozbrowser> and creates a BrowserElementParent
* object for that window.
*
* It creates a BrowserElementParent that injects script to listen for
* certain event.
*/ */
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementParent]);
function BrowserElementParentFactory() {
this._initialized = false;
}
BrowserElementParentFactory.prototype = {
classID: Components.ID("{ddeafdac-cb39-47c4-9cb8-c9027ee36d26}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
/**
* Called on app startup, and also when the browser frames enabled pref is
* changed.
*/
_init: function() {
if (this._initialized) {
return;
}
// If the pref is disabled, do nothing except wait for the pref to change.
// (This is important for tests, if nothing else.)
if (!this._browserFramesPrefEnabled()) {
Services.prefs.addObserver(BROWSER_FRAMES_ENABLED_PREF, this, /* ownsWeak = */ true);
return;
}
debug("_init");
this._initialized = true;
// Maps frame elements to BrowserElementParent objects. We never look up
// anything in this map; the purpose is to keep the BrowserElementParent
// alive for as long as its frame element lives.
this._bepMap = new WeakMap();
Services.obs.addObserver(this, 'remote-browser-pending', /* ownsWeak = */ true);
Services.obs.addObserver(this, 'inprocess-browser-shown', /* ownsWeak = */ true);
},
_browserFramesPrefEnabled: function() {
try {
return Services.prefs.getBoolPref(BROWSER_FRAMES_ENABLED_PREF);
}
catch(e) {
return false;
}
},
_observeInProcessBrowserFrameShown: function(frameLoader) {
// Ignore notifications that aren't from a BrowserOrApp
if (!frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsBrowserOrAppFrame) {
return;
}
debug("In-process browser frame shown " + frameLoader);
this._createBrowserElementParent(frameLoader,
/* hasRemoteFrame = */ false,
/* pending frame */ false);
},
_observeRemoteBrowserFramePending: function(frameLoader) {
// Ignore notifications that aren't from a BrowserOrApp
if (!frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsBrowserOrAppFrame) {
return;
}
debug("Remote browser frame shown " + frameLoader);
this._createBrowserElementParent(frameLoader,
/* hasRemoteFrame = */ true,
/* pending frame */ true);
},
_createBrowserElementParent: function(frameLoader, hasRemoteFrame, isPendingFrame) {
let frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
this._bepMap.set(frameElement, BrowserElementParentBuilder.create(
frameLoader, hasRemoteFrame, isPendingFrame));
},
observe: function(subject, topic, data) {
switch(topic) {
case 'app-startup':
this._init();
break;
case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
if (data == BROWSER_FRAMES_ENABLED_PREF) {
this._init();
}
break;
case 'remote-browser-pending':
this._observeRemoteBrowserFramePending(subject);
break;
case 'inprocess-browser-shown':
this._observeInProcessBrowserFrameShown(subject);
break;
}
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementParentFactory]);

View File

@ -14,7 +14,7 @@ let Cr = Components.results;
* appropriate action here in the parent. * appropriate action here in the parent.
*/ */
this.EXPORTED_SYMBOLS = ["BrowserElementParent"]; this.EXPORTED_SYMBOLS = ["BrowserElementParentBuilder"];
Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@ -25,8 +25,10 @@ XPCOMUtils.defineLazyGetter(this, "DOMApplicationRegistry", function () {
return DOMApplicationRegistry; return DOMApplicationRegistry;
}); });
const TOUCH_EVENTS_ENABLED_PREF = "dom.w3c_touch_events.enabled";
function debug(msg) { function debug(msg) {
//dump("BrowserElementParent - " + msg + "\n"); //dump("BrowserElementParent.jsm - " + msg + "\n");
} }
function getIntPref(prefName, def) { function getIntPref(prefName, def) {
@ -57,83 +59,138 @@ function visibilityChangeHandler(e) {
} }
} }
function defineNoReturnMethod(fn) { this.BrowserElementParentBuilder = {
return function method() { create: function create(frameLoader, hasRemoteFrame, isPendingFrame) {
if (!this._domRequestReady) { return new BrowserElementParent(frameLoader, hasRemoteFrame);
// Remote browser haven't been created, we just queue the API call. }
let args = Array.slice(arguments);
args.unshift(this);
this._pendingAPICalls.push(method.bind.apply(fn, args));
return;
}
if (this._isAlive()) {
fn.apply(this, arguments);
}
};
} }
function defineDOMRequestMethod(msgName) { function BrowserElementParent(frameLoader, hasRemoteFrame, isPendingFrame) {
return function() { debug("Creating new BrowserElementParent object for " + frameLoader);
return this._sendDOMRequest(msgName);
};
}
function BrowserElementParent() {
debug("Creating new BrowserElementParent object");
this._domRequestCounter = 0; this._domRequestCounter = 0;
this._domRequestReady = false; this._domRequestReady = false;
this._pendingAPICalls = []; this._pendingAPICalls = [];
this._pendingDOMRequests = {}; this._pendingDOMRequests = {};
this._pendingSetInputMethodActive = []; this._pendingSetInputMethodActive = [];
this._hasRemoteFrame = hasRemoteFrame;
this._nextPaintListeners = []; this._nextPaintListeners = [];
this._frameLoader = frameLoader;
this._frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
let self = this;
if (!this._frameElement) {
debug("No frame element?");
return;
}
Services.obs.addObserver(this, 'ask-children-to-exit-fullscreen', /* ownsWeak = */ true); Services.obs.addObserver(this, 'ask-children-to-exit-fullscreen', /* ownsWeak = */ true);
Services.obs.addObserver(this, 'oop-frameloader-crashed', /* ownsWeak = */ true); Services.obs.addObserver(this, 'oop-frameloader-crashed', /* ownsWeak = */ true);
Services.obs.addObserver(this, 'copypaste-docommand', /* ownsWeak = */ true); Services.obs.addObserver(this, 'copypaste-docommand', /* ownsWeak = */ true);
let defineMethod = function(name, fn) {
XPCNativeWrapper.unwrap(self._frameElement)[name] = Cu.exportFunction(function() {
if (self._isAlive()) {
return fn.apply(self, arguments);
}
}, self._frameElement);
}
let defineNoReturnMethod = function(name, fn) {
XPCNativeWrapper.unwrap(self._frameElement)[name] = Cu.exportFunction(function method() {
if (!self._domRequestReady) {
// Remote browser haven't been created, we just queue the API call.
let args = Array.slice(arguments);
args.unshift(self);
self._pendingAPICalls.push(method.bind.apply(fn, args));
return;
}
if (self._isAlive()) {
fn.apply(self, arguments);
}
}, self._frameElement);
};
let defineDOMRequestMethod = function(domName, msgName) {
XPCNativeWrapper.unwrap(self._frameElement)[domName] = Cu.exportFunction(function() {
return self._sendDOMRequest(msgName);
}, self._frameElement);
}
// Define methods on the frame element.
defineNoReturnMethod('setVisible', this._setVisible);
defineDOMRequestMethod('getVisible', 'get-visible');
// Not expose security sensitive browser API for widgets
if (!this._frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsWidget) {
defineNoReturnMethod('sendMouseEvent', this._sendMouseEvent);
// 0 = disabled, 1 = enabled, 2 - auto detect
if (getIntPref(TOUCH_EVENTS_ENABLED_PREF, 0) != 0) {
defineNoReturnMethod('sendTouchEvent', this._sendTouchEvent);
}
defineNoReturnMethod('goBack', this._goBack);
defineNoReturnMethod('goForward', this._goForward);
defineNoReturnMethod('reload', this._reload);
defineNoReturnMethod('stop', this._stop);
defineMethod('download', this._download);
defineDOMRequestMethod('purgeHistory', 'purge-history');
defineMethod('getScreenshot', this._getScreenshot);
defineNoReturnMethod('zoom', this._zoom);
defineDOMRequestMethod('getCanGoBack', 'get-can-go-back');
defineDOMRequestMethod('getCanGoForward', 'get-can-go-forward');
defineDOMRequestMethod('getContentDimensions', 'get-contentdimensions');
}
defineMethod('addNextPaintListener', this._addNextPaintListener);
defineMethod('removeNextPaintListener', this._removeNextPaintListener);
defineNoReturnMethod('setActive', this._setActive);
defineMethod('getActive', 'this._getActive');
let principal = this._frameElement.ownerDocument.nodePrincipal;
let perm = Services.perms
.testExactPermissionFromPrincipal(principal, "input-manage");
if (perm === Ci.nsIPermissionManager.ALLOW_ACTION) {
defineMethod('setInputMethodActive', this._setInputMethodActive);
}
// Listen to visibilitychange on the iframe's owner window, and forward
// changes down to the child. We want to do this while registering as few
// visibilitychange listeners on _window as possible, because such a listener
// may live longer than this BrowserElementParent object.
//
// To accomplish this, we register just one listener on the window, and have
// it reference a WeakMap whose keys are all the BrowserElementParent objects
// on the window. Then when the listener fires, we iterate over the
// WeakMap's keys (which we can do, because we're chrome) to notify the
// BrowserElementParents.
if (!this._window._browserElementParents) {
this._window._browserElementParents = new WeakMap();
this._window.addEventListener('visibilitychange',
visibilityChangeHandler,
/* useCapture = */ false,
/* wantsUntrusted = */ false);
}
this._window._browserElementParents.set(this, null);
// Insert ourself into the prompt service.
BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this);
if (!isPendingFrame) {
this._setupMessageListener();
this._registerAppManifest();
} else {
// if we are a pending frame, we setup message manager after
// observing remote-browser-frame-shown
Services.obs.addObserver(this, 'remote-browser-frame-shown', /* ownsWeak = */ true);
}
} }
BrowserElementParent.prototype = { BrowserElementParent.prototype = {
classDescription: "BrowserElementAPI implementation", QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
classID: Components.ID("{9f171ac4-0939-4ef8-b360-3408aedc3060}"),
contractID: "@mozilla.org/dom/browser-element-api;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserElementAPI,
Ci.nsIObserver,
Ci.nsISupportsWeakReference]), Ci.nsISupportsWeakReference]),
setFrameLoader: function(frameLoader) {
this._frameLoader = frameLoader;
this._frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
if (!this._frameElement) {
debug("No frame element?");
return;
}
// Listen to visibilitychange on the iframe's owner window, and forward
// changes down to the child. We want to do this while registering as few
// visibilitychange listeners on _window as possible, because such a listener
// may live longer than this BrowserElementParent object.
//
// To accomplish this, we register just one listener on the window, and have
// it reference a WeakMap whose keys are all the BrowserElementParent objects
// on the window. Then when the listener fires, we iterate over the
// WeakMap's keys (which we can do, because we're chrome) to notify the
// BrowserElementParents.
if (!this._window._browserElementParents) {
this._window._browserElementParents = new WeakMap();
this._window.addEventListener('visibilitychange',
visibilityChangeHandler,
/* useCapture = */ false,
/* wantsUntrusted = */ false);
}
this._window._browserElementParents.set(this, null);
// Insert ourself into the prompt service.
BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this);
this._setupMessageListener();
this._registerAppManifest();
},
_runPendingAPICall: function() { _runPendingAPICall: function() {
if (!this._pendingAPICalls) { if (!this._pendingAPICalls) {
return; return;
@ -535,27 +592,20 @@ BrowserElementParent.prototype = {
} }
}, },
setVisible: defineNoReturnMethod(function(visible) { _setVisible: function(visible) {
this._sendAsyncMsg('set-visible', {visible: visible}); this._sendAsyncMsg('set-visible', {visible: visible});
this._frameLoader.visible = visible; this._frameLoader.visible = visible;
}), },
getVisible: defineDOMRequestMethod('get-visible'), _setActive: function(active) {
setActive: defineNoReturnMethod(function(active) {
this._frameLoader.visible = active; this._frameLoader.visible = active;
}), },
getActive: function() {
if (!this._isAlive()) {
throw Components.Exception("Dead content process",
Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
}
_getActive: function() {
return this._frameLoader.visible; return this._frameLoader.visible;
}, },
sendMouseEvent: defineNoReturnMethod(function(type, x, y, button, clickCount, modifiers) { _sendMouseEvent: function(type, x, y, button, clickCount, modifiers) {
this._sendAsyncMsg("send-mouse-event", { this._sendAsyncMsg("send-mouse-event", {
"type": type, "type": type,
"x": x, "x": x,
@ -564,11 +614,11 @@ BrowserElementParent.prototype = {
"clickCount": clickCount, "clickCount": clickCount,
"modifiers": modifiers "modifiers": modifiers
}); });
}), },
sendTouchEvent: defineNoReturnMethod(function(type, identifiers, touchesX, touchesY, _sendTouchEvent: function(type, identifiers, touchesX, touchesY,
radiisX, radiisY, rotationAngles, forces, radiisX, radiisY, rotationAngles, forces,
count, modifiers) { count, modifiers) {
let tabParent = this._frameLoader.tabParent; let tabParent = this._frameLoader.tabParent;
if (tabParent && tabParent.useAsyncPanZoom) { if (tabParent && tabParent.useAsyncPanZoom) {
@ -596,45 +646,35 @@ BrowserElementParent.prototype = {
"modifiers": modifiers "modifiers": modifiers
}); });
} }
}), },
getCanGoBack: defineDOMRequestMethod('get-can-go-back'), _goBack: function() {
getCanGoForward: defineDOMRequestMethod('get-can-go-forward'),
getContentDimensions: defineDOMRequestMethod('get-contentdimensions'),
goBack: defineNoReturnMethod(function() {
this._sendAsyncMsg('go-back'); this._sendAsyncMsg('go-back');
}), },
goForward: defineNoReturnMethod(function() { _goForward: function() {
this._sendAsyncMsg('go-forward'); this._sendAsyncMsg('go-forward');
}), },
reload: defineNoReturnMethod(function(hardReload) { _reload: function(hardReload) {
this._sendAsyncMsg('reload', {hardReload: hardReload}); this._sendAsyncMsg('reload', {hardReload: hardReload});
}), },
stop: defineNoReturnMethod(function() { _stop: function() {
this._sendAsyncMsg('stop'); this._sendAsyncMsg('stop');
}), },
/* /*
* The valid range of zoom scale is defined in preference "zoom.maxPercent" and "zoom.minPercent". * The valid range of zoom scale is defined in preference "zoom.maxPercent" and "zoom.minPercent".
*/ */
zoom: defineNoReturnMethod(function(zoom) { _zoom: function(zoom) {
zoom *= 100; zoom *= 100;
zoom = Math.min(getIntPref("zoom.maxPercent", 300), zoom); zoom = Math.min(getIntPref("zoom.maxPercent", 300), zoom);
zoom = Math.max(getIntPref("zoom.minPercent", 50), zoom); zoom = Math.max(getIntPref("zoom.minPercent", 50), zoom);
this._sendAsyncMsg('zoom', {zoom: zoom / 100.0}); this._sendAsyncMsg('zoom', {zoom: zoom / 100.0});
}), },
purgeHistory: defineDOMRequestMethod('purge-history'), _download: function(_url, _options) {
download: function(_url, _options) {
if (!this._isAlive()) {
return null;
}
let ioService = let ioService =
Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService); Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
let uri = ioService.newURI(_url, null, null); let uri = ioService.newURI(_url, null, null);
@ -754,12 +794,7 @@ BrowserElementParent.prototype = {
return req; return req;
}, },
getScreenshot: function(_width, _height, _mimeType) { _getScreenshot: function(_width, _height, _mimeType) {
if (!this._isAlive()) {
throw Components.Exception("Dead content process",
Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
}
let width = parseInt(_width); let width = parseInt(_width);
let height = parseInt(_height); let height = parseInt(_height);
let mimeType = (typeof _mimeType === 'string') ? let mimeType = (typeof _mimeType === 'string') ?
@ -779,18 +814,16 @@ BrowserElementParent.prototype = {
this._nextPaintListeners = []; this._nextPaintListeners = [];
for (let listener of listeners) { for (let listener of listeners) {
try { try {
listener.recvNextPaint(); listener();
} catch (e) { } catch (e) {
// If a listener throws we'll continue. // If a listener throws we'll continue.
} }
} }
}, },
addNextPaintListener: function(listener) { _addNextPaintListener: function(listener) {
if (!this._isAlive()) { if (typeof listener != 'function')
throw Components.Exception("Dead content process", throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
}
let self = this; let self = this;
let run = function() { let run = function() {
@ -804,11 +837,9 @@ BrowserElementParent.prototype = {
} }
}, },
removeNextPaintListener: function(listener) { _removeNextPaintListener: function(listener) {
if (!this._isAlive()) { if (typeof listener != 'function')
throw Components.Exception("Dead content process", throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
}
let self = this; let self = this;
let run = function() { let run = function() {
@ -829,12 +860,7 @@ BrowserElementParent.prototype = {
} }
}, },
setInputMethodActive: function(isActive) { _setInputMethodActive: function(isActive) {
if (!this._isAlive()) {
throw Components.Exception("Dead content process",
Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
}
if (typeof isActive !== 'boolean') { if (typeof isActive !== 'boolean') {
throw Components.Exception("Invalid argument", throw Components.Exception("Invalid argument",
Cr.NS_ERROR_INVALID_ARG); Cr.NS_ERROR_INVALID_ARG);
@ -896,10 +922,18 @@ BrowserElementParent.prototype = {
case 'ask-children-to-exit-fullscreen': case 'ask-children-to-exit-fullscreen':
if (this._isAlive() && if (this._isAlive() &&
this._frameElement.ownerDocument == subject && this._frameElement.ownerDocument == subject &&
this._frameLoader.QueryInterface(Ci.nsIFrameLoader).tabParent) { this._hasRemoteFrame) {
this._sendAsyncMsg('exit-fullscreen'); this._sendAsyncMsg('exit-fullscreen');
} }
break; break;
case 'remote-browser-frame-shown':
if (this._frameLoader == subject) {
if (!this._mm) {
this._setupMessageListener();
this._registerAppManifest();
}
Services.obs.removeObserver(this, 'remote-browser-frame-shown');
}
case 'copypaste-docommand': case 'copypaste-docommand':
if (this._isAlive() && this._frameElement.isEqualNode(subject.wrappedJSObject)) { if (this._isAlive() && this._frameElement.isEqualNode(subject.wrappedJSObject)) {
this._sendAsyncMsg('do-command', { command: data }); this._sendAsyncMsg('do-command', { command: data });

View File

@ -1,2 +1,3 @@
component {9f171ac4-0939-4ef8-b360-3408aedc3060} BrowserElementParent.js component {ddeafdac-cb39-47c4-9cb8-c9027ee36d26} BrowserElementParent.js
contract @mozilla.org/dom/browser-element-api;1 {9f171ac4-0939-4ef8-b360-3408aedc3060} contract @mozilla.org/browser-element-parent-factory;1 {ddeafdac-cb39-47c4-9cb8-c9027ee36d26}
category app-startup BrowserElementParentFactory service,@mozilla.org/browser-element-parent-factory;1