From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001
From: Daniel Baumann <daniel.baumann@progress-linux.org>
Date: Sun, 7 Apr 2024 21:33:14 +0200
Subject: Adding upstream version 115.7.0esr.

Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
---
 .../components/extensions/ext-browser-content.js   | 275 +++++++++++++++++++++
 1 file changed, 275 insertions(+)
 create mode 100644 toolkit/components/extensions/ext-browser-content.js

(limited to 'toolkit/components/extensions/ext-browser-content.js')

diff --git a/toolkit/components/extensions/ext-browser-content.js b/toolkit/components/extensions/ext-browser-content.js
new file mode 100644
index 0000000000..199ba45661
--- /dev/null
+++ b/toolkit/components/extensions/ext-browser-content.js
@@ -0,0 +1,275 @@
+/* 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/frame-script */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+  ExtensionCommon: "resource://gre/modules/ExtensionCommon.sys.mjs",
+  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
+  setTimeout: "resource://gre/modules/Timer.sys.mjs",
+});
+
+// Minimum time between two resizes.
+const RESIZE_TIMEOUT = 100;
+
+const BrowserListener = {
+  init({
+    allowScriptsToClose,
+    blockParser,
+    fixedWidth,
+    maxHeight,
+    maxWidth,
+    stylesheets,
+    isInline,
+  }) {
+    this.fixedWidth = fixedWidth;
+    this.stylesheets = stylesheets || [];
+
+    this.isInline = isInline;
+    this.maxWidth = maxWidth;
+    this.maxHeight = maxHeight;
+
+    this.blockParser = blockParser;
+    this.needsResize = fixedWidth || maxHeight || maxWidth;
+
+    this.oldBackground = null;
+
+    if (allowScriptsToClose) {
+      content.windowUtils.allowScriptsToClose();
+    }
+
+    if (this.blockParser) {
+      this.blockingPromise = new Promise(resolve => {
+        this.unblockParser = resolve;
+      });
+      addEventListener("DOMDocElementInserted", this, true);
+    }
+
+    addEventListener("load", this, true);
+    addEventListener("DOMWindowCreated", this, true);
+    addEventListener("DOMContentLoaded", this, true);
+    addEventListener("MozScrolledAreaChanged", this, true);
+  },
+
+  destroy() {
+    if (this.blockParser) {
+      removeEventListener("DOMDocElementInserted", this, true);
+    }
+
+    removeEventListener("load", this, true);
+    removeEventListener("DOMWindowCreated", this, true);
+    removeEventListener("DOMContentLoaded", this, true);
+    removeEventListener("MozScrolledAreaChanged", this, true);
+  },
+
+  receiveMessage({ name, data }) {
+    if (name === "Extension:InitBrowser") {
+      this.init(data);
+    } else if (name === "Extension:UnblockParser") {
+      if (this.unblockParser) {
+        this.unblockParser();
+        this.blockingPromise = null;
+      }
+    } else if (name === "Extension:GrabFocus") {
+      content.window.requestAnimationFrame(() => {
+        Services.focus.focusedWindow = content.window;
+      });
+    }
+  },
+
+  loadStylesheets() {
+    let { windowUtils } = content;
+
+    for (let url of this.stylesheets) {
+      windowUtils.addSheet(
+        ExtensionCommon.stylesheetMap.get(url),
+        windowUtils.AGENT_SHEET
+      );
+    }
+  },
+
+  handleEvent(event) {
+    switch (event.type) {
+      case "DOMDocElementInserted":
+        if (this.blockingPromise) {
+          const doc = event.target;
+          const policy = doc?.nodePrincipal?.addonPolicy;
+          event.target.blockParsing(this.blockingPromise).then(() => {
+            policy?.weakExtension?.get()?.untrackBlockedParsingDocument(doc);
+          });
+          policy?.weakExtension?.get()?.trackBlockedParsingDocument(doc);
+        }
+        break;
+
+      case "DOMWindowCreated":
+        if (event.target === content.document) {
+          this.loadStylesheets();
+        }
+        break;
+
+      case "DOMContentLoaded":
+        if (event.target === content.document) {
+          sendAsyncMessage("Extension:BrowserContentLoaded", {
+            url: content.location.href,
+          });
+
+          if (this.needsResize) {
+            this.handleDOMChange(true);
+          }
+        }
+        break;
+
+      case "load":
+        if (event.target.contentWindow === content) {
+          // For about:addons inline <browser>s, we currently receive a load
+          // event on the <browser> element, but no load or DOMContentLoaded
+          // events from the content window.
+
+          // Inline browsers don't receive the "DOMWindowCreated" event, so this
+          // is a workaround to load the stylesheets.
+          if (this.isInline) {
+            this.loadStylesheets();
+          }
+          sendAsyncMessage("Extension:BrowserContentLoaded", {
+            url: content.location.href,
+          });
+        } else if (event.target !== content.document) {
+          break;
+        }
+
+        if (!this.needsResize) {
+          break;
+        }
+
+        // We use a capturing listener, so we get this event earlier than any
+        // load listeners in the content page. Resizing after a timeout ensures
+        // that we calculate the size after the entire event cycle has completed
+        // (unless someone spins the event loop, anyway), and hopefully after
+        // the content has made any modifications.
+        Promise.resolve().then(() => {
+          this.handleDOMChange(true);
+        });
+
+        // Mutation observer to make sure the panel shrinks when the content does.
+        new content.MutationObserver(this.handleDOMChange.bind(this)).observe(
+          content.document.documentElement,
+          {
+            attributes: true,
+            characterData: true,
+            childList: true,
+            subtree: true,
+          }
+        );
+        break;
+
+      case "MozScrolledAreaChanged":
+        if (this.needsResize) {
+          this.handleDOMChange();
+        }
+        break;
+    }
+  },
+
+  // Resizes the browser to match the preferred size of the content (debounced).
+  handleDOMChange(ignoreThrottling = false) {
+    if (ignoreThrottling && this.resizeTimeout) {
+      clearTimeout(this.resizeTimeout);
+      this.resizeTimeout = null;
+    }
+
+    if (this.resizeTimeout == null) {
+      this.resizeTimeout = setTimeout(() => {
+        try {
+          if (content) {
+            this._handleDOMChange("delayed");
+          }
+        } finally {
+          this.resizeTimeout = null;
+        }
+      }, RESIZE_TIMEOUT);
+
+      this._handleDOMChange();
+    }
+  },
+
+  _handleDOMChange(detail) {
+    let doc = content.document;
+
+    let body = doc.body;
+    if (!body || doc.compatMode === "BackCompat") {
+      // In quirks mode, the root element is used as the scroll frame, and the
+      // body lies about its scroll geometry, and returns the values for the
+      // root instead.
+      body = doc.documentElement;
+    }
+
+    let result;
+    const zoom = content.browsingContext.fullZoom;
+    if (this.fixedWidth) {
+      // If we're in a fixed-width area (namely a slide-in subview of the main
+      // menu panel), we need to calculate the view height based on the
+      // preferred height of the content document's root scrollable element at the
+      // current width, rather than the complete preferred dimensions of the
+      // content window.
+
+      // Compensate for any offsets (margin, padding, ...) between the scroll
+      // area of the body and the outer height of the document.
+      // This calculation is hard to get right for all cases, so take the lower
+      // number of the combination of all padding and margins of the document
+      // and body elements, or the difference between their heights.
+      let getHeight = elem => elem.getBoundingClientRect(elem).height;
+      let bodyPadding = getHeight(doc.documentElement) - getHeight(body);
+
+      if (body !== doc.documentElement) {
+        let bs = content.getComputedStyle(body);
+        let ds = content.getComputedStyle(doc.documentElement);
+
+        let p =
+          parseFloat(bs.marginTop) +
+          parseFloat(bs.marginBottom) +
+          parseFloat(ds.marginTop) +
+          parseFloat(ds.marginBottom) +
+          parseFloat(ds.paddingTop) +
+          parseFloat(ds.paddingBottom);
+        bodyPadding = Math.min(p, bodyPadding);
+      }
+
+      let height = Math.ceil((body.scrollHeight + bodyPadding) * zoom);
+
+      result = { height, detail };
+    } else {
+      let background = content.windowUtils.canvasBackgroundColor;
+      if (background !== this.oldBackground) {
+        sendAsyncMessage("Extension:BrowserBackgroundChanged", { background });
+      }
+      this.oldBackground = background;
+
+      // Adjust the size of the browser based on its content's preferred size.
+      let w = {},
+        h = {};
+      docShell.contentViewer.getContentSize(
+        this.maxWidth,
+        this.maxHeight,
+        /* prefWidth = */ 0,
+        w,
+        h
+      );
+
+      let width = Math.ceil(w.value * zoom);
+      let height = Math.ceil(h.value * zoom);
+      result = { width, height, detail };
+    }
+
+    sendAsyncMessage("Extension:BrowserResized", result);
+  },
+};
+
+addMessageListener("Extension:InitBrowser", BrowserListener);
+addMessageListener("Extension:UnblockParser", BrowserListener);
+addMessageListener("Extension:GrabFocus", BrowserListener);
+
+// This is a temporary hack to prevent regressions (bug 1471327).
+void content;
-- 
cgit v1.2.3