Bug 1495621 - convert wizard binding to Custom Element

Differential Revision: https://phabricator.services.mozilla.com/D26334
This commit is contained in:
Alexander Surkov 2019-04-04 11:42:15 -04:00
parent 3861f1c2b6
commit 3ef9e42f45
6 changed files with 372 additions and 434 deletions

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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(`
<html:link rel="stylesheet" href="chrome://global/content/widgets.css" />
<hbox class="wizard-header"></hbox>
<deck class="wizard-page-box" flex="1">
<slot xmlns="http://www.w3.org/1999/xhtml" name="wizardpage"></slot>
</deck>
<slot xmlns="http://www.w3.org/1999/xhtml"></slot>
<wizard-buttons class="wizard-buttons"></wizard-buttons>
`));
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" ?
`<stack class="wizard-header-stack" flex="1">
<vbox class="wizard-header-box-1">
<vbox class="wizard-header-box-text">
<label class="wizard-header-label"/>
</vbox>
</vbox>
<hbox class="wizard-header-box-icon">
<spacer flex="1"/>
<image class="wizard-header-icon"/>
</hbox>
</stack>` :
`<hbox class="wizard-header-box-1" flex="1">
<vbox class="wizard-header-box-text" flex="1">
<label class="wizard-header-label"/>
<label class="wizard-header-description"/>
</vbox>
<image class="wizard-header-icon"/>
</hbox>`
)
);
}
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");
}

View File

@ -1,428 +0,0 @@
<?xml version="1.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/. -->
<bindings id="wizardBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="wizard">
<content>
<xul:hbox class="wizard-header" anonid="Header"/>
<xul:deck class="wizard-page-box" flex="1" anonid="Deck">
<children includes="wizardpage"/>
</xul:deck>
<children/>
<xul:wizard-buttons class="wizard-buttons" anonid="Buttons" xbl:inherits="pagestep,firstpage,lastpage"/>
</content>
<implementation>
<property name="title" onget="return document.title;"
onset="return document.title = val;"/>
<property name="canAdvance" onget="return this._canAdvance;"
onset="this.getButton('next').disabled = !val; return this._canAdvance = val;"/>
<property name="canRewind" onget="return this._canRewind;"
onset="this.getButton('back').disabled = !val; return this._canRewind = val;"/>
<property name="pageStep" readonly="true" onget="return this._pageStack.length"/>
<field name="pageCount">0</field>
<field name="_accessMethod">null</field>
<field name="_pageStack">null</field>
<field name="_currentPage">null</field>
<property name="wizardPages">
<getter>
<![CDATA[
var xulns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
return this.getElementsByTagNameNS(xulns, "wizardpage");
]]>
</getter>
</property>
<property name="currentPage" onget="return this._currentPage">
<setter>
<![CDATA[
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 (/Linux/.test(navigator.platform)) {
this.getButton("back").setAttribute("hidden", "true");
}
} else {
this.canRewind = true;
this.setAttribute("firstpage", "false");
if (/Linux/.test(navigator.platform)) {
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;
]]>
</setter>
</property>
<property name="pageIndex"
onget="return this._currentPage ? this._currentPage.pageIndex : -1;">
<setter>
<![CDATA[
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;
]]>
</setter>
</property>
<property name="onFirstPage" readonly="true"
onget="return this._pageStack.length == 1;"/>
<property name="onLastPage" readonly="true">
<getter><![CDATA[
var cp = this.currentPage;
return cp && ((this._accessMethod == "sequential" && cp.pageIndex == this.pageCount-1) ||
(this._accessMethod == "random" && cp.next == ""));
]]></getter>
</property>
<method name="getButton">
<parameter name="aDlgType"/>
<body>
<![CDATA[
return this._wizardButtons.getButton(aDlgType);
]]>
</body>
</method>
<field name="_canAdvance"/>
<field name="_canRewind"/>
<field name="_wizardHeader"/>
<field name="_wizardButtons"/>
<field name="_deck"/>
<constructor><![CDATA[
this._canAdvance = true;
this._canRewind = false;
this._hasLoaded = false;
this._pageStack = [];
try {
// need to create string bundle manually instead of using <xul:stringbundle/>
// 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) ?
`<stack class="wizard-header-stack" flex="1">
<vbox class="wizard-header-box-1">
<vbox class="wizard-header-box-text">
<label class="wizard-header-label"/>
</vbox>
</vbox>
<hbox class="wizard-header-box-icon">
<spacer flex="1"/>
<image class="wizard-header-icon"/>
</hbox>
</stack>` :
`<hbox class="wizard-header-box-1" flex="1">
<vbox class="wizard-header-box-text" flex="1">
<label class="wizard-header-label"/>
<label class="wizard-header-description"/>
</vbox>
<image class="wizard-header-icon"/>
</hbox>`
)
);
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);
]]></constructor>
<method name="getPageById">
<parameter name="aPageId"/>
<body><![CDATA[
var els = this.getElementsByAttribute("pageid", aPageId);
return els.item(0);
]]></body>
</method>
<method name="extra1">
<body><![CDATA[
if (this.currentPage)
this._fireEvent(this.currentPage, "extra1");
]]></body>
</method>
<method name="extra2">
<body><![CDATA[
if (this.currentPage)
this._fireEvent(this.currentPage, "extra2");
]]></body>
</method>
<method name="rewind">
<body><![CDATA[
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);
]]></body>
</method>
<method name="advance">
<parameter name="aPageId"/>
<body><![CDATA[
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;
var 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;
}
}
]]></body>
</method>
<method name="goTo">
<parameter name="aPageId"/>
<body><![CDATA[
var page = this.getPageById(aPageId);
if (page) {
this._pageStack[this._pageStack.length-1] = page;
this.currentPage = page;
}
]]></body>
</method>
<method name="cancel">
<body><![CDATA[
if (!this._fireEvent(this, "wizardcancel"))
return true;
window.close();
window.setTimeout(function() {window.close();}, 1);
return false;
]]></body>
</method>
<method name="_setInitialFocus">
<parameter name="aEvent"/>
<body>
<![CDATA[
document.documentElement._hasLoaded = true;
var focusInit =
function() {
// give focus to the first focusable element in the dialog
if (!document.commandDispatcher.focusedElement)
document.commandDispatcher.advanceFocusIntoSubtree(document.documentElement);
try {
var button =
document.documentElement._wizardButtons.defaultButton;
if (button)
window.notifyDefaultButtonLoaded(button);
} catch (e) { }
};
// Give focus after onload completes, see bug 103197.
setTimeout(focusInit, 0);
]]>
</body>
</method>
<method name="_advanceFocusToPage">
<parameter name="aPage"/>
<body>
<![CDATA[
if (!this._hasLoaded)
return;
document.commandDispatcher.advanceFocusIntoSubtree(aPage);
// 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();
]]>
</body>
</method>
<method name="_initPages">
<body><![CDATA[
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;
]]></body>
</method>
<method name="_adjustWizardHeader">
<body><![CDATA[
var label = this.currentPage.getAttribute("label");
if (!label && this.onFirstPage && this._bundle) {
if (/Mac/.test(navigator.platform)) {
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 (/Mac/.test(navigator.platform)) {
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");
}
]]></body>
</method>
<method name="_hitEnter">
<parameter name="evt"/>
<body>
<![CDATA[
if (!evt.defaultPrevented)
this.advance();
]]>
</body>
</method>
<method name="_fireEvent">
<parameter name="aTarget"/>
<parameter name="aType"/>
<body>
<![CDATA[
var event = document.createEvent("Events");
event.initEvent(aType, true, true);
// handle dom event handlers
return aTarget.dispatchEvent(event);
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="keypress" keycode="VK_RETURN"
group="system" action="this._hitEnter(event)"/>
<handler event="keypress" keycode="VK_ESCAPE" group="system">
if (!event.defaultPrevented)
this.cancel();
</handler>
</handlers>
</binding>
</bindings>

View File

@ -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;