Bug 1648149 - Move GeckoViewContent to Actor. r=snorp

Differential Revision: https://phabricator.services.mozilla.com/D84040
This commit is contained in:
Agi Sferro 2020-08-13 20:24:54 +00:00
Родитель 10bb01cfc6
Коммит 4c8aa34ba2
13 изменённых файлов: 554 добавлений и 560 удалений

Просмотреть файл

@ -20,6 +20,7 @@ skip-if = toolkit == 'android' || e10s || os == 'mac' # mac(select form control
[test_bug477531.html]
[test_bug477700.html]
[test_bug478219.xhtml]
skip-if = toolkit == 'android' # Bug 1658807
[test_bug534785.html]
[test_bug542914.html]
[test_bug549170.html]

Просмотреть файл

@ -10,14 +10,81 @@ var { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
// This needs to match ScreenLength.java
const SCREEN_LENGTH_TYPE_PIXEL = 0;
const SCREEN_LENGTH_TYPE_VISUAL_VIEWPORT_WIDTH = 1;
const SCREEN_LENGTH_TYPE_VISUAL_VIEWPORT_HEIGHT = 2;
const SCREEN_LENGTH_DOCUMENT_WIDTH = 3;
const SCREEN_LENGTH_DOCUMENT_HEIGHT = 4;
// This need to match PanZoomController.java
const SCROLL_BEHAVIOR_SMOOTH = 0;
const SCROLL_BEHAVIOR_AUTO = 1;
XPCOMUtils.defineLazyModuleGetters(this, {
SessionHistory: "resource://gre/modules/sessionstore/SessionHistory.jsm",
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",
});
var EXPORTED_SYMBOLS = ["GeckoViewContentChild"];
class GeckoViewContentChild extends GeckoViewActorChild {
toPixels(aLength, aType) {
const { contentWindow } = this;
if (aType === SCREEN_LENGTH_TYPE_PIXEL) {
return aLength;
} else if (aType === SCREEN_LENGTH_TYPE_VISUAL_VIEWPORT_WIDTH) {
return aLength * contentWindow.visualViewport.width;
} else if (aType === SCREEN_LENGTH_TYPE_VISUAL_VIEWPORT_HEIGHT) {
return aLength * contentWindow.visualViewport.height;
} else if (aType === SCREEN_LENGTH_DOCUMENT_WIDTH) {
return aLength * contentWindow.document.body.scrollWidth;
} else if (aType === SCREEN_LENGTH_DOCUMENT_HEIGHT) {
return aLength * contentWindow.document.body.scrollHeight;
}
return aLength;
}
toScrollBehavior(aBehavior) {
const { contentWindow } = this;
if (!contentWindow) {
return 0;
}
const { windowUtils } = contentWindow;
if (aBehavior === SCROLL_BEHAVIOR_SMOOTH) {
return windowUtils.SCROLL_MODE_SMOOTH;
} else if (aBehavior === SCROLL_BEHAVIOR_AUTO) {
return windowUtils.SCROLL_MODE_INSTANT;
}
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);
@ -49,14 +116,371 @@ class GeckoViewContentChild extends GeckoViewActorChild {
return { history, formdata, scrolldata };
}
loadEntry(loadOptions, history) {
if (!loadOptions) {
history.QueryInterface(Ci.nsISHistory).reloadCurrentEntry();
return;
}
const webNavigation = this.docShell.QueryInterface(Ci.nsIWebNavigation);
const {
referrerInfo,
triggeringPrincipal,
uri,
flags,
csp,
headers,
} = loadOptions;
webNavigation.loadURI(uri, {
triggeringPrincipal: E10SUtils.deserializePrincipal(triggeringPrincipal),
referrerInfo: E10SUtils.deserializeReferrerInfo(referrerInfo),
loadFlags: flags,
csp: E10SUtils.deserializeCSP(csp),
headers,
});
}
receiveMessage(message) {
const { name } = message;
debug`receiveMessage: ${name}`;
switch (name) {
case "GeckoView:DOMFullscreenEntered":
this.contentWindow?.windowUtils.handleFullscreenRequests();
break;
case "GeckoView:DOMFullscreenExited":
this.contentWindow?.windowUtils.exitFullscreen();
break;
case "GeckoView:ZoomToInput": {
const { contentWindow } = this;
const dwu = contentWindow.windowUtils;
const zoomToFocusedInput = function() {
if (!dwu.flushApzRepaints()) {
dwu.zoomToFocusedInput();
return;
}
Services.obs.addObserver(function apzFlushDone() {
Services.obs.removeObserver(apzFlushDone, "apz-repaints-flushed");
dwu.zoomToFocusedInput();
}, "apz-repaints-flushed");
};
const { force } = message.data;
let gotResize = false;
const onResize = function() {
gotResize = true;
if (dwu.isMozAfterPaintPending) {
contentWindow.addEventListener(
"MozAfterPaint",
() => zoomToFocusedInput(),
{ capture: true, once: true }
);
} else {
zoomToFocusedInput();
}
};
contentWindow.addEventListener("resize", onResize, { capture: true });
// When the keyboard is displayed, we can get one resize event,
// multiple resize events, or none at all. Try to handle all these
// cases by allowing resizing within a set interval, and still zoom to
// input if there is no resize event at the end of the interval.
contentWindow.setTimeout(() => {
contentWindow.removeEventListener("resize", onResize, {
capture: true,
});
if (!gotResize && force) {
onResize();
}
}, 500);
break;
}
case "GeckoView:RestoreState": {
const { contentWindow, docShell } = this;
const { history, formdata, scrolldata, loadOptions } = message.data;
if (history) {
const restoredHistory = SessionHistory.restore(docShell, history);
contentWindow.addEventListener(
"load",
_ => {
if (formdata) {
Utils.restoreFrameTreeData(
contentWindow,
formdata,
(frame, data) => {
// restore() will return false, and thus abort restoration for the
// current |frame| and its descendants, if |data.url| is given but
// doesn't match the loaded document's URL.
return SessionStoreUtils.restoreFormData(
frame.document,
data
);
}
);
}
},
{ capture: true, mozSystemGroup: true, once: true }
);
const scrollRestore = _ => {
if (contentWindow.location != "about:blank") {
if (scrolldata) {
Utils.restoreFrameTreeData(
contentWindow,
scrolldata,
(frame, data) => {
if (data.scroll) {
SessionStoreUtils.restoreScrollPosition(frame, data);
}
}
);
}
contentWindow.removeEventListener("pageshow", scrollRestore, {
capture: true,
mozSystemGroup: true,
});
}
};
contentWindow.addEventListener("pageshow", scrollRestore, {
capture: true,
mozSystemGroup: true,
});
const progressFilter = Cc[
"@mozilla.org/appshell/component/browser-status-filter;1"
].createInstance(Ci.nsIWebProgress);
const flags = Ci.nsIWebProgress.NOTIFY_LOCATION;
const progressListener = {
QueryInterface: ChromeUtils.generateQI(["nsIWebProgressListener"]),
onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags) {
debug`onLocationChange`;
if (
scrolldata &&
scrolldata.zoom &&
scrolldata.zoom.displaySize
) {
const utils = contentWindow.windowUtils;
// Restore zoom level.
utils.setRestoreResolution(
scrolldata.zoom.resolution,
scrolldata.zoom.displaySize.width,
scrolldata.zoom.displaySize.height
);
}
progressFilter.removeProgressListener(this);
const webProgress = docShell
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
webProgress.removeProgressListener(progressFilter);
},
};
progressFilter.addProgressListener(progressListener, flags);
const webProgress = docShell
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(progressFilter, flags);
this.loadEntry(loadOptions, restoredHistory);
}
break;
}
case "GeckoView:UpdateInitData": {
// Provide a hook for native code to detect a transfer.
Services.obs.notifyObservers(
this.docShell,
"geckoview-content-global-transferred"
);
break;
}
case "GeckoView:ScrollBy": {
const x = {};
const y = {};
const { contentWindow } = this;
const {
widthValue,
widthType,
heightValue,
heightType,
behavior,
} = message.data;
contentWindow.windowUtils.getVisualViewportOffset(x, y);
contentWindow.windowUtils.scrollToVisual(
x.value + this.toPixels(widthValue, widthType),
y.value + this.toPixels(heightValue, heightType),
contentWindow.windowUtils.UPDATE_TYPE_MAIN_THREAD,
this.toScrollBehavior(behavior)
);
break;
}
case "GeckoView:ScrollTo": {
const { contentWindow } = this;
const {
widthValue,
widthType,
heightValue,
heightType,
behavior,
} = message.data;
contentWindow.windowUtils.scrollToVisual(
this.toPixels(widthValue, widthType),
this.toPixels(heightValue, heightType),
contentWindow.windowUtils.UPDATE_TYPE_MAIN_THREAD,
this.toScrollBehavior(behavior)
);
break;
}
case "CollectSessionState": {
return this.collectSessionState();
}
default:
return null;
}
return null;
}
// 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 "mozcaretstatechanged":
if (
aEvent.reason === "presscaret" ||
aEvent.reason === "releasecaret"
) {
this.eventDispatcher.sendRequest({
type: "GeckoView:PinOnScreen",
pinned: aEvent.reason === "presscaret",
});
}
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;
}
}
}
}

Просмотреть файл

@ -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";
var EXPORTED_SYMBOLS = ["GeckoViewContentParent"];
const { GeckoViewUtils } = ChromeUtils.import(
"resource://gre/modules/GeckoViewUtils.jsm"
);
const { debug, warn } = GeckoViewUtils.initLogging("GeckoViewContentParent"); // eslint-disable-line no-unused-vars
class GeckoViewContentParent extends JSWindowActorParent {
async receiveMessage(aMsg) {
debug`receiveMessage: ${aMsg.name} ${aMsg}`;
const browser = this.browsingContext.top.embedderElement;
const window = browser.ownerGlobal;
switch (aMsg.name) {
case "GeckoView:DOMFullscreenExit": {
window.windowUtils.remoteFrameFullscreenReverted();
break;
}
case "GeckoView:DOMFullscreenRequest": {
window.windowUtils.remoteFrameFullscreenChanged(browser);
break;
}
}
}
}

Просмотреть файл

@ -8,6 +8,7 @@ with Files('**'):
FINAL_TARGET_FILES.actors += [
'BrowserTabParent.jsm',
'GeckoViewContentChild.jsm',
'GeckoViewContentParent.jsm',
'LoadURIDelegateChild.jsm',
'WebBrowserChromeChild.jsm',
]

Просмотреть файл

@ -1,498 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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 { GeckoViewChildModule } = ChromeUtils.import(
"resource://gre/modules/GeckoViewChildModule.jsm"
);
var { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
// This needs to match ScreenLength.java
const SCREEN_LENGTH_TYPE_PIXEL = 0;
const SCREEN_LENGTH_TYPE_VISUAL_VIEWPORT_WIDTH = 1;
const SCREEN_LENGTH_TYPE_VISUAL_VIEWPORT_HEIGHT = 2;
const SCREEN_LENGTH_DOCUMENT_WIDTH = 3;
const SCREEN_LENGTH_DOCUMENT_HEIGHT = 4;
// This need to match PanZoomController.java
const SCROLL_BEHAVIOR_SMOOTH = 0;
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",
});
class GeckoViewContentChild extends GeckoViewChildModule {
onInit() {
debug`onInit`;
// We don't load this in the global namespace because
// a Utils.jsm in a11y will clobber us.
XPCOMUtils.defineLazyModuleGetters(this, {
Utils: "resource://gre/modules/sessionstore/Utils.jsm",
});
this.timeoutsSuspended = false;
this.lastViewportFit = "";
this.triggerViewportFitChange = null;
this.messageManager.addMessageListener(
"GeckoView:DOMFullscreenEntered",
this
);
this.messageManager.addMessageListener(
"GeckoView:DOMFullscreenExited",
this
);
this.messageManager.addMessageListener("GeckoView:RestoreState", this);
this.messageManager.addMessageListener("GeckoView:UpdateInitData", this);
this.messageManager.addMessageListener("GeckoView:ZoomToInput", this);
this.messageManager.addMessageListener("GeckoView:ScrollBy", this);
this.messageManager.addMessageListener("GeckoView:ScrollTo", this);
const options = {
mozSystemGroup: true,
capture: false,
};
addEventListener("mozcaretstatechanged", this, options);
// Notify WebExtension process script that this tab is ready for extension content to load.
Services.obs.notifyObservers(
this.messageManager,
"tab-content-frameloader-created"
);
}
onEnable() {
debug`onEnable`;
addEventListener("DOMTitleChanged", this, false);
addEventListener("DOMWindowClose", this, false);
addEventListener("MozDOMFullscreen:Entered", this, false);
addEventListener("MozDOMFullscreen:Exit", this, false);
addEventListener("MozDOMFullscreen:Exited", this, false);
addEventListener("MozDOMFullscreen:Request", this, false);
addEventListener("contextmenu", this, { capture: true });
addEventListener("DOMContentLoaded", this, false);
addEventListener("MozFirstContentfulPaint", this, false);
addEventListener("DOMMetaViewportFitChanged", this, false);
}
onDisable() {
debug`onDisable`;
removeEventListener("DOMTitleChanged", this);
removeEventListener("DOMWindowClose", this);
removeEventListener("MozDOMFullscreen:Entered", this);
removeEventListener("MozDOMFullscreen:Exit", this);
removeEventListener("MozDOMFullscreen:Exited", this);
removeEventListener("MozDOMFullscreen:Request", this);
removeEventListener("contextmenu", this, { capture: true });
removeEventListener("DOMContentLoaded", this);
removeEventListener("MozFirstContentfulPaint", this);
removeEventListener("DOMMetaViewportFitChanged", this);
}
toPixels(aLength, aType) {
if (aType === SCREEN_LENGTH_TYPE_PIXEL) {
return aLength;
} else if (aType === SCREEN_LENGTH_TYPE_VISUAL_VIEWPORT_WIDTH) {
return aLength * content.visualViewport.width;
} else if (aType === SCREEN_LENGTH_TYPE_VISUAL_VIEWPORT_HEIGHT) {
return aLength * content.visualViewport.height;
} else if (aType === SCREEN_LENGTH_DOCUMENT_WIDTH) {
return aLength * content.document.body.scrollWidth;
} else if (aType === SCREEN_LENGTH_DOCUMENT_HEIGHT) {
return aLength * content.document.body.scrollHeight;
}
return aLength;
}
toScrollBehavior(aBehavior) {
if (!content) {
return 0;
}
if (aBehavior === SCROLL_BEHAVIOR_SMOOTH) {
return content.windowUtils.SCROLL_MODE_SMOOTH;
} else if (aBehavior === SCROLL_BEHAVIOR_AUTO) {
return content.windowUtils.SCROLL_MODE_INSTANT;
}
return content.windowUtils.SCROLL_MODE_SMOOTH;
}
notifyParentOfViewportFit() {
if (this.triggerViewportFitChange) {
content.cancelIdleCallback(this.triggerViewportFitChange);
}
this.triggerViewportFitChange = content.requestIdleCallback(() => {
this.triggerViewportFitChange = null;
const viewportFit = content.windowUtils.getViewportFitInfo();
if (this.lastViewportFit === viewportFit) {
return;
}
this.lastViewportFit = viewportFit;
this.eventDispatcher.sendRequest({
type: "GeckoView:DOMMetaViewportFit",
viewportfit: viewportFit,
});
});
}
receiveMessage(aMsg) {
debug`receiveMessage: ${aMsg.name}`;
switch (aMsg.name) {
case "GeckoView:DOMFullscreenEntered":
if (content) {
content.windowUtils.handleFullscreenRequests();
}
break;
case "GeckoView:DOMFullscreenExited":
if (content) {
content.windowUtils.exitFullscreen();
}
break;
case "GeckoView:ZoomToInput": {
const dwu = content.windowUtils;
const zoomToFocusedInput = function() {
if (!dwu.flushApzRepaints()) {
dwu.zoomToFocusedInput();
return;
}
Services.obs.addObserver(function apzFlushDone() {
Services.obs.removeObserver(apzFlushDone, "apz-repaints-flushed");
dwu.zoomToFocusedInput();
}, "apz-repaints-flushed");
};
const { force } = aMsg.data;
let gotResize = false;
const onResize = function() {
gotResize = true;
if (dwu.isMozAfterPaintPending) {
addEventListener(
"MozAfterPaint",
function paintDone() {
removeEventListener("MozAfterPaint", paintDone, {
capture: true,
});
zoomToFocusedInput();
},
{ capture: true }
);
} else {
zoomToFocusedInput();
}
};
addEventListener("resize", onResize, { capture: true });
// When the keyboard is displayed, we can get one resize event,
// multiple resize events, or none at all. Try to handle all these
// cases by allowing resizing within a set interval, and still zoom to
// input if there is no resize event at the end of the interval.
content.setTimeout(() => {
removeEventListener("resize", onResize, { capture: true });
if (!gotResize && force) {
onResize();
}
}, 500);
break;
}
case "GeckoView:RestoreState":
const { history, formdata, scrolldata, loadOptions } = aMsg.data;
this._savedState = { history, formdata, scrolldata };
if (history) {
const restoredHistory = SessionHistory.restore(docShell, history);
addEventListener(
"load",
_ => {
if (formdata) {
this.Utils.restoreFrameTreeData(
content,
formdata,
(frame, data) => {
// restore() will return false, and thus abort restoration for the
// current |frame| and its descendants, if |data.url| is given but
// doesn't match the loaded document's URL.
return SessionStoreUtils.restoreFormData(
frame.document,
data
);
}
);
}
},
{ capture: true, mozSystemGroup: true, once: true }
);
const scrollRestore = _ => {
if (content.location != "about:blank") {
if (scrolldata) {
this.Utils.restoreFrameTreeData(
content,
scrolldata,
(frame, data) => {
if (data.scroll) {
SessionStoreUtils.restoreScrollPosition(frame, data);
}
}
);
}
delete this._savedState;
removeEventListener("pageshow", scrollRestore, {
capture: true,
mozSystemGroup: true,
});
}
};
addEventListener("pageshow", scrollRestore, {
capture: true,
mozSystemGroup: true,
});
if (!this.progressFilter) {
this.progressFilter = Cc[
"@mozilla.org/appshell/component/browser-status-filter;1"
].createInstance(Ci.nsIWebProgress);
this.flags = Ci.nsIWebProgress.NOTIFY_LOCATION;
}
this.progressFilter.addProgressListener(this, this.flags);
const webProgress = docShell
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(this.progressFilter, this.flags);
this.loadEntry(loadOptions, restoredHistory);
}
break;
case "GeckoView:UpdateInitData":
// Provide a hook for native code to detect a transfer.
Services.obs.notifyObservers(
docShell,
"geckoview-content-global-transferred"
);
break;
case "GeckoView:ScrollBy":
const x = {};
const y = {};
content.windowUtils.getVisualViewportOffset(x, y);
content.windowUtils.scrollToVisual(
x.value + this.toPixels(aMsg.data.widthValue, aMsg.data.widthType),
y.value + this.toPixels(aMsg.data.heightValue, aMsg.data.heightType),
content.windowUtils.UPDATE_TYPE_MAIN_THREAD,
this.toScrollBehavior(aMsg.data.behavior)
);
break;
case "GeckoView:ScrollTo":
content.windowUtils.scrollToVisual(
this.toPixels(aMsg.data.widthValue, aMsg.data.widthType),
this.toPixels(aMsg.data.heightValue, aMsg.data.heightType),
content.windowUtils.UPDATE_TYPE_MAIN_THREAD,
this.toScrollBehavior(aMsg.data.behavior)
);
break;
}
}
loadEntry(loadOptions, history) {
if (!loadOptions) {
history.QueryInterface(Ci.nsISHistory).reloadCurrentEntry();
return;
}
const webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
const {
referrerInfo,
triggeringPrincipal,
uri,
flags,
csp,
headers,
} = loadOptions;
webNavigation.loadURI(uri, {
triggeringPrincipal: E10SUtils.deserializePrincipal(triggeringPrincipal),
referrerInfo: E10SUtils.deserializeReferrerInfo(referrerInfo),
loadFlags: flags,
csp: E10SUtils.deserializeCSP(csp),
headers,
});
}
// 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":
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 (content && content.document.fullscreenElement) {
break;
}
// fall-through
case "MozDOMFullscreen:Exit":
sendAsyncMessage("GeckoView:DOMFullscreenExit");
break;
case "DOMMetaViewportFitChanged":
if (aEvent.originalTarget.ownerGlobal == content) {
this.notifyParentOfViewportFit();
}
break;
case "DOMTitleChanged":
this.eventDispatcher.sendRequest({
type: "GeckoView:DOMTitleChanged",
title: content.document.title,
});
break;
case "DOMWindowClose":
if (!aEvent.isTrusted) {
return;
}
aEvent.preventDefault();
this.eventDispatcher.sendRequest({
type: "GeckoView:DOMWindowClose",
});
break;
case "mozcaretstatechanged":
if (
aEvent.reason === "presscaret" ||
aEvent.reason === "releasecaret"
) {
this.eventDispatcher.sendRequest({
type: "GeckoView:PinOnScreen",
pinned: aEvent.reason === "presscaret",
});
}
break;
case "DOMContentLoaded": {
if (aEvent.originalTarget.ownerGlobal == content) {
// If loaded content doesn't have viewport-fit, parent still
// uses old value of previous content.
this.notifyParentOfViewportFit();
}
content.requestIdleCallback(async () => {
const manifest = await ManifestObtainer.contentObtainManifest(
content
);
if (manifest) {
this.eventDispatcher.sendRequest({
type: "GeckoView:WebAppManifest",
manifest,
});
}
});
break;
}
case "MozFirstContentfulPaint":
this.eventDispatcher.sendRequest({
type: "GeckoView:FirstContentfulPaint",
});
break;
}
}
// WebProgress event handler.
onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags) {
debug`onLocationChange`;
if (this._savedState) {
const scrolldata = this._savedState.scrolldata;
if (scrolldata && scrolldata.zoom && scrolldata.zoom.displaySize) {
const utils = content.windowUtils;
// Restore zoom level.
utils.setRestoreResolution(
scrolldata.zoom.resolution,
scrolldata.zoom.displaySize.width,
scrolldata.zoom.displaySize.height
);
}
}
this.progressFilter.removeProgressListener(this);
const webProgress = docShell
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
webProgress.removeProgressListener(this.progressFilter);
}
}
const { debug, warn } = GeckoViewContentChild.initLogging("GeckoViewContent"); // eslint-disable-line no-unused-vars
const module = GeckoViewContentChild.create(this);

Просмотреть файл

@ -0,0 +1,12 @@
/* 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/. */
/* This is needed for extensions to load content scripts */
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
// Send this notification from the content process so that the
// ExtensionPolicyService can register this content frame and be ready to load
// content scripts
Services.obs.notifyObservers(this, "tab-content-frameloader-created");

Просмотреть файл

@ -235,7 +235,7 @@ var ModuleManager = {
module.enabled = true;
});
this.messageManager.sendAsyncMessage(
this.getActor("GeckoViewContent").sendAsyncMessage(
"GeckoView:RestoreState",
sessionState
);
@ -508,11 +508,16 @@ function startup() {
const browser = createBrowser();
ModuleManager.init(browser, [
{
name: "ExtensionContent",
onInit: {
frameScript: "chrome://geckoview/content/extension-content.js",
},
},
{
name: "GeckoViewContent",
onInit: {
resource: "resource://gre/modules/GeckoViewContent.jsm",
frameScript: "chrome://geckoview/content/GeckoViewContentChild.js",
},
},
{

Просмотреть файл

@ -9,10 +9,10 @@ geckoview.jar:
content/config.js
% override chrome://global/content/config.xhtml chrome://geckoview/content/config.xhtml
content/extension-content.js
content/geckoview.xhtml
content/geckoview.js
content/GeckoViewAutofillChild.js
content/GeckoViewContentChild.js
content/GeckoViewMediaChild.js
content/GeckoViewProgressChild.js
content/GeckoViewPromptChild.js

Просмотреть файл

@ -29,8 +29,25 @@ const JSWINDOWACTORS = {
},
},
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: {

Просмотреть файл

@ -25,11 +25,12 @@ class GeckoViewContent extends GeckoViewModule {
"GeckoView:DisplayMatches",
"GeckoView:FindInPage",
"GeckoView:RestoreState",
"GeckoView:SetActive",
"GeckoView:SetFocused",
"GeckoView:ZoomToInput",
"GeckoView:ScrollBy",
"GeckoView:ScrollTo",
"GeckoView:SetActive",
"GeckoView:SetFocused",
"GeckoView:UpdateInitData",
"GeckoView:ZoomToInput",
]);
}
@ -53,12 +54,6 @@ class GeckoViewContent extends GeckoViewModule {
/* untrusted */ false
);
this.messageManager.addMessageListener("GeckoView:DOMFullscreenExit", this);
this.messageManager.addMessageListener(
"GeckoView:DOMFullscreenRequest",
this
);
Services.obs.addObserver(this, "oop-frameloader-crashed");
Services.obs.addObserver(this, "ipc:content-shutdown");
}
@ -80,26 +75,46 @@ class GeckoViewContent extends GeckoViewModule {
/* capture */ true
);
this.messageManager.removeMessageListener(
"GeckoView:DOMFullscreenExit",
this
);
this.messageManager.removeMessageListener(
"GeckoView:DOMFullscreenRequest",
this
);
Services.obs.removeObserver(this, "oop-frameloader-crashed");
Services.obs.removeObserver(this, "ipc:content-shutdown");
}
get actor() {
return this.getActor("GeckoViewContent");
}
// Goes up the browsingContext chain and sends the message every time
// we cross the process boundary so that every process in the chain is
// notified.
sendToAllChildren(aEvent, aData) {
let { browsingContext } = this.actor;
while (browsingContext) {
if (!browsingContext.currentWindowGlobal) {
break;
}
const currentPid = browsingContext.currentWindowGlobal.osPid;
const parentPid = browsingContext.parent?.currentWindowGlobal.osPid;
if (currentPid != parentPid) {
const actor = browsingContext.currentWindowGlobal.getActor(
"GeckoViewContent"
);
actor.sendAsyncMessage(aEvent, aData);
}
browsingContext = browsingContext.parent;
}
}
// Bundle event handler.
onEvent(aEvent, aData, aCallback) {
debug`onEvent: event=${aEvent}, data=${aData}`;
switch (aEvent) {
case "GeckoViewContent:ExitFullScreen":
this.messageManager.sendAsyncMessage("GeckoView:DOMFullscreenExited");
this.sendToAllChildren("GeckoView:DOMFullscreenExited");
break;
case "GeckoView:ClearMatches": {
this._clearMatches();
@ -114,13 +129,18 @@ class GeckoViewContent extends GeckoViewModule {
break;
}
case "GeckoView:ZoomToInput":
this.messageManager.sendAsyncMessage(aEvent, aData);
this.sendToAllChildren(aEvent, aData);
break;
case "GeckoView:ScrollBy":
this.messageManager.sendAsyncMessage(aEvent, aData);
// Unclear if that actually works with oop iframes?
this.sendToAllChildren(aEvent, aData);
break;
case "GeckoView:ScrollTo":
this.messageManager.sendAsyncMessage(aEvent, aData);
// Unclear if that actually works with oop iframes?
this.sendToAllChildren(aEvent, aData);
break;
case "GeckoView:UpdateInitData":
this.sendToAllChildren(aEvent, aData);
break;
case "GeckoView:SetActive":
this.browser.docShellIsActive = !!aData.active;
@ -135,7 +155,8 @@ class GeckoViewContent extends GeckoViewModule {
}
break;
case "GeckoView:RestoreState":
this.messageManager.sendAsyncMessage("GeckoView:RestoreState", aData);
// TODO: this needs parent process history to work properly
this.actor.sendAsyncMessage("GeckoView:RestoreState", aData);
break;
}
}
@ -160,27 +181,11 @@ class GeckoViewContent extends GeckoViewModule {
case "MozDOMFullscreen:Entered":
if (this.browser == aEvent.target) {
// Remote browser; dispatch to content process.
this.messageManager.sendAsyncMessage(
"GeckoView:DOMFullscreenEntered"
);
this.sendToAllChildren("GeckoView:DOMFullscreenEntered");
}
break;
case "MozDOMFullscreen:Exited":
this.messageManager.sendAsyncMessage("GeckoView:DOMFullscreenExited");
break;
}
}
// Message manager event handler.
receiveMessage(aMsg) {
debug`receiveMessage: ${aMsg.name}`;
switch (aMsg.name) {
case "GeckoView:DOMFullscreenExit":
this.window.windowUtils.remoteFrameFullscreenReverted();
break;
case "GeckoView:DOMFullscreenRequest":
this.window.windowUtils.remoteFrameFullscreenChanged(aMsg.target);
this.sendToAllChildren("GeckoView:DOMFullscreenExited");
break;
}
}

Просмотреть файл

@ -39,6 +39,10 @@ class GeckoViewModule {
return this.moduleManager.window;
}
getActor(aActorName) {
return this.moduleManager.getActor(aActorName);
}
get browser() {
return this.moduleManager.browser;
}

Просмотреть файл

@ -1,14 +1,10 @@
[close-method.window.html]
expected:
if (os == "android") and e10s: TIMEOUT
[window.close() affects name targeting immediately]
expected:
if (os == "android") and e10s: TIMEOUT
FAIL
[window.close() queues a task to discard, but window.closed knows immediately]
expected:
if debug and not webrender and not e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): FAIL
if (os == "android") and not e10s: FAIL
if (os == "android") and e10s: TIMEOUT
if (os == "android"): FAIL

Просмотреть файл

@ -1,24 +1,18 @@
[self-et-al.window.html]
expected:
if (os == "android") and e10s: TIMEOUT
max-asserts: 3
[popupWindow.window before, after closing, and after discarding]
expected:
if (os == "android") and not e10s: FAIL
if (os == "android") and e10s: TIMEOUT
if (os == "android"): FAIL
[popupWindow.globalThis before, after closing, and after discarding]
expected:
if (os == "android") and not e10s: FAIL
if (os == "android") and e10s: TIMEOUT
if (os == "android"): FAIL
[popupWindow.frames before, after closing, and after discarding]
expected:
if (os == "android") and not e10s: FAIL
if (os == "android") and e10s: TIMEOUT
if (os == "android"): FAIL
[popupWindow.self before, after closing, and after discarding]
expected:
if (os == "android") and not e10s: FAIL
if (os == "android") and e10s: TIMEOUT
if (os == "android"): FAIL