diff options
Diffstat (limited to 'browser/components/preferences/in-content/subdialogs.js')
-rw-r--r-- | browser/components/preferences/in-content/subdialogs.js | 434 |
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; - }, -}; |