Bug 1177162 - Show an info panel on the tracking protection doorhanger anchor the first time tracking elements are blocked. r=bgrins,ttaubert
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Thu, 16 Jul 2015 13:38:20 -0700
changeset 253549 d253511f576583d7b556d6f195c77eeb423f27de
parent 253548 081e6aa562f296c2a5157f6633dfe20decc93f92
child 253550 879c46e60e5c5052d209e8f4c32d1d7a78047b4a
push id29070
push userttaubert@mozilla.com
push dateMon, 20 Jul 2015 07:15:19 +0000
treeherdermozilla-central@202e9233d130 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbgrins, ttaubert
bugs1177162
milestone42.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 1177162 - Show an info panel on the tracking protection doorhanger anchor the first time tracking elements are blocked. r=bgrins,ttaubert
browser/app/profile/firefox.js
browser/base/content/browser-trackingprotection.js
browser/components/uitour/UITour.jsm
browser/components/uitour/test/browser.ini
browser/components/uitour/test/browser_UITour3.js
browser/components/uitour/test/browser_trackingProtection.js
browser/components/uitour/test/uitour.html
browser/locales/en-US/chrome/browser/browser.properties
browser/themes/shared/UITour.inc.css
testing/profiles/prefs_general.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1915,16 +1915,18 @@ pref("experiments.supported", true);
 pref("media.gmp-provider.enabled", true);
 
 pref("browser.apps.URL", "https://marketplace.firefox.com/discovery/");
 
 #ifdef NIGHTLY_BUILD
 pref("browser.polaris.enabled", false);
 pref("privacy.trackingprotection.ui.enabled", false);
 #endif
+pref("privacy.trackingprotection.introCount", 0);
+pref("privacy.trackingprotection.introURL", "https://support.mozilla.org/kb/tracking-protection-firefox");
 
 #ifdef NIGHTLY_BUILD
 // At the moment, autostart.2 is used, while autostart.1 is unused.
 // We leave it here set to false to reset users' defaults and allow
 // us to change everybody to true in the future, when desired.
 pref("browser.tabs.remote.autostart.1", false);
 pref("browser.tabs.remote.autostart.2", true);
 #endif
--- a/browser/base/content/browser-trackingprotection.js
+++ b/browser/base/content/browser-trackingprotection.js
@@ -1,13 +1,14 @@
 # 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/.
 
 let TrackingProtection = {
+  MAX_INTROS: 0,
   PREF_ENABLED_GLOBALLY: "privacy.trackingprotection.enabled",
   PREF_ENABLED_IN_PRIVATE_WINDOWS: "privacy.trackingprotection.pbmode.enabled",
   enabledGlobally: false,
   enabledInPrivateWindows: false,
 
   init() {
     let $ = selector => document.querySelector(selector);
     this.container = $("#tracking-protection-container");
@@ -66,16 +67,26 @@ let TrackingProtection = {
         element.setAttribute("state", "blocked-tracking-content");
       } else if (state & STATE_LOADED_TRACKING_CONTENT) {
         element.setAttribute("state", "loaded-tracking-content");
       } else {
         element.removeAttribute("state");
       }
     }
 
+    if (state & STATE_BLOCKED_TRACKING_CONTENT) {
+      // Open the tracking protection introduction panel, if applicable.
+      let introCount = gPrefService.getIntPref("privacy.trackingprotection.introCount");
+      if (introCount < TrackingProtection.MAX_INTROS) {
+        gPrefService.setIntPref("privacy.trackingprotection.introCount", ++introCount);
+        gPrefService.savePrefFile(null);
+        this.showIntroPanel();
+      }
+    }
+
     // Telemetry for state change.
     this.eventsHistogram.add(0);
   },
 
   disableForCurrentPage() {
     // Convert document URI into the format used by
     // nsChannelClassifier::ShouldEnableTrackingProtection.
     // Any scheme turned into https is correct.
@@ -106,9 +117,52 @@ let TrackingProtection = {
     Services.perms.remove(normalizedUrl,
       "trackingprotection");
 
     // Telemetry for enable protection.
     this.eventsHistogram.add(2);
 
     BrowserReload();
   },
+
+  showIntroPanel: Task.async(function*() {
+    let mm = gBrowser.selectedBrowser.messageManager;
+    let brandBundle = document.getElementById("bundle_brand");
+    let brandShortName = brandBundle.getString("brandShortName");
+
+    let openStep2 = () => {
+      // When the user proceeds in the tour, adjust the counter to indicate that
+      // the user doesn't need to see the intro anymore.
+      gPrefService.setIntPref("privacy.trackingprotection.introCount",
+                              this.MAX_INTROS);
+      gPrefService.savePrefFile(null);
+
+      let nextURL = Services.urlFormatter.formatURLPref("privacy.trackingprotection.introURL") +
+                    "#step2";
+      switchToTabHavingURI(nextURL, true, {
+        // Ignore the fragment in case the intro is shown on the tour page
+        // (e.g. if the user manually visited the tour or clicked the link from
+        // about:privatebrowsing) so we can avoid a reload.
+        ignoreFragment: true,
+      });
+    };
+
+    let buttons = [
+      {
+        label: gNavigatorBundle.getString("trackingProtection.intro.step1of3"),
+        style: "text",
+      },
+      {
+        callback: openStep2,
+        label: gNavigatorBundle.getString("trackingProtection.intro.nextButton.label"),
+        style: "primary",
+      },
+    ];
+
+    let panelTarget = yield UITour.getTarget(window, "trackingProtection");
+    UITour.initForBrowser(gBrowser.selectedBrowser);
+    UITour.showInfo(window, mm, panelTarget,
+                    gNavigatorBundle.getString("trackingProtection.intro.title"),
+                    gNavigatorBundle.getFormattedString("trackingProtection.intro.description",
+                                                        [brandShortName]),
+                    undefined, buttons);
+  }),
 };
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -258,16 +258,19 @@ this.UITour = {
                                                                "anonid",
                                                                "tab-icon-image");
         if (!element || !UITour.isElementVisible(element)) {
           return null;
         }
         return element;
       },
     }],
+    ["trackingProtection", {
+      query: "#tracking-protection-icon",
+    }],
     ["urlbar",      {
       query: "#urlbar",
       widgetName: "urlbar-container",
     }],
     ["webide",      {query: "#webide-button"}],
   ]),
 
   init: function() {
@@ -405,19 +408,16 @@ this.UITour = {
 
     if ((aEvent.pageVisibilityState == "hidden" ||
          aEvent.pageVisibilityState == "unloaded") &&
         !BACKGROUND_PAGE_ACTIONS_ALLOWED.has(action)) {
       log.warn("Ignoring disallowed action from a hidden page:", action);
       return false;
     }
 
-    // Do this before bailing if there's no tab, so later we can pick up the pieces:
-    window.gBrowser.tabContainer.addEventListener("TabSelect", this);
-
     switch (action) {
       case "registerPageID": {
         if (typeof data.pageID != "string") {
           log.warn("registerPageID: pageID must be a string");
           break;
         }
 
         this.pageIDsForSession.set(data.pageID, {lastSeen: Date.now()});
@@ -498,19 +498,22 @@ this.UITour = {
             iconURL = this.resolveURL(browser, data.icon);
 
           let buttons = [];
           if (Array.isArray(data.buttons) && data.buttons.length > 0) {
             for (let buttonData of data.buttons) {
               if (typeof buttonData == "object" &&
                   typeof buttonData.label == "string" &&
                   typeof buttonData.callbackID == "string") {
+                let callback = buttonData.callbackID;
                 let button = {
                   label: buttonData.label,
-                  callbackID: buttonData.callbackID,
+                  callback: event => {
+                    this.sendPageCallback(messageManager, callback);
+                  },
                 };
 
                 if (typeof buttonData.icon == "string")
                   button.iconURL = this.resolveURL(browser, buttonData.icon);
 
                 if (typeof buttonData.style == "string")
                   button.style = buttonData.style;
 
@@ -725,26 +728,37 @@ this.UITour = {
         let targetPromise = this.getTarget(window, "readerMode-urlBar");
         targetPromise.then(target => {
           ReaderParent.toggleReaderMode({target: target.node});
         });
         break;
       }
     }
 
+    this.initForBrowser(browser);
+
+    return true;
+  },
+
+  initForBrowser(aBrowser) {
+    let window = aBrowser.ownerDocument.defaultView;
+    let gBrowser = window.gBrowser;
+
+    if (gBrowser) {
+        gBrowser.tabContainer.addEventListener("TabSelect", this);
+    }
+
     if (!this.tourBrowsersByWindow.has(window)) {
       this.tourBrowsersByWindow.set(window, new Set());
     }
-    this.tourBrowsersByWindow.get(window).add(browser);
+    this.tourBrowsersByWindow.get(window).add(aBrowser);
 
     Services.obs.addObserver(this, "message-manager-close", false);
 
     window.addEventListener("SSWindowClosing", this);
-
-    return true;
   },
 
   handleEvent: function(aEvent) {
     log.debug("handleEvent: type =", aEvent.type, "event =", aEvent);
     switch (aEvent.type) {
       case "pagehide": {
         let window = this.getChromeWindow(aEvent.target);
         this.teardownTourForWindow(window);
@@ -1395,32 +1409,38 @@ this.UITour = {
       tooltipDesc.textContent = aDescription || "";
       tooltipIcon.src = aIconURL || "";
       tooltipIconContainer.hidden = !aIconURL;
 
       while (tooltipButtons.firstChild)
         tooltipButtons.firstChild.remove();
 
       for (let button of aButtons) {
-        let el = document.createElement("button");
-        el.setAttribute("label", button.label);
-        if (button.iconURL)
-          el.setAttribute("image", button.iconURL);
+        let isButton = button.style != "text";
+        let el = document.createElement(isButton ? "button" : "label");
+        el.setAttribute(isButton ? "label" : "value", button.label);
 
-        if (button.style == "link")
-          el.setAttribute("class", "button-link");
+        if (isButton) {
+          if (button.iconURL)
+            el.setAttribute("image", button.iconURL);
+
+          if (button.style == "link")
+            el.setAttribute("class", "button-link");
 
-        if (button.style == "primary")
-          el.setAttribute("class", "button-primary");
+          if (button.style == "primary")
+            el.setAttribute("class", "button-primary");
 
-        let callbackID = button.callbackID;
-        el.addEventListener("command", event => {
-          tooltip.hidePopup();
-          this.sendPageCallback(aMessageManager, callbackID);
-        });
+          // Don't close the popup or call the callback for style=text as they
+          // aren't links/buttons.
+          let callback = button.callback;
+          el.addEventListener("command", event => {
+            tooltip.hidePopup();
+            callback(event);
+          });
+        }
 
         tooltipButtons.appendChild(el);
       }
 
       tooltipButtons.hidden = !aButtons.length;
 
       let tooltipClose = document.getElementById("UITourTooltipClose");
       let closeButtonCallback = (event) => {
--- a/browser/components/uitour/test/browser.ini
+++ b/browser/components/uitour/test/browser.ini
@@ -7,16 +7,18 @@ support-files =
 
 [browser_backgroundTab.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_no_tabs.js]
 [browser_openPreferences.js]
 skip-if = e10s # Bug 1073247 - UITour tests not e10s friendly
 [browser_openSearchPanel.js]
 skip-if = true # Bug 1113038 - Intermittent "Popup was opened"
+[browser_trackingProtection.js]
+tag = trackingprotection
 [browser_UITour.js]
 skip-if = os == "linux" || e10s # Intermittent failures, bug 951965
 [browser_UITour2.js]
 skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly
 [browser_UITour3.js]
 skip-if = os == "linux" || e10s # Linux: Bug 986760, Bug 989101; e10s: Bug 1073247 - UITour.jsm not e10s friendly
 [browser_UITour_availableTargets.js]
 skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly
--- a/browser/components/uitour/test/browser_UITour3.js
+++ b/browser/components/uitour/test/browser_UITour3.js
@@ -50,62 +50,80 @@ let tests = [
     is(title.textContent, "another title", "Popup should have correct title");
     is(desc.textContent, "moar text", "Popup should have correct description text");
 
     let imageURL = getRootDirectory(gTestPath) + "image.png";
     imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.com/");
     is(icon.src, imageURL,  "Popup should have correct icon shown");
 
     buttons = document.getElementById("UITourTooltipButtons");
-    is(buttons.childElementCount, 2, "Popup should have two buttons");
+    is(buttons.childElementCount, 4, "Popup should have four buttons");
+
+    is(buttons.childNodes[0].nodeName, "label", "Text label should be a <label>");
+    is(buttons.childNodes[0].getAttribute("value"), "Regular text", "Text label should have correct value");
+    is(buttons.childNodes[0].getAttribute("image"), "", "Text should have no image");
+    is(buttons.childNodes[0].className, "", "Text should have no class");
 
-    is(buttons.childNodes[0].getAttribute("label"), "Button 1", "First button should have correct label");
-    is(buttons.childNodes[0].getAttribute("image"), "", "First button should have no image");
+    is(buttons.childNodes[1].nodeName, "button", "Link should be a <button>");
+    is(buttons.childNodes[1].getAttribute("label"), "Link", "Link should have correct label");
+    is(buttons.childNodes[1].getAttribute("image"), "", "Link should have no image");
+    is(buttons.childNodes[1].className, "button-link", "Check link class");
 
-    is(buttons.childNodes[1].getAttribute("label"), "Button 2", "Second button should have correct label");
-    is(buttons.childNodes[1].getAttribute("image"), imageURL, "Second button should have correct image");
+    is(buttons.childNodes[2].nodeName, "button", "Button 1 should be a <button>");
+    is(buttons.childNodes[2].getAttribute("label"), "Button 1", "First button should have correct label");
+    is(buttons.childNodes[2].getAttribute("image"), "", "First button should have no image");
+    is(buttons.childNodes[2].className, "", "Button 1 should have no class");
+
+    is(buttons.childNodes[3].nodeName, "button", "Button 2 should be a <button>");
+    is(buttons.childNodes[3].getAttribute("label"), "Button 2", "Second button should have correct label");
+    is(buttons.childNodes[3].getAttribute("image"), imageURL, "Second button should have correct image");
+    is(buttons.childNodes[3].className, "button-primary", "Check button 2 class");
 
     let promiseHidden = promisePanelElementHidden(window, popup);
-    EventUtils.synthesizeMouseAtCenter(buttons.childNodes[0], {}, window);
+    EventUtils.synthesizeMouseAtCenter(buttons.childNodes[2], {}, window);
     yield promiseHidden;
 
     ok(true, "Popup should close automatically");
 
     yield waitForCallbackResultPromise();
 
     is(gContentWindow.callbackResult, "button1", "Correct callback should have been called");
   }),
   taskify(function* test_info_buttons_2() {
     let popup = document.getElementById("UITourTooltip");
     let title = document.getElementById("UITourTooltipTitle");
     let desc = document.getElementById("UITourTooltipDescription");
     let icon = document.getElementById("UITourTooltipIcon");
 
     let buttons = gContentWindow.makeButtons();
 
-    yield showInfoPromise("urlbar", "another title", "moar text", "./image.png", buttons);
+    yield showInfoPromise("trackingProtection", "another title", "moar text", "./image.png", buttons);
 
     is(title.textContent, "another title", "Popup should have correct title");
     is(desc.textContent, "moar text", "Popup should have correct description text");
 
     let imageURL = getRootDirectory(gTestPath) + "image.png";
     imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.com/");
     is(icon.src, imageURL,  "Popup should have correct icon shown");
 
     buttons = document.getElementById("UITourTooltipButtons");
-    is(buttons.childElementCount, 2, "Popup should have two buttons");
+    is(buttons.childElementCount, 4, "Popup should have four buttons");
+
+    is(buttons.childNodes[1].getAttribute("label"), "Link", "Link should have correct label");
+    is(buttons.childNodes[1].getAttribute("image"), "", "Link should have no image");
+    ok(buttons.childNodes[1].classList.contains("button-link"), "Link should have button-link class");
 
-    is(buttons.childNodes[0].getAttribute("label"), "Button 1", "First button should have correct label");
-    is(buttons.childNodes[0].getAttribute("image"), "", "First button should have no image");
+    is(buttons.childNodes[2].getAttribute("label"), "Button 1", "First button should have correct label");
+    is(buttons.childNodes[2].getAttribute("image"), "", "First button should have no image");
 
-    is(buttons.childNodes[1].getAttribute("label"), "Button 2", "Second button should have correct label");
-    is(buttons.childNodes[1].getAttribute("image"), imageURL, "Second button should have correct image");
+    is(buttons.childNodes[3].getAttribute("label"), "Button 2", "Second button should have correct label");
+    is(buttons.childNodes[3].getAttribute("image"), imageURL, "Second button should have correct image");
 
     let promiseHidden = promisePanelElementHidden(window, popup);
-    EventUtils.synthesizeMouseAtCenter(buttons.childNodes[1], {}, window);
+    EventUtils.synthesizeMouseAtCenter(buttons.childNodes[3], {}, window);
     yield promiseHidden;
 
     ok(true, "Popup should close automatically");
 
     yield waitForCallbackResultPromise();
 
     is(gContentWindow.callbackResult, "button2", "Correct callback should have been called");
   }),
new file mode 100644
--- /dev/null
+++ b/browser/components/uitour/test/browser_trackingProtection.js
@@ -0,0 +1,90 @@
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+const PREF_INTRO_COUNT = "privacy.trackingprotection.introCount";
+const PREF_TP_ENABLED = "privacy.trackingprotection.enabled";
+const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html";
+const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
+const TOOLTIP_PANEL = document.getElementById("UITourTooltip");
+const TOOLTIP_ANCHOR = document.getElementById("tracking-protection-icon");
+
+let {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
+
+registerCleanupFunction(function() {
+  UrlClassifierTestUtils.cleanupTestTrackers();
+  Services.prefs.clearUserPref(PREF_TP_ENABLED);
+  Services.prefs.clearUserPref(PREF_INTRO_COUNT);
+});
+
+function allowOneIntro() {
+  Services.prefs.setIntPref(PREF_INTRO_COUNT, TrackingProtection.MAX_INTROS - 1);
+}
+
+add_task(function* setup_test() {
+  Services.prefs.setBoolPref(PREF_TP_ENABLED, true);
+  yield UrlClassifierTestUtils.addTestTrackers();
+});
+
+add_task(function* test_benignPage() {
+  info("Load a test page not containing tracking elements");
+  allowOneIntro();
+  yield BrowserTestUtils.withNewTab({gBrowser, url: BENIGN_PAGE}, function*() {
+    yield waitForConditionPromise(() => {
+      return is_visible(TOOLTIP_PANEL);
+    }, "Info panel shouldn't appear on a benign page").
+      then(() => ok(false, "Info panel shouldn't appear"),
+           () => {
+             ok(true, "Info panel didn't appear on a benign page");
+           });
+
+  });
+});
+
+add_task(function* test_trackingPages() {
+  info("Load a test page containing tracking elements");
+  allowOneIntro();
+  yield BrowserTestUtils.withNewTab({gBrowser, url: TRACKING_PAGE}, function*() {
+    yield new Promise((resolve, reject) => {
+      waitForPopupAtAnchor(TOOLTIP_PANEL, TOOLTIP_ANCHOR, resolve,
+                           "Intro panel should appear");
+    });
+
+    is(Services.prefs.getIntPref(PREF_INTRO_COUNT), TrackingProtection.MAX_INTROS, "Check intro count increased");
+
+    let step2URL = Services.urlFormatter.formatURLPref("privacy.trackingprotection.introURL") +
+                    "#step2";
+    let buttons = document.getElementById("UITourTooltipButtons");
+
+    info("Click the step text and nothing should happen");
+    let tabCount = gBrowser.tabs.length;
+    yield EventUtils.synthesizeMouseAtCenter(buttons.children[0], {});
+    is(gBrowser.tabs.length, tabCount, "Same number of tabs should be open");
+
+    info("Resetting count to test that viewing the tour prevents future panels");
+    allowOneIntro();
+
+    let panelHiddenPromise = promisePanelElementHidden(window, TOOLTIP_PANEL);
+    let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, step2URL);
+    info("Clicking the main button");
+    EventUtils.synthesizeMouseAtCenter(buttons.children[1], {});
+    let tab = yield tabPromise;
+    is(Services.prefs.getIntPref(PREF_INTRO_COUNT), TrackingProtection.MAX_INTROS,
+       "Check intro count is at the max after opening step 2");
+    is(gBrowser.tabs.length, tabCount + 1, "Tour step 2 tab opened");
+    yield panelHiddenPromise;
+    ok(true, "Panel hid when the button was clicked");
+    yield BrowserTestUtils.removeTab(tab);
+  });
+
+  info("Open another tracking page and make sure we don't show the panel again");
+  yield BrowserTestUtils.withNewTab({gBrowser, url: TRACKING_PAGE}, function*() {
+    yield waitForConditionPromise(() => {
+      return is_visible(TOOLTIP_PANEL);
+    }, "Info panel shouldn't appear more than MAX_INTROS").
+      then(() => ok(false, "Info panel shouldn't appear again"),
+           () => {
+             ok(true, "Info panel didn't appear more than MAX_INTROS on tracking pages");
+           });
+
+  });
+});
--- a/browser/components/uitour/test/uitour.html
+++ b/browser/components/uitour/test/uitour.html
@@ -12,18 +12,21 @@
           callbackResult = name;
           callbackData = data;
         });
       }
 
       // Defined in content to avoid weird issues when crossing between chrome/content.
       function makeButtons() {
         return [
+          {label: "Regular text", style: "text"},
+          {label: "Link", callback: makeCallback("link"), style: "link"},
           {label: "Button 1", callback: makeCallback("button1")},
-          {label: "Button 2", callback: makeCallback("button2"), icon: "image.png"}
+          {label: "Button 2", callback: makeCallback("button2"), icon: "image.png",
+           style: "primary"}
         ];
       }
 
       function makeInfoOptions() {
         return {
           closeButtonCallback: makeCallback("closeButton"),
           targetCallback: makeCallback("target"),
         };
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -331,16 +331,23 @@ identity.identified.state_and_country=%S
 
 identity.not_secure=Your connection to this site is not private. Information you submit could be viewable to others (for example passwords, messages, credit cards, etc.).
 identity.uses_weak_cipher=Your connection to this website uses weak encryption and is not private. Other people can view your information or modify the website's behavior.
 identity.mixed_display_loaded=The connection to this website is not fully secure because it contains unencrypted elements (such as images).
 identity.mixed_active_loaded2=This website contains interactive content that isn't encrypted (such as scripts). Other people can view your information or modify the website's behavior.
 
 identity.unknown.tooltip=This website does not supply identity information.
 
+trackingProtection.intro.title=How Tracking Protection works
+# LOCALIZATION NOTE (trackingProtection.intro.description): %S is brandShortName
+trackingProtection.intro.description=When the shield is visible, that means Firefox is actively blocking content that tracks you.
+# LOCALIZATION NOTE (trackingProtection.intro.step1of3): Indicates that the intro panel is step one of three in a tour.
+trackingProtection.intro.step1of3=1 of 3
+trackingProtection.intro.nextButton.label=Next
+
 # Edit Bookmark UI
 editBookmarkPanel.pageBookmarkedTitle=Page Bookmarked
 editBookmarkPanel.pageBookmarkedDescription=%S will always remember this page for you.
 editBookmarkPanel.bookmarkedRemovedTitle=Bookmark Removed
 editBookmarkPanel.editBookmarkTitle=Edit This Bookmark
 
 # LOCALIZATION NOTE (editBookmark.removeBookmarks.label): Semicolon-separated list of plural forms.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
--- a/browser/themes/shared/UITour.inc.css
+++ b/browser/themes/shared/UITour.inc.css
@@ -81,30 +81,33 @@
 #UITourTooltipButtons {
   -moz-box-pack: end;
   background-color: hsla(210,4%,10%,.07);
   border-top: 1px solid hsla(210,4%,10%,.14);
   margin: 24px -16px -16px;
   padding: 2em 15px;
 }
 
+#UITourTooltipButtons > label,
 #UITourTooltipButtons > button {
   margin: 0 15px;
 }
 
+#UITourTooltipButtons > label:first-child,
 #UITourTooltipButtons > button:first-child {
   -moz-margin-start: 0;
 }
 
 #UITourTooltipButtons > button[image] > .button-box > .button-icon {
   width: 16px;
   height: 16px;
   -moz-margin-end: 5px;
 }
 
+#UITourTooltipButtons > label,
 #UITourTooltipButtons > button .button-text {
   font-size: 1.15rem;
 }
 
 #UITourTooltipButtons > button:not(.button-link) {
   -moz-appearance: none;
   background-color: rgb(251,251,251);
   border-radius: 3px;
@@ -117,16 +120,17 @@
 }
 
 #UITourTooltipButtons > button:not(.button-link):not(:active):hover {
   background-color: hsla(210,4%,10%,.15);
   border-color: hsla(210,4%,10%,.15);
   box-shadow: 0 1px 0 0 hsla(210,4%,10%,.05) inset;
 }
 
+#UITourTooltipButtons > label,
 #UITourTooltipButtons > button.button-link {
   -moz-appearance: none;
   background: transparent;
   border: none;
   box-shadow: none;
   color: rgba(0,0,0,0.35);
   padding-left: 10px;
   padding-right: 10px;
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -85,16 +85,17 @@ user_pref("geo.wifi.logging.enabled", tr
 // Make url-classifier updates so rare that they won't affect tests
 user_pref("urlclassifier.updateinterval", 172800);
 // Point the url-classifier to the local testing server for fast failures
 user_pref("browser.safebrowsing.gethashURL", "http://%(server)s/safebrowsing-dummy/gethash");
 user_pref("browser.safebrowsing.updateURL", "http://%(server)s/safebrowsing-dummy/update");
 user_pref("browser.safebrowsing.appRepURL", "http://%(server)s/safebrowsing-dummy/update");
 user_pref("browser.trackingprotection.gethashURL", "http://%(server)s/safebrowsing-dummy/gethash");
 user_pref("browser.trackingprotection.updateURL", "http://%(server)s/safebrowsing-dummy/update");
+user_pref("privacy.trackingprotection.introURL", "http://%(server)s/trackingprotection/tour");
 // Point update checks to the local testing server for fast failures
 user_pref("extensions.update.url", "http://%(server)s/extensions-dummy/updateURL");
 user_pref("extensions.update.background.url", "http://%(server)s/extensions-dummy/updateBackgroundURL");
 user_pref("extensions.blocklist.url", "http://%(server)s/extensions-dummy/blocklistURL");
 user_pref("extensions.hotfix.url", "http://%(server)s/extensions-dummy/hotfixURL");
 // Turn off extension updates so they don't bother tests
 user_pref("extensions.update.enabled", false);
 // Make sure opening about:addons won't hit the network