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 const { GeckoViewActorChild } = ChromeUtils.import(
6 "resource://gre/modules/GeckoViewActorChild.jsm"
9 var { XPCOMUtils } = ChromeUtils.import(
10 "resource://gre/modules/XPCOMUtils.jsm"
13 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
15 // This needs to match ScreenLength.java
16 const SCREEN_LENGTH_TYPE_PIXEL = 0;
17 const SCREEN_LENGTH_TYPE_VISUAL_VIEWPORT_WIDTH = 1;
18 const SCREEN_LENGTH_TYPE_VISUAL_VIEWPORT_HEIGHT = 2;
19 const SCREEN_LENGTH_DOCUMENT_WIDTH = 3;
20 const SCREEN_LENGTH_DOCUMENT_HEIGHT = 4;
22 // This need to match PanZoomController.java
23 const SCROLL_BEHAVIOR_SMOOTH = 0;
24 const SCROLL_BEHAVIOR_AUTO = 1;
26 const SCREEN_ORIENTATION_PORTRAIT = 0;
27 const SCREEN_ORIENTATION_LANDSCAPE = 1;
29 XPCOMUtils.defineLazyModuleGetters(this, {
30 E10SUtils: "resource://gre/modules/E10SUtils.jsm",
31 PrivacyFilter: "resource://gre/modules/sessionstore/PrivacyFilter.jsm",
32 SessionHistory: "resource://gre/modules/sessionstore/SessionHistory.jsm",
33 Utils: "resource://gre/modules/sessionstore/Utils.jsm",
36 var EXPORTED_SYMBOLS = ["GeckoViewContentChild"];
38 class GeckoViewContentChild extends GeckoViewActorChild {
41 this.lastOrientation = SCREEN_ORIENTATION_PORTRAIT;
47 this.pageShow = new Promise(resolve => {
48 this.receivedPageShow = resolve;
52 toPixels(aLength, aType) {
53 const { contentWindow } = this;
54 if (aType === SCREEN_LENGTH_TYPE_PIXEL) {
56 } else if (aType === SCREEN_LENGTH_TYPE_VISUAL_VIEWPORT_WIDTH) {
57 return aLength * contentWindow.visualViewport.width;
58 } else if (aType === SCREEN_LENGTH_TYPE_VISUAL_VIEWPORT_HEIGHT) {
59 return aLength * contentWindow.visualViewport.height;
60 } else if (aType === SCREEN_LENGTH_DOCUMENT_WIDTH) {
61 return aLength * contentWindow.document.body.scrollWidth;
62 } else if (aType === SCREEN_LENGTH_DOCUMENT_HEIGHT) {
63 return aLength * contentWindow.document.body.scrollHeight;
69 toScrollBehavior(aBehavior) {
70 const { contentWindow } = this;
74 const { windowUtils } = contentWindow;
75 if (aBehavior === SCROLL_BEHAVIOR_SMOOTH) {
76 return windowUtils.SCROLL_MODE_SMOOTH;
77 } else if (aBehavior === SCROLL_BEHAVIOR_AUTO) {
78 return windowUtils.SCROLL_MODE_INSTANT;
80 return windowUtils.SCROLL_MODE_SMOOTH;
83 collectSessionState() {
84 const { docShell, contentWindow } = this;
85 const history = SessionHistory.collect(docShell);
86 let formdata = SessionStoreUtils.collectFormData(contentWindow);
87 let scrolldata = SessionStoreUtils.collectScrollPosition(contentWindow);
89 // Save the current document resolution.
91 const domWindowUtils = contentWindow.windowUtils;
92 zoom = domWindowUtils.getResolution();
93 scrolldata = scrolldata || {};
95 scrolldata.zoom.resolution = zoom;
97 // Save some data that'll help in adjusting the zoom level
98 // when restoring in a different screen orientation.
99 const displaySize = {};
102 domWindowUtils.getContentViewerSize(width, height);
104 displaySize.width = width.value;
105 displaySize.height = height.value;
107 scrolldata.zoom.displaySize = displaySize;
109 formdata = PrivacyFilter.filterFormData(formdata || {});
111 return { history, formdata, scrolldata };
115 const currentOrientationType = this.contentWindow?.screen.orientation.type;
116 if (!currentOrientationType) {
117 // Unfortunately, we don't know current screen orientation.
118 // Return portrait as default.
119 return SCREEN_ORIENTATION_PORTRAIT;
121 if (currentOrientationType.startsWith("landscape")) {
122 return SCREEN_ORIENTATION_LANDSCAPE;
124 return SCREEN_ORIENTATION_PORTRAIT;
127 receiveMessage(message) {
128 const { name } = message;
129 debug`receiveMessage: ${name}`;
132 case "GeckoView:DOMFullscreenEntered":
133 this.lastOrientation = this.orientation();
134 this.contentWindow?.windowUtils.handleFullscreenRequests();
136 case "GeckoView:DOMFullscreenExited":
137 // During fullscreen, window size is changed. So don't restore viewport size.
138 const restoreViewSize = this.orientation() == this.lastOrientation;
139 this.contentWindow?.windowUtils.exitFullscreen(!restoreViewSize);
141 case "GeckoView:ZoomToInput": {
142 const { contentWindow } = this;
143 const dwu = contentWindow.windowUtils;
145 const zoomToFocusedInput = function() {
146 if (!dwu.flushApzRepaints()) {
147 dwu.zoomToFocusedInput();
150 Services.obs.addObserver(function apzFlushDone() {
151 Services.obs.removeObserver(apzFlushDone, "apz-repaints-flushed");
152 dwu.zoomToFocusedInput();
153 }, "apz-repaints-flushed");
156 const { force } = message.data;
158 let gotResize = false;
159 const onResize = function() {
161 if (dwu.isMozAfterPaintPending) {
162 contentWindow.windowRoot.addEventListener(
164 () => zoomToFocusedInput(),
165 { capture: true, once: true }
168 zoomToFocusedInput();
172 contentWindow.addEventListener("resize", onResize, { capture: true });
174 // When the keyboard is displayed, we can get one resize event,
175 // multiple resize events, or none at all. Try to handle all these
176 // cases by allowing resizing within a set interval, and still zoom to
177 // input if there is no resize event at the end of the interval.
178 contentWindow.setTimeout(() => {
179 contentWindow.removeEventListener("resize", onResize, {
182 if (!gotResize && force) {
188 case "RestoreSessionState": {
189 this.restoreSessionState(message);
192 case "RestoreHistoryAndNavigate": {
193 const { history, switchId } = message.data;
195 SessionHistory.restore(this.docShell, history);
196 const historyIndex = history.requestedIndex - 1;
197 const webNavigation = this.docShell.QueryInterface(
202 // TODO: Bug 1648158 This won't work for Fission or HistoryInParent.
203 webNavigation.sessionHistory.legacySHistory.reloadCurrentEntry();
205 webNavigation.resumeRedirectedLoad(switchId, historyIndex);
210 case "GeckoView:UpdateInitData": {
211 // Provide a hook for native code to detect a transfer.
212 Services.obs.notifyObservers(
214 "geckoview-content-global-transferred"
218 case "GeckoView:ScrollBy": {
221 const { contentWindow } = this;
229 contentWindow.windowUtils.getVisualViewportOffset(x, y);
230 contentWindow.windowUtils.scrollToVisual(
231 x.value + this.toPixels(widthValue, widthType),
232 y.value + this.toPixels(heightValue, heightType),
233 contentWindow.windowUtils.UPDATE_TYPE_MAIN_THREAD,
234 this.toScrollBehavior(behavior)
238 case "GeckoView:ScrollTo": {
239 const { contentWindow } = this;
247 contentWindow.windowUtils.scrollToVisual(
248 this.toPixels(widthValue, widthType),
249 this.toPixels(heightValue, heightType),
250 contentWindow.windowUtils.UPDATE_TYPE_MAIN_THREAD,
251 this.toScrollBehavior(behavior)
255 case "CollectSessionState": {
256 return this.collectSessionState();
263 async restoreSessionState(message) {
264 // Make sure we showed something before restoring scrolling and form data
267 const { contentWindow } = this;
268 const { formdata, scrolldata } = message.data;
271 Utils.restoreFrameTreeData(contentWindow, formdata, (frame, data) => {
272 // restore() will return false, and thus abort restoration for the
273 // current |frame| and its descendants, if |data.url| is given but
274 // doesn't match the loaded document's URL.
275 return SessionStoreUtils.restoreFormData(frame.document, data);
280 Utils.restoreFrameTreeData(contentWindow, scrolldata, (frame, data) => {
282 SessionStoreUtils.restoreScrollPosition(frame, data);
287 if (scrolldata && scrolldata.zoom && scrolldata.zoom.displaySize) {
288 const utils = contentWindow.windowUtils;
289 // Restore zoom level.
290 utils.setRestoreResolution(
291 scrolldata.zoom.resolution,
292 scrolldata.zoom.displaySize.width,
293 scrolldata.zoom.displaySize.height
298 // eslint-disable-next-line complexity
299 handleEvent(aEvent) {
300 debug`handleEvent: ${aEvent.type}`;
302 switch (aEvent.type) {
304 this.receivedPageShow();
308 case "mozcaretstatechanged":
310 aEvent.reason === "presscaret" ||
311 aEvent.reason === "releasecaret"
313 this.eventDispatcher.sendRequest({
314 type: "GeckoView:PinOnScreen",
315 pinned: aEvent.reason === "presscaret",
323 const { debug, warn } = GeckoViewContentChild.initLogging("GeckoViewContent");