summaryrefslogtreecommitdiffstats
path: root/browser/components/preferences/in-content/subdialogs.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/preferences/in-content/subdialogs.js')
-rw-r--r--browser/components/preferences/in-content/subdialogs.js434
1 files changed, 0 insertions, 434 deletions
diff --git a/browser/components/preferences/in-content/subdialogs.js b/browser/components/preferences/in-content/subdialogs.js
deleted file mode 100644
index bb8d0048f..000000000
--- a/browser/components/preferences/in-content/subdialogs.js
+++ /dev/null
@@ -1,434 +0,0 @@
-/* - 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 gSubDialog = {
- _closingCallback: null,
- _closingEvent: null,
- _isClosing: false,
- _frame: null,
- _overlay: null,
- _box: null,
- _injectedStyleSheets: [
- "chrome://browser/skin/preferences/preferences.css",
- "chrome://global/skin/in-content/common.css",
- "chrome://browser/skin/preferences/in-content/preferences.css",
- "chrome://browser/skin/preferences/in-content/dialog.css",
- ],
- _resizeObserver: null,
-
- init: function() {
- this._frame = document.getElementById("dialogFrame");
- this._overlay = document.getElementById("dialogOverlay");
- this._box = document.getElementById("dialogBox");
- this._closeButton = document.getElementById("dialogClose");
- },
-
- updateTitle: function(aEvent) {
- if (aEvent.target != gSubDialog._frame.contentDocument)
- return;
- document.getElementById("dialogTitle").textContent = gSubDialog._frame.contentDocument.title;
- },
-
- injectXMLStylesheet: function(aStylesheetURL) {
- let contentStylesheet = this._frame.contentDocument.createProcessingInstruction(
- 'xml-stylesheet',
- 'href="' + aStylesheetURL + '" type="text/css"'
- );
- this._frame.contentDocument.insertBefore(contentStylesheet,
- this._frame.contentDocument.documentElement);
- },
-
- open: function(aURL, aFeatures = null, aParams = null, aClosingCallback = null) {
- // If we're already open/opening on this URL, do nothing.
- if (this._openedURL == aURL && !this._isClosing) {
- return;
- }
- // If we're open on some (other) URL or we're closing, open when closing has finished.
- if (this._openedURL || this._isClosing) {
- if (!this._isClosing) {
- this.close();
- }
- let args = Array.from(arguments);
- this._closingPromise.then(() => {
- this.open.apply(this, args);
- });
- return;
- }
- this._addDialogEventListeners();
-
- let features = (aFeatures ? aFeatures + "," : "") + "resizable,dialog=no,centerscreen";
- let dialog = window.openDialog(aURL, "dialogFrame", features, aParams);
- if (aClosingCallback) {
- this._closingCallback = aClosingCallback.bind(dialog);
- }
-
- this._closingEvent = null;
- this._isClosing = false;
- this._openedURL = aURL;
-
- features = features.replace(/,/g, "&");
- let featureParams = new URLSearchParams(features.toLowerCase());
- this._box.setAttribute("resizable", featureParams.has("resizable") &&
- featureParams.get("resizable") != "no" &&
- featureParams.get("resizable") != "0");
- },
-
- close: function(aEvent = null) {
- if (this._isClosing) {
- return;
- }
- this._isClosing = true;
- this._closingPromise = new Promise(resolve => {
- this._resolveClosePromise = resolve;
- });
-
- if (this._closingCallback) {
- try {
- this._closingCallback.call(null, aEvent);
- } catch (ex) {
- Cu.reportError(ex);
- }
- this._closingCallback = null;
- }
-
- this._removeDialogEventListeners();
-
- this._overlay.style.visibility = "";
- // Clear the sizing inline styles.
- this._frame.removeAttribute("style");
- // Clear the sizing attributes
- this._box.removeAttribute("width");
- this._box.removeAttribute("height");
- this._box.style.removeProperty("min-height");
- this._box.style.removeProperty("min-width");
-
- setTimeout(() => {
- // Unload the dialog after the event listeners run so that the load of about:blank isn't
- // cancelled by the ESC <key>.
- let onBlankLoad = e => {
- if (this._frame.contentWindow.location.href == "about:blank") {
- this._frame.removeEventListener("load", onBlankLoad);
- // We're now officially done closing, so update the state to reflect that.
- delete this._openedURL;
- this._isClosing = false;
- this._resolveClosePromise();
- }
- };
- this._frame.addEventListener("load", onBlankLoad);
- this._frame.loadURI("about:blank");
- }, 0);
- },
-
- handleEvent: function(aEvent) {
- switch (aEvent.type) {
- case "command":
- this._frame.contentWindow.close();
- break;
- case "dialogclosing":
- this._onDialogClosing(aEvent);
- break;
- case "DOMTitleChanged":
- this.updateTitle(aEvent);
- break;
- case "DOMFrameContentLoaded":
- this._onContentLoaded(aEvent);
- break;
- case "load":
- this._onLoad(aEvent);
- break;
- case "unload":
- this._onUnload(aEvent);
- break;
- case "keydown":
- this._onKeyDown(aEvent);
- break;
- case "focus":
- this._onParentWinFocus(aEvent);
- break;
- }
- },
-
- /* Private methods */
-
- _onUnload: function(aEvent) {
- if (aEvent.target.location.href == this._openedURL) {
- this._frame.contentWindow.close();
- }
- },
-
- _onContentLoaded: function(aEvent) {
- if (aEvent.target != this._frame || aEvent.target.contentWindow.location == "about:blank") {
- return;
- }
-
- for (let styleSheetURL of this._injectedStyleSheets) {
- this.injectXMLStylesheet(styleSheetURL);
- }
-
- // Provide the ability for the dialog to know that it is being loaded "in-content".
- this._frame.contentDocument.documentElement.setAttribute("subdialog", "true");
-
- this._frame.contentWindow.addEventListener("dialogclosing", this);
-
- let oldResizeBy = this._frame.contentWindow.resizeBy;
- this._frame.contentWindow.resizeBy = function(resizeByWidth, resizeByHeight) {
- // Only handle resizeByHeight currently.
- let frameHeight = gSubDialog._frame.clientHeight;
- let boxMinHeight = parseFloat(getComputedStyle(gSubDialog._box).minHeight, 10);
-
- gSubDialog._frame.style.height = (frameHeight + resizeByHeight) + "px";
- gSubDialog._box.style.minHeight = (boxMinHeight + resizeByHeight) + "px";
-
- oldResizeBy.call(gSubDialog._frame.contentWindow, resizeByWidth, resizeByHeight);
- };
-
- // Make window.close calls work like dialog closing.
- let oldClose = this._frame.contentWindow.close;
- this._frame.contentWindow.close = function() {
- var closingEvent = gSubDialog._closingEvent;
- if (!closingEvent) {
- closingEvent = new CustomEvent("dialogclosing", {
- bubbles: true,
- detail: { button: null },
- });
-
- gSubDialog._frame.contentWindow.dispatchEvent(closingEvent);
- }
-
- gSubDialog.close(closingEvent);
- oldClose.call(gSubDialog._frame.contentWindow);
- };
-
- // XXX: Hack to make focus during the dialog's load functions work. Make the element visible
- // sooner in DOMContentLoaded but mostly invisible instead of changing visibility just before
- // the dialog's load event.
- this._overlay.style.visibility = "visible";
- this._overlay.style.opacity = "0.01";
- },
-
- _onLoad: function(aEvent) {
- if (aEvent.target.contentWindow.location == "about:blank") {
- return;
- }
-
- // Do this on load to wait for the CSS to load and apply before calculating the size.
- let docEl = this._frame.contentDocument.documentElement;
-
- let groupBoxTitle = document.getAnonymousElementByAttribute(this._box, "class", "groupbox-title");
- let groupBoxTitleHeight = groupBoxTitle.clientHeight +
- parseFloat(getComputedStyle(groupBoxTitle).borderBottomWidth);
-
- let groupBoxBody = document.getAnonymousElementByAttribute(this._box, "class", "groupbox-body");
- // These are deduced from styles which we don't change, so it's safe to get them now:
- let boxVerticalPadding = 2 * parseFloat(getComputedStyle(groupBoxBody).paddingTop);
- let boxHorizontalPadding = 2 * parseFloat(getComputedStyle(groupBoxBody).paddingLeft);
- let boxHorizontalBorder = 2 * parseFloat(getComputedStyle(this._box).borderLeftWidth);
- let boxVerticalBorder = 2 * parseFloat(getComputedStyle(this._box).borderTopWidth);
-
- // The difference between the frame and box shouldn't change, either:
- let boxRect = this._box.getBoundingClientRect();
- let frameRect = this._frame.getBoundingClientRect();
- let frameSizeDifference = (frameRect.top - boxRect.top) + (boxRect.bottom - frameRect.bottom);
-
- // Then determine and set a bunch of width stuff:
- let frameMinWidth = docEl.style.width || docEl.scrollWidth + "px";
- let frameWidth = docEl.getAttribute("width") ? docEl.getAttribute("width") + "px" :
- frameMinWidth;
- this._frame.style.width = frameWidth;
- this._box.style.minWidth = "calc(" +
- (boxHorizontalBorder + boxHorizontalPadding) +
- "px + " + frameMinWidth + ")";
-
- // Now do the same but for the height. We need to do this afterwards because otherwise
- // XUL assumes we'll optimize for height and gives us "wrong" values which then are no
- // longer correct after we set the width:
- let frameMinHeight = docEl.style.height || docEl.scrollHeight + "px";
- let frameHeight = docEl.getAttribute("height") ? docEl.getAttribute("height") + "px" :
- frameMinHeight;
-
- // Now check if the frame height we calculated is possible at this window size,
- // accounting for titlebar, padding/border and some spacing.
- let maxHeight = window.innerHeight - frameSizeDifference - 30;
- // Do this with a frame height in pixels...
- let comparisonFrameHeight;
- if (frameHeight.endsWith("em")) {
- let fontSize = parseFloat(getComputedStyle(this._frame).fontSize);
- comparisonFrameHeight = parseFloat(frameHeight, 10) * fontSize;
- } else if (frameHeight.endsWith("px")) {
- comparisonFrameHeight = parseFloat(frameHeight, 10);
- } else {
- Cu.reportError("This dialog (" + this._frame.contentWindow.location.href + ") " +
- "set a height in non-px-non-em units ('" + frameHeight + "'), " +
- "which is likely to lead to bad sizing in in-content preferences. " +
- "Please consider changing this.");
- comparisonFrameHeight = parseFloat(frameHeight);
- }
-
- if (comparisonFrameHeight > maxHeight) {
- // If the height is bigger than that of the window, we should let the contents scroll:
- frameHeight = maxHeight + "px";
- frameMinHeight = maxHeight + "px";
- let containers = this._frame.contentDocument.querySelectorAll('.largeDialogContainer');
- for (let container of containers) {
- container.classList.add("doScroll");
- }
- }
-
- this._frame.style.height = frameHeight;
- this._box.style.minHeight = "calc(" +
- (boxVerticalBorder + groupBoxTitleHeight + boxVerticalPadding) +
- "px + " + frameMinHeight + ")";
-
- this._overlay.style.visibility = "visible";
- this._overlay.style.opacity = ""; // XXX: focus hack continued from _onContentLoaded
-
- if (this._box.getAttribute("resizable") == "true") {
- this._resizeObserver = new MutationObserver(this._onResize);
- this._resizeObserver.observe(this._box, {attributes: true});
- }
-
- this._trapFocus();
- },
-
- _onResize: function(mutations) {
- let frame = gSubDialog._frame;
- // The width and height styles are needed for the initial
- // layout of the frame, but afterward they need to be removed
- // or their presence will restrict the contents of the <browser>
- // from resizing to a smaller size.
- frame.style.removeProperty("width");
- frame.style.removeProperty("height");
-
- let docEl = frame.contentDocument.documentElement;
- let persistedAttributes = docEl.getAttribute("persist");
- if (!persistedAttributes ||
- (!persistedAttributes.includes("width") &&
- !persistedAttributes.includes("height"))) {
- return;
- }
-
- for (let mutation of mutations) {
- if (mutation.attributeName == "width") {
- docEl.setAttribute("width", docEl.scrollWidth);
- } else if (mutation.attributeName == "height") {
- docEl.setAttribute("height", docEl.scrollHeight);
- }
- }
- },
-
- _onDialogClosing: function(aEvent) {
- this._frame.contentWindow.removeEventListener("dialogclosing", this);
- this._closingEvent = aEvent;
- },
-
- _onKeyDown: function(aEvent) {
- if (aEvent.currentTarget == window && aEvent.keyCode == aEvent.DOM_VK_ESCAPE &&
- !aEvent.defaultPrevented) {
- this.close(aEvent);
- return;
- }
- if (aEvent.keyCode != aEvent.DOM_VK_TAB ||
- aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey) {
- return;
- }
-
- let fm = Services.focus;
-
- function isLastFocusableElement(el) {
- // XXXgijs unfortunately there is no way to get the last focusable element without asking
- // the focus manager to move focus to it.
- let rv = el == fm.moveFocus(gSubDialog._frame.contentWindow, null, fm.MOVEFOCUS_LAST, 0);
- fm.setFocus(el, 0);
- return rv;
- }
-
- let forward = !aEvent.shiftKey;
- // check if focus is leaving the frame (incl. the close button):
- if ((aEvent.target == this._closeButton && !forward) ||
- (isLastFocusableElement(aEvent.originalTarget) && forward)) {
- aEvent.preventDefault();
- aEvent.stopImmediatePropagation();
- let parentWin = this._getBrowser().ownerGlobal;
- if (forward) {
- fm.moveFocus(parentWin, null, fm.MOVEFOCUS_FIRST, fm.FLAG_BYKEY);
- } else {
- // Somehow, moving back 'past' the opening doc is not trivial. Cheat by doing it in 2 steps:
- fm.moveFocus(window, null, fm.MOVEFOCUS_ROOT, fm.FLAG_BYKEY);
- fm.moveFocus(parentWin, null, fm.MOVEFOCUS_BACKWARD, fm.FLAG_BYKEY);
- }
- }
- },
-
- _onParentWinFocus: function(aEvent) {
- // Explicitly check for the focus target of |window| to avoid triggering this when the window
- // is refocused
- if (aEvent.target != this._closeButton && aEvent.target != window) {
- this._closeButton.focus();
- }
- },
-
- _addDialogEventListeners: function() {
- // Make the close button work.
- this._closeButton.addEventListener("command", this);
-
- // DOMTitleChanged isn't fired on the frame, only on the chromeEventHandler
- let chromeBrowser = this._getBrowser();
- chromeBrowser.addEventListener("DOMTitleChanged", this, true);
-
- // Similarly DOMFrameContentLoaded only fires on the top window
- window.addEventListener("DOMFrameContentLoaded", this, true);
-
- // Wait for the stylesheets injected during DOMContentLoaded to load before showing the dialog
- // otherwise there is a flicker of the stylesheet applying.
- this._frame.addEventListener("load", this);
-
- chromeBrowser.addEventListener("unload", this, true);
- // Ensure we get <esc> keypresses even if nothing in the subdialog is focusable
- // (happens on OS X when only text inputs and lists are focusable, and
- // the subdialog only has checkboxes/radiobuttons/buttons)
- window.addEventListener("keydown", this, true);
- },
-
- _removeDialogEventListeners: function() {
- let chromeBrowser = this._getBrowser();
- chromeBrowser.removeEventListener("DOMTitleChanged", this, true);
- chromeBrowser.removeEventListener("unload", this, true);
-
- this._closeButton.removeEventListener("command", this);
-
- window.removeEventListener("DOMFrameContentLoaded", this, true);
- this._frame.removeEventListener("load", this);
- this._frame.contentWindow.removeEventListener("dialogclosing", this);
- window.removeEventListener("keydown", this, true);
- if (this._resizeObserver) {
- this._resizeObserver.disconnect();
- this._resizeObserver = null;
- }
- this._untrapFocus();
- },
-
- _trapFocus: function() {
- let fm = Services.focus;
- fm.moveFocus(this._frame.contentWindow, null, fm.MOVEFOCUS_FIRST, 0);
- this._frame.contentDocument.addEventListener("keydown", this, true);
- this._closeButton.addEventListener("keydown", this);
-
- window.addEventListener("focus", this, true);
- },
-
- _untrapFocus: function() {
- this._frame.contentDocument.removeEventListener("keydown", this, true);
- this._closeButton.removeEventListener("keydown", this);
- window.removeEventListener("focus", this);
- },
-
- _getBrowser: function() {
- return window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShell)
- .chromeEventHandler;
- },
-};