diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 1d93d5d26768..ea68aa2a40f7 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1918,6 +1918,8 @@ pref("browser.apps.URL", "https://marketplace.firefox.com/discovery/"); 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. diff --git a/browser/base/content/browser-trackingprotection.js b/browser/base/content/browser-trackingprotection.js index aa070fa4e5eb..2255c4bafa2a 100644 --- a/browser/base/content/browser-trackingprotection.js +++ b/browser/base/content/browser-trackingprotection.js @@ -3,6 +3,7 @@ # 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, @@ -64,6 +65,15 @@ let TrackingProtection = { for (let element of [this.icon, this.content]) { if (state & STATE_BLOCKED_TRACKING_CONTENT) { element.setAttribute("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(); + } + } else if (state & STATE_LOADED_TRACKING_CONTENT) { element.setAttribute("state", "loaded-tracking-content"); } else { @@ -107,4 +117,47 @@ let TrackingProtection = { 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, "siteIdentity"); + UITour.initForBrowser(gBrowser.selectedBrowser); + UITour.showInfo(window, mm, panelTarget, + gNavigatorBundle.getString("trackingProtection.intro.title"), + gNavigatorBundle.getFormattedString("trackingProtection.intro.description", + [brandShortName]), + undefined, buttons); + }), }; diff --git a/browser/components/uitour/UITour.jsm b/browser/components/uitour/UITour.jsm index 555f96c42e87..cf7ed81cdba7 100644 --- a/browser/components/uitour/UITour.jsm +++ b/browser/components/uitour/UITour.jsm @@ -263,6 +263,9 @@ this.UITour = { return element; }, }], + ["siteIdentity", { + query: "#page-proxy-favicon", + }], ["urlbar", { query: "#urlbar", widgetName: "urlbar-container", @@ -410,9 +413,6 @@ this.UITour = { 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") { @@ -503,9 +503,12 @@ this.UITour = { 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") @@ -730,16 +733,24 @@ this.UITour = { } } + this.initForBrowser(browser); + + return true; + }, + + initForBrowser(aBrowser) { + let window = aBrowser.ownerDocument.defaultView; + + window.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) { @@ -1400,22 +1411,28 @@ this.UITour = { 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 == "primary") - el.setAttribute("class", "button-primary"); + if (button.style == "link") + el.setAttribute("class", "button-link"); - let callbackID = button.callbackID; - el.addEventListener("command", event => { - tooltip.hidePopup(); - this.sendPageCallback(aMessageManager, callbackID); - }); + if (button.style == "primary") + el.setAttribute("class", "button-primary"); + + // 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); } diff --git a/browser/components/uitour/test/browser.ini b/browser/components/uitour/test/browser.ini index b9b7c7404aaa..cf7e75726c7b 100644 --- a/browser/components/uitour/test/browser.ini +++ b/browser/components/uitour/test/browser.ini @@ -12,6 +12,8 @@ skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly 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] diff --git a/browser/components/uitour/test/browser_UITour3.js b/browser/components/uitour/test/browser_UITour3.js index 71cd965c8973..31961e03a0bc 100644 --- a/browser/components/uitour/test/browser_UITour3.js +++ b/browser/components/uitour/test/browser_UITour3.js @@ -55,16 +55,30 @@ let tests = [ 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].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[0].nodeName, "label", "Text label should be a