Bug 1450813 - Create print preview toolbar as customized built-in Custom Element;r=timdream draft
authorBrian Grinstead <bgrinstead@mozilla.com>
Mon, 02 Jul 2018 13:33:09 -0700
changeset 813247 59adc5abea1540b8e66f9e48229355b24e5c9b0e
parent 813062 9c02d2ecf22050bfee5d70c04a359d8aaff6eb91
push id114839
push userbgrinstead@mozilla.com
push dateMon, 02 Jul 2018 20:33:22 +0000
reviewerstimdream
bugs1450813
milestone63.0a1
Bug 1450813 - Create print preview toolbar as customized built-in Custom Element;r=timdream MozReview-Commit-ID: LYlQ8xxJNA8
browser/base/content/browser.css
toolkit/components/printing/content/printPreviewBindings.xml
toolkit/components/printing/content/printPreviewToolbar.js
toolkit/components/printing/content/printUtils.js
toolkit/components/printing/jar.mn
toolkit/components/printing/tests/browser_page_change_print_original.js
toolkit/content/customElements.js
toolkit/themes/linux/global/global.css
toolkit/themes/linux/global/jar.mn
toolkit/themes/linux/global/printPreview.css
toolkit/themes/windows/global/global.css
toolkit/themes/windows/global/jar.mn
toolkit/themes/windows/global/printPreview.css
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -246,20 +246,16 @@ panelview[mainview] > .panel-header {
 /* Allow dropping a tab on buttons with associated drop actions. */
 #TabsToolbar[movingtab] + #nav-bar > #nav-bar-customization-target > #personal-bookmarks,
 #TabsToolbar[movingtab] + #nav-bar > #nav-bar-customization-target > #home-button,
 #TabsToolbar[movingtab] + #nav-bar > #nav-bar-customization-target > #downloads-button,
 #TabsToolbar[movingtab] + #nav-bar > #nav-bar-customization-target > #bookmarks-menu-button {
   pointer-events: auto;
 }
 
-toolbar[printpreview="true"] {
-  -moz-binding: url("chrome://global/content/printPreviewBindings.xml#printpreviewtoolbar");
-}
-
 toolbar[overflowable] > .customization-target {
   overflow: hidden;
 }
 
 toolbar:not([overflowing]) > .overflow-button,
 toolbar[customizing] > .overflow-button {
   display: none;
 }
rename from toolkit/components/printing/content/printPreviewBindings.xml
rename to toolkit/components/printing/content/printPreviewToolbar.js
--- a/toolkit/components/printing/content/printPreviewBindings.xml
+++ b/toolkit/components/printing/content/printPreviewToolbar.js
@@ -1,459 +1,340 @@
-<?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/. -->
-
-<!-- this file depends on printUtils.js -->
+// This file is loaded into the browser window scope.
+/* eslint-env mozilla/browser-window */
 
-<!DOCTYPE bindings [
-<!ENTITY % printPreviewDTD SYSTEM "chrome://global/locale/printPreview.dtd" >
-%printPreviewDTD;
-]>
-
-<bindings id="printPreviewBindings"
-   xmlns="http://www.mozilla.org/xbl"
-   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-
-  <binding id="printpreviewtoolbar">
-    <resources>
-      <stylesheet src="chrome://global/skin/printPreview.css"/>
-    </resources>
+// -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*-
 
-    <content>
-      <xul:button label="&print.label;" accesskey="&print.accesskey;"
-        oncommand="this.parentNode.print();" icon="print"/>
-
-      <xul:button anonid="pageSetup" label="&pageSetup.label;" accesskey="&pageSetup.accesskey;"
-        oncommand="this.parentNode.doPageSetup();"/>
+/* 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/. */
 
-      <xul:vbox align="center" pack="center">
-        <xul:label value="&page.label;" accesskey="&page.accesskey;" control="pageNumber"/>
-      </xul:vbox>
-      <xul:toolbarbutton anonid="navigateHome" class="navigate-button tabbable"
-        oncommand="parentNode.navigate(0, 0, 'home');" tooltiptext="&homearrow.tooltip;"/>
-      <xul:toolbarbutton anonid="navigatePrevious" class="navigate-button tabbable"
-        oncommand="parentNode.navigate(-1, 0, 0);" tooltiptext="&previousarrow.tooltip;"/>
-      <xul:hbox align="center" pack="center">
-        <xul:textbox id="pageNumber" value="1" min="1" type="number"
-          hidespinbuttons="true" onchange="navigate(0, this.valueNumber, 0);"/>
-        <xul:label value="&of.label;"/>
-        <xul:label value="1"/>
-      </xul:hbox>
-      <xul:toolbarbutton anonid="navigateNext" class="navigate-button tabbable"
-        oncommand="parentNode.navigate(1, 0, 0);" tooltiptext="&nextarrow.tooltip;"/>
-      <xul:toolbarbutton anonid="navigateEnd" class="navigate-button tabbable"
-        oncommand="parentNode.navigate(0, 0, 'end');" tooltiptext="&endarrow.tooltip;"/>
+customElements.define("printpreview-toolbar", class PrintPreviewToolbar extends MozXULElement {
 
-      <xul:toolbarseparator class="toolbarseparator-primary"/>
-      <xul:vbox align="center" pack="center">
-        <xul:label value="&scale.label;" accesskey="&scale.accesskey;" control="scale"/>
-      </xul:vbox>
+  constructor() {
+    super();
+    this.disconnectedCallback = this.disconnectedCallback.bind(this);
+  }
+  connectedCallback() {
+    window.addEventListener("unload", this.disconnectedCallback, { once: true });
+    this.appendChild(MozXULElement.parseXULToFragment(`
+      <button label="&print.label;" accesskey="&print.accesskey;" oncommand="this.parentNode.print();" icon="print"></button>
+      <button anonid="pageSetup" label="&pageSetup.label;" accesskey="&pageSetup.accesskey;" oncommand="this.parentNode.doPageSetup();"></button>
+      <vbox align="center" pack="center">
+        <label value="&page.label;" accesskey="&page.accesskey;" control="pageNumber"></label>
+      </vbox>
+      <toolbarbutton anonid="navigateHome" class="navigate-button tabbable" oncommand="parentNode.navigate(0, 0, 'home');" tooltiptext="&homearrow.tooltip;"></toolbarbutton>
+      <toolbarbutton anonid="navigatePrevious" class="navigate-button tabbable" oncommand="parentNode.navigate(-1, 0, 0);" tooltiptext="&previousarrow.tooltip;"></toolbarbutton>
+      <hbox align="center" pack="center">
+        <textbox id="pageNumber" value="1" min="1" type="number" hidespinbuttons="true" onchange="navigate(0, this.valueNumber, 0);"></textbox>
+        <label value="&of.label;"></label>
+        <label value="1"></label>
+      </hbox>
+      <toolbarbutton anonid="navigateNext" class="navigate-button tabbable" oncommand="parentNode.navigate(1, 0, 0);" tooltiptext="&nextarrow.tooltip;"></toolbarbutton>
+      <toolbarbutton anonid="navigateEnd" class="navigate-button tabbable" oncommand="parentNode.navigate(0, 0, 'end');" tooltiptext="&endarrow.tooltip;"></toolbarbutton>
+      <toolbarseparator class="toolbarseparator-primary"></toolbarseparator>
+      <vbox align="center" pack="center">
+        <label value="&scale.label;" accesskey="&scale.accesskey;" control="scale"></label>
+      </vbox>
+      <hbox align="center" pack="center">
+        <menulist id="scale" crop="none" oncommand="parentNode.parentNode.scale(this.selectedItem.value);">
+          <menupopup>
+            <menuitem value="0.3" label="&p30.label;"></menuitem>
+            <menuitem value="0.4" label="&p40.label;"></menuitem>
+            <menuitem value="0.5" label="&p50.label;"></menuitem>
+            <menuitem value="0.6" label="&p60.label;"></menuitem>
+            <menuitem value="0.7" label="&p70.label;"></menuitem>
+            <menuitem value="0.8" label="&p80.label;"></menuitem>
+            <menuitem value="0.9" label="&p90.label;"></menuitem>
+            <menuitem value="1" label="&p100.label;"></menuitem>
+            <menuitem value="1.25" label="&p125.label;"></menuitem>
+            <menuitem value="1.5" label="&p150.label;"></menuitem>
+            <menuitem value="1.75" label="&p175.label;"></menuitem>
+            <menuitem value="2" label="&p200.label;"></menuitem>
+            <menuseparator></menuseparator>
+            <menuitem flex="1" value="ShrinkToFit" label="&ShrinkToFit.label;"></menuitem>
+            <menuitem value="Custom" label="&Custom.label;"></menuitem>
+          </menupopup>
+        </menulist>
+      </hbox>
+      <toolbarseparator class="toolbarseparator-primary"></toolbarseparator>
+      <hbox align="center" pack="center">
+        <toolbarbutton label="&portrait.label;" checked="true" accesskey="&portrait.accesskey;" type="radio" group="orient" class="toolbar-portrait-page tabbable" oncommand="parentNode.parentNode.orient('portrait');"></toolbarbutton>
+        <toolbarbutton label="&landscape.label;" accesskey="&landscape.accesskey;" type="radio" group="orient" class="toolbar-landscape-page tabbable" oncommand="parentNode.parentNode.orient('landscape');"></toolbarbutton>
+      </hbox>
+      <toolbarseparator class="toolbarseparator-primary"></toolbarseparator>
+      <checkbox label="&simplifyPage.label;" checked="false" disabled="true" accesskey="&simplifyPage.accesskey;" tooltiptext-disabled="&simplifyPage.disabled.tooltip;" tooltiptext-enabled="&simplifyPage.enabled.tooltip;" oncommand="this.parentNode.simplify();"></checkbox>
+      <toolbarseparator class="toolbarseparator-primary"></toolbarseparator>
+      <button label="&close.label;" accesskey="&close.accesskey;" oncommand="PrintUtils.exitPrintPreview();" icon="close"></button>
+      <data value="&customPrompt.title;"></data>
+    `, `
+    <!DOCTYPE bindings [
+      <!ENTITY % printPreviewDTD SYSTEM "chrome://global/locale/printPreview.dtd" >
+      %printPreviewDTD;
+    ]>`));
 
-      <xul:hbox align="center" pack="center">
-        <xul:menulist id="scale" crop="none"
-          oncommand="parentNode.parentNode.scale(this.selectedItem.value);">
-          <xul:menupopup>
-            <xul:menuitem value="0.3" label="&p30.label;"/>
-            <xul:menuitem value="0.4" label="&p40.label;"/>
-            <xul:menuitem value="0.5" label="&p50.label;"/>
-            <xul:menuitem value="0.6" label="&p60.label;"/>
-            <xul:menuitem value="0.7" label="&p70.label;"/>
-            <xul:menuitem value="0.8" label="&p80.label;"/>
-            <xul:menuitem value="0.9" label="&p90.label;"/>
-            <xul:menuitem value="1" label="&p100.label;"/>
-            <xul:menuitem value="1.25" label="&p125.label;"/>
-            <xul:menuitem value="1.5" label="&p150.label;"/>
-            <xul:menuitem value="1.75" label="&p175.label;"/>
-            <xul:menuitem value="2" label="&p200.label;"/>
-            <xul:menuseparator/>
-            <xul:menuitem flex="1" value="ShrinkToFit"
-              label="&ShrinkToFit.label;"/>
-            <xul:menuitem value="Custom" label="&Custom.label;"/>
-          </xul:menupopup>
-        </xul:menulist>
-      </xul:hbox>
+    this.mPrintButton = this.childNodes[0];
+
+    this.mPageSetupButton = this.querySelector("[anonid=pageSetup]");
 
-      <xul:toolbarseparator class="toolbarseparator-primary"/>
-      <xul:hbox align="center" pack="center">
-        <xul:toolbarbutton label="&portrait.label;" checked="true"
-          accesskey="&portrait.accesskey;"
-          type="radio" group="orient" class="toolbar-portrait-page tabbable"
-          oncommand="parentNode.parentNode.orient('portrait');"/>
-        <xul:toolbarbutton label="&landscape.label;"
-          accesskey="&landscape.accesskey;"
-          type="radio" group="orient" class="toolbar-landscape-page tabbable"
-          oncommand="parentNode.parentNode.orient('landscape');"/>
-      </xul:hbox>
+    this.mNavigateHomeButton = this.querySelector("[anonid=navigateHome]");
 
-      <xul:toolbarseparator class="toolbarseparator-primary"/>
-      <xul:checkbox label="&simplifyPage.label;" checked="false" disabled="true"
-        accesskey="&simplifyPage.accesskey;"
-        tooltiptext-disabled="&simplifyPage.disabled.tooltip;"
-        tooltiptext-enabled="&simplifyPage.enabled.tooltip;"
-        oncommand="this.parentNode.simplify();"/>
+    this.mNavigatePreviousButton = this.querySelector("[anonid=navigatePrevious]");
 
-      <xul:toolbarseparator class="toolbarseparator-primary"/>
-      <xul:button label="&close.label;" accesskey="&close.accesskey;"
-        oncommand="PrintUtils.exitPrintPreview();" icon="close"/>
-      <xul:data value="&customPrompt.title;"/>
-    </content>
+    this.mPageTextBox = this.childNodes[5].childNodes[0];
 
-    <implementation>
-      <field name="mPrintButton">
-        document.getAnonymousNodes(this)[0]
-      </field>
-      <field name="mPageSetupButton">
-        document.getAnonymousElementByAttribute(this, "anonid", "pageSetup");
-      </field>
-      <field name="mNavigateHomeButton">
-        document.getAnonymousElementByAttribute(this, "anonid", "navigateHome");
-      </field>
-      <field name="mNavigatePreviousButton">
-        document.getAnonymousElementByAttribute(this, "anonid", "navigatePrevious");
-      </field>
-      <field name="mPageTextBox">
-        document.getAnonymousNodes(this)[5].childNodes[0]
-      </field>
-      <field name="mNavigateNextButton">
-        document.getAnonymousElementByAttribute(this, "anonid", "navigateNext");
-      </field>
-      <field name="mNavigateEndButton">
-        document.getAnonymousElementByAttribute(this, "anonid", "navigateEnd");
-      </field>
-      <field name="mTotalPages">
-        document.getAnonymousNodes(this)[5].childNodes[2]
-      </field>
-      <field name="mScaleLabel">
-        document.getAnonymousNodes(this)[9].firstChild
-      </field>
-      <field name="mScaleCombobox">
-        document.getAnonymousNodes(this)[10].firstChild
-      </field>
-      <field name="mOrientButtonsBox">
-        document.getAnonymousNodes(this)[12]
-      </field>
-      <field name="mPortaitButton">
-        this.mOrientButtonsBox.childNodes[0]
-      </field>
-      <field name="mLandscapeButton">
-        this.mOrientButtonsBox.childNodes[1]
-      </field>
-      <field name="mSimplifyPageCheckbox">
-        document.getAnonymousNodes(this)[14]
-      </field>
-      <field name="mSimplifyPageNotAllowed">
-        this.mSimplifyPageCheckbox.disabled
-      </field>
-      <field name="mSimplifyPageToolbarSeparator">
-        document.getAnonymousNodes(this)[15]
-      </field>
-      <field name="mCustomTitle">
-        document.getAnonymousNodes(this)[17].firstChild
-      </field>
-      <field name="mPrintPreviewObs">
-      </field>
-      <field name="mWebProgress">
-      </field>
-      <field name="mPPBrowser">
-        null
-      </field>
-      <field name="mMessageManager">
-        null
-      </field>
+    this.mNavigateNextButton = this.querySelector("[anonid=navigateNext]");
+
+    this.mNavigateEndButton = this.querySelector("[anonid=navigateEnd]");
+
+    this.mTotalPages = this.childNodes[5].childNodes[2];
+
+    this.mScaleLabel = this.childNodes[9].firstChild;
+
+    this.mScaleCombobox = this.childNodes[10].firstChild;
+
+    this.mOrientButtonsBox = this.childNodes[12];
+
+    this.mPortaitButton = this.mOrientButtonsBox.childNodes[0];
+
+    this.mLandscapeButton = this.mOrientButtonsBox.childNodes[1];
+
+    this.mSimplifyPageCheckbox = this.childNodes[14];
+
+    this.mSimplifyPageNotAllowed = this.mSimplifyPageCheckbox.disabled;
+
+    this.mSimplifyPageToolbarSeparator = this.childNodes[15];
+
+    this.mCustomTitle = this.childNodes[17].firstChild;
+
+    this.mPrintPreviewObs = "";
+
+    this.mWebProgress = "";
+
+    this.mPPBrowser = null;
+
+    this.mMessageManager = null;
+  }
 
-      <method name="initialize">
-        <parameter name="aPPBrowser"/>
-        <body>
-        <![CDATA[
-          let {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
-          if (!Services.prefs.getBoolPref("print.use_simplify_page")) {
-            this.mSimplifyPageCheckbox.hidden = true;
-            this.mSimplifyPageToolbarSeparator.hidden = true;
-          }
-          this.mPPBrowser = aPPBrowser;
-          this.mMessageManager = aPPBrowser.messageManager;
-          this.mMessageManager.addMessageListener("Printing:Preview:UpdatePageCount", this);
-          this.updateToolbar();
+  initialize(aPPBrowser) {
+    let { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
+    if (!Services.prefs.getBoolPref("print.use_simplify_page")) {
+      this.mSimplifyPageCheckbox.hidden = true;
+      this.mSimplifyPageToolbarSeparator.hidden = true;
+    }
+    this.mPPBrowser = aPPBrowser;
+    this.mMessageManager = aPPBrowser.messageManager;
+    this.mMessageManager.addMessageListener("Printing:Preview:UpdatePageCount", this);
+    this.updateToolbar();
+
+    let ltr = document.documentElement.matches(":root:-moz-locale-dir(ltr)");
+    // Windows 7 doesn't support ⏮ and ⏭ by default, and fallback doesn't
+    // always work (bug 1343330).
+    let { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
+    let useCompatCharacters = AppConstants.isPlatformAndVersionAtMost("win", "6.1");
+    let leftEnd = useCompatCharacters ? "\u23EA" : "\u23EE";
+    let rightEnd = useCompatCharacters ? "\u23E9" : "\u23ED";
+    this.querySelector("[anonid=navigateHome]").label = ltr ? leftEnd : rightEnd;
+    this.querySelector("[anonid=navigatePrevious]").label = ltr ? "\u25C2" : "\u25B8";
+    this.querySelector("[anonid=navigateNext]").label = ltr ? "\u25B8" : "\u25C2";
+    this.querySelector("[anonid=navigateEnd]").label = ltr ? rightEnd : leftEnd;
+  }
 
-          let $ = id => document.getAnonymousElementByAttribute(this, "anonid", id);
-          let ltr = document.documentElement.matches(":root:-moz-locale-dir(ltr)");
-          // Windows 7 doesn't support ⏮ and ⏭ by default, and fallback doesn't
-          // always work (bug 1343330).
-          let {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
-          let useCompatCharacters = AppConstants.isPlatformAndVersionAtMost("win", "6.1");
-          let leftEnd = useCompatCharacters ? "⏪" : "⏮";
-          let rightEnd = useCompatCharacters ? "⏩" : "⏭";
-          $("navigateHome").label = ltr ? leftEnd : rightEnd;
-          $("navigatePrevious").label = ltr ? "◂" : "▸";
-          $("navigateNext").label = ltr ? "▸" : "◂";
-          $("navigateEnd").label = ltr ? rightEnd : leftEnd;
-        ]]>
-        </body>
-      </method>
+  destroy() {
+    if (this.mMessageManager) {
+      this.mMessageManager.removeMessageListener("Printing:Preview:UpdatePageCount", this);
+      delete this.mMessageManager;
+      delete this.mPPBrowser;
+    }
+  }
+
+  disconnectedCallback() {
+    window.removeEventListener("unload", this.disconnectedCallback);
+    this.destroy();
+  }
 
-      <method name="destroy">
-        <body>
-        <![CDATA[
-          this.mMessageManager.removeMessageListener("Printing:Preview:UpdatePageCount", this);
-          delete this.mMessageManager;
-          delete this.mPPBrowser;
-        ]]>
-        </body>
-      </method>
-
-      <method name="disableUpdateTriggers">
-        <parameter name="aDisabled"/>
-        <body>
-        <![CDATA[
-          this.mPrintButton.disabled = aDisabled;
-          this.mPageSetupButton.disabled = aDisabled;
-          this.mNavigateHomeButton.disabled = aDisabled;
-          this.mNavigatePreviousButton.disabled = aDisabled;
-          this.mPageTextBox.disabled = aDisabled;
-          this.mNavigateNextButton.disabled = aDisabled;
-          this.mNavigateEndButton.disabled = aDisabled;
-          this.mScaleCombobox.disabled = aDisabled;
-          this.mPortaitButton.disabled = aDisabled;
-          this.mLandscapeButton.disabled = aDisabled;
-          this.mSimplifyPageCheckbox.disabled = this.mSimplifyPageNotAllowed || aDisabled;
-        ]]>
-        </body>
-      </method>
+  disableUpdateTriggers(aDisabled) {
+    this.mPrintButton.disabled = aDisabled;
+    this.mPageSetupButton.disabled = aDisabled;
+    this.mNavigateHomeButton.disabled = aDisabled;
+    this.mNavigatePreviousButton.disabled = aDisabled;
+    this.mPageTextBox.disabled = aDisabled;
+    this.mNavigateNextButton.disabled = aDisabled;
+    this.mNavigateEndButton.disabled = aDisabled;
+    this.mScaleCombobox.disabled = aDisabled;
+    this.mPortaitButton.disabled = aDisabled;
+    this.mLandscapeButton.disabled = aDisabled;
+    this.mSimplifyPageCheckbox.disabled = this.mSimplifyPageNotAllowed || aDisabled;
+  }
 
-      <method name="doPageSetup">
-        <body>
-        <![CDATA[
-          /* import-globals-from printUtils.js */
-          var didOK = PrintUtils.showPageSetup();
-          if (didOK) {
-            // the changes that effect the UI
-            this.updateToolbar();
-
-            // Now do PrintPreview
-            PrintUtils.printPreview();
-          }
-        ]]>
-        </body>
-      </method>
-
-      <method name="navigate">
-        <parameter name="aDirection"/>
-        <parameter name="aPageNum"/>
-        <parameter name="aHomeOrEnd"/>
-        <body>
-        <![CDATA[
-          const nsIWebBrowserPrint = Ci.nsIWebBrowserPrint;
-          let navType, pageNum;
+  doPageSetup() {
+    /* import-globals-from printUtils.js */
+    var didOK = PrintUtils.showPageSetup();
+    if (didOK) {
+      // the changes that effect the UI
+      this.updateToolbar();
 
-          // we use only one of aHomeOrEnd, aDirection, or aPageNum
-          if (aHomeOrEnd) {
-            // We're going to either the very first page ("home"), or the
-            // very last page ("end").
-            if (aHomeOrEnd == "home") {
-              navType = nsIWebBrowserPrint.PRINTPREVIEW_HOME;
-              this.mPageTextBox.value = 1;
-            } else {
-              navType = nsIWebBrowserPrint.PRINTPREVIEW_END;
-              this.mPageTextBox.value = this.mPageTextBox.max;
-            }
-            pageNum = 0;
-          } else if (aDirection) {
-            // aDirection is either +1 or -1, and allows us to increment
-            // or decrement our currently viewed page.
-            this.mPageTextBox.valueNumber += aDirection;
-            navType = nsIWebBrowserPrint.PRINTPREVIEW_GOTO_PAGENUM;
-            pageNum = this.mPageTextBox.value; // TODO: back to valueNumber?
-          } else {
-            // We're going to a specific page (aPageNum)
-            navType = nsIWebBrowserPrint.PRINTPREVIEW_GOTO_PAGENUM;
-            pageNum = aPageNum;
-          }
+      // Now do PrintPreview
+      PrintUtils.printPreview();
+    }
+  }
 
-          this.mMessageManager.sendAsyncMessage("Printing:Preview:Navigate", {
-            navType,
-            pageNum,
-          });
-        ]]>
-        </body>
-      </method>
+  navigate(aDirection, aPageNum, aHomeOrEnd) {
+    const nsIWebBrowserPrint = Ci.nsIWebBrowserPrint;
+    let navType, pageNum;
 
-      <method name="print">
-        <body>
-        <![CDATA[
-          PrintUtils.printWindow(this.mPPBrowser.outerWindowID, this.mPPBrowser);
-        ]]>
-        </body>
-      </method>
+    // we use only one of aHomeOrEnd, aDirection, or aPageNum
+    if (aHomeOrEnd) {
+      // We're going to either the very first page ("home"), or the
+      // very last page ("end").
+      if (aHomeOrEnd == "home") {
+        navType = nsIWebBrowserPrint.PRINTPREVIEW_HOME;
+        this.mPageTextBox.value = 1;
+      } else {
+        navType = nsIWebBrowserPrint.PRINTPREVIEW_END;
+        this.mPageTextBox.value = this.mPageTextBox.max;
+      }
+      pageNum = 0;
+    } else if (aDirection) {
+      // aDirection is either +1 or -1, and allows us to increment
+      // or decrement our currently viewed page.
+      this.mPageTextBox.valueNumber += aDirection;
+      navType = nsIWebBrowserPrint.PRINTPREVIEW_GOTO_PAGENUM;
+      pageNum = this.mPageTextBox.value; // TODO: back to valueNumber?
+    } else {
+      // We're going to a specific page (aPageNum)
+      navType = nsIWebBrowserPrint.PRINTPREVIEW_GOTO_PAGENUM;
+      pageNum = aPageNum;
+    }
 
-      <method name="promptForScaleValue">
-        <parameter name="aValue"/>
-        <body>
-        <![CDATA[
-          var value = Math.round(aValue);
-          var promptStr = this.mScaleLabel.value;
-          var renameTitle = this.mCustomTitle;
-          var result = {value};
-          let {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
-          var confirmed = Services.prompt.prompt(window, renameTitle, promptStr, result, null, {value});
-          if (!confirmed || (!result.value) || (result.value == "")) {
-            return -1;
-          }
-          return result.value;
-        ]]>
-        </body>
-      </method>
+    this.mMessageManager.sendAsyncMessage("Printing:Preview:Navigate", {
+      navType,
+      pageNum,
+    });
+  }
 
-      <method name="setScaleCombobox">
-        <parameter name="aValue"/>
-        <body>
-        <![CDATA[
-          var scaleValues = [0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.25, 1.5, 1.75, 2];
-
-          aValue = Number(aValue);
+  print() {
+    PrintUtils.printWindow(this.mPPBrowser.outerWindowID, this.mPPBrowser);
+  }
 
-          for (var i = 0; i < scaleValues.length; i++) {
-            if (aValue == scaleValues[i]) {
-              this.mScaleCombobox.selectedIndex = i;
-              return;
-            }
-          }
-          this.mScaleCombobox.value = "Custom";
-        ]]>
-        </body>
-      </method>
+  promptForScaleValue(aValue) {
+    var value = Math.round(aValue);
+    var promptStr = this.mScaleLabel.value;
+    var renameTitle = this.mCustomTitle;
+    var result = { value };
+    let { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
+    var confirmed = Services.prompt.prompt(window, renameTitle, promptStr, result, null, { value });
+    if (!confirmed || (!result.value) || (result.value == "")) {
+      return -1;
+    }
+    return result.value;
+  }
 
-      <method name="scale">
-        <parameter name="aValue"/>
-        <body>
-        <![CDATA[
-          var settings = PrintUtils.getPrintSettings();
-          if (aValue == "ShrinkToFit") {
-            if (!settings.shrinkToFit) {
-              settings.shrinkToFit = true;
-              this.savePrintSettings(settings, settings.kInitSaveShrinkToFit | settings.kInitSaveScaling);
-              PrintUtils.printPreview();
-            }
-            return;
-          }
+  setScaleCombobox(aValue) {
+    var scaleValues = [0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.25, 1.5, 1.75, 2];
+
+    aValue = Number(aValue);
+
+    for (var i = 0; i < scaleValues.length; i++) {
+      if (aValue == scaleValues[i]) {
+        this.mScaleCombobox.selectedIndex = i;
+        return;
+      }
+    }
+    this.mScaleCombobox.value = "Custom";
+  }
 
-          if (aValue == "Custom") {
-            aValue = this.promptForScaleValue(settings.scaling * 100.0);
-            if (aValue >= 10) {
-              aValue /= 100.0;
-            } else {
-              if (this.mScaleCombobox.hasAttribute("lastValidInx")) {
-                this.mScaleCombobox.selectedIndex = this.mScaleCombobox.getAttribute("lastValidInx");
-              }
-              return;
-            }
-          }
+  scale(aValue) {
+    var settings = PrintUtils.getPrintSettings();
+    if (aValue == "ShrinkToFit") {
+      if (!settings.shrinkToFit) {
+        settings.shrinkToFit = true;
+        this.savePrintSettings(settings, settings.kInitSaveShrinkToFit | settings.kInitSaveScaling);
+        PrintUtils.printPreview();
+      }
+      return;
+    }
 
-          this.setScaleCombobox(aValue);
-          this.mScaleCombobox.setAttribute("lastValidInx", this.mScaleCombobox.selectedIndex);
-
-          if (settings.scaling != aValue || settings.shrinkToFit) {
-            settings.shrinkToFit = false;
-            settings.scaling = aValue;
-            this.savePrintSettings(settings, settings.kInitSaveShrinkToFit | settings.kInitSaveScaling);
-            PrintUtils.printPreview();
-          }
-        ]]>
-        </body>
-      </method>
+    if (aValue == "Custom") {
+      aValue = this.promptForScaleValue(settings.scaling * 100.0);
+      if (aValue >= 10) {
+        aValue /= 100.0;
+      } else {
+        if (this.mScaleCombobox.hasAttribute("lastValidInx")) {
+          this.mScaleCombobox.selectedIndex = this.mScaleCombobox.getAttribute("lastValidInx");
+        }
+        return;
+      }
+    }
 
-      <method name="orient">
-        <parameter name="aOrientation"/>
-        <body>
-        <![CDATA[
-          const kIPrintSettings = Ci.nsIPrintSettings;
-          var orientValue = (aOrientation == "portrait") ? kIPrintSettings.kPortraitOrientation :
-                                                           kIPrintSettings.kLandscapeOrientation;
-          var settings = PrintUtils.getPrintSettings();
-          if (settings.orientation != orientValue) {
-            settings.orientation = orientValue;
-            this.savePrintSettings(settings, settings.kInitSaveOrientation);
-            PrintUtils.printPreview();
-          }
-        ]]>
-        </body>
-      </method>
+    this.setScaleCombobox(aValue);
+    this.mScaleCombobox.setAttribute("lastValidInx", this.mScaleCombobox.selectedIndex);
+
+    if (settings.scaling != aValue || settings.shrinkToFit) {
+      settings.shrinkToFit = false;
+      settings.scaling = aValue;
+      this.savePrintSettings(settings, settings.kInitSaveShrinkToFit | settings.kInitSaveScaling);
+      PrintUtils.printPreview();
+    }
+  }
 
-      <method name="simplify">
-        <body>
-        <![CDATA[
-          PrintUtils.setSimplifiedMode(this.mSimplifyPageCheckbox.checked);
-          PrintUtils.printPreview();
-        ]]>
-        </body>
-      </method>
+  orient(aOrientation) {
+    const kIPrintSettings = Ci.nsIPrintSettings;
+    var orientValue = (aOrientation == "portrait") ? kIPrintSettings.kPortraitOrientation :
+      kIPrintSettings.kLandscapeOrientation;
+    var settings = PrintUtils.getPrintSettings();
+    if (settings.orientation != orientValue) {
+      settings.orientation = orientValue;
+      this.savePrintSettings(settings, settings.kInitSaveOrientation);
+      PrintUtils.printPreview();
+    }
+  }
 
-      <method name="enableSimplifyPage">
-        <body>
-        <![CDATA[
-          this.mSimplifyPageNotAllowed = false;
-          this.mSimplifyPageCheckbox.disabled = false;
-          this.mSimplifyPageCheckbox.setAttribute("tooltiptext",
-               this.mSimplifyPageCheckbox.getAttribute("tooltiptext-enabled"));
-        ]]>
-        </body>
-      </method>
+  simplify() {
+    PrintUtils.setSimplifiedMode(this.mSimplifyPageCheckbox.checked);
+    PrintUtils.printPreview();
+  }
+
+  enableSimplifyPage() {
+    this.mSimplifyPageNotAllowed = false;
+    this.mSimplifyPageCheckbox.disabled = false;
+    this.mSimplifyPageCheckbox.setAttribute("tooltiptext",
+      this.mSimplifyPageCheckbox.getAttribute("tooltiptext-enabled"));
+  }
 
-      <method name="disableSimplifyPage">
-        <body>
-        <![CDATA[
-          this.mSimplifyPageNotAllowed = true;
-          this.mSimplifyPageCheckbox.disabled = true;
-          this.mSimplifyPageCheckbox.setAttribute("tooltiptext",
-               this.mSimplifyPageCheckbox.getAttribute("tooltiptext-disabled"));
-        ]]>
-        </body>
-      </method>
+  disableSimplifyPage() {
+    this.mSimplifyPageNotAllowed = true;
+    this.mSimplifyPageCheckbox.disabled = true;
+    this.mSimplifyPageCheckbox.setAttribute("tooltiptext",
+      this.mSimplifyPageCheckbox.getAttribute("tooltiptext-disabled"));
+  }
 
-      <method name="updateToolbar">
-        <body>
-        <![CDATA[
-          var settings = PrintUtils.getPrintSettings();
+  updateToolbar() {
+    var settings = PrintUtils.getPrintSettings();
 
-          var isPortrait = settings.orientation == Ci.nsIPrintSettings.kPortraitOrientation;
-
-          this.mPortaitButton.checked = isPortrait;
-          this.mLandscapeButton.checked = !isPortrait;
+    var isPortrait = settings.orientation == Ci.nsIPrintSettings.kPortraitOrientation;
 
-          if (settings.shrinkToFit) {
-            this.mScaleCombobox.value = "ShrinkToFit";
-          } else {
-            this.setScaleCombobox(settings.scaling);
-          }
+    this.mPortaitButton.checked = isPortrait;
+    this.mLandscapeButton.checked = !isPortrait;
 
-          this.mPageTextBox.value = 1;
-        ]]>
-        </body>
-      </method>
+    if (settings.shrinkToFit) {
+      this.mScaleCombobox.value = "ShrinkToFit";
+    } else {
+      this.setScaleCombobox(settings.scaling);
+    }
+
+    this.mPageTextBox.value = 1;
+  }
 
-      <method name="savePrintSettings">
-        <parameter name="settings"/>
-        <parameter name="flags"/>
-        <body><![CDATA[
-          var PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"]
-                        .getService(Ci.nsIPrintSettingsService);
-          PSSVC.savePrintSettingsToPrefs(settings, true, flags);
-        ]]></body>
-      </method>
+  savePrintSettings(settings, flags) {
+    var PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"]
+      .getService(Ci.nsIPrintSettingsService);
+    PSSVC.savePrintSettingsToPrefs(settings, true, flags);
+  }
 
-      <method name="receiveMessage">
-        <parameter name="message"/>
-        <body>
-        <![CDATA[
-          if (message.name == "Printing:Preview:UpdatePageCount") {
-            let numPages = message.data.numPages;
-            this.mTotalPages.value = numPages;
-            this.mPageTextBox.max = numPages;
-          }
-        ]]>
-        </body>
-      </method>
-    </implementation>
-  </binding>
-
-</bindings>
+  receiveMessage(message) {
+    if (message.name == "Printing:Preview:UpdatePageCount") {
+      let numPages = message.data.numPages;
+      this.mTotalPages.value = numPages;
+      this.mPageTextBox.max = numPages;
+    }
+  }
+}, { extends: "toolbar" });
--- a/toolkit/components/printing/content/printUtils.js
+++ b/toolkit/components/printing/content/printUtils.js
@@ -540,18 +540,19 @@ var PrintUtils = {
       } else {
         this._sourceBrowser.docShellIsActive = true;
       }
 
       // show the toolbar after we go into print preview mode so
       // that we can initialize the toolbar with total num pages
       const XUL_NS =
         "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-      printPreviewTB = document.createElementNS(XUL_NS, "toolbar");
-      printPreviewTB.setAttribute("printpreview", true);
+
+      printPreviewTB = document.createElementNS(XUL_NS, "toolbar",
+        { is: "printpreview-toolbar" });
       printPreviewTB.setAttribute("fullscreentoolbar", true);
       printPreviewTB.id = "print-preview-toolbar";
 
       let navToolbox = this._listener.getNavToolbox();
       navToolbox.parentNode.insertBefore(printPreviewTB, navToolbox);
       printPreviewTB.initialize(ppBrowser);
 
       // The print preview processing may not have fully completed, so if we
--- a/toolkit/components/printing/jar.mn
+++ b/toolkit/components/printing/jar.mn
@@ -8,11 +8,11 @@ toolkit.jar:
    content/global/printPageSetup.js                 (content/printPageSetup.js)
    content/global/printPageSetup.xul                (content/printPageSetup.xul)
 #endif
    content/global/printPreviewProgress.js           (content/printPreviewProgress.js)
    content/global/printPreviewProgress.xul          (content/printPreviewProgress.xul)
    content/global/printProgress.js                  (content/printProgress.js)
    content/global/printProgress.xul                 (content/printProgress.xul)
 #endif
-   content/global/printPreviewBindings.xml          (content/printPreviewBindings.xml)
+   content/global/printPreviewToolbar.js            (content/printPreviewToolbar.js)
    content/global/printUtils.js                     (content/printUtils.js)
    content/global/simplifyMode.css                  (content/simplifyMode.css)
--- a/toolkit/components/printing/tests/browser_page_change_print_original.js
+++ b/toolkit/components/printing/tests/browser_page_change_print_original.js
@@ -36,17 +36,17 @@ add_task(async function pp_after_orienta
   });
 
   await originalTabNavigated;
 
   // Change orientation and wait for print preview to re-enter:
   let orient = PrintUtils.getPrintSettings().orientation;
   let orientToSwitchTo = orient != Ci.nsIPrintSettings.kPortraitOrientation ?
     "portrait" : "landscape";
-  let printPreviewToolbar = document.querySelector("toolbar[printpreview=true]");
+  let printPreviewToolbar = document.getElementById("print-preview-toolbar");
 
   printPreviewEntered = BrowserTestUtils.waitForMessage(ppBrowser.messageManager, "Printing:Preview:Entered");
   printPreviewToolbar.orient(orientToSwitchTo);
   await printPreviewEntered;
 
   // Check that we're still showing the original page.
   await ContentTask.spawn(ppBrowser, null, async function() {
     is(content.document.body.textContent.trim(), "INITIAL PAGE", "Should still have initial page print previewed.");
--- a/toolkit/content/customElements.js
+++ b/toolkit/content/customElements.js
@@ -32,42 +32,48 @@ class MozXULElement extends XULElement {
    * the reflector is garbage collected and the element is touched again.
    *
    * @param str
    *        String with the XML representation of XUL elements.
    *
    * @return DocumentFragment containing the corresponding element tree, including
    *         element nodes but excluding any text node.
    */
-  static parseXULToFragment(str) {
+  static parseXULToFragment(str, entities = "") {
     let doc = gXULDOMParser.parseFromString(`
+      ${entities}
       <box xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
         ${str}
       </box>
     `, "application/xml");
     // The XUL/XBL parser is set to ignore all-whitespace nodes, whereas (X)HTML
     // does not do this. Most XUL code assumes that the whitespace has been
     // stripped out, so we simply remove all text nodes after using the parser.
     let nodeIterator = doc.createNodeIterator(doc, NodeFilter.SHOW_TEXT);
     let currentNode = nodeIterator.nextNode();
     while (currentNode) {
       currentNode.remove();
       currentNode = nodeIterator.nextNode();
     }
     // We use a range here so that we don't access the inner DOM elements from
     // JavaScript before they are imported and inserted into a document.
     let range = doc.createRange();
-    range.selectNodeContents(doc.firstChild);
+    range.selectNodeContents(doc.querySelector("box"));
     return range.extractContents();
   }
 }
 
 // Attach the base class to the window so other scripts can use it:
 window.MozXULElement = MozXULElement;
 
 for (let script of [
   "chrome://global/content/elements/stringbundle.js",
   "chrome://global/content/elements/general.js",
 ]) {
   Services.scriptloader.loadSubScript(script, window);
 }
 
+customElements.setElementCreationCallback("printpreview-toolbar", type => {
+  Services.scriptloader.loadSubScript(
+    "chrome://global/content/printPreviewToolbar.js", window);
+});
+
 }
--- a/toolkit/themes/linux/global/global.css
+++ b/toolkit/themes/linux/global/global.css
@@ -116,16 +116,37 @@ sidebarheader > label {
   -moz-user-focus: ignore !important;
 }
 
 toolbar[mode="text"] .toolbarbutton-text {
   padding: 0 !important;
   margin: 3px 5px !important;
 }
 
+toolbar[is="printpreview-toolbar"] .navigate-button {
+  min-width: 1.9em;
+}
+
+toolbar[is="printpreview-toolbar"] .navigate-button > .toolbarbutton-icon {
+  display: none;
+}
+
+toolbar[is="printpreview-toolbar"] .toolbar-portrait-page {
+  list-style-image: url("moz-icon://stock/gtk-orientation-portrait?size=button");
+}
+
+toolbar[is="printpreview-toolbar"] .toolbar-landscape-page {
+  list-style-image: url("moz-icon://stock/gtk-orientation-landscape?size=button");
+}
+
+toolbar[is="printpreview-toolbar"] #pageNumber {
+  /* 3 chars + 4px padding left + 2px padding right + 2*6px border */
+  width: calc(18px + 3ch);
+}
+
 /* ::::: miscellaneous formatting ::::: */
 
 :root:-moz-lwtheme {
   -moz-appearance: none;
 }
 
 :root[lwtheme-image]:-moz-lwtheme-darktext {
   text-shadow: 0 -0.5px 1.5px white;
--- a/toolkit/themes/linux/global/jar.mn
+++ b/toolkit/themes/linux/global/jar.mn
@@ -16,17 +16,16 @@ toolkit.jar:
    skin/classic/global/groupbox.css
    skin/classic/global/listbox.css
    skin/classic/global/menu.css
    skin/classic/global/menulist.css
    skin/classic/global/netError.css
 *  skin/classic/global/notification.css
 *  skin/classic/global/numberbox.css
    skin/classic/global/popup.css
-   skin/classic/global/printPreview.css
    skin/classic/global/radio.css
    skin/classic/global/scrollbox.css
    skin/classic/global/splitter.css
    skin/classic/global/tabbox.css
    skin/classic/global/textbox.css
    skin/classic/global/toolbar.css
    skin/classic/global/toolbarbutton.css
    skin/classic/global/tree.css
deleted file mode 100644
--- a/toolkit/themes/linux/global/printPreview.css
+++ /dev/null
@@ -1,24 +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/. */
-
-.navigate-button {
-  min-width: 1.9em;
-}
-
-.navigate-button > .toolbarbutton-icon {
-  display: none;
-}
-
-.toolbar-portrait-page {
-  list-style-image: url("moz-icon://stock/gtk-orientation-portrait?size=button");
-}
-
-.toolbar-landscape-page {
-  list-style-image: url("moz-icon://stock/gtk-orientation-landscape?size=button");
-}
-
-#pageNumber {
-  /* 3 chars + 4px padding left + 2px padding right + 2*6px border */
-  width: calc(18px + 3ch);
-}
--- a/toolkit/themes/windows/global/global.css
+++ b/toolkit/themes/windows/global/global.css
@@ -126,16 +126,38 @@ sidebarheader > label {
   -moz-user-focus: ignore !important;
 }
 
 toolbar[mode="text"] .toolbarbutton-text {
   padding: 0 !important;
   margin: 3px 5px !important;
 }
 
+toolbar[is="printpreview-toolbar"] .navigate-button {
+  min-width: 1.9em;
+}
+
+toolbar[is="printpreview-toolbar"] .navigate-button > .toolbarbutton-icon {
+  display: none;
+}
+
+toolbar[is="printpreview-toolbar"] .toolbar-portrait-page {
+  list-style-image: url("chrome://global/skin/icons/Print-preview.png");
+  -moz-image-region: rect(0px 16px 16px 0px);
+}
+
+toolbar[is="printpreview-toolbar"] .toolbar-landscape-page {
+  list-style-image: url("chrome://global/skin/icons/Print-preview.png");
+  -moz-image-region: rect(0px 32px 16px 16px);
+}
+
+toolbar[is="printpreview-toolbar"] #pageNumber {
+  width: 3ch;
+}
+
 /* ::::: miscellaneous formatting ::::: */
 
 :root[lwtheme-image]:-moz-lwtheme-darktext {
   text-shadow: 0 -0.5px 1.5px white;
 }
 
 :root[lwtheme-image]:-moz-lwtheme-brighttext {
   text-shadow: 1px 1px 1.5px black;
--- a/toolkit/themes/windows/global/jar.mn
+++ b/toolkit/themes/windows/global/jar.mn
@@ -20,17 +20,16 @@ toolkit.jar:
   skin/classic/global/commonDialog.css
 * skin/classic/global/findBar.css
 * skin/classic/global/global.css
   skin/classic/global/listbox.css
   skin/classic/global/netError.css
 * skin/classic/global/numberbox.css
 * skin/classic/global/notification.css
   skin/classic/global/printPageSetup.css
-  skin/classic/global/printPreview.css
   skin/classic/global/scrollbox.css
   skin/classic/global/splitter.css
   skin/classic/global/toolbar.css
   skin/classic/global/toolbarbutton.css
 * skin/classic/global/tree.css
 * skin/classic/global/alerts/alert.css                     (alerts/alert.css)
   skin/classic/global/arrow/arrow-lft.gif                  (arrow/arrow-lft.gif)
   skin/classic/global/arrow/arrow-lft-dis.gif              (arrow/arrow-lft-dis.gif)
deleted file mode 100644
--- a/toolkit/themes/windows/global/printPreview.css
+++ /dev/null
@@ -1,25 +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/. */
-
-.navigate-button {
-  min-width: 1.9em;
-}
-
-.navigate-button > .toolbarbutton-icon {
-  display: none;
-}
-
-.toolbar-portrait-page {
-  list-style-image: url("chrome://global/skin/icons/Print-preview.png");
-  -moz-image-region: rect(0px 16px 16px 0px);
-}
-
-.toolbar-landscape-page {
-  list-style-image: url("chrome://global/skin/icons/Print-preview.png");
-  -moz-image-region: rect(0px 32px 16px 16px);
-}
-
-#pageNumber {
-  width: 3ch;
-}