зеркало из https://github.com/mozilla/gecko-dev.git
655 строки
19 KiB
JavaScript
655 строки
19 KiB
JavaScript
/* 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";
|
|
|
|
// This is loaded into chrome windows with the subscript loader. Wrap in
|
|
// 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 XUL_NS =
|
|
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
|
|
|
// Note: MozWizard currently supports adding, but not removing MozWizardPage
|
|
// children.
|
|
class MozWizard extends MozXULElement {
|
|
constructor() {
|
|
super();
|
|
|
|
// About this._accessMethod:
|
|
// There are two possible access methods: "sequential" and "random".
|
|
// "sequential" causes the MozWizardPage's to be displayed in the order
|
|
// that they are added to the DOM.
|
|
// The "random" method name is a bit misleading since the pages aren't
|
|
// displayed in a random order. Instead, each MozWizardPage must have
|
|
// a "next" attribute containing the id of the MozWizardPage that should
|
|
// be loaded next.
|
|
this._accessMethod = null;
|
|
this._currentPage = null;
|
|
this._canAdvance = true;
|
|
this._canRewind = false;
|
|
this._hasLoaded = false;
|
|
this._hasStarted = false; // Whether any MozWizardPage has been shown yet
|
|
this._wizardButtonsReady = false;
|
|
this.pageCount = 0;
|
|
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 }
|
|
);
|
|
|
|
/*
|
|
XXX(ntim): We import button.css here for the wizard-buttons children
|
|
This won't be needed after bug 1624888.
|
|
*/
|
|
this.attachShadow({ mode: "open" }).appendChild(
|
|
MozXULElement.parseXULToFragment(`
|
|
<html:link rel="stylesheet" href="chrome://global/skin/button.css"/>
|
|
<html:link rel="stylesheet" href="chrome://global/skin/wizard.css"/>
|
|
<hbox class="wizard-header"></hbox>
|
|
<html:slot name="wizardpage" class="wizard-page-box" style="display: grid; -moz-box-flex: 1;"/>
|
|
<html:slot/>
|
|
<wizard-buttons class="wizard-buttons"></wizard-buttons>
|
|
`)
|
|
);
|
|
this.initializeAttributeInheritance();
|
|
|
|
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 (document.l10n) {
|
|
document.l10n.connectRoot(this.shadowRoot);
|
|
}
|
|
document.documentElement.setAttribute("role", "dialog");
|
|
this._maybeStartWizard();
|
|
|
|
window.addEventListener("close", event => {
|
|
if (this.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(() => {
|
|
this._hasLoaded = true;
|
|
if (!document.commandDispatcher.focusedElement) {
|
|
document.commandDispatcher.advanceFocusIntoSubtree(this);
|
|
}
|
|
try {
|
|
let button = this._wizardButtons.defaultButton;
|
|
if (button) {
|
|
window.notifyDefaultButtonLoaded(button);
|
|
}
|
|
} catch (e) {}
|
|
}, 0)
|
|
);
|
|
}
|
|
|
|
set title(val) {
|
|
document.title = val;
|
|
}
|
|
|
|
get title() {
|
|
return document.title;
|
|
}
|
|
|
|
set canAdvance(val) {
|
|
this.getButton("next").disabled = !val;
|
|
this._canAdvance = val;
|
|
}
|
|
|
|
get canAdvance() {
|
|
return this._canAdvance;
|
|
}
|
|
|
|
set canRewind(val) {
|
|
this.getButton("back").disabled = !val;
|
|
this._canRewind = val;
|
|
}
|
|
|
|
get canRewind() {
|
|
return this._canRewind;
|
|
}
|
|
|
|
get pageStep() {
|
|
return this._pageStack.length;
|
|
}
|
|
|
|
get wizardPages() {
|
|
return this.getElementsByTagNameNS(XUL_NS, "wizardpage");
|
|
}
|
|
|
|
set currentPage(val) {
|
|
if (!val) {
|
|
return;
|
|
}
|
|
|
|
this._currentPage?.classList.remove("selected");
|
|
val.classList.add("selected");
|
|
|
|
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);
|
|
|
|
this._initCurrentPage();
|
|
|
|
this._advanceFocusToPage(val);
|
|
|
|
this._fireEvent(val, "pageshow");
|
|
}
|
|
|
|
get currentPage() {
|
|
return this._currentPage;
|
|
}
|
|
|
|
set pageIndex(val) {
|
|
if (val < 0 || val >= this.pageCount) {
|
|
return;
|
|
}
|
|
|
|
var page = this.wizardPages[val];
|
|
this._pageStack[this._pageStack.length - 1] = page;
|
|
this.currentPage = page;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
_initCurrentPage() {
|
|
this.canRewind = !this.onFirstPage;
|
|
this.setAttribute("firstpage", String(this.onFirstPage));
|
|
if (AppConstants.platform == "linux") {
|
|
this.getButton("back").hidden = this.onFirstPage;
|
|
}
|
|
|
|
if (this.onLastPage) {
|
|
this.canAdvance = true;
|
|
this.setAttribute("lastpage", "true");
|
|
} else {
|
|
this.setAttribute("lastpage", "false");
|
|
}
|
|
|
|
this._adjustWizardHeader();
|
|
this._wizardButtons.onPageChange();
|
|
}
|
|
|
|
_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();
|
|
}
|
|
}
|
|
|
|
_registerPage(aPage) {
|
|
aPage.pageIndex = this.pageCount;
|
|
this.pageCount += 1;
|
|
if (!this._accessMethod) {
|
|
this._accessMethod = aPage.next == "" ? "sequential" : "random";
|
|
}
|
|
if (!this._maybeStartWizard() && this._hasStarted) {
|
|
// If the wizard has already started, adding a page might require
|
|
// updating elements to reflect that (ex: changing the Finish button to
|
|
// the Next button).
|
|
this._initCurrentPage();
|
|
}
|
|
}
|
|
|
|
_onWizardButtonsReady() {
|
|
this._wizardButtonsReady = true;
|
|
this._maybeStartWizard();
|
|
}
|
|
|
|
_maybeStartWizard(aIsConnected) {
|
|
if (
|
|
!this._hasStarted &&
|
|
this.isConnected &&
|
|
this._wizardButtonsReady &&
|
|
this.pageCount > 0
|
|
) {
|
|
this._hasStarted = true;
|
|
this.advance();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
_adjustWizardHeader() {
|
|
let labelElement = this._wizardHeader.querySelector(
|
|
".wizard-header-label"
|
|
);
|
|
// First deal with fluent. Ideally, we'd stop supporting anything else,
|
|
// but right now the migration wizard still uses non-fluent l10n
|
|
// (fixing is bug 1518234), as do some comm-central consumers
|
|
// (bug 1627049). Removing the DTD support is bug 1627051.
|
|
if (this.currentPage.hasAttribute("data-header-label-id")) {
|
|
let id = this.currentPage.getAttribute("data-header-label-id");
|
|
document.l10n.setAttributes(labelElement, id);
|
|
} else {
|
|
// Otherwise, make sure we remove any fluent IDs leftover:
|
|
if (labelElement.hasAttribute("data-l10n-id")) {
|
|
labelElement.removeAttribute("data-l10n-id");
|
|
}
|
|
// And use the label attribute or the default:
|
|
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,
|
|
]);
|
|
}
|
|
}
|
|
labelElement.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");
|
|
|
|
let wizard = this.closest("wizard");
|
|
if (wizard) {
|
|
wizard._registerPage(this);
|
|
}
|
|
}
|
|
get pageid() {
|
|
return this.getAttribute("pageid");
|
|
}
|
|
set pageid(val) {
|
|
this.setAttribute("pageid", val);
|
|
}
|
|
get next() {
|
|
return this.getAttribute("next");
|
|
}
|
|
set next(val) {
|
|
this.setAttribute("next", val);
|
|
this.parentNode._accessMethod = "random";
|
|
}
|
|
}
|
|
|
|
customElements.define("wizardpage", MozWizardPage);
|
|
|
|
class MozWizardButtons extends MozXULElement {
|
|
connectedCallback() {
|
|
this._wizard = this.getRootNode().host;
|
|
|
|
this.textContent = "";
|
|
this.appendChild(this.constructor.fragment);
|
|
|
|
MozXULElement.insertFTLIfNeeded("toolkit/global/wizard.ftl");
|
|
|
|
this._wizardButtonDeck = this.querySelector(".wizard-next-deck");
|
|
|
|
this.initializeAttributeInheritance();
|
|
|
|
const listeners = [
|
|
["back", () => this._wizard.rewind()],
|
|
["next", () => this._wizard.advance()],
|
|
["finish", () => this._wizard.advance()],
|
|
["cancel", () => this._wizard.cancel()],
|
|
["extra1", () => this._wizard.extra1()],
|
|
["extra2", () => this._wizard.extra2()],
|
|
];
|
|
for (let [name, listener] of listeners) {
|
|
let btn = this.getButton(name);
|
|
if (btn) {
|
|
btn.addEventListener("command", listener);
|
|
}
|
|
}
|
|
|
|
this._wizard._onWizardButtonsReady();
|
|
}
|
|
|
|
static get inheritedAttributes() {
|
|
return AppConstants.platform == "macosx"
|
|
? {
|
|
"[dlgtype='next']": "hidden=lastpage",
|
|
}
|
|
: null;
|
|
}
|
|
|
|
static get markup() {
|
|
if (AppConstants.platform == "macosx") {
|
|
return `
|
|
<vbox flex="1">
|
|
<hbox class="wizard-buttons-btm">
|
|
<button class="wizard-button" dlgtype="extra1" hidden="true"/>
|
|
<button class="wizard-button" dlgtype="extra2" hidden="true"/>
|
|
<button data-l10n-id="wizard-macos-button-cancel"
|
|
class="wizard-button" dlgtype="cancel"/>
|
|
<spacer flex="1"/>
|
|
<button data-l10n-id="wizard-macos-button-back"
|
|
class="wizard-button wizard-nav-button" dlgtype="back"/>
|
|
<button data-l10n-id="wizard-macos-button-next"
|
|
class="wizard-button wizard-nav-button" dlgtype="next"
|
|
default="true" />
|
|
<button data-l10n-id="wizard-macos-button-finish" class="wizard-button"
|
|
dlgtype="finish" default="true" />
|
|
</hbox>
|
|
</vbox>`;
|
|
}
|
|
|
|
let buttons =
|
|
AppConstants.platform == "linux"
|
|
? `
|
|
<button data-l10n-id="wizard-linux-button-cancel"
|
|
class="wizard-button"
|
|
dlgtype="cancel"/>
|
|
<spacer style="width: 24px;"/>
|
|
<button data-l10n-id="wizard-linux-button-back"
|
|
class="wizard-button" dlgtype="back"/>
|
|
<deck class="wizard-next-deck">
|
|
<hbox>
|
|
<button data-l10n-id="wizard-linux-button-finish"
|
|
class="wizard-button"
|
|
dlgtype="finish" default="true" flex="1"/>
|
|
</hbox>
|
|
<hbox>
|
|
<button data-l10n-id="wizard-linux-button-next"
|
|
class="wizard-button" dlgtype="next"
|
|
default="true" flex="1"/>
|
|
</hbox>
|
|
</deck>`
|
|
: `
|
|
<button data-l10n-id="wizard-win-button-back"
|
|
class="wizard-button" dlgtype="back"/>
|
|
<deck class="wizard-next-deck">
|
|
<hbox>
|
|
<button data-l10n-id="wizard-win-button-finish"
|
|
class="wizard-button"
|
|
dlgtype="finish" default="true" flex="1"/>
|
|
</hbox>
|
|
<hbox>
|
|
<button data-l10n-id="wizard-win-button-next"
|
|
class="wizard-button" dlgtype="next"
|
|
default="true" flex="1"/>
|
|
</hbox>
|
|
</deck>
|
|
<button data-l10n-id="wizard-win-button-cancel"
|
|
class="wizard-button"
|
|
dlgtype="cancel"/>`;
|
|
|
|
return `
|
|
<vbox class="wizard-buttons-box-1" flex="1">
|
|
<separator class="wizard-buttons-separator groove"/>
|
|
<hbox class="wizard-buttons-box-2">
|
|
<button class="wizard-button" dlgtype="extra1" hidden="true"/>
|
|
<button class="wizard-button" dlgtype="extra2" hidden="true"/>
|
|
<spacer flex="1" anonid="spacer"/>
|
|
${buttons}
|
|
</hbox>
|
|
</vbox>`;
|
|
}
|
|
|
|
onPageChange() {
|
|
if (AppConstants.platform == "macosx") {
|
|
this.getButton("finish").hidden = !(
|
|
this.getAttribute("lastpage") == "true"
|
|
);
|
|
} else if (this.getAttribute("lastpage") == "true") {
|
|
this._wizardButtonDeck.setAttribute("selectedIndex", 0);
|
|
} else {
|
|
this._wizardButtonDeck.setAttribute("selectedIndex", 1);
|
|
}
|
|
}
|
|
|
|
getButton(type) {
|
|
return this.querySelector(`[dlgtype="${type}"]`);
|
|
}
|
|
|
|
get defaultButton() {
|
|
let buttons = this._wizardButtonDeck.selectedPanel.getElementsByTagNameNS(
|
|
XUL_NS,
|
|
"button"
|
|
);
|
|
for (let i = 0; i < buttons.length; i++) {
|
|
if (
|
|
buttons[i].getAttribute("default") == "true" &&
|
|
!buttons[i].hidden &&
|
|
!buttons[i].disabled
|
|
) {
|
|
return buttons[i];
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
customElements.define("wizard-buttons", MozWizardButtons);
|
|
}
|