forked from mirrors/gecko-dev
Bug 1648149 - Split GeckoViewContent in enable and init phases. r=snorp
This commit splits the `GeckoViewContent` actor in two parts: - `GeckoViewContent` proper, which runs unconditionally and handles code that needs to run regardless of whether we have a delegate installed or not. - `ContentDelegate` which runs only when a delegate is first installed. This emulates the previous paradigm of installing some listeners only when the delegate is installed. I discussed it briefly with :nika and she thinks that splitting modules in two should not affect performance in a measurable manner. Note that actors cannot be registered per-window, so we will get messages from all windows as long as one content delegate is registered. Differential Revision: https://phabricator.services.mozilla.com/D86776
This commit is contained in:
parent
4c8aa34ba2
commit
aaa44e891b
8 changed files with 258 additions and 162 deletions
169
mobile/android/actors/ContentDelegateChild.jsm
Normal file
169
mobile/android/actors/ContentDelegateChild.jsm
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
/* 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/. */
|
||||
|
||||
const { GeckoViewActorChild } = ChromeUtils.import(
|
||||
"resource://gre/modules/GeckoViewActorChild.jsm"
|
||||
);
|
||||
|
||||
var { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
ManifestObtainer: "resource://gre/modules/ManifestObtainer.jsm",
|
||||
});
|
||||
|
||||
var EXPORTED_SYMBOLS = ["ContentDelegateChild"];
|
||||
|
||||
class ContentDelegateChild extends GeckoViewActorChild {
|
||||
notifyParentOfViewportFit() {
|
||||
if (this.triggerViewportFitChange) {
|
||||
this.contentWindow.cancelIdleCallback(this.triggerViewportFitChange);
|
||||
}
|
||||
this.triggerViewportFitChange = this.contentWindow.requestIdleCallback(
|
||||
() => {
|
||||
this.triggerViewportFitChange = null;
|
||||
const viewportFit = this.contentWindow.windowUtils.getViewportFitInfo();
|
||||
if (this.lastViewportFit === viewportFit) {
|
||||
return;
|
||||
}
|
||||
this.lastViewportFit = viewportFit;
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:DOMMetaViewportFit",
|
||||
viewportfit: viewportFit,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
handleEvent(aEvent) {
|
||||
debug`handleEvent: ${aEvent.type}`;
|
||||
|
||||
switch (aEvent.type) {
|
||||
case "contextmenu": {
|
||||
function nearestParentAttribute(aNode, aAttribute) {
|
||||
while (
|
||||
aNode &&
|
||||
aNode.hasAttribute &&
|
||||
!aNode.hasAttribute(aAttribute)
|
||||
) {
|
||||
aNode = aNode.parentNode;
|
||||
}
|
||||
return aNode && aNode.getAttribute && aNode.getAttribute(aAttribute);
|
||||
}
|
||||
|
||||
function createAbsoluteUri(aBaseUri, aUri) {
|
||||
if (!aUri || !aBaseUri || !aBaseUri.displaySpec) {
|
||||
return null;
|
||||
}
|
||||
return Services.io.newURI(aUri, null, aBaseUri).displaySpec;
|
||||
}
|
||||
|
||||
const node = aEvent.composedTarget;
|
||||
const baseUri = node.ownerDocument.baseURIObject;
|
||||
const uri = createAbsoluteUri(
|
||||
baseUri,
|
||||
nearestParentAttribute(node, "href")
|
||||
);
|
||||
const title = nearestParentAttribute(node, "title");
|
||||
const alt = nearestParentAttribute(node, "alt");
|
||||
const elementType = ChromeUtils.getClassName(node);
|
||||
const isImage = elementType === "HTMLImageElement";
|
||||
const isMedia =
|
||||
elementType === "HTMLVideoElement" ||
|
||||
elementType === "HTMLAudioElement";
|
||||
const elementSrc =
|
||||
(isImage || isMedia) && (node.currentSrc || node.src);
|
||||
|
||||
if (uri || isImage || isMedia) {
|
||||
const msg = {
|
||||
type: "GeckoView:ContextMenu",
|
||||
screenX: aEvent.screenX,
|
||||
screenY: aEvent.screenY,
|
||||
baseUri: (baseUri && baseUri.displaySpec) || null,
|
||||
uri,
|
||||
title,
|
||||
alt,
|
||||
elementType,
|
||||
elementSrc: elementSrc || null,
|
||||
};
|
||||
|
||||
this.eventDispatcher.sendRequest(msg);
|
||||
aEvent.preventDefault();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "MozDOMFullscreen:Request": {
|
||||
this.sendAsyncMessage("GeckoView:DOMFullscreenRequest", {});
|
||||
break;
|
||||
}
|
||||
case "MozDOMFullscreen:Entered":
|
||||
case "MozDOMFullscreen:Exited":
|
||||
// Content may change fullscreen state by itself, and we should ensure
|
||||
// that the parent always exits fullscreen when content has left
|
||||
// full screen mode.
|
||||
if (this.contentWindow?.document.fullscreenElement) {
|
||||
break;
|
||||
}
|
||||
// fall-through
|
||||
case "MozDOMFullscreen:Exit":
|
||||
this.sendAsyncMessage("GeckoView:DOMFullscreenExit", {});
|
||||
break;
|
||||
case "DOMMetaViewportFitChanged":
|
||||
if (aEvent.originalTarget.ownerGlobal == this.contentWindow) {
|
||||
this.notifyParentOfViewportFit();
|
||||
}
|
||||
break;
|
||||
case "DOMTitleChanged":
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:DOMTitleChanged",
|
||||
title: this.contentWindow.document.title,
|
||||
});
|
||||
break;
|
||||
case "DOMWindowClose":
|
||||
if (!aEvent.isTrusted) {
|
||||
return;
|
||||
}
|
||||
|
||||
aEvent.preventDefault();
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:DOMWindowClose",
|
||||
});
|
||||
break;
|
||||
case "DOMContentLoaded": {
|
||||
if (aEvent.originalTarget.ownerGlobal == this.contentWindow) {
|
||||
// If loaded content doesn't have viewport-fit, parent still
|
||||
// uses old value of previous content.
|
||||
this.notifyParentOfViewportFit();
|
||||
}
|
||||
this.contentWindow.requestIdleCallback(async () => {
|
||||
const manifest = await ManifestObtainer.contentObtainManifest(
|
||||
this.contentWindow
|
||||
);
|
||||
if (manifest) {
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:WebAppManifest",
|
||||
manifest,
|
||||
});
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "MozFirstContentfulPaint": {
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:FirstContentfulPaint",
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { debug, warn } = ContentDelegateChild.initLogging(
|
||||
"ContentDelegateChild"
|
||||
);
|
||||
|
|
@ -3,15 +3,15 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["GeckoViewContentParent"];
|
||||
var EXPORTED_SYMBOLS = ["ContentDelegateParent"];
|
||||
|
||||
const { GeckoViewUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/GeckoViewUtils.jsm"
|
||||
);
|
||||
|
||||
const { debug, warn } = GeckoViewUtils.initLogging("GeckoViewContentParent"); // eslint-disable-line no-unused-vars
|
||||
const { debug, warn } = GeckoViewUtils.initLogging("ContentDelegateParent"); // eslint-disable-line no-unused-vars
|
||||
|
||||
class GeckoViewContentParent extends JSWindowActorParent {
|
||||
class ContentDelegateParent extends JSWindowActorParent {
|
||||
async receiveMessage(aMsg) {
|
||||
debug`receiveMessage: ${aMsg.name} ${aMsg}`;
|
||||
|
||||
|
|
@ -25,7 +25,6 @@ const SCROLL_BEHAVIOR_AUTO = 1;
|
|||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
E10SUtils: "resource://gre/modules/E10SUtils.jsm",
|
||||
ManifestObtainer: "resource://gre/modules/ManifestObtainer.jsm",
|
||||
PrivacyFilter: "resource://gre/modules/sessionstore/PrivacyFilter.jsm",
|
||||
SessionHistory: "resource://gre/modules/sessionstore/SessionHistory.jsm",
|
||||
Utils: "resource://gre/modules/sessionstore/Utils.jsm",
|
||||
|
|
@ -65,26 +64,6 @@ class GeckoViewContentChild extends GeckoViewActorChild {
|
|||
return windowUtils.SCROLL_MODE_SMOOTH;
|
||||
}
|
||||
|
||||
notifyParentOfViewportFit() {
|
||||
if (this.triggerViewportFitChange) {
|
||||
this.contentWindow.cancelIdleCallback(this.triggerViewportFitChange);
|
||||
}
|
||||
this.triggerViewportFitChange = this.contentWindow.requestIdleCallback(
|
||||
() => {
|
||||
this.triggerViewportFitChange = null;
|
||||
const viewportFit = this.contentWindow.windowUtils.getViewportFitInfo();
|
||||
if (this.lastViewportFit === viewportFit) {
|
||||
return;
|
||||
}
|
||||
this.lastViewportFit = viewportFit;
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:DOMMetaViewportFit",
|
||||
viewportfit: viewportFit,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
collectSessionState() {
|
||||
const { docShell, contentWindow } = this;
|
||||
const history = SessionHistory.collect(docShell);
|
||||
|
|
@ -355,96 +334,6 @@ class GeckoViewContentChild extends GeckoViewActorChild {
|
|||
debug`handleEvent: ${aEvent.type}`;
|
||||
|
||||
switch (aEvent.type) {
|
||||
case "contextmenu": {
|
||||
function nearestParentAttribute(aNode, aAttribute) {
|
||||
while (
|
||||
aNode &&
|
||||
aNode.hasAttribute &&
|
||||
!aNode.hasAttribute(aAttribute)
|
||||
) {
|
||||
aNode = aNode.parentNode;
|
||||
}
|
||||
return aNode && aNode.getAttribute && aNode.getAttribute(aAttribute);
|
||||
}
|
||||
|
||||
function createAbsoluteUri(aBaseUri, aUri) {
|
||||
if (!aUri || !aBaseUri || !aBaseUri.displaySpec) {
|
||||
return null;
|
||||
}
|
||||
return Services.io.newURI(aUri, null, aBaseUri).displaySpec;
|
||||
}
|
||||
|
||||
const node = aEvent.composedTarget;
|
||||
const baseUri = node.ownerDocument.baseURIObject;
|
||||
const uri = createAbsoluteUri(
|
||||
baseUri,
|
||||
nearestParentAttribute(node, "href")
|
||||
);
|
||||
const title = nearestParentAttribute(node, "title");
|
||||
const alt = nearestParentAttribute(node, "alt");
|
||||
const elementType = ChromeUtils.getClassName(node);
|
||||
const isImage = elementType === "HTMLImageElement";
|
||||
const isMedia =
|
||||
elementType === "HTMLVideoElement" ||
|
||||
elementType === "HTMLAudioElement";
|
||||
const elementSrc =
|
||||
(isImage || isMedia) && (node.currentSrc || node.src);
|
||||
|
||||
if (uri || isImage || isMedia) {
|
||||
const msg = {
|
||||
type: "GeckoView:ContextMenu",
|
||||
screenX: aEvent.screenX,
|
||||
screenY: aEvent.screenY,
|
||||
baseUri: (baseUri && baseUri.displaySpec) || null,
|
||||
uri,
|
||||
title,
|
||||
alt,
|
||||
elementType,
|
||||
elementSrc: elementSrc || null,
|
||||
};
|
||||
|
||||
this.eventDispatcher.sendRequest(msg);
|
||||
aEvent.preventDefault();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "MozDOMFullscreen:Request": {
|
||||
this.sendAsyncMessage("GeckoView:DOMFullscreenRequest", {});
|
||||
break;
|
||||
}
|
||||
case "MozDOMFullscreen:Entered":
|
||||
case "MozDOMFullscreen:Exited":
|
||||
// Content may change fullscreen state by itself, and we should ensure
|
||||
// that the parent always exits fullscreen when content has left
|
||||
// full screen mode.
|
||||
if (this.contentWindow?.document.fullscreenElement) {
|
||||
break;
|
||||
}
|
||||
// fall-through
|
||||
case "MozDOMFullscreen:Exit":
|
||||
this.sendAsyncMessage("GeckoView:DOMFullscreenExit", {});
|
||||
break;
|
||||
case "DOMMetaViewportFitChanged":
|
||||
if (aEvent.originalTarget.ownerGlobal == this.contentWindow) {
|
||||
this.notifyParentOfViewportFit();
|
||||
}
|
||||
break;
|
||||
case "DOMTitleChanged":
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:DOMTitleChanged",
|
||||
title: this.contentWindow.document.title,
|
||||
});
|
||||
break;
|
||||
case "DOMWindowClose":
|
||||
if (!aEvent.isTrusted) {
|
||||
return;
|
||||
}
|
||||
|
||||
aEvent.preventDefault();
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:DOMWindowClose",
|
||||
});
|
||||
break;
|
||||
case "mozcaretstatechanged":
|
||||
if (
|
||||
aEvent.reason === "presscaret" ||
|
||||
|
|
@ -456,31 +345,6 @@ class GeckoViewContentChild extends GeckoViewActorChild {
|
|||
});
|
||||
}
|
||||
break;
|
||||
case "DOMContentLoaded": {
|
||||
if (aEvent.originalTarget.ownerGlobal == this.contentWindow) {
|
||||
// If loaded content doesn't have viewport-fit, parent still
|
||||
// uses old value of previous content.
|
||||
this.notifyParentOfViewportFit();
|
||||
}
|
||||
this.contentWindow.requestIdleCallback(async () => {
|
||||
const manifest = await ManifestObtainer.contentObtainManifest(
|
||||
this.contentWindow
|
||||
);
|
||||
if (manifest) {
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:WebAppManifest",
|
||||
manifest,
|
||||
});
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "MozFirstContentfulPaint": {
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:FirstContentfulPaint",
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ with Files('**'):
|
|||
|
||||
FINAL_TARGET_FILES.actors += [
|
||||
'BrowserTabParent.jsm',
|
||||
'ContentDelegateChild.jsm',
|
||||
'ContentDelegateParent.jsm',
|
||||
'GeckoViewContentChild.jsm',
|
||||
'GeckoViewContentParent.jsm',
|
||||
'LoadURIDelegateChild.jsm',
|
||||
'WebBrowserChromeChild.jsm',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
E10SUtils: "resource://gre/modules/E10SUtils.jsm",
|
||||
EventDispatcher: "resource://gre/modules/Messaging.jsm",
|
||||
GeckoViewActorManager: "resource://gre/modules/GeckoViewActorManager.jsm",
|
||||
GeckoViewSettings: "resource://gre/modules/GeckoViewSettings.jsm",
|
||||
GeckoViewUtils: "resource://gre/modules/GeckoViewUtils.jsm",
|
||||
HistogramStopwatch: "resource://gre/modules/GeckoViewTelemetry.jsm",
|
||||
|
|
@ -346,6 +347,10 @@ class ModuleInfo {
|
|||
// onInitBrowser() override. However, load content module after initializing
|
||||
// browser, because we don't have a message manager before then.
|
||||
this._loadResource(onInit);
|
||||
this._loadActors(onInit);
|
||||
if (this._enabledOnInit) {
|
||||
this._loadActors(onEnable);
|
||||
}
|
||||
|
||||
this._onInitPhase = onInit;
|
||||
this._onEnablePhase = onEnable;
|
||||
|
|
@ -383,6 +388,14 @@ class ModuleInfo {
|
|||
this._contentModuleLoaded = false;
|
||||
}
|
||||
|
||||
_loadActors(aPhase) {
|
||||
if (!aPhase || !aPhase.actors) {
|
||||
return;
|
||||
}
|
||||
|
||||
GeckoViewActorManager.addJSWindowActors(aPhase.actors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load resource according to a phase object that contains possible keys,
|
||||
*
|
||||
|
|
@ -445,6 +458,7 @@ class ModuleInfo {
|
|||
if (aEnabled) {
|
||||
this._loadResource(this._onEnablePhase);
|
||||
this._loadFrameScript(this._onEnablePhase);
|
||||
this._loadActors(this._onEnablePhase);
|
||||
if (this._impl) {
|
||||
this._impl.onEnable();
|
||||
this._impl.onSettingsUpdate();
|
||||
|
|
@ -518,6 +532,42 @@ function startup() {
|
|||
name: "GeckoViewContent",
|
||||
onInit: {
|
||||
resource: "resource://gre/modules/GeckoViewContent.jsm",
|
||||
actors: {
|
||||
GeckoViewContent: {
|
||||
child: {
|
||||
moduleURI: "resource:///actors/GeckoViewContentChild.jsm",
|
||||
events: {
|
||||
mozcaretstatechanged: { capture: true, mozSystemGroup: true },
|
||||
},
|
||||
allFrames: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
onEnable: {
|
||||
actors: {
|
||||
ContentDelegate: {
|
||||
parent: {
|
||||
moduleURI: "resource:///actors/ContentDelegateParent.jsm",
|
||||
},
|
||||
child: {
|
||||
moduleURI: "resource:///actors/ContentDelegateChild.jsm",
|
||||
events: {
|
||||
DOMContentLoaded: {},
|
||||
DOMMetaViewportFitChanged: {},
|
||||
DOMTitleChanged: {},
|
||||
DOMWindowClose: {},
|
||||
"MozDOMFullscreen:Entered": {},
|
||||
"MozDOMFullscreen:Exit": {},
|
||||
"MozDOMFullscreen:Exited": {},
|
||||
"MozDOMFullscreen:Request": {},
|
||||
MozFirstContentfulPaint: {},
|
||||
contextmenu: { capture: true },
|
||||
},
|
||||
allFrames: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -28,28 +28,6 @@ const JSWINDOWACTORS = {
|
|||
moduleURI: "resource:///actors/BrowserTabParent.jsm",
|
||||
},
|
||||
},
|
||||
GeckoViewContent: {
|
||||
parent: {
|
||||
moduleURI: "resource:///actors/GeckoViewContentParent.jsm",
|
||||
},
|
||||
child: {
|
||||
moduleURI: "resource:///actors/GeckoViewContentChild.jsm",
|
||||
events: {
|
||||
DOMContentLoaded: {},
|
||||
DOMMetaViewportFitChanged: {},
|
||||
DOMTitleChanged: {},
|
||||
DOMWindowClose: {},
|
||||
"MozDOMFullscreen:Entered": {},
|
||||
"MozDOMFullscreen:Exit": {},
|
||||
"MozDOMFullscreen:Exited": {},
|
||||
"MozDOMFullscreen:Request": {},
|
||||
MozFirstContentfulPaint: {},
|
||||
contextmenu: { capture: true },
|
||||
mozcaretstatechanged: { capture: true, mozSystemGroup: true },
|
||||
},
|
||||
allFrames: true,
|
||||
},
|
||||
},
|
||||
LoadURIDelegate: {
|
||||
child: {
|
||||
moduleURI: "resource:///actors/LoadURIDelegateChild.jsm",
|
||||
|
|
|
|||
33
mobile/android/modules/geckoview/GeckoViewActorManager.jsm
Normal file
33
mobile/android/modules/geckoview/GeckoViewActorManager.jsm
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/* 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 EXPORTED_SYMBOLS = ["GeckoViewActorManager"];
|
||||
|
||||
const { GeckoViewUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/GeckoViewUtils.jsm"
|
||||
);
|
||||
|
||||
const actors = new Set();
|
||||
|
||||
var GeckoViewActorManager = {
|
||||
addJSWindowActors(actors) {
|
||||
for (const [actorName, actor] of Object.entries(actors)) {
|
||||
this._register(actorName, actor);
|
||||
}
|
||||
},
|
||||
|
||||
_register(actorName, actor) {
|
||||
if (actors.has(actorName)) {
|
||||
// Actor already registered, nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
ChromeUtils.registerWindowActor(actorName, actor);
|
||||
actors.add(actorName);
|
||||
},
|
||||
};
|
||||
|
||||
const { debug, warn } = GeckoViewUtils.initLogging("GeckoViewActorManager"); // eslint-disable-line no-unused-vars
|
||||
|
|
@ -10,6 +10,7 @@ EXTRA_JS_MODULES += [
|
|||
'ContentCrashHandler.jsm',
|
||||
'DelayedInit.jsm',
|
||||
'GeckoViewActorChild.jsm',
|
||||
'GeckoViewActorManager.jsm',
|
||||
'GeckoViewAutocomplete.jsm',
|
||||
'GeckoViewAutofill.jsm',
|
||||
'GeckoViewChildModule.jsm',
|
||||
|
|
|
|||
Loading…
Reference in a new issue