Bug 1120967 - Broken middle/right click on links via about:preferences pages. r=jaws
authorFischer.json <fischer.json@gmail.com>
Fri, 19 Aug 2016 17:32:31 +0800
changeset 354411 7ec0c9ca41df45727d4526f039b2eeb441a8356f
parent 354410 4329f53c9f9f6cb4733cc46382dfd0eaa086c772
child 354412 b9d4367322d408fbc42c01268b477cd92fee76c1
push id6570
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:26:13 +0000
treeherdermozilla-beta@f455459b2ae5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjaws
bugs1120967
milestone51.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 1120967 - Broken middle/right click on links via about:preferences pages. r=jaws MozReview-Commit-ID: FejMTP3invj
browser/base/content/browser.js
browser/base/content/nsContextMenu.js
browser/base/content/sync/utils.js
browser/base/content/test/general/browser.ini
browser/base/content/test/general/browser_contextmenu.js
browser/base/content/test/general/subtst_contextmenu_xul.xul
browser/base/content/utilityOverlay.js
browser/components/preferences/in-content/advanced.xul
browser/components/preferences/in-content/applications.xul
browser/components/preferences/in-content/content.xul
browser/components/preferences/in-content/main.xul
browser/components/preferences/in-content/preferences.js
browser/components/preferences/in-content/privacy.xul
browser/components/preferences/in-content/search.js
browser/components/preferences/in-content/search.xul
browser/components/preferences/in-content/security.xul
browser/components/preferences/in-content/sync.js
browser/components/preferences/in-content/sync.xul
browser/themes/shared/incontentprefs/preferences.inc.css
toolkit/mozapps/update/content/history.xul
toolkit/themes/shared/in-content/common.inc.css
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3709,21 +3709,24 @@ const BrowserSearch = {
 
   /**
    * Returns the search bar element if it is present in the toolbar, null otherwise.
    */
   get searchBar() {
     return document.getElementById("searchbar");
   },
 
+  get searchEnginesURL() {
+    return formatURL("browser.search.searchEnginesURL", true);
+  },
+
   loadAddEngines: function BrowserSearch_loadAddEngines() {
     var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow");
     var where = newWindowPref == 3 ? "tab" : "window";
-    var searchEnginesURL = formatURL("browser.search.searchEnginesURL", true);
-    openUILinkIn(searchEnginesURL, where);
+    openUILinkIn(this.searchEnginesURL, where);
   },
 
   get _isExtendedTelemetryEnabled() {
     return Services.prefs.getBoolPref("toolkit.telemetry.enabled");
   },
 
   _getSearchEngineId: function (engine) {
     if (engine && engine.identifier) {
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -598,18 +598,19 @@ nsContextMenu.prototype = {
     if (this.isRemote) {
       aNode = gContextMenuContentData.event.target;
       aRangeParent = gContextMenuContentData.event.rangeParent;
       aRangeOffset = gContextMenuContentData.event.rangeOffset;
       editFlags = gContextMenuContentData.editFlags;
     }
 
     const xulNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-    if (aNode.namespaceURI == xulNS ||
-        aNode.nodeType == Node.DOCUMENT_NODE) {
+    if (aNode.nodeType == Node.DOCUMENT_NODE ||
+        // Not display on XUL element but relax for <label class="text-link">
+        (aNode.namespaceURI == xulNS && !isXULTextLinkLabel(aNode))) {
       this.shouldDisplay = false;
       return;
     }
 
     // Initialize contextual info.
     this.onImage           = false;
     this.onLoadedImage     = false;
     this.onCompletedImage  = false;
@@ -793,17 +794,18 @@ nsContextMenu.prototype = {
     const XMLNS = "http://www.w3.org/XML/1998/namespace";
     var elem = this.target;
     while (elem) {
       if (elem.nodeType == Node.ELEMENT_NODE) {
         // Link?
         if (!this.onLink &&
             // Be consistent with what hrefAndLinkNodeForClickEvent
             // does in browser.js
-             ((elem instanceof HTMLAnchorElement && elem.href) ||
+             (isXULTextLinkLabel(elem) ||
+              (elem instanceof HTMLAnchorElement && elem.href) ||
               (elem instanceof HTMLAreaElement && elem.href) ||
               elem instanceof HTMLLinkElement ||
               elem.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple")) {
 
           // Target is a link or a descendant of a link.
           this.onLink = true;
 
           // Remember corresponding element.
@@ -893,16 +895,23 @@ nsContextMenu.prototype = {
           InlineSpellCheckerUI.init(editingSession.getEditorForWindow(targetWin));
           InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
         }
         var canSpell = InlineSpellCheckerUI.canSpellCheck && this.canSpellCheck;
         this.showItem("spell-check-enabled", canSpell);
         this.showItem("spell-separator", canSpell);
       }
     }
+
+    function isXULTextLinkLabel(node) {
+      return node.namespaceURI == xulNS &&
+             node.tagName == "label" &&
+             node.classList.contains('text-link') &&
+             node.href;
+    }
   },
 
   // Returns the computed style attribute for the given element.
   getComputedStyle: function(aElem, aProp) {
     return aElem.ownerDocument
                 .defaultView
                 .getComputedStyle(aElem, "").getPropertyValue(aProp);
   },
--- a/browser/base/content/sync/utils.js
+++ b/browser/base/content/sync/utils.js
@@ -72,24 +72,32 @@ var gSyncUtils = {
     if (Weave.Utils.ensureMPUnlocked())
       this.openChange("UpdatePassphrase");
   },
 
   resetPassword: function () {
     this._openLink(Weave.Service.pwResetURL);
   },
 
-  openToS: function () {
+  get tosURL() {
     let root = this.fxAccountsEnabled ? "fxa." : "";
-    this._openLink(Weave.Svc.Prefs.get(root + "termsURL"));
+    return  Weave.Svc.Prefs.get(root + "termsURL");
+  },
+
+  openToS: function () {
+    this._openLink(this.tosURL);
+  },
+
+  get privacyPolicyURL() {
+    let root = this.fxAccountsEnabled ? "fxa." : "";
+    return  Weave.Svc.Prefs.get(root + "privacyURL");
   },
 
   openPrivacyPolicy: function () {
-    let root = this.fxAccountsEnabled ? "fxa." : "";
-    this._openLink(Weave.Svc.Prefs.get(root + "privacyURL"));
+    this._openLink(this.privacyPolicyURL);
   },
 
   /**
    * Prepare an invisible iframe with the passphrase backup document.
    * Used by both the print and saving methods.
    *
    * @param elid : ID of the form element containing the passphrase.
    * @param callback : Function called once the iframe has loaded.
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -82,16 +82,17 @@ support-files =
   ssl_error_reports.sjs
   popup_blocker.html
   print_postdata.sjs
   searchSuggestionEngine.sjs
   searchSuggestionEngine.xml
   searchSuggestionEngine2.xml
   subtst_contextmenu.html
   subtst_contextmenu_input.html
+  subtst_contextmenu_xul.xul
   test-mixedcontent-securityerrors.html
   test_bug435035.html
   test_bug462673.html
   test_bug628179.html
   test_bug839103.html
   test_bug959531.html
   test_process_flags_chrome.html
   title_test.svg
--- a/browser/base/content/test/general/browser_contextmenu.js
+++ b/browser/base/content/test/general/browser_contextmenu.js
@@ -5,28 +5,58 @@ let LOGIN_FILL_ITEMS = [
   "---", null,
   "fill-login", null,
     [
       "fill-login-no-logins", false,
       "---", null,
       "fill-login-saved-passwords", true
     ], null,
 ];
-
 let hasPocket = Services.prefs.getBoolPref("extensions.pocket.enabled");
 let hasContainers = Services.prefs.getBoolPref("privacy.userContext.enabled");
 
-add_task(function* test_setup() {
-  const example_base = "http://example.com/browser/browser/base/content/test/general/";
-  const url = example_base + "subtst_contextmenu.html";
+const example_base = "http://example.com/browser/browser/base/content/test/general/";
+const chrome_base = "chrome://mochitests/content/browser/browser/base/content/test/general/";
+
+Services.scriptloader.loadSubScript(chrome_base + "contextmenu_common.js", this);
+
+// Below are test cases for XUL element
+add_task(function* test_xul_text_link_label() {
+  let  url = chrome_base + "subtst_contextmenu_xul.xul";
+
   yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
 
-  const chrome_base = "chrome://mochitests/content/browser/browser/base/content/test/general/";
-  const contextmenu_common = chrome_base + "contextmenu_common.js";
-  Services.scriptloader.loadSubScript(contextmenu_common, this);
+  yield test_contextmenu("#test-xul-text-link-label",
+    ["context-openlinkintab", true,
+     ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+     // We need a blank entry here because the containers submenu is
+     // dynamically generated with no ids.
+     ...(hasContainers ? ["", null] : []),
+     "context-openlink",      true,
+     "context-openlinkprivate", true,
+     "---",                   null,
+     "context-bookmarklink",  true,
+     "context-savelink",      true,
+     ...(hasPocket ? ["context-savelinktopocket", true] : []),
+     "context-copylink",      true,
+     "context-searchselect",  true
+    ]
+  );
+
+  // Clean up so won't affect HTML element test cases
+  lastElementSelector = null;
+  gBrowser.removeCurrentTab();
+});
+
+// Below are test cases for HTML element
+
+add_task(function* test_setup_html() {
+  let url = example_base + "subtst_contextmenu.html";
+
+  yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
 
   yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
     let doc = content.document;
     let videoIframe = doc.querySelector("#test-video-in-iframe");
     let video = videoIframe.contentDocument.querySelector("video");
     let awaitPause = ContentTaskUtils.waitForEvent(video, "pause");
     video.pause();
     yield awaitPause;
@@ -921,17 +951,17 @@ add_task(function* test_link_sendlinktod
       *onContextMenuShown() {
         yield openMenuItemSubmenu("context-sendlinktodevice");
       }
     });
 
   restoreRemoteClients(oldGetter);
 });
 
-add_task(function* test_cleanup() {
+add_task(function* test_cleanup_html() {
   gBrowser.removeCurrentTab();
 });
 
 /**
  * Selects the text of the element that matches the provided `selector`
  *
  * @param {String} selector
  *        A selector passed to querySelector to find
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/subtst_contextmenu_xul.xul
@@ -0,0 +1,9 @@
+<?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/. -->
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+      xmlns:html="http://www.w3.org/1999/xhtml">
+  <label id="test-xul-text-link-label" class="text-link" value="XUL text-link label" href="https://www.mozilla.com"/>
+</page>
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -820,23 +820,26 @@ function openNewWindowWith(aURL, aDocume
              { charset: originCharset,
                postData: aPostData,
                allowThirdPartyFixup: aAllowThirdPartyFixup,
                referrerURI: aReferrer,
                referrerPolicy: aReferrerPolicy,
              });
 }
 
-// aCalledFromModal is optional
-function openHelpLink(aHelpTopic, aCalledFromModal, aWhere) {
+function getHelpLinkURL(aHelpTopic) {
   var url = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
                       .getService(Components.interfaces.nsIURLFormatter)
                       .formatURLPref("app.support.baseURL");
-  url += aHelpTopic;
+  return url + aHelpTopic;
+}
 
+// aCalledFromModal is optional
+function openHelpLink(aHelpTopic, aCalledFromModal, aWhere) {
+  var url = getHelpLinkURL(aHelpTopic);
   var where = aWhere;
   if (!aWhere)
     where = aCalledFromModal ? "window" : "tab";
 
   openUILinkIn(url, where);
 }
 
 function openPrefsHelp() {
--- a/browser/components/preferences/in-content/advanced.xul
+++ b/browser/components/preferences/in-content/advanced.xul
@@ -115,18 +115,17 @@
 #endif
   <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
 
 <hbox id="header-advanced"
       class="header"
       hidden="true"
       data-category="paneAdvanced">
   <label class="header-name" flex="1">&paneAdvanced.title;</label>
-  <button class="help-button"
-          aria-label="&helpButton.label;"/>
+  <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
 </hbox>
 
 <tabbox id="advancedPrefs"
         handleCtrlTab="false"
         handleCtrlPageUpDown="false"
         flex="1"
         data-category="paneAdvanced"
         hidden="true">
--- a/browser/components/preferences/in-content/applications.xul
+++ b/browser/components/preferences/in-content/applications.xul
@@ -53,23 +53,22 @@
 </preferences>
 
 <keyset>
   <!-- These <key>s have oncommand attributes because of bug 371900 -->
   <key key="&focusSearch1.key;" modifiers="accel" id="focusSearch1" oncommand=";"/>
   <key key="&focusSearch2.key;" modifiers="accel" id="focusSearch2" oncommand=";"/>
 </keyset>
 
-<hbox id="header-application"
+<hbox id="header-applications"
       class="header"
       hidden="true"
       data-category="paneApplications">
   <label class="header-name" flex="1">&paneApplications.title;</label>
-  <button class="help-button"
-          aria-label="&helpButton.label;"/>
+  <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
 </hbox>
 
 <vbox id="applicationsContent"
       data-category="paneApplications"
       hidden="true"
       flex="1">
   <hbox>
     <textbox id="filter" flex="1"
--- a/browser/components/preferences/in-content/content.xul
+++ b/browser/components/preferences/in-content/content.xul
@@ -32,18 +32,17 @@
 <script type="application/javascript"
         src="chrome://browser/content/preferences/in-content/content.js"/>
 
 <hbox id="header-content"
       class="header"
       hidden="true"
       data-category="paneContent">
   <label class="header-name" flex="1">&paneContent.title;</label>
-  <button class="help-button"
-          aria-label="&helpButton.label;"/>
+  <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
 </hbox>
 
 <groupbox id="drmGroup" data-category="paneContent" hidden="true">
   <caption><label>&drmContent.label;</label></caption>
   <grid id="contentGrid2">
     <columns>
       <column flex="1"/>
       <column/>
--- a/browser/components/preferences/in-content/main.xul
+++ b/browser/components/preferences/in-content/main.xul
@@ -110,18 +110,17 @@
                 type="bool"/>
 </preferences>
 
 <hbox id="header-general"
       class="header"
       hidden="true"
       data-category="paneGeneral">
   <label class="header-name" flex="1">&paneGeneral.title;</label>
-  <button class="help-button"
-          aria-label="&helpButton.label;"/>
+  <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
 </hbox>
 
 <!-- Startup -->
 <groupbox id="startupGroup"
           data-category="paneGeneral"
           hidden="true">
   <caption><label>&startup.label;</label></caption>
 
--- a/browser/components/preferences/in-content/preferences.js
+++ b/browser/components/preferences/in-content/preferences.js
@@ -73,19 +73,23 @@ function init_all() {
   init_dynamic_padding();
 
   var initFinished = new CustomEvent("Initialized", {
     'bubbles': true,
     'cancelable': true
   });
   document.dispatchEvent(initFinished);
 
-  let helpCmds = document.querySelectorAll(".help-button");
-  for (let helpCmd of helpCmds)
-    helpCmd.addEventListener("command", helpButtonCommand);
+  categories = categories.querySelectorAll("richlistitem.category");
+  for (let category of categories) {
+    let name = internalPrefCategoryNameToFriendlyName(category.value);
+    let helpSelector = `#header-${name} > .help-button`;
+    let helpButton = document.querySelector(helpSelector);
+    helpButton.setAttribute("href", getHelpLinkURL(category.getAttribute("helpTopic")));
+  }
 
   // Wait until initialization of all preferences are complete before
   // notifying observers that the UI is now ready.
   Services.obs.notifyObservers(window, "advanced-pane-loaded", null);
 }
 
 // Make the space above the categories list shrink on low window heights
 function init_dynamic_padding() {
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -75,18 +75,17 @@
               type="bool"/>
 </preferences>
 
 <hbox id="header-privacy"
       class="header"
       hidden="true"
       data-category="panePrivacy">
   <label class="header-name" flex="1">&panePrivacy.title;</label>
-  <button class="help-button"
-          aria-label="&helpButton.label;"/>
+  <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
 </hbox>
 
 <!-- Tracking -->
 <groupbox id="trackingGroup" data-category="panePrivacy" hidden="true">
   <vbox id="trackingprotectionbox" hidden="true">
     <hbox align="start">
       <vbox>
         <caption><label>&trackingProtectionHeader.label;
--- a/browser/components/preferences/in-content/search.js
+++ b/browser/components/preferences/in-content/search.js
@@ -31,16 +31,21 @@ var gSearchPane = {
   },
 
   init: function ()
   {
     gEngineView = new EngineView(new EngineStore());
     document.getElementById("engineList").view = gEngineView;
     this.buildDefaultEngineDropDown();
 
+    let addEnginesLink = document.getElementById("addEngines");
+    let searchEnginesURL = Services.wm.getMostRecentWindow('navigator:browser')
+                                      .BrowserSearch.searchEnginesURL;
+    addEnginesLink.setAttribute("href", searchEnginesURL);
+
     window.addEventListener("click", this, false);
     window.addEventListener("command", this, false);
     window.addEventListener("dragstart", this, false);
     window.addEventListener("keypress", this, false);
     window.addEventListener("select", this, false);
     window.addEventListener("blur", this, true);
 
     Services.obs.addObserver(this, "browser-search-engine-modified", false);
@@ -118,20 +123,16 @@ var gSearchPane = {
           if (engineList.inputField.hidden && engineList.view) {
             let selection = engineList.view.selection;
             if (selection.count > 0) {
               selection.toggleSelect(selection.currentIndex);
             }
             engineList.blur();
           }
         }
-        if (aEvent.target.id == "addEngines" && aEvent.button == 0) {
-          Services.wm.getMostRecentWindow('navigator:browser')
-                     .BrowserSearch.loadAddEngines();
-        }
         break;
       case "command":
         switch (aEvent.target.id) {
           case "":
             if (aEvent.target.parentNode &&
                 aEvent.target.parentNode.parentNode &&
                 aEvent.target.parentNode.parentNode.id == "defaultEngine") {
               gSearchPane.setDefaultEngine();
--- a/browser/components/preferences/in-content/search.xul
+++ b/browser/components/preferences/in-content/search.xul
@@ -23,18 +23,17 @@
 
     <stringbundle id="engineManagerBundle" src="chrome://browser/locale/engineManager.properties"/>
 
     <hbox id="header-search"
           class="header"
           hidden="true"
           data-category="paneSearch">
       <label class="header-name" flex="1">&paneSearch.title;</label>
-      <button class="help-button"
-              aria-label="&helpButton.label;"/>
+      <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
     </hbox>
 
     <!-- Default Search Engine -->
     <groupbox id="defaultEngineGroup" align="start" data-category="paneSearch">
       <caption label="&defaultSearchEngine.label;"/>
       <label>&chooseYourDefaultSearchEngine.label;</label>
       <menulist id="defaultEngine">
         <menupopup/>
--- a/browser/components/preferences/in-content/security.xul
+++ b/browser/components/preferences/in-content/security.xul
@@ -48,18 +48,17 @@
 
 </preferences>
 
 <hbox id="header-security"
       class="header"
       hidden="true"
       data-category="paneSecurity">
   <label class="header-name" flex="1">&paneSecurity.title;</label>
-  <button class="help-button"
-          aria-label="&helpButton.label;"/>
+  <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
 </hbox>
 
 <!-- addons, forgery (phishing) UI -->
 <groupbox id="addonsPhishingGroup" data-category="paneSecurity" hidden="true">
   <caption><label>&general.label;</label></caption>
 
   <hbox id="addonInstallBox">
     <checkbox id="warnAddonInstall"
--- a/browser/components/preferences/in-content/sync.js
+++ b/browser/components/preferences/in-content/sync.js
@@ -137,18 +137,29 @@ var gSyncPane = {
     });
 
     XPCOMUtils.defineLazyGetter(this, '_accountsStringBundle', () => {
       return Services.strings.createBundle("chrome://browser/locale/accounts.properties");
     });
 
     let url = Services.prefs.getCharPref("identity.mobilepromo.android") + "sync-preferences";
     document.getElementById("fxaMobilePromo-android").setAttribute("href", url);
+    document.getElementById("fxaMobilePromo-android-hasFxaAccount").setAttribute("href", url);
     url = Services.prefs.getCharPref("identity.mobilepromo.ios") + "sync-preferences";
     document.getElementById("fxaMobilePromo-ios").setAttribute("href", url);
+    document.getElementById("fxaMobilePromo-ios-hasFxaAccount").setAttribute("href", url);
+
+    document.getElementById("tosPP-small-ToS").setAttribute("href", gSyncUtils.tosURL);
+    document.getElementById("tosPP-normal-ToS").setAttribute("href", gSyncUtils.tosURL);
+    document.getElementById("tosPP-small-PP").setAttribute("href", gSyncUtils.privacyPolicyURL);
+    document.getElementById("tosPP-normal-PP").setAttribute("href", gSyncUtils.privacyPolicyURL);
+
+    fxAccounts.promiseAccountsManageURI(this._getEntryPoint()).then(url => {
+      document.getElementById("verifiedManage").setAttribute("href", url);
+    });
 
     this.updateWeavePrefs();
 
     this._initProfileImageUI();
   },
 
   _toggleComputerNameControls: function(editMode) {
     let textbox = document.getElementById("fxaSyncComputerName");
@@ -235,18 +246,16 @@ var gSyncPane = {
       this._toggleComputerNameControls(false);
       this._updateComputerNameValue(true);
       this._focusAfterComputerNameTextbox();
     });
     setEventListener("unlinkDevice", "click", function () {
       gSyncPane.startOver(true);
       return false;
     });
-    setEventListener("tosPP-normal-ToS", "click", gSyncPane.openToS);
-    setEventListener("tosPP-normal-PP", "click", gSyncPane.openPrivacyPolicy);
     setEventListener("loginErrorUpdatePass", "click", function () {
       gSyncPane.updatePass();
       return false;
     });
     setEventListener("loginErrorResetPass", "click", function () {
       gSyncPane.resetPass();
       return false;
     });
@@ -271,18 +280,16 @@ var gSyncPane = {
       /* no warning as account can't have previously synced */
       gSyncPane.unlinkFirefoxAccount(false);
     });
     setEventListener("rejectReSignIn", "command",
       gSyncPane.reSignIn);
     setEventListener("rejectUnlinkFxaAccount", "command", function () {
       gSyncPane.unlinkFirefoxAccount(true);
     });
-    setEventListener("tosPP-small-ToS", "click", gSyncPane.openToS);
-    setEventListener("tosPP-small-PP", "click", gSyncPane.openPrivacyPolicy);
     setEventListener("fxaSyncComputerName", "keypress", function (e) {
       if (e.keyCode == KeyEvent.DOM_VK_RETURN) {
         document.getElementById("fxaSaveChangeDeviceName").click();
       } else if (e.keyCode == KeyEvent.DOM_VK_ESCAPE) {
         document.getElementById("fxaCancelChangeDeviceName").click();
       }
     });
   },
@@ -518,26 +525,16 @@ var gSyncPane = {
     let browser = window.QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIWebNavigation)
                         .QueryInterface(Ci.nsIDocShell)
                         .chromeEventHandler;
     // And tell it to load our URL.
     browser.loadURI(url);
   },
 
-  openPrivacyPolicy: function(aEvent) {
-    aEvent.stopPropagation();
-    gSyncUtils.openPrivacyPolicy();
-  },
-
-  openToS: function(aEvent) {
-    aEvent.stopPropagation();
-    gSyncUtils.openToS();
-  },
-
   signUp: function() {
     this._openAboutAccounts("signup");
   },
 
   signIn: function() {
     this._openAboutAccounts("signin");
   },
 
--- a/browser/components/preferences/in-content/sync.xul
+++ b/browser/components/preferences/in-content/sync.xul
@@ -30,18 +30,17 @@
 <script type="application/javascript"
         src="chrome://browser/content/sync/utils.js"/>
 
 <hbox id="header-sync"
       class="header"
       hidden="true"
       data-category="paneSync">
   <label class="header-name" flex="1">&paneSync.title;</label>
-  <button class="help-button"
-          aria-label="&helpButton.label;"/>
+  <html:a class="help-button text-link" target="_blank" aria-label="&helpButton.label;"></html:a>
 </hbox>
 
 <deck id="weavePrefsDeck" data-category="paneSync" hidden="true">
   <!-- These panels are for the "legacy" sync provider -->
   <vbox id="noAccount" align="center">
     <spacer flex="1"/>
     <description id="syncDesc">
       &weaveDesc.label;
@@ -229,17 +228,16 @@
                     tooltiptext="&profilePicture.tooltip;"/>
               </vbox>
               <vbox flex="1" pack="center">
                 <label id="fxaDisplayName" hidden="true"/>
                 <label id="fxaEmailAddress1"/>
                 <hbox class="fxaAccountBoxButtons" align="center">
                   <button id="fxaUnlinkButton" label="&disconnect.label;"/>
                   <label id="verifiedManage" class="text-link"
-                         onclick="gSyncPane.openManageFirefoxAccount(event);"
                          onkeypress="gSyncPane.openManageFirefoxAccount(event);"><!--
                   -->&verifiedManage.label;</label>
                 </hbox>
               </vbox>
             </hbox>
 
             <!-- logged in to an unverified account -->
             <hbox id="fxaLoginUnverified" class="fxaAccountBox">
@@ -335,22 +333,20 @@
           <button id="fxaSaveChangeDeviceName"
                   label="&saveChangeSyncDeviceName.label;"
                   hidden="true"/>
         </hbox>
       </hbox>
     </groupbox>
     <label class="fxaMobilePromo">
         &mobilePromo3.start;<!-- We put these comments to avoid inserting white spaces
-        --><label class="androidLink text-link"
-                  href="https://www.mozilla.org/firefox/android/?utm_source=firefox-browser&amp;utm_medium=firefox-browser&amp;utm_campaign=sync-preferences"><!--
+        --><label class="androidLink text-link" id="fxaMobilePromo-android-hasFxaAccount"><!--
         -->&mobilePromo3.androidLink;</label><!--
         -->&mobilePromo3.iOSBefore;<!--
-        --><label class="iOSLink text-link"
-                  href="https://www.mozilla.org/firefox/ios/?utm_source=firefox-browser&amp;utm_medium=firefox-browser&amp;utm_campaign=sync-preferences"><!--
+        --><label class="iOSLink text-link" id="fxaMobilePromo-ios-hasFxaAccount"><!--
         -->&mobilePromo3.iOSLink;</label><!--
         -->&mobilePromo3.end;
     </label>
     <vbox id="tosPP-small" align="start">
       <label id="tosPP-small-ToS" class="text-link">
         &prefs.tosLink.label;
       </label>
       <label id="tosPP-small-PP" class="text-link">
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -97,16 +97,25 @@ treecol {
 
 @media (max-width: 800px) {
   .category-name {
     display: none;
   }
 }
 
 /* header */
+.header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.header[hidden=true] {
+  display: none;
+}
 
 #header-advanced {
   border-bottom: none;
   padding-bottom: 0;
 }
 
 /* General Pane */
 
--- a/toolkit/mozapps/update/content/history.xul
+++ b/toolkit/mozapps/update/content/history.xul
@@ -19,22 +19,21 @@
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         style="width: 35em;"
         buttons="cancel"
         defaultButton="cancel"
         buttonlabelcancel="&closebutton.label;"
         title="&history.title;"
         onload="gUpdateHistory.onLoad();">
 
-  <script type="application/javascript" 
+  <script type="application/javascript"
           src="chrome://mozapps/content/update/history.js"/>
-          
-  <stringbundle id="updateBundle" 
+
+  <stringbundle id="updateBundle"
                 src="chrome://mozapps/locale/update/updates.properties"/>
-  
+
   <label>&history.intro;</label>
   <separator class="thin"/>
   <richlistbox id="historyItems" flex="1">
     <label>&noupdates.label;</label>
   </richlistbox>
   <separator class="thin"/>
 </dialog>
-
--- a/toolkit/themes/shared/in-content/common.inc.css
+++ b/toolkit/themes/shared/in-content/common.inc.css
@@ -292,16 +292,36 @@ xul|*.help-button > xul|*.button-box > x
   width: 16px;
   height: 16px;
 }
 
 xul|*.help-button > xul|*.button-box > xul|*.button-text {
   display: none;
 }
 
+html|*.help-button {
+  width: 16px;
+  height: 16px;
+  border: 0;
+  padding: 0;
+  display: inline-block;
+  background-image: url("chrome://global/skin/in-content/help-glyph.svg#help");
+  background-repeat: no-repeat;
+  background-position: center center;
+  background-size: contain;
+}
+
+html|*.help-button:hover {
+  background-image: url("chrome://global/skin/in-content/help-glyph.svg#help-hover");
+}
+
+html|*.help-button:hover:active {
+  background-image: url("chrome://global/skin/in-content/help-glyph.svg#help-pressed");
+}
+
 xul|*.spinbuttons-button {
   min-height: initial;
   margin-inline-start: 10px !important;
   margin-inline-end: 2px !important;
 }
 
 xul|*.spinbuttons-up {
   margin-top: 2px !important;