From 5ac5ee9fcfd2fae3da261167144acc9d92083c61 Mon Sep 17 00:00:00 2001 From: Jared Wein Date: Wed, 15 Apr 2015 23:45:50 -0400 Subject: [PATCH] Bug 1134501 - UITour: Force page into Reader View automatically whenever the ReaderView/ReadingList tour page is loaded. r=gijs --- browser/app/profile/firefox.js | 2 + browser/base/content/browser.js | 1 + browser/components/uitour/UITour-lib.js | 6 ++- browser/components/uitour/UITour.jsm | 41 ++++++++++++++++++ browser/components/uitour/test/browser.ini | 28 ++++++------ .../test/browser_UITour_availableTargets.js | 3 ++ .../test/browser_UITour_toggleReaderMode.js | 20 +++++++++ browser/modules/ReaderParent.jsm | 18 ++++++++ browser/themes/linux/jar.mn | 1 + browser/themes/osx/jar.mn | 1 + browser/themes/shared/reader/reader-tour.png | Bin 0 -> 12172 bytes browser/themes/windows/jar.mn | 1 + 12 files changed, 108 insertions(+), 14 deletions(-) create mode 100644 browser/components/uitour/test/browser_UITour_toggleReaderMode.js create mode 100644 browser/themes/shared/reader/reader-tour.png diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 8225051ea3ce..fd100ed4819f 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -251,6 +251,8 @@ pref("browser.uitour.loglevel", "Error"); pref("browser.uitour.requireSecure", true); pref("browser.uitour.themeOrigin", "https://addons.mozilla.org/%LOCALE%/firefox/themes/"); pref("browser.uitour.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/tour/"); +// This is used as a regexp match against the page's URL. +pref("browser.uitour.readerViewTrigger", "^https:\/\/www\.mozilla\.org\/[^\/]+\/firefox\/reading\/start"); pref("browser.customizemode.tip0.shown", false); pref("browser.customizemode.tip0.learnMoreUrl", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/customize"); diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 45afd13c4439..f5fe439aa7fa 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -4203,6 +4203,7 @@ var XULBrowserWindow = { BookmarkingUI.onLocationChange(); SocialUI.updateState(location); + UITour.onLocationChange(location); } // Utility functions for disabling find diff --git a/browser/components/uitour/UITour-lib.js b/browser/components/uitour/UITour-lib.js index 673ddb3f34bf..7d621342e882 100644 --- a/browser/components/uitour/UITour-lib.js +++ b/browser/components/uitour/UITour-lib.js @@ -285,9 +285,13 @@ if (typeof Mozilla == 'undefined') { _sendEvent('forceShowReaderIcon'); }; + Mozilla.UITour.toggleReaderMode = function(feature) { + _sendEvent('toggleReaderMode'); + }; + })(); // Make this library Require-able. if (typeof module !== 'undefined' && module.exports) { - module.exports = Mozilla.UITour; + module.exports = Mozilla.UITour; } diff --git a/browser/components/uitour/UITour.jsm b/browser/components/uitour/UITour.jsm index f3a889268cac..0fcc15367f33 100644 --- a/browser/components/uitour/UITour.jsm +++ b/browser/components/uitour/UITour.jsm @@ -27,12 +27,15 @@ XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry", "resource:///modules/BrowserUITelemetry.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Metrics", "resource://gre/modules/Metrics.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", + "resource://gre/modules/ReaderMode.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "ReaderParent", "resource:///modules/ReaderParent.jsm"); // See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error". const PREF_LOG_LEVEL = "browser.uitour.loglevel"; const PREF_SEENPAGEIDS = "browser.uitour.seenPageIDs"; +const PREF_READERVIEW_TRIGGER = "browser.uitour.readerViewTrigger"; const BACKGROUND_PAGE_ACTIONS_ALLOWED = new Set([ "endUrlbarCapture", @@ -190,6 +193,7 @@ this.UITour = { }], ["privateWindow", {query: "#privatebrowsing-button"}], ["quit", {query: "#PanelUI-quit"}], + ["readerMode-urlBar", {query: "#reader-mode-button"}], ["search", { infoPanelOffsetX: 18, infoPanelPosition: "after_start", @@ -344,6 +348,22 @@ this.UITour = { JSON.stringify([...this.seenPageIDs])); }, + get _readerViewTriggerRegEx() { + delete this._readerViewTriggerRegEx; + let readerViewUITourTrigger = Services.prefs.getCharPref(PREF_READERVIEW_TRIGGER); + return this._readerViewTriggerRegEx = new RegExp(readerViewUITourTrigger, "i"); + }, + + onLocationChange: function(aLocation) { + // The ReadingList/ReaderView tour page is expected to run in Reader View, + // which disables JavaScript on the page. To get around that, we + // automatically start a pre-defined tour on page load. + let originalUrl = ReaderMode.getOriginalUrl(aLocation); + if (this._readerViewTriggerRegEx.test(originalUrl)) { + this.startSubTour("readinglist"); + } + }, + onPageEvent: function(aMessage, aEvent) { let browser = aMessage.target; let window = browser.ownerDocument.defaultView; @@ -685,6 +705,13 @@ this.UITour = { ReaderParent.forceShowReaderIcon(browser); break; } + + case "toggleReaderMode": { + let targetPromise = this.getTarget(window, "readerMode-urlBar"); + targetPromise.then(target => { + ReaderParent.toggleReaderMode({target: target.node}); + }); + } } if (!this.tourBrowsersByWindow.has(window)) { @@ -1720,6 +1747,20 @@ this.UITour = { }); }, + startSubTour: function (aFeature) { + if (aFeature != "string") { + log.error("startSubTour: No feature option specified"); + return; + } + + if (aFeature == "readinglist") { + ReaderParent.showReaderModeInfoPanel(browser); + } else { + log.error("startSubTour: Unknown feature option specified"); + return; + } + }, + addNavBarWidget: function (aTarget, aMessageManager, aCallbackID) { if (aTarget.node) { log.error("addNavBarWidget: can't add a widget already present:", aTarget); diff --git a/browser/components/uitour/test/browser.ini b/browser/components/uitour/test/browser.ini index 02144a7797d1..9e0a851f0cae 100644 --- a/browser/components/uitour/test/browser.ini +++ b/browser/components/uitour/test/browser.ini @@ -9,30 +9,32 @@ support-files = [browser_UITour.js] skip-if = os == "linux" || e10s # Intermittent failures, bug 951965 [browser_UITour2.js] -skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly +skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly # [browser_UITour3.js] Bug 1113038 -# skip-if = os == "linux" || e10s # Linux: Bug 986760, Bug 989101; e10s: Bug 941428 - UITour.jsm not e10s friendly +# 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 941428 - UITour.jsm not e10s friendly +skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly [browser_UITour_detach_tab.js] -skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly +skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly [browser_UITour_annotation_size_attributes.js] -skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly. +skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly. [browser_UITour_forceReaderMode.js] -skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly. +skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly. +[browser_UITour_toggleReaderMode.js] +skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly [browser_UITour_heartbeat.js] -skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly. +skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly. [browser_UITour_loop.js] -skip-if = os == "linux" || e10s # Bug 941428 - UITour.jsm not e10s friendly. +skip-if = os == "linux" || e10s # Bug 1073247 - UITour.jsm not e10s friendly. [browser_UITour_modalDialog.js] -skip-if = os != "mac" || e10s # modal dialog disabling only working on OS X.Bug 941428 - UITour.jsm not e10s friendly +skip-if = os != "mac" || e10s # modal dialog disabling only working on OS X.Bug 1073247 - UITour.jsm not e10s friendly [browser_UITour_observe.js] -skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly. +skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly. [browser_UITour_panel_close_annotation.js] skip-if = true # Disabled due to frequent failures, bugs 1026310 and 1032137 [browser_UITour_registerPageID.js] -skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly +skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly [browser_UITour_sync.js] -skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly +skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly [browser_UITour_resetProfile.js] -skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly +skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly diff --git a/browser/components/uitour/test/browser_UITour_availableTargets.js b/browser/components/uitour/test/browser_UITour_availableTargets.js index 6c12916b5753..301e5c5b0ca5 100644 --- a/browser/components/uitour/test/browser_UITour_availableTargets.js +++ b/browser/components/uitour/test/browser_UITour_availableTargets.js @@ -39,6 +39,7 @@ let tests = [ "devtools", "privateWindow", "quit", + "readerMode-urlBar", "search", "searchIcon", "urlbar", @@ -69,6 +70,7 @@ let tests = [ "home", "privateWindow", "quit", + "readerMode-urlBar", "search", "searchIcon", "urlbar", @@ -104,6 +106,7 @@ let tests = [ "devtools", "privateWindow", "quit", + "readerMode-urlBar", "urlbar", ...(hasWebIDE ? ["webide"] : []) ]); diff --git a/browser/components/uitour/test/browser_UITour_toggleReaderMode.js b/browser/components/uitour/test/browser_UITour_toggleReaderMode.js new file mode 100644 index 000000000000..f4c7109dd827 --- /dev/null +++ b/browser/components/uitour/test/browser_UITour_toggleReaderMode.js @@ -0,0 +1,20 @@ +"use strict"; + +let gTestTab; +let gContentAPI; +let gContentWindow; + +Components.utils.import("resource:///modules/UITour.jsm"); + +function test() { + UITourTest(); +} + +let tests = [ + taskify(function*() { + ok(!gBrowser.selectedBrowser.currentURI.spec.startsWith("about:reader"), "Should not be in reader mode at start of test."); + gContentAPI.toggleReaderMode(); + yield waitForConditionPromise(() => gBrowser.selectedBrowser.currentURI.spec.startsWith("about:reader")); + ok(gBrowser.selectedBrowser.currentURI.spec.startsWith("about:reader"), "Should be in reader mode now."); + }) +]; diff --git a/browser/modules/ReaderParent.jsm b/browser/modules/ReaderParent.jsm index 5f0f12c4c019..9c0518039134 100644 --- a/browser/modules/ReaderParent.jsm +++ b/browser/modules/ReaderParent.jsm @@ -16,6 +16,7 @@ Cu.import("resource://gre/modules/Task.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils","resource://gre/modules/PlacesUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "ReadingList", "resource:///modules/readinglist/ReadingList.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "UITour", "resource:///modules/UITour.jsm"); const gStringBundle = Services.strings.createBundle("chrome://global/locale/aboutReader.properties"); @@ -188,6 +189,23 @@ let ReaderParent = { } }, + /** + * Shows an info panel from the UITour for Reader Mode. + * + * @param browser The that the tour should be started for. + */ + showReaderModeInfoPanel(browser) { + let win = browser.ownerDocument.defaultView; + let targetPromise = UITour.getTarget(win, "readerMode-urlBar"); + targetPromise.then(target => { + let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); + UITour.showInfo(win, browser.messageManager, target, + browserBundle.GetStringFromName("readerView.promo.firstDetectedArticle.title"), + browserBundle.GetStringFromName("readerView.promo.firstDetectedArticle.body"), + "chrome://browser/skin/reader-tour.png"); + }); + }, + /** * Gets an article for a given URL. This method will download and parse a document. * diff --git a/browser/themes/linux/jar.mn b/browser/themes/linux/jar.mn index 40dcd3984004..c605e51a6b02 100644 --- a/browser/themes/linux/jar.mn +++ b/browser/themes/linux/jar.mn @@ -93,6 +93,7 @@ browser.jar: skin/classic/browser/session-restore.svg (../shared/incontent-icons/session-restore.svg) skin/classic/browser/tab-crashed.svg (../shared/incontent-icons/tab-crashed.svg) skin/classic/browser/welcome-back.svg (../shared/incontent-icons/welcome-back.svg) + skin/classic/browser/reader-tour.png (../shared/reader/reader-tour.png) skin/classic/browser/readerMode.svg (../shared/reader/readerMode.svg) skin/classic/browser/readinglist/icons.svg (../shared/readinglist/icons.svg) skin/classic/browser/readinglist/readinglist-icon.svg (../shared/readinglist/readinglist-icon.svg) diff --git a/browser/themes/osx/jar.mn b/browser/themes/osx/jar.mn index c52ffd0943cc..11398a9d60e8 100644 --- a/browser/themes/osx/jar.mn +++ b/browser/themes/osx/jar.mn @@ -145,6 +145,7 @@ browser.jar: skin/classic/browser/session-restore.svg (../shared/incontent-icons/session-restore.svg) skin/classic/browser/tab-crashed.svg (../shared/incontent-icons/tab-crashed.svg) skin/classic/browser/welcome-back.svg (../shared/incontent-icons/welcome-back.svg) + skin/classic/browser/reader-tour.png (../shared/reader/reader-tour.png) skin/classic/browser/readerMode.svg (../shared/reader/readerMode.svg) skin/classic/browser/readinglist/icons.svg (../shared/readinglist/icons.svg) skin/classic/browser/readinglist/readinglist-icon.svg (../shared/readinglist/readinglist-icon.svg) diff --git a/browser/themes/shared/reader/reader-tour.png b/browser/themes/shared/reader/reader-tour.png new file mode 100644 index 0000000000000000000000000000000000000000..8e557e03821219211c19f65a90ca03d2f0b3b03f GIT binary patch literal 12172 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