Bug 1450813 - Create print preview toolbar as customized built-in Custom Element;r=mconley,timdream
authorBrian Grinstead <bgrinstead@mozilla.com>
Mon, 02 Jul 2018 13:33:09 -0700
changeset 424701 71d21ed3f88e478b63557e702aa56aa33303d2f1
parent 424700 583f3742e6c918ab1824e6e351adc3317e9af020
child 424702 d7750a13f9b5dae0aa5779da04a3c62a9941abd8
push id65827
push userbgrinstead@mozilla.com
push dateMon, 02 Jul 2018 21:24:23 +0000
treeherderautoland@71d21ed3f88e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley, timdream
bugs1450813
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1450813 - Create print preview toolbar as customized built-in Custom Element;r=mconley,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;
-}