--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -1027,16 +1027,24 @@ nsFrameLoader::SwapWithOtherRemoteLoader
aOther->mRemoteBrowser->GetBrowserDOMWindow();
nsCOMPtr<nsIBrowserDOMWindow> browserDOMWindow =
mRemoteBrowser->GetBrowserDOMWindow();
if (!!otherBrowserDOMWindow != !!browserDOMWindow) {
return NS_ERROR_NOT_IMPLEMENTED;
}
+ // Destroy browser frame scripts for content leaving a frame with browser API
+ if (OwnerIsMozBrowserOrAppFrame() && !aOther->OwnerIsMozBrowserOrAppFrame()) {
+ DestroyBrowserFrameScripts();
+ }
+ if (!OwnerIsMozBrowserOrAppFrame() && aOther->OwnerIsMozBrowserOrAppFrame()) {
+ aOther->DestroyBrowserFrameScripts();
+ }
+
aOther->mRemoteBrowser->SetBrowserDOMWindow(browserDOMWindow);
mRemoteBrowser->SetBrowserDOMWindow(otherBrowserDOMWindow);
// Native plugin windows used by this remote content need to be reparented.
if (nsPIDOMWindowOuter* newWin = ourDoc->GetWindow()) {
RefPtr<nsIWidget> newParent = nsGlobalWindow::Cast(newWin)->GetMainWidget();
const ManagedContainer<mozilla::plugins::PPluginWidgetParent>& plugins =
aOther->mRemoteBrowser->ManagedPPluginWidgetParent();
@@ -1401,16 +1409,24 @@ nsFrameLoader::SwapWithOtherLoader(nsFra
}
// OK. First begin to swap the docshells in the two nsIFrames
rv = ourFrameFrame->BeginSwapDocShells(otherFrame);
if (NS_FAILED(rv)) {
return rv;
}
+ // Destroy browser frame scripts for content leaving a frame with browser API
+ if (OwnerIsMozBrowserOrAppFrame() && !aOther->OwnerIsMozBrowserOrAppFrame()) {
+ DestroyBrowserFrameScripts();
+ }
+ if (!OwnerIsMozBrowserOrAppFrame() && aOther->OwnerIsMozBrowserOrAppFrame()) {
+ aOther->DestroyBrowserFrameScripts();
+ }
+
// Now move the docshells to the right docshell trees. Note that this
// resets their treeowners to null.
ourParentItem->RemoveChild(ourDocshell);
otherParentItem->RemoveChild(otherDocshell);
if (ourType == nsIDocShellTreeItem::typeContent) {
ourOwner->ContentShellRemoved(ourDocshell);
otherOwner->ContentShellRemoved(otherDocshell);
}
@@ -3357,33 +3373,46 @@ nsFrameLoader::GetLoadContext(nsILoadCon
}
loadContext.forget(aLoadContext);
return NS_OK;
}
void
nsFrameLoader::InitializeBrowserAPI()
{
- if (OwnerIsMozBrowserOrAppFrame()) {
- if (!IsRemoteFrame()) {
- nsresult rv = EnsureMessageManager();
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return;
- }
- if (mMessageManager) {
- mMessageManager->LoadFrameScript(
- NS_LITERAL_STRING("chrome://global/content/BrowserElementChild.js"),
- /* allowDelayedLoad = */ true,
- /* aRunInGlobalScope */ true);
- }
+ if (!OwnerIsMozBrowserOrAppFrame()) {
+ return;
+ }
+ if (!IsRemoteFrame()) {
+ nsresult rv = EnsureMessageManager();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ if (mMessageManager) {
+ mMessageManager->LoadFrameScript(
+ NS_LITERAL_STRING("chrome://global/content/BrowserElementChild.js"),
+ /* allowDelayedLoad = */ true,
+ /* aRunInGlobalScope */ true);
}
- nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
- if (browserFrame) {
- browserFrame->InitializeBrowserAPI();
- }
+ }
+ nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
+ if (browserFrame) {
+ browserFrame->InitializeBrowserAPI();
+ }
+}
+
+void
+nsFrameLoader::DestroyBrowserFrameScripts()
+{
+ if (!OwnerIsMozBrowserOrAppFrame()) {
+ return;
+ }
+ nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
+ if (browserFrame) {
+ browserFrame->DestroyBrowserFrameScripts();
}
}
NS_IMETHODIMP
nsFrameLoader::StartPersistence(uint64_t aOuterWindowID,
nsIWebBrowserPersistDocumentReceiver* aRecv)
{
if (!aRecv) {
--- a/dom/base/nsFrameLoader.h
+++ b/dom/base/nsFrameLoader.h
@@ -328,16 +328,17 @@ private:
? nsGkAtoms::type : nsGkAtoms::mozframetype;
}
// Update the permission manager's app-id refcount based on mOwnerContent's
// own-or-containing-app.
void ResetPermissionManagerStatus();
void InitializeBrowserAPI();
+ void DestroyBrowserFrameScripts();
nsresult GetNewTabContext(mozilla::dom::MutableTabContext* aTabContext,
nsIURI* aURI = nullptr,
const nsACString& aPackageId = EmptyCString());
enum TabParentChange {
eTabParentRemoved,
eTabParentChanged
--- a/dom/base/test/chrome/window_swapFrameLoaders.xul
+++ b/dom/base/test/chrome/window_swapFrameLoaders.xul
@@ -5,17 +5,17 @@
https://bugzilla.mozilla.org/show_bug.cgi?id=1242644
Test swapFrameLoaders with different frame types and remoteness
-->
<window title="Mozilla Bug 1242644"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
<script type="application/javascript"><![CDATA[
- ["SimpleTest", "SpecialPowers", "info", "is"].forEach(key => {
+ ["SimpleTest", "SpecialPowers", "info", "is", "ok"].forEach(key => {
window[key] = window.opener[key];
})
const { interfaces: Ci } = Components;
const NS = {
xul: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
html: "http://www.w3.org/1999/xhtml",
}
@@ -112,16 +112,23 @@ Test swapFrameLoaders with different fra
info(`Adding frame B, type ${typeB}, remote ${remote}, height ${heightB}`);
let frameB = yield addFrame(typeB, remote, heightB);
let frameScriptFactory = function(name) {
return `function() {
addMessageListener("ping", function() {
sendAsyncMessage("pong", "${name}");
});
+ addMessageListener("check-browser-api", function() {
+ let exists = "api" in this;
+ sendAsyncMessage("check-browser-api", {
+ exists,
+ running: exists && !this.api._shuttingDown,
+ });
+ });
}`;
}
// Load frame script into each frame
{
let mmA = frameA.frameLoader.messageManager;
let mmB = frameB.frameLoader.messageManager;
@@ -208,14 +215,43 @@ Test swapFrameLoaders with different fra
is(pongA, "B", "Frame A message manager acquired after swap gets reply B after swap");
info("Ping message manager for frame B");
mmB.sendAsyncMessage("ping");
let [ { data: pongB } ] = yield inflightB;
is(pongB, "A", "Frame B message manager acquired after swap gets reply A after swap");
}
+ // Verify browser API frame scripts destroyed if swapped out of browser frame
+ if (frameA.hasAttribute("mozbrowser") != frameB.hasAttribute("mozbrowser")) {
+ let mmA = frameA.frameLoader.messageManager;
+ let mmB = frameB.frameLoader.messageManager;
+
+ let inflightA = once(mmA, "check-browser-api");
+ let inflightB = once(mmB, "check-browser-api");
+
+ info("Check browser API for frame A");
+ mmA.sendAsyncMessage("check-browser-api");
+ let [ { data: apiA } ] = yield inflightA;
+ if (frameA.hasAttribute("mozbrowser")) {
+ ok(apiA.exists && apiA.running, "Frame A browser API exists and is running");
+ } else {
+ ok(apiA.exists && !apiA.running, "Frame A browser API did exist but is now destroyed");
+ }
+
+ info("Check browser API for frame B");
+ mmB.sendAsyncMessage("check-browser-api");
+ let [ { data: apiB } ] = yield inflightB;
+ if (frameB.hasAttribute("mozbrowser")) {
+ ok(apiB.exists && apiB.running, "Frame B browser API exists and is running");
+ } else {
+ ok(apiB.exists && !apiB.running, "Frame B browser API did exist but is now destroyed");
+ }
+ } else {
+ info("Frames have matching mozbrowser state, skipping browser API destruction check");
+ }
+
frameA.remove();
frameB.remove();
}
});
]]></script>
</window>
--- a/dom/browser-element/BrowserElementChild.js
+++ b/dom/browser-element/BrowserElementChild.js
@@ -72,14 +72,35 @@ if (!BrowserElementIsReady) {
} else {
if (Services.prefs.getIntPref("dom.w3c_touch_events.enabled") != 0) {
if (docShell.asyncPanZoomEnabled === false) {
ContentPanningAPZDisabled.init();
}
ContentPanning.init();
}
}
+
+ function onDestroy() {
+ removeMessageListener("browser-element-api:destroy", onDestroy);
+
+ if (api) {
+ api.destroy();
+ }
+ if ("ContentPanning" in this) {
+ ContentPanning.destroy();
+ }
+ if ("ContentPanningAPZDisabled" in this) {
+ ContentPanningAPZDisabled.destroy();
+ }
+ if ("CopyPasteAssistent" in this) {
+ CopyPasteAssistent.destroy();
+ }
+
+ BrowserElementIsReady = false;
+ }
+ addMessageListener("browser-element-api:destroy", onDestroy);
+
BrowserElementIsReady = true;
} else {
debug("BE already loaded, abort");
}
sendAsyncMessage('browser-element-api:call', { 'msg_name': 'hello' });
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -25,19 +25,19 @@ XPCOMUtils.defineLazyServiceGetter(this,
XPCOMUtils.defineLazyModuleGetter(this, "ManifestFinder",
"resource://gre/modules/ManifestFinder.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ManifestObtainer",
"resource://gre/modules/ManifestObtainer.jsm");
var kLongestReturnedString = 128;
-const Timer = Components.Constructor("@mozilla.org/timer;1",
- "nsITimer",
- "initWithCallback");
+var Timer = Components.Constructor("@mozilla.org/timer;1",
+ "nsITimer",
+ "initWithCallback");
function sendAsyncMsg(msg, data) {
// Ensure that we don't send any messages before BrowserElementChild.js
// finishes loading.
if (!BrowserElementIsReady) {
return;
}
@@ -61,24 +61,54 @@ function sendSyncMsg(msg, data) {
}
data.msg_name = msg;
return sendSyncMessage('browser-element-api:call', data);
}
var CERTIFICATE_ERROR_PAGE_PREF = 'security.alternate_certificate_error_page';
-const OBSERVED_EVENTS = [
+var OBSERVED_EVENTS = [
'xpcom-shutdown',
'audio-playback',
'activity-done',
'invalid-widget',
'will-launch-app'
];
+var LISTENED_EVENTS = [
+ { type: "DOMTitleChanged", useCapture: true, wantsUntrusted: false },
+ { type: "DOMLinkAdded", useCapture: true, wantsUntrusted: false },
+ { type: "MozScrolledAreaChanged", useCapture: true, wantsUntrusted: false },
+ { type: "MozDOMFullscreen:Request", useCapture: true, wantsUntrusted: false },
+ { type: "MozDOMFullscreen:NewOrigin", useCapture: true, wantsUntrusted: false },
+ { type: "MozDOMFullscreen:Exit", useCapture: true, wantsUntrusted: false },
+ { type: "DOMMetaAdded", useCapture: true, wantsUntrusted: false },
+ { type: "DOMMetaChanged", useCapture: true, wantsUntrusted: false },
+ { type: "DOMMetaRemoved", useCapture: true, wantsUntrusted: false },
+ { type: "scrollviewchange", useCapture: true, wantsUntrusted: false },
+ { type: "click", useCapture: false, wantsUntrusted: false },
+ // This listens to unload events from our message manager, but /not/ from
+ // the |content| window. That's because the window's unload event doesn't
+ // bubble, and we're not using a capturing listener. If we'd used
+ // useCapture == true, we /would/ hear unload events from the window, which
+ // is not what we want!
+ { type: "unload", useCapture: false, wantsUntrusted: false },
+];
+
+// We are using the system group for those events so if something in the
+// content called .stopPropagation() this will still be called.
+var LISTENED_SYSTEM_EVENTS = [
+ { type: "DOMWindowClose", useCapture: false },
+ { type: "DOMWindowCreated", useCapture: false },
+ { type: "DOMWindowResize", useCapture: false },
+ { type: "contextmenu", useCapture: false },
+ { type: "scroll", useCapture: false },
+];
+
/**
* The BrowserElementChild implements one half of <iframe mozbrowser>.
* (The other half is, unsurprisingly, BrowserElementParent.)
*
* This script is injected into an <iframe mozbrowser> via
* nsIMessageManager::LoadFrameScript().
*
* Our job here is to listen for events within this frame and bubble them up to
@@ -172,160 +202,179 @@ BrowserElementChild.prototype = {
// A cache of the menuitem dom objects keyed by the id we generate
// and pass to the embedder
this._ctxHandlers = {};
// Counter of contextmenu events fired
this._ctxCounter = 0;
this._shuttingDown = false;
- addEventListener('DOMTitleChanged',
- this._titleChangedHandler.bind(this),
- /* useCapture = */ true,
- /* wantsUntrusted = */ false);
-
- addEventListener('DOMLinkAdded',
- this._linkAddedHandler.bind(this),
- /* useCapture = */ true,
- /* wantsUntrusted = */ false);
-
- addEventListener('MozScrolledAreaChanged',
- this._mozScrollAreaChanged.bind(this),
- /* useCapture = */ true,
- /* wantsUntrusted = */ false);
-
- addEventListener("MozDOMFullscreen:Request",
- this._mozRequestedDOMFullscreen.bind(this),
- /* useCapture = */ true,
- /* wantsUntrusted = */ false);
-
- addEventListener("MozDOMFullscreen:NewOrigin",
- this._mozFullscreenOriginChange.bind(this),
- /* useCapture = */ true,
- /* wantsUntrusted = */ false);
-
- addEventListener("MozDOMFullscreen:Exit",
- this._mozExitDomFullscreen.bind(this),
- /* useCapture = */ true,
- /* wantsUntrusted = */ false);
-
- addEventListener('DOMMetaAdded',
- this._metaChangedHandler.bind(this),
- /* useCapture = */ true,
- /* wantsUntrusted = */ false);
-
- addEventListener('DOMMetaChanged',
- this._metaChangedHandler.bind(this),
- /* useCapture = */ true,
- /* wantsUntrusted = */ false);
-
- addEventListener('DOMMetaRemoved',
- this._metaChangedHandler.bind(this),
- /* useCapture = */ true,
- /* wantsUntrusted = */ false);
-
- addEventListener('scrollviewchange',
- this._ScrollViewChangeHandler.bind(this),
- /* useCapture = */ true,
- /* wantsUntrusted = */ false);
-
- addEventListener('click',
- this._ClickHandler.bind(this),
- /* useCapture = */ false,
- /* wantsUntrusted = */ false);
-
- // This listens to unload events from our message manager, but /not/ from
- // the |content| window. That's because the window's unload event doesn't
- // bubble, and we're not using a capturing listener. If we'd used
- // useCapture == true, we /would/ hear unload events from the window, which
- // is not what we want!
- addEventListener('unload',
- this._unloadHandler.bind(this),
- /* useCapture = */ false,
- /* wantsUntrusted = */ false);
+ LISTENED_EVENTS.forEach(event => {
+ addEventListener(event.type, this, event.useCapture, event.wantsUntrusted);
+ });
// Registers a MozAfterPaint handler for the very first paint.
this._addMozAfterPaintHandler(function () {
sendAsyncMsg('firstpaint');
});
+ addMessageListener("browser-element-api:call", this);
+
+ let els = Cc["@mozilla.org/eventlistenerservice;1"]
+ .getService(Ci.nsIEventListenerService);
+ LISTENED_SYSTEM_EVENTS.forEach(event => {
+ els.addSystemEventListener(global, event.type, this, event.useCapture);
+ });
+
+ OBSERVED_EVENTS.forEach((aTopic) => {
+ Services.obs.addObserver(this, aTopic, false);
+ });
+
+ this.forwarder.init();
+ },
+
+ /**
+ * Shut down the frame's side of the browser API. This is called when:
+ * - our TabChildGlobal starts to die
+ * - the content is moved to frame without the browser API
+ * This is not called when the page inside |content| unloads.
+ */
+ destroy: function() {
+ debug("Destroying");
+ this._shuttingDown = true;
+
+ BrowserElementPromptService.unmapWindowToBrowserElementChild(content);
+
+ docShell.QueryInterface(Ci.nsIWebProgress)
+ .removeProgressListener(this._progressListener);
+
+ LISTENED_EVENTS.forEach(event => {
+ removeEventListener(event.type, this, event.useCapture, event.wantsUntrusted);
+ });
+
+ this._deactivateNextPaintListener();
+
+ removeMessageListener("browser-element-api:call", this);
+
+ let els = Cc["@mozilla.org/eventlistenerservice;1"]
+ .getService(Ci.nsIEventListenerService);
+ LISTENED_SYSTEM_EVENTS.forEach(event => {
+ els.removeSystemEventListener(global, event.type, this, event.useCapture);
+ });
+
+ OBSERVED_EVENTS.forEach((aTopic) => {
+ Services.obs.removeObserver(this, aTopic);
+ });
+
+ this.forwarder.uninit();
+ this.forwarder = null;
+ },
+
+ handleEvent: function(event) {
+ switch (event.type) {
+ case "DOMTitleChanged":
+ this._titleChangedHandler(event);
+ break;
+ case "DOMLinkAdded":
+ this._linkAddedHandler(event);
+ break;
+ case "MozScrolledAreaChanged":
+ this._mozScrollAreaChanged(event);
+ break;
+ case "MozDOMFullscreen:Request":
+ this._mozRequestedDOMFullscreen(event);
+ break;
+ case "MozDOMFullscreen:NewOrigin":
+ this._mozFullscreenOriginChange(event);
+ break;
+ case "MozDOMFullscreen:Exit":
+ this._mozExitDomFullscreen(event);
+ break;
+ case "DOMMetaAdded":
+ this._metaChangedHandler(event);
+ break;
+ case "DOMMetaChanged":
+ this._metaChangedHandler(event);
+ break;
+ case "DOMMetaRemoved":
+ this._metaChangedHandler(event);
+ break;
+ case "scrollviewchange":
+ this._ScrollViewChangeHandler(event);
+ break;
+ case "click":
+ this._ClickHandler(event);
+ break;
+ case "unload":
+ this.destroy(event);
+ break;
+ case "DOMWindowClose":
+ this._windowCloseHandler(event);
+ break;
+ case "DOMWindowCreated":
+ this._windowCreatedHandler(event);
+ break;
+ case "DOMWindowResize":
+ this._windowResizeHandler(event);
+ break;
+ case "contextmenu":
+ this._contextmenuHandler(event);
+ break;
+ case "scroll":
+ this._scrollEventHandler(event);
+ break;
+ }
+ },
+
+ receiveMessage: function(message) {
let self = this;
let mmCalls = {
"purge-history": this._recvPurgeHistory,
"get-screenshot": this._recvGetScreenshot,
"get-contentdimensions": this._recvGetContentDimensions,
"set-visible": this._recvSetVisible,
"get-visible": this._recvVisible,
"send-mouse-event": this._recvSendMouseEvent,
"send-touch-event": this._recvSendTouchEvent,
"get-can-go-back": this._recvCanGoBack,
"get-can-go-forward": this._recvCanGoForward,
- "mute": this._recvMute.bind(this),
- "unmute": this._recvUnmute.bind(this),
- "get-muted": this._recvGetMuted.bind(this),
- "set-volume": this._recvSetVolume.bind(this),
- "get-volume": this._recvGetVolume.bind(this),
+ "mute": this._recvMute,
+ "unmute": this._recvUnmute,
+ "get-muted": this._recvGetMuted,
+ "set-volume": this._recvSetVolume,
+ "get-volume": this._recvGetVolume,
"go-back": this._recvGoBack,
"go-forward": this._recvGoForward,
"reload": this._recvReload,
"stop": this._recvStop,
"zoom": this._recvZoom,
"unblock-modal-prompt": this._recvStopWaiting,
"fire-ctx-callback": this._recvFireCtxCallback,
"owner-visibility-change": this._recvOwnerVisibilityChange,
"entered-fullscreen": this._recvEnteredFullscreen,
- "exit-fullscreen": this._recvExitFullscreen.bind(this),
- "activate-next-paint-listener": this._activateNextPaintListener.bind(this),
- "set-input-method-active": this._recvSetInputMethodActive.bind(this),
- "deactivate-next-paint-listener": this._deactivateNextPaintListener.bind(this),
- "find-all": this._recvFindAll.bind(this),
- "find-next": this._recvFindNext.bind(this),
- "clear-match": this._recvClearMatch.bind(this),
+ "exit-fullscreen": this._recvExitFullscreen,
+ "activate-next-paint-listener": this._activateNextPaintListener,
+ "set-input-method-active": this._recvSetInputMethodActive,
+ "deactivate-next-paint-listener": this._deactivateNextPaintListener,
+ "find-all": this._recvFindAll,
+ "find-next": this._recvFindNext,
+ "clear-match": this._recvClearMatch,
"execute-script": this._recvExecuteScript,
"get-audio-channel-volume": this._recvGetAudioChannelVolume,
"set-audio-channel-volume": this._recvSetAudioChannelVolume,
"get-audio-channel-muted": this._recvGetAudioChannelMuted,
"set-audio-channel-muted": this._recvSetAudioChannelMuted,
"get-is-audio-channel-active": this._recvIsAudioChannelActive,
"get-web-manifest": this._recvGetWebManifest,
}
- addMessageListener("browser-element-api:call", function(aMessage) {
- if (aMessage.data.msg_name in mmCalls) {
- return mmCalls[aMessage.data.msg_name].apply(self, arguments);
- }
- });
-
- let els = Cc["@mozilla.org/eventlistenerservice;1"]
- .getService(Ci.nsIEventListenerService);
-
- // We are using the system group for those events so if something in the
- // content called .stopPropagation() this will still be called.
- els.addSystemEventListener(global, 'DOMWindowClose',
- this._windowCloseHandler.bind(this),
- /* useCapture = */ false);
- els.addSystemEventListener(global, 'DOMWindowCreated',
- this._windowCreatedHandler.bind(this),
- /* useCapture = */ true);
- els.addSystemEventListener(global, 'DOMWindowResize',
- this._windowResizeHandler.bind(this),
- /* useCapture = */ false);
- els.addSystemEventListener(global, 'contextmenu',
- this._contextmenuHandler.bind(this),
- /* useCapture = */ false);
- els.addSystemEventListener(global, 'scroll',
- this._scrollEventHandler.bind(this),
- /* useCapture = */ false);
-
- OBSERVED_EVENTS.forEach((aTopic) => {
- Services.obs.addObserver(this, aTopic, false);
- });
-
- this.forwarder.init();
+ if (message.data.msg_name in mmCalls) {
+ return mmCalls[message.data.msg_name].apply(self, arguments);
+ }
},
_paintFrozenTimer: null,
observe: function(subject, topic, data) {
// Ignore notifications not about our document. (Note that |content| /can/
// be null; see bug 874900.)
if (topic !== 'activity-done' &&
@@ -371,30 +420,16 @@ BrowserElementChild.prototype = {
},
notify: function(timer) {
docShell.contentViewer.resumePainting();
this._paintFrozenTimer.cancel();
this._paintFrozenTimer = null;
},
- /**
- * Called when our TabChildGlobal starts to die. This is not called when the
- * page inside |content| unloads.
- */
- _unloadHandler: function() {
- this._shuttingDown = true;
- OBSERVED_EVENTS.forEach((aTopic) => {
- Services.obs.removeObserver(this, aTopic);
- });
-
- this.forwarder.uninit();
- this.forwarder = null;
- },
-
get _windowUtils() {
return content.document.defaultView
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
},
_tryGetInnerWindowID: function(win) {
let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
--- a/dom/browser-element/BrowserElementCopyPaste.js
+++ b/dom/browser-element/BrowserElementCopyPaste.js
@@ -18,21 +18,41 @@ var CopyPasteAssistent = {
COMMAND_MAP: {
'cut': 'cmd_cut',
'copy': 'cmd_copyAndCollapseToEnd',
'paste': 'cmd_paste',
'selectall': 'cmd_selectAll'
},
init: function() {
- addEventListener('mozcaretstatechanged',
- this._caretStateChangedHandler.bind(this),
- /* useCapture = */ true,
- /* wantsUntrusted = */ false);
- addMessageListener('browser-element-api:call', this._browserAPIHandler.bind(this));
+ addEventListener("mozcaretstatechanged", this,
+ /* useCapture = */ true, /* wantsUntrusted = */ false);
+ addMessageListener("browser-element-api:call", this);
+ },
+
+ destroy: function() {
+ removeEventListener("mozcaretstatechanged", this,
+ /* useCapture = */ true, /* wantsUntrusted = */ false);
+ removeMessageListener("browser-element-api:call", this);
+ },
+
+ handleEvent: function(event) {
+ switch (event.type) {
+ case "mozcaretstatechanged":
+ this._caretStateChangedHandler(event);
+ break;
+ }
+ },
+
+ receiveMessage: function(message) {
+ switch (message.name) {
+ case "browser-element-api:call":
+ this._browserAPIHandler(message);
+ break;
+ }
},
_browserAPIHandler: function(e) {
switch (e.data.msg_name) {
case 'copypaste-do-command':
if (this._isCommandEnabled(e.data.command)) {
docShell.doCommand(this.COMMAND_MAP[e.data.command]);
}
--- a/dom/browser-element/BrowserElementPanning.js
+++ b/dom/browser-element/BrowserElementPanning.js
@@ -12,37 +12,67 @@ function debug(msg) {
}
debug("loaded");
var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Geometry.jsm");
-const kObservedEvents = [
+var kObservedEvents = [
"BEC:ShownModalPrompt",
"Activity:Success",
"Activity:Error"
];
-const ContentPanning = {
+var ContentPanning = {
init: function cp_init() {
- addEventListener("unload",
- this._unloadHandler.bind(this),
- /* useCapture = */ false,
- /* wantsUntrusted = */ false);
-
- addMessageListener("Viewport:Change", this._recvViewportChange.bind(this));
- addMessageListener("Gesture:DoubleTap", this._recvDoubleTap.bind(this));
- addEventListener("visibilitychange", this._handleVisibilityChange.bind(this));
+ addEventListener("unload", this,
+ /* useCapture = */ false, /* wantsUntrusted = */ false);
+ addMessageListener("Viewport:Change", this);
+ addMessageListener("Gesture:DoubleTap", this);
+ addEventListener("visibilitychange", this);
kObservedEvents.forEach((topic) => {
Services.obs.addObserver(this, topic, false);
});
},
+ destroy: function() {
+ removeEventListener("unload", this,
+ /* useCapture = */ false, /* wantsUntrusted = */ false);
+ removeMessageListener("Viewport:Change", this);
+ removeMessageListener("Gesture:DoubleTap", this);
+ removeEventListener("visibilitychange", this);
+ kObservedEvents.forEach((topic) => {
+ Services.obs.removeObserver(this, topic, false);
+ });
+ },
+
+ handleEvent: function(event) {
+ switch (event.type) {
+ case "unload":
+ this._unloadHandler(event);
+ break;
+ case "visibilitychange":
+ this._handleVisibilityChange(event);
+ break;
+ }
+ },
+
+ receiveMessage: function(message) {
+ switch (message.name) {
+ case "Viewport:Change":
+ this._recvViewportChange(message);
+ break;
+ case "Gesture:DoubleTap":
+ this._recvDoubleTap(message);
+ break;
+ }
+ },
+
observe: function cp_observe(subject, topic, data) {
this._resetHover();
},
get _domUtils() {
delete this._domUtils;
return this._domUtils = Cc['@mozilla.org/inspector/dom-utils;1']
.getService(Ci.inIDOMUtils);
@@ -171,17 +201,17 @@ const ContentPanning = {
_unloadHandler: function() {
kObservedEvents.forEach((topic) => {
Services.obs.removeObserver(this, topic);
});
}
};
-const ElementTouchHelper = {
+var ElementTouchHelper = {
anyElementFromPoint: function(aWindow, aX, aY) {
let cwu = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
let elem = cwu.elementFromPoint(aX, aY, true, true);
let HTMLIFrameElement = Ci.nsIDOMHTMLIFrameElement;
let HTMLFrameElement = Ci.nsIDOMHTMLFrameElement;
while (elem && (elem instanceof HTMLIFrameElement || elem instanceof HTMLFrameElement)) {
let rect = elem.getBoundingClientRect();
--- a/dom/browser-element/BrowserElementPanningAPZDisabled.js
+++ b/dom/browser-element/BrowserElementPanningAPZDisabled.js
@@ -9,29 +9,45 @@
dump("############################### browserElementPanningAPZDisabled.js loaded\n");
var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Geometry.jsm");
var global = this;
-const ContentPanningAPZDisabled = {
+var ContentPanningAPZDisabled = {
// Are we listening to touch or mouse events?
watchedEventsType: '',
// Are mouse events being delivered to this content along with touch
// events, in violation of spec?
hybridEvents: false,
init: function cp_init() {
- this._setupListenersForPanning();
+ let events = this._getEventsList();
+ let els = Cc["@mozilla.org/eventlistenerservice;1"]
+ .getService(Ci.nsIEventListenerService);
+ events.forEach(type => {
+ // Using the system group for mouse/touch events to avoid
+ // missing events if .stopPropagation() has been called.
+ els.addSystemEventListener(global, type, this, /* useCapture = */ false);
+ });
},
- _setupListenersForPanning: function cp_setupListenersForPanning() {
+ destroy: function () {
+ let events = this._getEventsList();
+ let els = Cc["@mozilla.org/eventlistenerservice;1"]
+ .getService(Ci.nsIEventListenerService);
+ events.forEach(type => {
+ els.removeSystemEventListener(global, type, this, /* useCapture = */ false);
+ });
+ },
+
+ _getEventsList: function () {
let events;
if (content.TouchEvent) {
events = ['touchstart', 'touchend', 'touchmove'];
this.watchedEventsType = 'touch';
#ifdef MOZ_WIDGET_GONK
// The gonk widget backend does not deliver mouse events per
// spec. Third-party content isn't exposed to this behavior,
@@ -43,26 +59,17 @@ const ContentPanningAPZDisabled = {
this.hybridEvents = isParentProcess;
#endif
} else {
// Touch events aren't supported, so fall back on mouse.
events = ['mousedown', 'mouseup', 'mousemove'];
this.watchedEventsType = 'mouse';
}
- let els = Cc["@mozilla.org/eventlistenerservice;1"]
- .getService(Ci.nsIEventListenerService);
-
- events.forEach(function(type) {
- // Using the system group for mouse/touch events to avoid
- // missing events if .stopPropagation() has been called.
- els.addSystemEventListener(global, type,
- this.handleEvent.bind(this),
- /* useCapture = */ false);
- }.bind(this));
+ return events;
},
handleEvent: function cp_handleEvent(evt) {
// Ignore events targeting an oop <iframe mozbrowser> since those will be
// handle by the BrowserElementPanning.js instance in the child process.
if (evt.target instanceof Ci.nsIMozBrowserFrame) {
return;
}
@@ -473,33 +480,33 @@ const ContentPanningAPZDisabled = {
// If there is a scroll action, let's do a manual kinetic panning action.
if (this.panning) {
KineticPanning.start(this);
}
},
};
// Min/max velocity of kinetic panning. This is in pixels/millisecond.
-const kMinVelocity = 0.2;
-const kMaxVelocity = 6;
+var kMinVelocity = 0.2;
+var kMaxVelocity = 6;
// Constants that affect the "friction" of the scroll pane.
-const kExponentialC = 1000;
-const kPolynomialC = 100 / 1000000;
+var kExponentialC = 1000;
+var kPolynomialC = 100 / 1000000;
// How often do we change the position of the scroll pane?
// Too often and panning may jerk near the end.
// Too little and panning will be choppy. In milliseconds.
-const kUpdateInterval = 16;
+var kUpdateInterval = 16;
// The numbers of momentums to use for calculating the velocity of the pan.
// Those are taken from the end of the action
-const kSamples = 5;
+var kSamples = 5;
-const KineticPanning = {
+var KineticPanning = {
_position: new Point(0, 0),
_velocity: new Point(0, 0),
_acceleration: new Point(0, 0),
get active() {
return this.target !== null;
},
--- a/dom/browser-element/BrowserElementParent.js
+++ b/dom/browser-element/BrowserElementParent.js
@@ -258,16 +258,17 @@ BrowserElementParent.prototype = {
classDescription: "BrowserElementAPI implementation",
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]),
setFrameLoader: function(frameLoader) {
+ debug("Setting 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
@@ -297,16 +298,21 @@ BrowserElementParent.prototype = {
BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this);
this._setupMessageListener();
this._registerAppManifest();
this.proxyCallHandler.init(
this._frameElement, this._frameLoader.messageManager);
},
+ destroyFrameScripts() {
+ debug("Destroying frame scripts");
+ this._mm.sendAsyncMessage("browser-element-api:destroy");
+ },
+
_runPendingAPICall: function() {
if (!this._pendingAPICalls) {
return;
}
for (let i = 0; i < this._pendingAPICalls.length; i++) {
try {
this._pendingAPICalls[i]();
} catch (e) {
--- a/dom/browser-element/BrowserElementPromptService.jsm
+++ b/dom/browser-element/BrowserElementPromptService.jsm
@@ -49,17 +49,17 @@ BrowserElementPrompt.prototype = {
confirmCheck: function(title, text, checkMsg, checkState) {
return this.confirm(title, text);
},
// Each button is described by an object with the following schema
// {
// string messageType, // 'builtin' or 'custom'
- // string message, // 'ok', 'cancel', 'yes', 'no', 'save', 'dontsave',
+ // string message, // 'ok', 'cancel', 'yes', 'no', 'save', 'dontsave',
// // 'revert' or a string from caller if messageType was 'custom'.
// }
//
// Expected result from embedder:
// {
// int button, // Index of the button that user pressed.
// boolean checked, // True if the check box is checked.
// }
@@ -626,16 +626,19 @@ this.BrowserElementPromptService = {
.getInterface(Ci.nsIDOMWindowUtils)
.outerWindowID;
},
_browserElementChildMap: {},
mapWindowToBrowserElementChild: function(win, browserElementChild) {
this._browserElementChildMap[this._getOuterWindowID(win)] = browserElementChild;
},
+ unmapWindowToBrowserElementChild: function(win) {
+ delete this._browserElementChildMap[this._getOuterWindowID(win)];
+ },
getBrowserElementChildForWindow: function(win) {
// We only have a mapping for <iframe mozbrowser>s, not their inner
// <iframes>, so we look up win.top below. window.top (when called from
// script) respects <iframe mozbrowser> boundaries.
return this._browserElementChildMap[this._getOuterWindowID(win.top)];
},
--- a/dom/browser-element/nsIBrowserElementAPI.idl
+++ b/dom/browser-element/nsIBrowserElementAPI.idl
@@ -30,16 +30,21 @@ interface nsIBrowserElementNextPaintList
interface nsIBrowserElementAPI : nsISupports
{
const long FIND_CASE_SENSITIVE = 0;
const long FIND_CASE_INSENSITIVE = 1;
const long FIND_FORWARD = 0;
const long FIND_BACKWARD = 1;
+ /**
+ * Notify frame scripts that support the API to destroy.
+ */
+ void destroyFrameScripts();
+
void setFrameLoader(in nsIFrameLoader frameLoader);
void setVisible(in boolean visible);
nsIDOMDOMRequest getVisible();
void setActive(in boolean active);
boolean getActive();
void sendMouseEvent(in DOMString type,
--- a/dom/html/nsBrowserElement.cpp
+++ b/dom/html/nsBrowserElement.cpp
@@ -70,16 +70,25 @@ nsBrowserElement::InitBrowserElementAPI(
if (NS_WARN_IF(!mBrowserElementAPI)) {
return;
}
}
mBrowserElementAPI->SetFrameLoader(frameLoader);
}
void
+nsBrowserElement::DestroyBrowserElementFrameScripts()
+{
+ if (!mBrowserElementAPI) {
+ return;
+ }
+ mBrowserElementAPI->DestroyFrameScripts();
+}
+
+void
nsBrowserElement::SetVisible(bool aVisible, ErrorResult& aRv)
{
NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
nsresult rv = mBrowserElementAPI->SetVisible(aVisible);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
--- a/dom/html/nsBrowserElement.h
+++ b/dom/html/nsBrowserElement.h
@@ -125,16 +125,17 @@ public:
nsTArray<RefPtr<dom::BrowserElementAudioChannel>>& aAudioChannels,
ErrorResult& aRv);
protected:
NS_IMETHOD_(already_AddRefed<nsFrameLoader>) GetFrameLoader() = 0;
NS_IMETHOD GetParentApplication(mozIApplication** aApplication) = 0;
void InitBrowserElementAPI();
+ void DestroyBrowserElementFrameScripts();
nsCOMPtr<nsIBrowserElementAPI> mBrowserElementAPI;
nsTArray<RefPtr<dom::BrowserElementAudioChannel>> mBrowserElementAudioChannels;
private:
bool IsBrowserElementOrThrow(ErrorResult& aRv);
bool IsNotWidgetOrThrow(ErrorResult& aRv);
bool mOwnerIsWidget;
};
--- a/dom/html/nsGenericHTMLFrameElement.cpp
+++ b/dom/html/nsGenericHTMLFrameElement.cpp
@@ -721,8 +721,16 @@ nsGenericHTMLFrameElement::AllowCreateFr
NS_IMETHODIMP
nsGenericHTMLFrameElement::InitializeBrowserAPI()
{
MOZ_ASSERT(mFrameLoader);
InitBrowserElementAPI();
return NS_OK;
}
+
+NS_IMETHODIMP
+nsGenericHTMLFrameElement::DestroyBrowserFrameScripts()
+{
+ MOZ_ASSERT(mFrameLoader);
+ DestroyBrowserElementFrameScripts();
+ return NS_OK;
+}
--- a/dom/interfaces/html/nsIMozBrowserFrame.idl
+++ b/dom/interfaces/html/nsIMozBrowserFrame.idl
@@ -86,13 +86,18 @@ interface nsIMozBrowserFrame : nsIDOMMoz
* Create a remote (i.e., out-of-process) frame loader attached to the given
* tab parent.
*
* It is an error to call this method if we already have a frame loader.
*/
void createRemoteFrameLoader(in nsITabParent aTabParent);
/**
- * Initialize the API, and add frame message listener to listen to API
+ * Initialize the API, and add frame message listener that supports API
* invocations.
*/
[noscript] void initializeBrowserAPI();
+
+ /**
+ * Notify frame scripts that support the API to destroy.
+ */
+ [noscript] void destroyBrowserFrameScripts();
};