diff --git a/.eslintignore b/.eslintignore index 5a047f0bd273..f2808fd85fda 100644 --- a/.eslintignore +++ b/.eslintignore @@ -335,9 +335,6 @@ toolkit/components/reader/JSDOMParser.js # Uses preprocessing toolkit/components/reader/Readerable.jsm -# Should be going away soon -toolkit/content/widgets/wizard.xml - # Uses preprocessing toolkit/mozapps/update/tests/data/xpcshellConstantsPP.js toolkit/modules/AppConstants.jsm diff --git a/testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/update_wizard/wizard.py b/testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/update_wizard/wizard.py index cc1215078a0b..afacc8e95dec 100644 --- a/testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/update_wizard/wizard.py +++ b/testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/update_wizard/wizard.py @@ -50,7 +50,7 @@ class Wizard(UIBaseLib): @property def _buttons(self): - return self.element.find_element(By.ANON_ATTRIBUTE, {'anonid': 'Buttons'}) + return self.element.get_property('_wizardButtons') @property def cancel_button(self): diff --git a/toolkit/content/jar.mn b/toolkit/content/jar.mn index 7837239c4264..565572a85102 100644 --- a/toolkit/content/jar.mn +++ b/toolkit/content/jar.mn @@ -75,7 +75,6 @@ toolkit.jar: * content/global/bindings/textbox.xml (widgets/textbox.xml) content/global/bindings/timekeeper.js (widgets/timekeeper.js) content/global/bindings/timepicker.js (widgets/timepicker.js) - content/global/bindings/wizard.xml (widgets/wizard.xml) content/global/elements/autocomplete-popup.js (widgets/autocomplete-popup.js) content/global/elements/autocomplete-richlistitem.js (widgets/autocomplete-richlistitem.js) content/global/elements/browser-custom-element.js (widgets/browser-custom-element.js) diff --git a/toolkit/content/widgets/wizard.js b/toolkit/content/widgets/wizard.js index bec7c7dfaa71..b8b1e3df63c4 100644 --- a/toolkit/content/widgets/wizard.js +++ b/toolkit/content/widgets/wizard.js @@ -8,14 +8,385 @@ // a block to prevent accidentally leaking globals onto `window`. { const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm"); +const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const kDTDs = [ "chrome://global/locale/wizard.dtd" ]; +class MozWizard extends MozXULElement { + constructor() { + super(); + + this._accessMethod = null; + this._currentPage = null; + this._canAdvance = true; + this._canRewind = false; + this._hasLoaded = false; + this._pageStack = []; + + this._bundle = + Services.strings.createBundle("chrome://global/locale/wizard.properties"); + + this.addEventListener("keypress", (event) => { + if (event.keyCode == KeyEvent.DOM_VK_RETURN) { + this._hitEnter(event); + } else if (event.keyCode == KeyEvent.DOM_VK_ESCAPE && !event.defaultPrevented) { + this.cancel(); + } + }, { mozSystemGroup: true }); + + this.attachShadow({ mode: "open" }).appendChild( + MozXULElement.parseXULToFragment(` + + + + + + + + `)); + this.initializeAttributeInheritance(); + + this._deck = this.shadowRoot.querySelector(".wizard-page-box"); + this._wizardButtons = this.shadowRoot.querySelector(".wizard-buttons"); + + this._wizardHeader = this.shadowRoot.querySelector(".wizard-header"); + this._wizardHeader.appendChild( + MozXULElement.parseXULToFragment(AppConstants.platform == "macosx" ? + ` + + + + + + + + + ` : + ` + + + + ` + ) + ); + } + + static get inheritedAttributes() { + return { + ".wizard-buttons": "pagestep,firstpage,lastpage", + }; + } + + connectedCallback() { + if (this.delayConnectedCallback()) { + return; + } + + this.pageCount = this.wizardPages.length; + + this._initPages(); + this.advance(); // start off on the first page + + window.addEventListener("close", (event) => { + if (document.documentElement.cancel()) { + event.preventDefault(); + } + }); + + // Give focus to the first focusable element in the wizard, do it after + // onload completes, see bug 103197. + window.addEventListener("load", () => window.setTimeout(() => { + document.documentElement._hasLoaded = true; + if (!document.commandDispatcher.focusedElement) { + document.commandDispatcher.advanceFocusIntoSubtree(this); + } + try { + let button = document.documentElement._wizardButtons.defaultButton; + if (button) { + window.notifyDefaultButtonLoaded(button); + } + } catch (e) {} + }, 0)); + } + + set title(val) { + return document.title = val; + } + + get title() { + return document.title; + } + + set canAdvance(val) { + this.getButton("next").disabled = !val; + return this._canAdvance = val; + } + + get canAdvance() { + return this._canAdvance; + } + + set canRewind(val) { + this.getButton("back").disabled = !val; + return this._canRewind = val; + } + + get canRewind() { + return this._canRewind; + } + + get pageStep() { + return this._pageStack.length; + } + + get wizardPages() { + const xulns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + return this.getElementsByTagNameNS(xulns, "wizardpage"); + } + + set currentPage(val) { + if (!val) + return val; + + this._currentPage = val; + + // Setting this attribute allows wizard's clients to dynamically + // change the styles of each page based on purpose of the page. + this.setAttribute("currentpageid", val.pageid); + if (this.onFirstPage) { + this.canRewind = false; + this.setAttribute("firstpage", "true"); + if (AppConstants.platform == "linux") { + this.getButton("back").setAttribute("hidden", "true"); + } + } else { + this.canRewind = true; + this.setAttribute("firstpage", "false"); + if (AppConstants.platform == "linux") { + this.getButton("back").setAttribute("hidden", "false"); + } + } + + if (this.onLastPage) { + this.canAdvance = true; + this.setAttribute("lastpage", "true"); + } else { + this.setAttribute("lastpage", "false"); + } + + this._deck.setAttribute("selectedIndex", val.pageIndex); + this._advanceFocusToPage(val); + + this._adjustWizardHeader(); + this._wizardButtons.onPageChange(); + + this._fireEvent(val, "pageshow"); + + return val; + } + + get currentPage() { + return this._currentPage; + } + + set pageIndex(val) { + if (val < 0 || val >= this.pageCount) + return val; + + var page = this.wizardPages[val]; + this._pageStack[this._pageStack.length - 1] = page; + this.currentPage = page; + + return val; + } + + get pageIndex() { + return this._currentPage ? this._currentPage.pageIndex : -1; + } + + get onFirstPage() { + return this._pageStack.length == 1; + } + + get onLastPage() { + var cp = this.currentPage; + return cp && ((this._accessMethod == "sequential" && cp.pageIndex == this.pageCount - 1) || + (this._accessMethod == "random" && cp.next == "")); + } + + getButton(aDlgType) { + return this._wizardButtons.getButton(aDlgType); + } + + getPageById(aPageId) { + var els = this.getElementsByAttribute("pageid", aPageId); + return els.item(0); + } + + extra1() { + if (this.currentPage) + this._fireEvent(this.currentPage, "extra1"); + } + + extra2() { + if (this.currentPage) + this._fireEvent(this.currentPage, "extra2"); + } + + rewind() { + if (!this.canRewind) + return; + + if (this.currentPage && !this._fireEvent(this.currentPage, "pagehide")) + return; + + if (this.currentPage && !this._fireEvent(this.currentPage, "pagerewound")) + return; + + if (!this._fireEvent(this, "wizardback")) + return; + + this._pageStack.pop(); + this.currentPage = this._pageStack[this._pageStack.length - 1]; + this.setAttribute("pagestep", this._pageStack.length); + } + + advance(aPageId) { + if (!this.canAdvance) + return; + + if (this.currentPage && !this._fireEvent(this.currentPage, "pagehide")) + return; + + if (this.currentPage && !this._fireEvent(this.currentPage, "pageadvanced")) + return; + + if (this.onLastPage && !aPageId) { + if (this._fireEvent(this, "wizardfinish")) + window.setTimeout(function() { window.close(); }, 1); + } else { + if (!this._fireEvent(this, "wizardnext")) + return; + + let page; + if (aPageId) { + page = this.getPageById(aPageId); + } else if (this.currentPage) { + if (this._accessMethod == "random") { + page = this.getPageById(this.currentPage.next); + } else { + page = this.wizardPages[this.currentPage.pageIndex + 1]; + } + } else { + page = this.wizardPages[0]; + } + + if (page) { + this._pageStack.push(page); + this.setAttribute("pagestep", this._pageStack.length); + + this.currentPage = page; + } + } + } + + goTo(aPageId) { + var page = this.getPageById(aPageId); + if (page) { + this._pageStack[this._pageStack.length - 1] = page; + this.currentPage = page; + } + } + + cancel() { + if (!this._fireEvent(this, "wizardcancel")) + return true; + + window.close(); + window.setTimeout(function() { window.close(); }, 1); + return false; + } + + _advanceFocusToPage(aPage) { + if (!this._hasLoaded) + return; + + // XXX: it'd be correct to advance focus into the panel, however we can't do + // it until bug 1558990 is fixed, so moving the focus into a wizard itsef + // as a workaround - it's same behavior but less optimal. + document.commandDispatcher.advanceFocusIntoSubtree(this); + + // if advanceFocusIntoSubtree tries to focus one of our + // dialog buttons, then remove it and put it on the root + var focused = document.commandDispatcher.focusedElement; + if (focused && focused.hasAttribute("dlgtype")) + this.focus(); + } + + _initPages() { + var meth = "sequential"; + var pages = this.wizardPages; + for (var i = 0; i < pages.length; ++i) { + var page = pages[i]; + page.pageIndex = i; + if (page.next != "") + meth = "random"; + } + this._accessMethod = meth; + } + + _adjustWizardHeader() { + var label = this.currentPage.getAttribute("label"); + if (!label && this.onFirstPage && this._bundle) { + if (AppConstants.platform == "macosx") { + label = this._bundle.GetStringFromName("default-first-title-mac"); + } else { + label = this._bundle.formatStringFromName("default-first-title", [this.title]); + } + } else if (!label && this.onLastPage && this._bundle) { + if (AppConstants.platform == "macosx") { + label = this._bundle.GetStringFromName("default-last-title-mac"); + } else { + label = this._bundle.formatStringFromName("default-last-title", [this.title]); + } + } + this._wizardHeader. + querySelector(".wizard-header-label").textContent = label; + let headerDescEl = + this._wizardHeader.querySelector(".wizard-header-description"); + if (headerDescEl) { + headerDescEl.textContent = + this.currentPage.getAttribute("description"); + } + } + + _hitEnter(evt) { + if (!evt.defaultPrevented) + this.advance(); + } + + _fireEvent(aTarget, aType) { + var event = document.createEvent("Events"); + event.initEvent(aType, true, true); + + // handle dom event handlers + return aTarget.dispatchEvent(event); + } +} + +customElements.define("wizard", MozWizard); + class MozWizardPage extends MozXULElement { constructor() { super(); this.pageIndex = -1; } + connectedCallback() { + this.setAttribute("slot", "wizardpage"); + } get pageid() { return this.getAttribute("pageid"); } diff --git a/toolkit/content/widgets/wizard.xml b/toolkit/content/widgets/wizard.xml deleted file mode 100644 index ad40e7445b6d..000000000000 --- a/toolkit/content/widgets/wizard.xml +++ /dev/null @@ -1,428 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - - null - null - null - - - - - - - - - - - - - - - - = this.pageCount) - return val; - - var page = this.wizardPages[val]; - this._pageStack[this._pageStack.length-1] = page; - this.currentPage = page; - - return val; - ]]> - - - - - - - - - - - - - - - - - - - - - - - - // see bug 63370 for details - this._bundle = Cc["@mozilla.org/intl/stringbundle;1"] - .getService(Ci.nsIStringBundleService) - .createBundle("chrome://global/locale/wizard.properties"); - } catch (e) { - // This fails in remote XUL, which has to provide titles for all pages - // see bug 142502 - } - - // get anonymous content references - this._wizardHeader = document.getAnonymousElementByAttribute(this, "anonid", "Header"); - - this._wizardHeader.appendChild( - MozXULElement.parseXULToFragment(/Mac/.test(navigator.platform) ? - ` - - - - - - - - - ` : - ` - - - - ` - ) - ); - - this._wizardButtons = document.getAnonymousElementByAttribute(this, "anonid", "Buttons"); - customElements.upgrade(this._wizardButtons); - - this._deck = document.getAnonymousElementByAttribute(this, "anonid", "Deck"); - - this._initPages(); - - window.addEventListener("close", (event) => { - if (document.documentElement.cancel()) { - event.preventDefault(); - } - }); - - // start off on the first page - this.pageCount = this.wizardPages.length; - this.advance(); - - // give focus to the first focusable element in the dialog - window.addEventListener("load", this._setInitialFocus); - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if (!event.defaultPrevented) - this.cancel(); - - - - diff --git a/toolkit/content/xul.css b/toolkit/content/xul.css index f9b3b7f1b334..98cd83975d0a 100644 --- a/toolkit/content/xul.css +++ b/toolkit/content/xul.css @@ -588,7 +588,6 @@ page { wizard, wizard:root /* override :root from above */ { - -moz-binding: url("chrome://global/content/bindings/wizard.xml#wizard"); -moz-box-orient: vertical; width: 40em; height: 30em;