1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 import { GeckoViewActorChild } from "resource://gre/modules/GeckoViewActorChild.sys.mjs";
7 // This needs to match ScreenLength.java
8 const SCREEN_LENGTH_TYPE_PIXEL = 0;
9 const SCREEN_LENGTH_TYPE_VISUAL_VIEWPORT_WIDTH = 1;
10 const SCREEN_LENGTH_TYPE_VISUAL_VIEWPORT_HEIGHT = 2;
11 const SCREEN_LENGTH_DOCUMENT_WIDTH = 3;
12 const SCREEN_LENGTH_DOCUMENT_HEIGHT = 4;
14 // This need to match PanZoomController.java
15 const SCROLL_BEHAVIOR_SMOOTH = 0;
16 const SCROLL_BEHAVIOR_AUTO = 1;
18 const SCREEN_ORIENTATION_PORTRAIT = 0;
19 const SCREEN_ORIENTATION_LANDSCAPE = 1;
23 ChromeUtils.defineESModuleGetters(lazy, {
24 PrivacyFilter: "resource://gre/modules/sessionstore/PrivacyFilter.sys.mjs",
25 SessionHistory: "resource://gre/modules/sessionstore/SessionHistory.sys.mjs",
26 Utils: "resource://gre/modules/sessionstore/Utils.sys.mjs",
29 export class GeckoViewContentChild extends GeckoViewActorChild {
32 this.lastOrientation = SCREEN_ORIENTATION_PORTRAIT;
38 this.pageShow = new Promise(resolve => {
39 this.receivedPageShow = resolve;
43 toPixels(aLength, aType) {
44 const { contentWindow } = this;
45 if (aType === SCREEN_LENGTH_TYPE_PIXEL) {
47 } else if (aType === SCREEN_LENGTH_TYPE_VISUAL_VIEWPORT_WIDTH) {
48 return aLength * contentWindow.visualViewport.width;
49 } else if (aType === SCREEN_LENGTH_TYPE_VISUAL_VIEWPORT_HEIGHT) {
50 return aLength * contentWindow.visualViewport.height;
51 } else if (aType === SCREEN_LENGTH_DOCUMENT_WIDTH) {
52 return aLength * contentWindow.document.body.scrollWidth;
53 } else if (aType === SCREEN_LENGTH_DOCUMENT_HEIGHT) {
54 return aLength * contentWindow.document.body.scrollHeight;
60 toScrollBehavior(aBehavior) {
61 const { contentWindow } = this;
65 const { windowUtils } = contentWindow;
66 if (aBehavior === SCROLL_BEHAVIOR_SMOOTH) {
67 return windowUtils.SCROLL_MODE_SMOOTH;
68 } else if (aBehavior === SCROLL_BEHAVIOR_AUTO) {
69 return windowUtils.SCROLL_MODE_INSTANT;
71 return windowUtils.SCROLL_MODE_SMOOTH;
74 collectSessionState() {
75 const { docShell, contentWindow } = this;
76 const history = lazy.SessionHistory.collect(docShell);
77 let formdata = SessionStoreUtils.collectFormData(contentWindow);
78 let scrolldata = SessionStoreUtils.collectScrollPosition(contentWindow);
80 // Save the current document resolution.
82 const domWindowUtils = contentWindow.windowUtils;
83 zoom = domWindowUtils.getResolution();
84 scrolldata = scrolldata || {};
86 scrolldata.zoom.resolution = zoom;
88 // Save some data that'll help in adjusting the zoom level
89 // when restoring in a different screen orientation.
90 const displaySize = {};
93 domWindowUtils.getDocumentViewerSize(width, height);
95 displaySize.width = width.value;
96 displaySize.height = height.value;
98 scrolldata.zoom.displaySize = displaySize;
100 formdata = lazy.PrivacyFilter.filterFormData(formdata || {});
102 return { history, formdata, scrolldata };
106 const currentOrientationType = this.contentWindow?.screen.orientation.type;
107 if (!currentOrientationType) {
108 // Unfortunately, we don't know current screen orientation.
109 // Return portrait as default.
110 return SCREEN_ORIENTATION_PORTRAIT;
112 if (currentOrientationType.startsWith("landscape")) {
113 return SCREEN_ORIENTATION_LANDSCAPE;
115 return SCREEN_ORIENTATION_PORTRAIT;
118 receiveMessage(message) {
119 const { name } = message;
120 debug`receiveMessage: ${name}`;
123 case "GeckoView:DOMFullscreenEntered":
124 this.lastOrientation = this.orientation();
126 !this.contentWindow?.windowUtils.handleFullscreenRequests() &&
127 !this.contentWindow?.document.fullscreenElement
129 // If we don't actually have any pending fullscreen request
130 // to handle, neither we have been in fullscreen, tell the
131 // parent to just exit.
133 this.contentWindow?.windowGlobalChild?.getActor("ContentDelegate");
134 actor?.sendAsyncMessage("GeckoView:DOMFullscreenExit", {});
137 case "GeckoView:DOMFullscreenExited":
138 // During fullscreen, window size is changed. So don't restore viewport size.
139 const restoreViewSize = this.orientation() == this.lastOrientation;
140 this.contentWindow?.windowUtils.exitFullscreen(!restoreViewSize);
142 case "GeckoView:ZoomToInput": {
143 const { contentWindow } = this;
144 const dwu = contentWindow.windowUtils;
146 const zoomToFocusedInput = function () {
147 if (!dwu.flushApzRepaints()) {
148 dwu.zoomToFocusedInput();
151 Services.obs.addObserver(function apzFlushDone() {
152 Services.obs.removeObserver(apzFlushDone, "apz-repaints-flushed");
153 dwu.zoomToFocusedInput();
154 }, "apz-repaints-flushed");
157 zoomToFocusedInput();
160 case "RestoreSessionState": {
161 this.restoreSessionState(message);
164 case "RestoreHistoryAndNavigate": {
165 const { history, switchId } = message.data;
167 lazy.SessionHistory.restore(this.docShell, history);
168 const historyIndex = history.requestedIndex - 1;
169 const webNavigation = this.docShell.QueryInterface(
174 // TODO: Bug 1648158 This won't work for Fission or HistoryInParent.
175 webNavigation.sessionHistory.legacySHistory.reloadCurrentEntry();
177 webNavigation.resumeRedirectedLoad(switchId, historyIndex);
182 case "GeckoView:UpdateInitData": {
183 // Provide a hook for native code to detect a transfer.
184 Services.obs.notifyObservers(
186 "geckoview-content-global-transferred"
190 case "GeckoView:ScrollBy": {
193 const { contentWindow } = this;
194 const { widthValue, widthType, heightValue, heightType, behavior } =
196 contentWindow.windowUtils.getVisualViewportOffset(x, y);
197 contentWindow.windowUtils.scrollToVisual(
198 x.value + this.toPixels(widthValue, widthType),
199 y.value + this.toPixels(heightValue, heightType),
200 contentWindow.windowUtils.UPDATE_TYPE_MAIN_THREAD,
201 this.toScrollBehavior(behavior)
205 case "GeckoView:ScrollTo": {
206 const { contentWindow } = this;
207 const { widthValue, widthType, heightValue, heightType, behavior } =
209 contentWindow.windowUtils.scrollToVisual(
210 this.toPixels(widthValue, widthType),
211 this.toPixels(heightValue, heightType),
212 contentWindow.windowUtils.UPDATE_TYPE_MAIN_THREAD,
213 this.toScrollBehavior(behavior)
217 case "CollectSessionState": {
218 return this.collectSessionState();
220 case "ContainsFormData": {
221 return this.containsFormData();
228 async containsFormData() {
229 const { contentWindow } = this;
230 let formdata = SessionStoreUtils.collectFormData(contentWindow);
231 formdata = lazy.PrivacyFilter.filterFormData(formdata || {});
238 async restoreSessionState(message) {
239 // Make sure we showed something before restoring scrolling and form data
242 const { contentWindow } = this;
243 const { formdata, scrolldata } = message.data;
246 lazy.Utils.restoreFrameTreeData(
250 // restore() will return false, and thus abort restoration for the
251 // current |frame| and its descendants, if |data.url| is given but
252 // doesn't match the loaded document's URL.
253 return SessionStoreUtils.restoreFormData(frame.document, data);
259 lazy.Utils.restoreFrameTreeData(
264 SessionStoreUtils.restoreScrollPosition(frame, data);
270 if (scrolldata && scrolldata.zoom && scrolldata.zoom.displaySize) {
271 const utils = contentWindow.windowUtils;
272 // Restore zoom level.
273 utils.setRestoreResolution(
274 scrolldata.zoom.resolution,
275 scrolldata.zoom.displaySize.width,
276 scrolldata.zoom.displaySize.height
281 // eslint-disable-next-line complexity
282 handleEvent(aEvent) {
283 debug`handleEvent: ${aEvent.type}`;
285 switch (aEvent.type) {
287 this.receivedPageShow();
291 case "mozcaretstatechanged":
293 aEvent.reason === "presscaret" ||
294 aEvent.reason === "releasecaret"
296 this.eventDispatcher.sendRequest({
297 type: "GeckoView:PinOnScreen",
298 pinned: aEvent.reason === "presscaret",
306 const { debug, warn } = GeckoViewContentChild.initLogging("GeckoViewContent");