2017-01-26 00:13:58 +03:00
|
|
|
/* 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 = ["GeckoViewContent"];
|
|
|
|
|
2019-01-17 21:18:31 +03:00
|
|
|
const { GeckoViewModule } = ChromeUtils.import(
|
|
|
|
"resource://gre/modules/GeckoViewModule.jsm"
|
|
|
|
);
|
2022-07-12 07:21:34 +03:00
|
|
|
const { XPCOMUtils } = ChromeUtils.importESModule(
|
|
|
|
"resource://gre/modules/XPCOMUtils.sys.mjs"
|
2019-01-17 21:18:31 +03:00
|
|
|
);
|
2018-05-30 21:10:54 +03:00
|
|
|
|
2017-02-08 23:16:30 +03:00
|
|
|
class GeckoViewContent extends GeckoViewModule {
|
2018-04-12 23:10:14 +03:00
|
|
|
onInit() {
|
2018-05-05 04:08:10 +03:00
|
|
|
this.registerListener([
|
2019-04-10 04:51:24 +03:00
|
|
|
"GeckoViewContent:ExitFullScreen",
|
|
|
|
"GeckoView:ClearMatches",
|
|
|
|
"GeckoView:DisplayMatches",
|
|
|
|
"GeckoView:FindInPage",
|
|
|
|
"GeckoView:RestoreState",
|
2020-08-14 17:48:37 +03:00
|
|
|
"GeckoView:ScrollBy",
|
|
|
|
"GeckoView:ScrollTo",
|
2019-04-10 04:51:24 +03:00
|
|
|
"GeckoView:SetActive",
|
|
|
|
"GeckoView:SetFocused",
|
2022-05-11 04:05:50 +03:00
|
|
|
"GeckoView:SetPriorityHint",
|
2020-08-14 17:48:37 +03:00
|
|
|
"GeckoView:UpdateInitData",
|
2019-04-10 04:51:24 +03:00
|
|
|
"GeckoView:ZoomToInput",
|
2017-09-22 23:57:46 +03:00
|
|
|
]);
|
2018-05-05 04:08:10 +03:00
|
|
|
}
|
2017-07-11 20:28:18 +03:00
|
|
|
|
2018-05-05 04:08:10 +03:00
|
|
|
onEnable() {
|
2018-05-17 01:39:55 +03:00
|
|
|
this.window.addEventListener(
|
|
|
|
"MozDOMFullscreen:Entered",
|
|
|
|
this,
|
2019-04-10 04:51:24 +03:00
|
|
|
/* capture */ true,
|
|
|
|
/* untrusted */ false
|
|
|
|
);
|
2018-05-17 01:39:55 +03:00
|
|
|
this.window.addEventListener(
|
|
|
|
"MozDOMFullscreen:Exited",
|
|
|
|
this,
|
2019-04-10 04:51:24 +03:00
|
|
|
/* capture */ true,
|
|
|
|
/* untrusted */ false
|
|
|
|
);
|
2019-06-03 22:42:28 +03:00
|
|
|
this.window.addEventListener(
|
|
|
|
"framefocusrequested",
|
|
|
|
this,
|
|
|
|
/* capture */ true,
|
|
|
|
/* untrusted */ false
|
|
|
|
);
|
2017-06-06 20:34:53 +03:00
|
|
|
|
2020-09-02 04:53:20 +03:00
|
|
|
this.window.addEventListener("DOMWindowClose", this);
|
2020-09-02 18:31:54 +03:00
|
|
|
this.window.addEventListener("pagetitlechanged", this);
|
2021-10-22 09:04:04 +03:00
|
|
|
this.window.addEventListener("pageinfo", this);
|
2020-09-02 04:53:20 +03:00
|
|
|
|
2022-06-06 07:42:07 +03:00
|
|
|
Services.obs.addObserver(this, "oop-frameloader-crashed");
|
|
|
|
Services.obs.addObserver(this, "ipc:content-shutdown");
|
2017-06-06 20:34:53 +03:00
|
|
|
}
|
|
|
|
|
2018-04-12 23:10:15 +03:00
|
|
|
onDisable() {
|
2018-05-17 01:39:55 +03:00
|
|
|
this.window.removeEventListener(
|
|
|
|
"MozDOMFullscreen:Entered",
|
|
|
|
this,
|
2019-04-10 04:51:24 +03:00
|
|
|
/* capture */ true
|
|
|
|
);
|
2018-05-17 01:39:55 +03:00
|
|
|
this.window.removeEventListener(
|
|
|
|
"MozDOMFullscreen:Exited",
|
|
|
|
this,
|
2019-04-10 04:51:24 +03:00
|
|
|
/* capture */ true
|
|
|
|
);
|
2019-06-03 22:42:28 +03:00
|
|
|
this.window.removeEventListener(
|
|
|
|
"framefocusrequested",
|
|
|
|
this,
|
|
|
|
/* capture */ true
|
|
|
|
);
|
2019-07-05 11:53:35 +03:00
|
|
|
|
2020-09-02 04:53:20 +03:00
|
|
|
this.window.removeEventListener("DOMWindowClose", this);
|
2020-09-02 18:31:54 +03:00
|
|
|
this.window.removeEventListener("pagetitlechanged", this);
|
2021-10-22 09:04:04 +03:00
|
|
|
this.window.removeEventListener("pageinfo", this);
|
2020-09-02 04:53:20 +03:00
|
|
|
|
2022-06-06 07:42:07 +03:00
|
|
|
Services.obs.removeObserver(this, "oop-frameloader-crashed");
|
|
|
|
Services.obs.removeObserver(this, "ipc:content-shutdown");
|
2018-04-12 23:10:15 +03:00
|
|
|
}
|
|
|
|
|
2020-08-14 17:48:37 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-06 20:34:53 +03:00
|
|
|
// Bundle event handler.
|
|
|
|
onEvent(aEvent, aData, aCallback) {
|
2018-04-15 21:53:29 +03:00
|
|
|
debug`onEvent: event=${aEvent}, data=${aData}`;
|
|
|
|
|
2017-06-06 20:34:53 +03:00
|
|
|
switch (aEvent) {
|
|
|
|
case "GeckoViewContent:ExitFullScreen":
|
2021-07-23 19:54:15 +03:00
|
|
|
this.browser.ownerDocument.exitFullscreen();
|
2018-01-24 01:18:15 +03:00
|
|
|
break;
|
2018-07-10 20:12:55 +03:00
|
|
|
case "GeckoView:ClearMatches": {
|
|
|
|
this._clearMatches();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "GeckoView:DisplayMatches": {
|
|
|
|
this._displayMatches(aData);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "GeckoView:FindInPage": {
|
|
|
|
this._findInPage(aData, aCallback);
|
|
|
|
break;
|
|
|
|
}
|
2017-11-21 01:17:01 +03:00
|
|
|
case "GeckoView:ZoomToInput":
|
2020-08-14 17:48:37 +03:00
|
|
|
this.sendToAllChildren(aEvent, aData);
|
2017-06-06 20:34:53 +03:00
|
|
|
break;
|
2019-02-14 22:04:06 +03:00
|
|
|
case "GeckoView:ScrollBy":
|
2020-08-14 17:48:37 +03:00
|
|
|
// Unclear if that actually works with oop iframes?
|
|
|
|
this.sendToAllChildren(aEvent, aData);
|
2019-02-14 22:04:06 +03:00
|
|
|
break;
|
|
|
|
case "GeckoView:ScrollTo":
|
2020-08-14 17:48:37 +03:00
|
|
|
// Unclear if that actually works with oop iframes?
|
|
|
|
this.sendToAllChildren(aEvent, aData);
|
|
|
|
break;
|
|
|
|
case "GeckoView:UpdateInitData":
|
|
|
|
this.sendToAllChildren(aEvent, aData);
|
2019-02-14 22:04:06 +03:00
|
|
|
break;
|
2017-09-22 23:57:46 +03:00
|
|
|
case "GeckoView:SetActive":
|
2020-10-15 19:29:25 +03:00
|
|
|
this.browser.docShellIsActive = !!aData.active;
|
2018-08-21 22:23:28 +03:00
|
|
|
break;
|
|
|
|
case "GeckoView:SetFocused":
|
|
|
|
if (aData.focused) {
|
|
|
|
this.browser.focus();
|
|
|
|
this.browser.setAttribute("primary", "true");
|
|
|
|
} else {
|
|
|
|
this.browser.removeAttribute("primary");
|
2018-02-12 21:38:11 +03:00
|
|
|
this.browser.blur();
|
|
|
|
}
|
2017-09-22 23:57:46 +03:00
|
|
|
break;
|
2022-05-11 04:05:50 +03:00
|
|
|
case "GeckoView:SetPriorityHint":
|
|
|
|
if (this.browser.isRemoteBrowser) {
|
|
|
|
const remoteTab = this.browser.frameLoader?.remoteTab;
|
|
|
|
if (remoteTab) {
|
|
|
|
remoteTab.priorityHint = aData.priorityHint;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2018-04-17 22:13:10 +03:00
|
|
|
case "GeckoView:RestoreState":
|
2020-08-28 23:19:44 +03:00
|
|
|
this.actor.restoreState(aData);
|
2018-04-17 22:13:10 +03:00
|
|
|
break;
|
2017-06-06 20:34:53 +03:00
|
|
|
}
|
2017-01-26 00:13:58 +03:00
|
|
|
}
|
|
|
|
|
2017-06-06 20:34:53 +03:00
|
|
|
// DOM event handler
|
2017-02-24 21:39:35 +03:00
|
|
|
handleEvent(aEvent) {
|
2018-04-15 21:53:29 +03:00
|
|
|
debug`handleEvent: ${aEvent.type}`;
|
2017-05-26 01:35:19 +03:00
|
|
|
|
|
|
|
switch (aEvent.type) {
|
2019-06-03 22:42:28 +03:00
|
|
|
case "framefocusrequested":
|
|
|
|
if (this.browser != aEvent.target) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (this.browser.hasAttribute("primary")) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.eventDispatcher.sendRequest({
|
|
|
|
type: "GeckoView:FocusRequest",
|
|
|
|
});
|
|
|
|
aEvent.preventDefault();
|
|
|
|
break;
|
2017-05-26 01:35:19 +03:00
|
|
|
case "MozDOMFullscreen:Entered":
|
|
|
|
if (this.browser == aEvent.target) {
|
|
|
|
// Remote browser; dispatch to content process.
|
2020-08-14 17:48:37 +03:00
|
|
|
this.sendToAllChildren("GeckoView:DOMFullscreenEntered");
|
2017-05-26 01:35:19 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "MozDOMFullscreen:Exited":
|
2020-08-14 17:48:37 +03:00
|
|
|
this.sendToAllChildren("GeckoView:DOMFullscreenExited");
|
2017-05-26 01:35:19 +03:00
|
|
|
break;
|
2020-09-02 18:31:54 +03:00
|
|
|
case "pagetitlechanged":
|
|
|
|
this.eventDispatcher.sendRequest({
|
|
|
|
type: "GeckoView:PageTitleChanged",
|
|
|
|
title: this.browser.contentTitle,
|
|
|
|
});
|
|
|
|
break;
|
2020-09-02 23:02:52 +03:00
|
|
|
case "DOMWindowClose":
|
|
|
|
// We need this because we want to allow the app
|
|
|
|
// to close the window itself. If we don't preventDefault()
|
|
|
|
// here Gecko will close it immediately.
|
|
|
|
aEvent.preventDefault();
|
|
|
|
|
|
|
|
this.eventDispatcher.sendRequest({
|
|
|
|
type: "GeckoView:DOMWindowClose",
|
|
|
|
});
|
|
|
|
break;
|
2021-10-22 09:04:04 +03:00
|
|
|
case "pageinfo":
|
|
|
|
if (aEvent.detail.previewImageURL) {
|
|
|
|
this.eventDispatcher.sendRequest({
|
|
|
|
type: "GeckoView:PreviewImage",
|
|
|
|
previewImageUrl: aEvent.detail.previewImageURL,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
break;
|
2017-01-26 00:13:58 +03:00
|
|
|
}
|
|
|
|
}
|
2018-05-30 21:10:54 +03:00
|
|
|
|
|
|
|
// nsIObserver event handler
|
|
|
|
observe(aSubject, aTopic, aData) {
|
|
|
|
debug`observe: ${aTopic}`;
|
2019-07-18 18:12:28 +03:00
|
|
|
this._contentCrashed = false;
|
|
|
|
const browser = aSubject.ownerElement;
|
2018-05-30 21:10:54 +03:00
|
|
|
|
|
|
|
switch (aTopic) {
|
|
|
|
case "oop-frameloader-crashed": {
|
|
|
|
if (!browser || browser != this.browser) {
|
|
|
|
return;
|
|
|
|
}
|
2019-07-18 18:12:28 +03:00
|
|
|
this.window.setTimeout(() => {
|
|
|
|
if (this._contentCrashed) {
|
|
|
|
this.eventDispatcher.sendRequest({
|
|
|
|
type: "GeckoView:ContentCrash",
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
this.eventDispatcher.sendRequest({
|
|
|
|
type: "GeckoView:ContentKill",
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}, 250);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "ipc:content-shutdown": {
|
|
|
|
aSubject.QueryInterface(Ci.nsIPropertyBag2);
|
|
|
|
if (aSubject.get("dumpID")) {
|
|
|
|
if (
|
|
|
|
browser &&
|
|
|
|
aSubject.get("childID") != browser.frameLoader.childID
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._contentCrashed = true;
|
|
|
|
}
|
2019-04-10 04:51:24 +03:00
|
|
|
break;
|
2018-05-30 21:10:54 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-07-10 20:12:55 +03:00
|
|
|
|
|
|
|
_findInPage(aData, aCallback) {
|
|
|
|
debug`findInPage: data=${aData} callback=${aCallback && "non-null"}`;
|
|
|
|
|
|
|
|
let finder;
|
|
|
|
try {
|
|
|
|
finder = this.browser.finder;
|
|
|
|
} catch (e) {
|
|
|
|
if (aCallback) {
|
|
|
|
aCallback.onError(`No finder: ${e}`);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._finderListener) {
|
|
|
|
finder.removeResultListener(this._finderListener);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._finderListener = {
|
|
|
|
response: {
|
|
|
|
found: false,
|
|
|
|
wrapped: false,
|
|
|
|
current: 0,
|
|
|
|
total: -1,
|
|
|
|
searchString: aData.searchString || finder.searchString,
|
|
|
|
linkURL: null,
|
|
|
|
clientRect: null,
|
|
|
|
flags: {
|
|
|
|
backwards: !!aData.backwards,
|
|
|
|
linksOnly: !!aData.linksOnly,
|
|
|
|
matchCase: !!aData.matchCase,
|
|
|
|
wholeWord: !!aData.wholeWord,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
onFindResult(aOptions) {
|
|
|
|
if (!aCallback || aOptions.searchString !== aData.searchString) {
|
|
|
|
// Result from a previous search.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Object.assign(this.response, {
|
|
|
|
found: aOptions.result !== Ci.nsITypeAheadFind.FIND_NOTFOUND,
|
|
|
|
wrapped: aOptions.result !== Ci.nsITypeAheadFind.FIND_FOUND,
|
|
|
|
linkURL: aOptions.linkURL,
|
|
|
|
clientRect: aOptions.rect && {
|
|
|
|
left: aOptions.rect.left,
|
|
|
|
top: aOptions.rect.top,
|
|
|
|
right: aOptions.rect.right,
|
|
|
|
bottom: aOptions.rect.bottom,
|
|
|
|
},
|
|
|
|
flags: {
|
|
|
|
backwards: aOptions.findBackwards,
|
|
|
|
linksOnly: aOptions.linksOnly,
|
|
|
|
matchCase: this.response.flags.matchCase,
|
|
|
|
wholeWord: this.response.flags.wholeWord,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!this.response.found) {
|
|
|
|
this.response.current = 0;
|
|
|
|
this.response.total = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only send response if we have a count.
|
|
|
|
if (!this.response.found || this.response.current !== 0) {
|
|
|
|
debug`onFindResult: ${this.response}`;
|
|
|
|
aCallback.onSuccess(this.response);
|
|
|
|
aCallback = undefined;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onMatchesCountResult(aResult) {
|
|
|
|
if (!aCallback || finder.searchString !== aData.searchString) {
|
|
|
|
// Result from a previous search.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Object.assign(this.response, {
|
|
|
|
current: aResult.current,
|
|
|
|
total: aResult.total,
|
|
|
|
});
|
|
|
|
|
|
|
|
// Only send response if we have a result. `found` and `wrapped` are
|
|
|
|
// both false only when we haven't received a result yet.
|
|
|
|
if (this.response.found || this.response.wrapped) {
|
|
|
|
debug`onMatchesCountResult: ${this.response}`;
|
|
|
|
aCallback.onSuccess(this.response);
|
|
|
|
aCallback = undefined;
|
|
|
|
}
|
|
|
|
},
|
2018-07-27 00:51:45 +03:00
|
|
|
|
|
|
|
onCurrentSelection() {},
|
|
|
|
|
|
|
|
onHighlightFinished() {},
|
2018-07-10 20:12:55 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
finder.caseSensitive = !!aData.matchCase;
|
|
|
|
finder.entireWord = !!aData.wholeWord;
|
2019-12-09 22:26:40 +03:00
|
|
|
finder.matchDiacritics = !!aData.matchDiacritics;
|
2018-07-30 23:21:29 +03:00
|
|
|
finder.addResultListener(this._finderListener);
|
2018-07-10 20:12:55 +03:00
|
|
|
|
|
|
|
const drawOutline =
|
|
|
|
this._matchDisplayOptions && !!this._matchDisplayOptions.drawOutline;
|
|
|
|
|
|
|
|
if (!aData.searchString || aData.searchString === finder.searchString) {
|
|
|
|
// Search again.
|
|
|
|
aData.searchString = finder.searchString;
|
2019-09-18 02:28:41 +03:00
|
|
|
finder.findAgain(
|
|
|
|
aData.searchString,
|
|
|
|
!!aData.backwards,
|
|
|
|
!!aData.linksOnly,
|
|
|
|
drawOutline
|
|
|
|
);
|
2018-07-10 20:12:55 +03:00
|
|
|
} else {
|
|
|
|
finder.fastFind(aData.searchString, !!aData.linksOnly, drawOutline);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_clearMatches() {
|
2018-07-30 23:21:29 +03:00
|
|
|
debug`clearMatches`;
|
|
|
|
|
|
|
|
let finder;
|
2018-07-10 20:12:55 +03:00
|
|
|
try {
|
2018-07-30 23:21:29 +03:00
|
|
|
finder = this.browser.finder;
|
2018-07-10 20:12:55 +03:00
|
|
|
} catch (e) {
|
2018-07-30 23:21:29 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
finder.removeSelection();
|
|
|
|
finder.highlight(false);
|
|
|
|
|
|
|
|
if (this._finderListener) {
|
|
|
|
finder.removeResultListener(this._finderListener);
|
|
|
|
this._finderListener = null;
|
2018-07-10 20:12:55 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_displayMatches(aData) {
|
|
|
|
debug`displayMatches: data=${aData}`;
|
|
|
|
|
|
|
|
let finder;
|
|
|
|
try {
|
|
|
|
finder = this.browser.finder;
|
|
|
|
} catch (e) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._matchDisplayOptions = aData;
|
|
|
|
finder.onModalHighlightChange(!!aData.dimPage);
|
2018-07-27 00:51:45 +03:00
|
|
|
finder.onHighlightAllChange(!!aData.highlightAll);
|
2018-07-10 20:12:55 +03:00
|
|
|
|
2018-07-27 00:51:45 +03:00
|
|
|
if (!aData.highlightAll && !aData.dimPage) {
|
|
|
|
finder.highlight(false);
|
2018-07-10 20:12:55 +03:00
|
|
|
return;
|
|
|
|
}
|
2018-07-30 23:21:29 +03:00
|
|
|
|
|
|
|
if (!this._finderListener || !finder.searchString) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const linksOnly = this._finderListener.response.linksOnly;
|
2018-07-27 00:51:45 +03:00
|
|
|
finder.highlight(true, finder.searchString, linksOnly, !!aData.drawOutline);
|
2018-07-10 20:12:55 +03:00
|
|
|
}
|
2017-01-26 00:13:58 +03:00
|
|
|
}
|
2019-02-20 19:11:57 +03:00
|
|
|
|
2020-08-17 22:41:18 +03:00
|
|
|
const { debug, warn } = GeckoViewContent.initLogging("GeckoViewContent");
|