summaryrefslogtreecommitdiffstats
path: root/browser/actors/ScreenshotsComponentChild.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/actors/ScreenshotsComponentChild.sys.mjs')
-rw-r--r--browser/actors/ScreenshotsComponentChild.sys.mjs273
1 files changed, 273 insertions, 0 deletions
diff --git a/browser/actors/ScreenshotsComponentChild.sys.mjs b/browser/actors/ScreenshotsComponentChild.sys.mjs
new file mode 100644
index 0000000000..285915ce41
--- /dev/null
+++ b/browser/actors/ScreenshotsComponentChild.sys.mjs
@@ -0,0 +1,273 @@
+/* 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/. */
+/* eslint-env mozilla/browser-window */
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
+ ScreenshotsOverlayChild:
+ "resource:///modules/ScreenshotsOverlayChild.sys.mjs",
+});
+
+export class ScreenshotsComponentChild extends JSWindowActorChild {
+ receiveMessage(message) {
+ switch (message.name) {
+ case "Screenshots:ShowOverlay":
+ return this.startScreenshotsOverlay();
+ case "Screenshots:HideOverlay":
+ return this.endScreenshotsOverlay();
+ case "Screenshots:isOverlayShowing":
+ return this._overlay?._initialized;
+ case "Screenshots:getFullPageBounds":
+ return this.getFullPageBounds();
+ case "Screenshots:getVisibleBounds":
+ return this.getVisibleBounds();
+ case "Screenshots:getDocumentTitle":
+ return this.getTitle();
+ }
+ return null;
+ }
+
+ handleEvent(event) {
+ switch (event.type) {
+ case "keydown":
+ if (event.key === "Escape") {
+ this.requestCancelScreenshot("escape");
+ }
+ break;
+ case "beforeunload":
+ this.requestCancelScreenshot("navigation");
+ break;
+ case "resize":
+ if (!this._resizeTask && this._overlay?._initialized) {
+ this._resizeTask = new lazy.DeferredTask(() => {
+ this._overlay.updateScreenshotsSize("resize");
+ }, 16);
+ }
+ this._resizeTask.arm();
+ break;
+ case "scroll":
+ if (!this._scrollTask && this._overlay?._initialized) {
+ this._scrollTask = new lazy.DeferredTask(() => {
+ this._overlay.updateScreenshotsSize("scroll");
+ }, 16);
+ }
+ this._scrollTask.arm();
+ break;
+ case "visibilitychange":
+ if (
+ event.target.visibilityState === "hidden" &&
+ this._overlay?.stateHandler.getState() === "crosshairs"
+ ) {
+ this.requestCancelScreenshot("navigation");
+ }
+ break;
+ }
+ }
+
+ /**
+ * Send a request to cancel the screenshot to the parent process
+ */
+ requestCancelScreenshot(reason) {
+ this.sendAsyncMessage("Screenshots:CancelScreenshot", {
+ closeOverlay: false,
+ reason,
+ });
+ this.endScreenshotsOverlay();
+ }
+
+ requestCopyScreenshot(box) {
+ box.devicePixelRatio = this.contentWindow.devicePixelRatio;
+ this.sendAsyncMessage("Screenshots:CopyScreenshot", box);
+ this.endScreenshotsOverlay();
+ }
+
+ requestDownloadScreenshot(box) {
+ box.devicePixelRatio = this.contentWindow.devicePixelRatio;
+ this.sendAsyncMessage("Screenshots:DownloadScreenshot", {
+ title: this.getTitle(),
+ downloadBox: box,
+ });
+ this.endScreenshotsOverlay();
+ }
+
+ showPanel() {
+ this.sendAsyncMessage("Screenshots:ShowPanel");
+ }
+
+ hidePanel() {
+ this.sendAsyncMessage("Screenshots:HidePanel");
+ }
+
+ getTitle() {
+ return this.document.title;
+ }
+
+ scrollWindow(x, y) {
+ this.contentWindow.scrollBy(x, y);
+ this._overlay.updateScreenshotsSize("scroll");
+ }
+
+ /**
+ * Resolves when the document is ready to have an overlay injected into it.
+ *
+ * @returns {Promise}
+ * @resolves {Boolean} true when document is ready or rejects
+ */
+ documentIsReady() {
+ const document = this.document;
+ // Some pages take ages to finish loading - if at all.
+ // We want to respond to enable the screenshots UI as soon that is possible
+ function readyEnough() {
+ return (
+ document.readyState !== "uninitialized" && document.documentElement
+ );
+ }
+
+ if (readyEnough()) {
+ return Promise.resolve();
+ }
+ return new Promise((resolve, reject) => {
+ function onChange(event) {
+ if (event.type === "pagehide") {
+ document.removeEventListener("readystatechange", onChange);
+ this.contentWindow.removeEventListener("pagehide", onChange);
+ reject(new Error("document unloaded before it was ready"));
+ } else if (readyEnough()) {
+ document.removeEventListener("readystatechange", onChange);
+ this.contentWindow.removeEventListener("pagehide", onChange);
+ resolve();
+ }
+ }
+ document.addEventListener("readystatechange", onChange);
+ this.contentWindow.addEventListener("pagehide", onChange, { once: true });
+ });
+ }
+
+ /**
+ * Wait until the document is ready and then show the screenshots overlay
+ *
+ * @returns {Boolean} true when document is ready and the overlay is shown
+ * otherwise false
+ */
+ async startScreenshotsOverlay() {
+ try {
+ await this.documentIsReady();
+ } catch (ex) {
+ console.warn(`ScreenshotsComponentChild: ${ex.message}`);
+ return false;
+ }
+ await this.documentIsReady();
+ let overlay =
+ this._overlay ||
+ (this._overlay = new lazy.ScreenshotsOverlayChild.AnonymousContentOverlay(
+ this.document,
+ this
+ ));
+ this.document.addEventListener("keydown", this);
+ this.document.ownerGlobal.addEventListener("beforeunload", this);
+ this.contentWindow.addEventListener("resize", this);
+ this.contentWindow.addEventListener("scroll", this);
+ this.contentWindow.addEventListener("visibilitychange", this);
+ overlay.initialize();
+ return true;
+ }
+
+ /**
+ * Remove the screenshots overlay.
+ *
+ * @returns {Boolean}
+ * true when the overlay has been removed otherwise false
+ */
+ endScreenshotsOverlay() {
+ this.document.removeEventListener("keydown", this);
+ this.document.ownerGlobal.removeEventListener("beforeunload", this);
+ this.contentWindow.removeEventListener("resize", this);
+ this.contentWindow.removeEventListener("scroll", this);
+ this.contentWindow.removeEventListener("visibilitychange", this);
+ this._overlay?.tearDown();
+ this._resizeTask?.disarm();
+ this._scrollTask?.disarm();
+ return true;
+ }
+
+ didDestroy() {
+ this._resizeTask?.disarm();
+ this._scrollTask?.disarm();
+ }
+
+ /**
+ * Gets the full page bounds for a full page screenshot.
+ *
+ * @returns { object }
+ * The device pixel ratio and a DOMRect of the scrollable content bounds.
+ *
+ * devicePixelRatio (float):
+ * The device pixel ratio of the screen
+ *
+ * rect (object):
+ * top (int):
+ * The scroll top position for the content window.
+ *
+ * left (int):
+ * The scroll left position for the content window.
+ *
+ * width (int):
+ * The scroll width of the content window.
+ *
+ * height (int):
+ * The scroll height of the content window.
+ */
+ getFullPageBounds() {
+ let doc = this.document.documentElement;
+ let rect = {
+ x1: doc.clientLeft,
+ y1: doc.clientTop,
+ width: doc.scrollWidth,
+ height: doc.scrollHeight,
+ devicePixelRatio: this.contentWindow.devicePixelRatio,
+ };
+ return rect;
+ }
+
+ /**
+ * Gets the visible page bounds for a visible screenshot.
+ *
+ * @returns { object }
+ * The device pixel ratio and a DOMRect of the current visible
+ * content bounds.
+ *
+ * devicePixelRatio (float):
+ * The device pixel ratio of the screen
+ *
+ * rect (object):
+ * top (int):
+ * The top position for the content window.
+ *
+ * left (int):
+ * The left position for the content window.
+ *
+ * width (int):
+ * The width of the content window.
+ *
+ * height (int):
+ * The height of the content window.
+ */
+ getVisibleBounds() {
+ let doc = this.document.documentElement;
+ let rect = {
+ x1: doc.scrollLeft,
+ y1: doc.scrollTop,
+ width: doc.clientWidth,
+ height: doc.clientHeight,
+ devicePixelRatio: this.contentWindow.devicePixelRatio,
+ };
+ return rect;
+ }
+
+ recordTelemetryEvent(type, object, args) {
+ Services.telemetry.recordEvent("screenshots", type, object, null, args);
+ }
+}