author | Neil Deakin <neil@mozilla.com> |
Wed, 13 May 2020 19:22:20 +0000 (2020-05-13) | |
changeset 529710 | 217394da44ac9b55df2f91bf639944ea47949b3d |
parent 529709 | c060bcf8ea7a089333bfd0d99f096ef797ceb390 |
child 529711 | e781cf38f088c02d2b336aa8ec990756447683c9 |
push id | 37414 |
push user | nbeleuzu@mozilla.com |
push date | Thu, 14 May 2020 02:40:10 +0000 (2020-05-14) |
treeherder | mozilla-central@045d696faa87 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | Gijs |
bugs | 1591469 |
milestone | 78.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
|
--- a/browser/actors/AboutReaderChild.jsm +++ b/browser/actors/AboutReaderChild.jsm @@ -1,182 +1,209 @@ /* vim: set ts=2 sw=2 sts=2 et tw=80: */ /* 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/. */ "use strict"; var EXPORTED_SYMBOLS = ["AboutReaderChild"]; -const { ActorChild } = ChromeUtils.import( - "resource://gre/modules/ActorChild.jsm" -); - ChromeUtils.defineModuleGetter( this, "AboutReader", "resource://gre/modules/AboutReader.jsm" ); ChromeUtils.defineModuleGetter( this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm" ); ChromeUtils.defineModuleGetter( this, "Readerable", "resource://gre/modules/Readerable.jsm" ); -class AboutReaderChild extends ActorChild { - constructor(dispatcher) { - super(dispatcher); +class AboutReaderChild extends JSWindowActorChild { + constructor() { + super(); + this._reader = null; this._articlePromise = null; this._isLeavingReaderableReaderMode = false; } + willDestroy() { + this.cancelPotentialPendingReadabilityCheck(); + this.readerModeHidden(); + } + + readerModeHidden() { + if (this._reader) { + this._reader.clearActor(); + } + this._reader = null; + } + receiveMessage(message) { switch (message.name) { case "Reader:ToggleReaderMode": if (!this.isAboutReader) { - this._articlePromise = ReaderMode.parseDocument( - this.content.document - ).catch(Cu.reportError); - ReaderMode.enterReaderMode(this.mm.docShell, this.content); + this._articlePromise = ReaderMode.parseDocument(this.document).catch( + Cu.reportError + ); + ReaderMode.enterReaderMode(this.docShell, this.contentWindow); } else { this._isLeavingReaderableReaderMode = this.isReaderableAboutReader; - ReaderMode.leaveReaderMode(this.mm.docShell, this.content); + ReaderMode.leaveReaderMode(this.docShell, this.contentWindow); } break; case "Reader:PushState": this.updateReaderButton(!!(message.data && message.data.isArticle)); break; } + + // Forward the message to the reader if it has been created. + if (this._reader) { + this._reader.receiveMessage(message); + } } get isAboutReader() { - if (!this.content) { + if (!this.document) { return false; } - return this.content.document.documentURI.startsWith("about:reader"); + return this.document.documentURI.startsWith("about:reader"); } get isReaderableAboutReader() { - return ( - this.isAboutReader && - !this.content.document.documentElement.dataset.isError - ); + return this.isAboutReader && !this.document.documentElement.dataset.isError; } handleEvent(aEvent) { - if (aEvent.originalTarget.defaultView != this.content) { + if (aEvent.originalTarget.defaultView != this.contentWindow) { return; } switch (aEvent.type) { - case "AboutReaderContentLoaded": + case "DOMContentLoaded": if (!this.isAboutReader) { + this.updateReaderButton(); return; } - if (this.content.document.body) { + if (this.document.body) { // Update the toolbar icon to show the "reader active" icon. - this.mm.sendAsyncMessage("Reader:UpdateReaderButton"); - new AboutReader(this.mm, this.content, this._articlePromise); + this.sendAsyncMessage("Reader:UpdateReaderButton"); + this._reader = new AboutReader(this, this._articlePromise); this._articlePromise = null; } break; case "pagehide": this.cancelPotentialPendingReadabilityCheck(); // this._isLeavingReaderableReaderMode is used here to keep the Reader Mode icon // visible in the location bar when transitioning from reader-mode page // back to the readable source page. - this.mm.sendAsyncMessage("Reader:UpdateReaderButton", { + this.sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: this._isLeavingReaderableReaderMode, }); if (this._isLeavingReaderableReaderMode) { this._isLeavingReaderableReaderMode = false; } break; case "pageshow": // If a page is loaded from the bfcache, we won't get a "DOMContentLoaded" // event, so we need to rely on "pageshow" in this case. if (aEvent.persisted) { this.updateReaderButton(); } break; - case "DOMContentLoaded": - this.updateReaderButton(); - break; } } /** * NB: this function will update the state of the reader button asynchronously * after the next mozAfterPaint call (assuming reader mode is enabled and * this is a suitable document). Calling it on things which won't be * painted is not going to work. */ updateReaderButton(forceNonArticle) { if ( !Readerable.isEnabledForParseOnLoad || this.isAboutReader || - !this.content || - !(this.content.document instanceof this.content.HTMLDocument) || - this.content.document.mozSyntheticDocument + !this.contentWindow || + !(this.document instanceof this.contentWindow.HTMLDocument) || + this.document.mozSyntheticDocument ) { return; } this.scheduleReadabilityCheckPostPaint(forceNonArticle); } cancelPotentialPendingReadabilityCheck() { if (this._pendingReadabilityCheck) { - this.mm.removeEventListener( - "MozAfterPaint", - this._pendingReadabilityCheck - ); + if (this._listenerWindow) { + this._listenerWindow.removeEventListener( + "MozAfterPaint", + this._pendingReadabilityCheck + ); + } delete this._pendingReadabilityCheck; + delete this._listenerWindow; } } scheduleReadabilityCheckPostPaint(forceNonArticle) { if (this._pendingReadabilityCheck) { // We need to stop this check before we re-add one because we don't know // if forceNonArticle was true or false last time. this.cancelPotentialPendingReadabilityCheck(); } this._pendingReadabilityCheck = this.onPaintWhenWaitedFor.bind( this, forceNonArticle ); - this.mm.addEventListener("MozAfterPaint", this._pendingReadabilityCheck); + + this._listenerWindow = this.contentWindow.windowRoot; + this.contentWindow.windowRoot.addEventListener( + "MozAfterPaint", + this._pendingReadabilityCheck + ); } onPaintWhenWaitedFor(forceNonArticle, event) { // In non-e10s, we'll get called for paints other than ours, and so it's // possible that this page hasn't been laid out yet, in which case we // should wait until we get an event that does relate to our layout. We - // determine whether any of our this.content got painted by checking if there - // are any painted rects. + // determine whether any of our this.contentWindow got painted by checking + // if there are any painted rects. if (!event.clientRects.length) { return; } this.cancelPotentialPendingReadabilityCheck(); + + // Ignore errors from actors that have been unloaded before the + // paint event timer fires. + let document; + try { + document = this.document; + } catch (ex) { + return; + } + // Only send updates when there are articles; there's no point updating with // |false| all the time. - if (Readerable.isProbablyReaderable(this.content.document)) { - this.mm.sendAsyncMessage("Reader:UpdateReaderButton", { + if (Readerable.isProbablyReaderable(document)) { + this.sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: true, }); } else if (forceNonArticle) { - this.mm.sendAsyncMessage("Reader:UpdateReaderButton", { + this.sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: false, }); } } }
rename from browser/modules/ReaderParent.jsm rename to browser/actors/AboutReaderParent.jsm --- a/browser/modules/ReaderParent.jsm +++ b/browser/actors/AboutReaderParent.jsm @@ -1,16 +1,16 @@ // -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /* 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/. */ "use strict"; -var EXPORTED_SYMBOLS = ["ReaderParent"]; +var EXPORTED_SYMBOLS = ["AboutReaderParent"]; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); ChromeUtils.defineModuleGetter( this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm" ); @@ -19,71 +19,156 @@ ChromeUtils.defineModuleGetter( "ReaderMode", "resource://gre/modules/ReaderMode.jsm" ); const gStringBundle = Services.strings.createBundle( "chrome://global/locale/aboutReader.properties" ); -var ReaderParent = { - // Listeners are added in BrowserGlue.jsm - receiveMessage(message) { +// A set of all of the AboutReaderParent actors that exist. +// See bug 1631146 for a request for a less manual way of doing this. +let gAllActors = new Set(); + +// A map of message names to listeners that listen to messages +// received by the AboutReaderParent actors. +let gListeners = new Map(); + +class AboutReaderParent extends JSWindowActorParent { + didDestroy() { + gAllActors.delete(this); + } + + isReaderMode(browser) { + return browser.currentURI.spec.startsWith("about:reader"); + } + + static addMessageListener(name, listener) { + if (!gListeners.has(name)) { + gListeners.set(name, new Set([listener])); + } else { + gListeners.get(name).add(listener); + } + } + + static removeMessageListener(name, listener) { + if (!gListeners.has(name)) { + return; + } + + gListeners.get(name).delete(listener); + } + + static broadcastAsyncMessage(name, data) { + for (let actor of gAllActors) { + // Ignore errors for actors that might not be valid yet or anymore. + try { + actor.sendAsyncMessage(name, data); + } catch (ex) {} + } + } + + callListeners(message) { + let listeners = gListeners.get(message.name); + if (!listeners) { + return; + } + + message.target = this.browsingContext.embedderElement; + for (let listener of listeners.values()) { + try { + listener.receiveMessage(message); + } catch (e) { + Cu.reportError(e); + } + } + } + + async receiveMessage(message) { switch (message.name) { case "Reader:FaviconRequest": { - if (message.target.messageManager) { - try { - let preferredWidth = message.data.preferredWidth || 0; - let uri = Services.io.newURI(message.data.url); + try { + let preferredWidth = message.data.preferredWidth || 0; + let uri = Services.io.newURI(message.data.url); + + let result = await new Promise(resolve => { PlacesUtils.favicons.getFaviconURLForPage( uri, iconUri => { if (iconUri) { iconUri = PlacesUtils.favicons.getFaviconLinkForIcon(iconUri); - message.target.messageManager.sendAsyncMessage( - "Reader:FaviconReturn", - { - url: message.data.url, - faviconUrl: iconUri.pathQueryRef.replace(/^favicon:/, ""), - } - ); + resolve({ + url: message.data.url, + faviconUrl: iconUri.pathQueryRef.replace(/^favicon:/, ""), + }); + } else { + resolve(null); } }, preferredWidth ); - } catch (ex) { - Cu.reportError( - "Error requesting favicon URL for about:reader content: " + ex - ); - } + }); + + this.callListeners(message); + return result; + } catch (ex) { + Cu.reportError( + "Error requesting favicon URL for about:reader content: " + ex + ); } + break; } case "Reader:UpdateReaderButton": { - let browser = message.target; + let browser = this.browsingContext.embedderElement; + if (!browser) { + return undefined; + } + + if (browser.outerBrowser) { + browser = browser.outerBrowser; // handle RDM mode + } + if (message.data && message.data.isArticle !== undefined) { browser.isArticle = message.data.isArticle; } this.updateReaderButton(browser); + this.callListeners(message); break; } + + default: + this.callListeners(message); + break; } - }, + + return undefined; + } + + static updateReaderButton(browser) { + let windowGlobal = browser.browsingContext.currentWindowGlobal; + let actor = windowGlobal.getActor("AboutReader"); + actor.updateReaderButton(browser); + } updateReaderButton(browser) { - let win = browser.ownerGlobal; - if (browser != win.gBrowser.selectedBrowser) { + let tabBrowser = browser.getTabBrowser(); + if (!tabBrowser || browser != tabBrowser.selectedBrowser) { return; } + let win = browser.ownerGlobal; + let button = win.document.getElementById("reader-mode-button"); let menuitem = win.document.getElementById("menu_readerModeItem"); let key = win.document.getElementById("key_toggleReaderMode"); - if (browser.currentURI.spec.startsWith("about:reader")) { + if (this.isReaderMode(browser)) { + gAllActors.add(this); + let closeText = gStringBundle.GetStringFromName("readerView.close"); button.setAttribute("readeractive", true); button.hidden = false; button.setAttribute("aria-label", closeText); menuitem.setAttribute("label", closeText); menuitem.setAttribute("hidden", false); @@ -110,35 +195,45 @@ var ReaderParent = { ); key.setAttribute("disabled", !browser.isArticle); if (browser.isArticle) { Services.obs.notifyObservers(null, "reader-mode-available"); } } - }, + } - forceShowReaderIcon(browser) { + static forceShowReaderIcon(browser) { browser.isArticle = true; - this.updateReaderButton(browser); - }, + AboutReaderParent.updateReaderButton(browser); + } - buttonClick(event) { + static buttonClick(event) { if (event.button != 0) { return; } - this.toggleReaderMode(event); - }, + AboutReaderParent.toggleReaderMode(event); + } + + static toggleReaderMode(event) { + let win = event.target.ownerGlobal; + if (win.gBrowser) { + let browser = win.gBrowser.selectedBrowser; - toggleReaderMode(event) { - let win = event.target.ownerGlobal; - let browser = win.gBrowser.selectedBrowser; - browser.messageManager.sendAsyncMessage("Reader:ToggleReaderMode"); - }, + let windowGlobal = browser.browsingContext.currentWindowGlobal; + let actor = windowGlobal.getActor("AboutReader"); + if (actor) { + if (actor.isReaderMode(browser)) { + gAllActors.delete(this); + } + actor.sendAsyncMessage("Reader:ToggleReaderMode", {}); + } + } + } /** * Gets an article for a given URL. This method will download and parse a document. * * @param url The article URL. * @param browser The browser where the article is currently loaded. * @return {Promise} * @resolves JS object representing the article, or null if no article is found. @@ -147,10 +242,10 @@ var ReaderParent = { return ReaderMode.downloadAndParseDocument(url).catch(e => { if (e && e.newURL) { // Pass up the error so we can navigate the browser in question to the new URL: throw e; } Cu.reportError("Error downloading and parsing document: " + e); return null; }); - }, -}; + } +}
--- a/browser/actors/moz.build +++ b/browser/actors/moz.build @@ -5,16 +5,19 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. with Files("**"): BUG_COMPONENT = ("Firefox", "General") with Files("ContentSearch*.jsm"): BUG_COMPONENT = ("Firefox", "Search") +with Files("AboutReaderParent.jsm"): + BUG_COMPONENT = ("Toolkit", "Reader Mode") + with Files("LightweightThemeChild.jsm"): BUG_COMPONENT = ("WebExtensions", "Themes") with Files("PageInfoChild.jsm"): BUG_COMPONENT = ("Firefox", "Page Info Window") with Files("PageStyleChild.jsm"): BUG_COMPONENT = ("Firefox", "Menus") @@ -29,16 +32,17 @@ FINAL_TARGET_FILES.actors += [ 'AboutNewTabChild.jsm', 'AboutPluginsChild.jsm', 'AboutPluginsParent.jsm', 'AboutPrivateBrowsingChild.jsm', 'AboutPrivateBrowsingParent.jsm', 'AboutProtectionsChild.jsm', 'AboutProtectionsParent.jsm', 'AboutReaderChild.jsm', + 'AboutReaderParent.jsm', 'AboutTabCrashedChild.jsm', 'AboutTabCrashedParent.jsm', 'BlockedSiteChild.jsm', 'BlockedSiteParent.jsm', 'BrowserTabChild.jsm', 'BrowserTabParent.jsm', 'ClickHandlerChild.jsm', 'ClickHandlerParent.jsm',
--- a/browser/base/content/browser-fullZoom.js +++ b/browser/base/content/browser-fullZoom.js @@ -329,34 +329,34 @@ var FullZoom = { /** * If browser in reader mode sends message to reader in order to decrease font size, * Otherwise reduces the zoom level of the page in the current browser. */ async reduce() { let browser = gBrowser.selectedBrowser; if (browser.currentURI.spec.startsWith("about:reader")) { - browser.messageManager.sendAsyncMessage("Reader:ZoomOut"); + browser.sendMessageToActor("Reader:ZoomOut", {}, "AboutReader"); } else if (this._isPDFViewer(browser)) { browser.messageManager.sendAsyncMessage("PDFJS:ZoomOut"); } else { ZoomManager.reduce(); this._ignorePendingZoomAccesses(browser); await this._applyZoomToPref(browser); } }, /** * If browser in reader mode sends message to reader in order to increase font size, * Otherwise enlarges the zoom level of the page in the current browser. */ async enlarge() { let browser = gBrowser.selectedBrowser; if (browser.currentURI.spec.startsWith("about:reader")) { - browser.messageManager.sendAsyncMessage("Reader:ZoomIn"); + browser.sendMessageToActor("Reader:ZoomIn", {}, "AboutReader"); } else if (this._isPDFViewer(browser)) { browser.messageManager.sendAsyncMessage("PDFJS:ZoomIn"); } else { ZoomManager.enlarge(); this._ignorePendingZoomAccesses(browser); await this._applyZoomToPref(browser); } }, @@ -366,17 +366,17 @@ var FullZoom = { * Otherwise enlarges the zoom level of the page in the current browser. * This function is not async like reduce/enlarge, because it is invoked by our * event handler. This means that the call to _applyZoomToPref is not awaited and * will happen asynchronously. */ changeZoomBy(aBrowser, aValue) { if (aBrowser.currentURI.spec.startsWith("about:reader")) { const message = aValue > 0 ? "Reader::ZoomIn" : "Reader:ZoomOut"; - aBrowser.messageManager.sendAsyncMessage(message); + aBrowser.sendMessageToActor(message, {}, "AboutReader"); return; } else if (this._isPDFViewer(aBrowser)) { const message = aValue > 0 ? "PDFJS::ZoomIn" : "PDFJS:ZoomOut"; aBrowser.messageManager.sendAsyncMessage(message); return; } let zoom = ZoomManager.getZoomForBrowser(aBrowser); zoom += aValue; @@ -407,17 +407,17 @@ var FullZoom = { * Sets the zoom level of the page in the given browser to the global zoom * level. * * @return A promise which resolves when the zoom reset has been applied. */ reset: function FullZoom_reset(browser = gBrowser.selectedBrowser) { let forceValue; if (browser.currentURI.spec.startsWith("about:reader")) { - browser.messageManager.sendAsyncMessage("Reader:ResetZoom"); + browser.sendMessageToActor("Reader:ResetZoom", {}, "AboutReader"); } else if (this._isPDFViewer(browser)) { browser.messageManager.sendAsyncMessage("PDFJS:ZoomReset"); // Ensure that the UI elements of the PDF viewer won't be zoomed in/out // on reset, even if/when browser default zoom value is not set to 100%. forceValue = 1; } let token = this._getBrowserToken(browser); let result = ZoomUI.getGlobalValue().then(value => {
--- a/browser/base/content/browser-sets.inc +++ b/browser/base/content/browser-sets.inc @@ -37,17 +37,17 @@ <command id="cmd_toggleMute" oncommand="gBrowser.toggleMuteAudioOnMultiSelectedTabs(gBrowser.selectedTab)"/> <command id="cmd_CustomizeToolbars" oncommand="gCustomizeMode.enter()"/> <command id="cmd_toggleOfflineStatus" oncommand="BrowserOffline.toggleOfflineStatus();"/> <command id="cmd_quitApplication" oncommand="goQuitApplication()"/> <command id="View:PageSource" oncommand="BrowserViewSource(window.gBrowser.selectedBrowser);"/> <command id="View:PageInfo" oncommand="BrowserPageInfo();"/> <command id="View:FullScreen" oncommand="BrowserFullScreen();"/> - <command id="View:ReaderView" oncommand="ReaderParent.toggleReaderMode(event);"/> + <command id="View:ReaderView" oncommand="AboutReaderParent.toggleReaderMode(event);"/> <command id="View:PictureInPicture" oncommand="PictureInPicture.onCommand(event);"/> <command id="cmd_find" oncommand="gLazyFindCommand('onFindCommand')"/> <command id="cmd_findAgain" oncommand="gLazyFindCommand('onFindAgainCommand', false)"/> <command id="cmd_findPrevious" oncommand="gLazyFindCommand('onFindAgainCommand', true)"/> #ifdef XP_MACOSX <command id="cmd_findSelection" oncommand="gLazyFindCommand('onFindSelectionCommand')"/> #endif <!-- work-around bug 392512 -->
--- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -11,16 +11,17 @@ var { AppConstants } = ChromeUtils.impor "resource://gre/modules/AppConstants.jsm" ); ChromeUtils.import("resource://gre/modules/NotificationDB.jsm"); // lazy module getters XPCOMUtils.defineLazyModuleGetters(this, { AboutNewTab: "resource:///modules/AboutNewTab.jsm", + AboutReaderParent: "resource:///actors/AboutReaderParent.jsm", AddonManager: "resource://gre/modules/AddonManager.jsm", AMTelemetry: "resource://gre/modules/AddonManager.jsm", NewTabPagePreloading: "resource:///modules/NewTabPagePreloading.jsm", BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.jsm", BrowserUtils: "resource://gre/modules/BrowserUtils.jsm", BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm", CFRPageActions: "resource://activity-stream/lib/CFRPageActions.jsm", CharsetMenu: "resource://gre/modules/CharsetMenu.jsm", @@ -55,17 +56,16 @@ XPCOMUtils.defineLazyModuleGetters(this, PlacesTransactions: "resource://gre/modules/PlacesTransactions.jsm", PluralForm: "resource://gre/modules/PluralForm.jsm", Pocket: "chrome://pocket/content/Pocket.jsm", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm", ProcessHangMonitor: "resource:///modules/ProcessHangMonitor.jsm", PromiseUtils: "resource://gre/modules/PromiseUtils.jsm", // TODO (Bug 1529552): Remove once old urlbar code goes away. ReaderMode: "resource://gre/modules/ReaderMode.jsm", - ReaderParent: "resource:///modules/ReaderParent.jsm", RFPHelper: "resource://gre/modules/RFPHelper.jsm", SafeBrowsing: "resource://gre/modules/SafeBrowsing.jsm", Sanitizer: "resource:///modules/Sanitizer.jsm", SessionStartup: "resource:///modules/sessionstore/SessionStartup.jsm", SessionStore: "resource:///modules/sessionstore/SessionStore.jsm", ShortcutUtils: "resource://gre/modules/ShortcutUtils.jsm", SimpleServiceDiscovery: "resource://gre/modules/SimpleServiceDiscovery.jsm", SiteDataManager: "resource:///modules/SiteDataManager.jsm", @@ -5279,17 +5279,17 @@ var XULBrowserWindow = { ) { gCustomizeMode.exit(); } CFRPageActions.updatePageActions(gBrowser.selectedBrowser); } Services.obs.notifyObservers(null, "touchbar-location-change", location); UpdateBackForwardCommands(gBrowser.webNavigation); - ReaderParent.updateReaderButton(gBrowser.selectedBrowser); + AboutReaderParent.updateReaderButton(gBrowser.selectedBrowser); if (!gMultiProcessBrowser) { // Bug 1108553 - Cannot rotate images with e10s gGestureSupport.restoreRotationState(); } // See bug 358202, when tabs are switched during a drag operation, // timers don't fire on windows (bug 203573) @@ -5882,19 +5882,23 @@ var TabsProgressListener = { }, onLocationChange(aBrowser, aWebProgress, aRequest, aLocationURI, aFlags) { // Filter out location changes caused by anchor navigation // or history.push/pop/replaceState. if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) { // Reader mode cares about history.pushState and friends. // FIXME: The content process should manage this directly (bug 1445351). - aBrowser.messageManager.sendAsyncMessage("Reader:PushState", { - isArticle: aBrowser.isArticle, - }); + aBrowser.sendMessageToActor( + "Reader:PushState", + { + isArticle: aBrowser.isArticle, + }, + "AboutReader" + ); return; } // Filter out location changes in sub documents. if (!aWebProgress.isTopLevel) { return; }
--- a/browser/base/content/browser.xhtml +++ b/browser/base/content/browser.xhtml @@ -1024,17 +1024,17 @@ <label id="userContext-label"/> <image id="userContext-indicator"/> </hbox> <image id="reader-mode-button" class="urlbar-icon urlbar-page-action" tooltip="dynamic-shortcut-tooltip" role="button" hidden="true" - onclick="ReaderParent.buttonClick(event);"/> + onclick="AboutReaderParent.buttonClick(event);"/> <toolbarbutton id="urlbar-zoom-button" onclick="FullZoom.reset();" tooltip="dynamic-shortcut-tooltip" hidden="true"/> <box id="pageActionSeparator" class="urlbar-page-action"/> <image id="pageActionButton" class="urlbar-icon urlbar-page-action" role="button"
--- a/browser/base/content/test/performance/browser_startup_content.js +++ b/browser/base/content/test/performance/browser_startup_content.js @@ -40,17 +40,16 @@ const whitelist = { // Browser front-end "resource:///actors/AboutReaderChild.jsm", "resource:///actors/BrowserTabChild.jsm", "resource:///actors/LinkHandlerChild.jsm", "resource:///actors/SearchTelemetryChild.jsm", "resource:///actors/PromptChild.jsm", "resource://gre/actors/AutoCompleteChild.jsm", - "resource://gre/modules/ActorChild.jsm", "resource://gre/modules/ActorManagerChild.jsm", "resource://gre/modules/E10SUtils.jsm", "resource://gre/modules/Readerable.jsm", // Telemetry "resource://gre/modules/TelemetryController.jsm", // bug 1470339 "resource://gre/modules/TelemetryUtils.jsm", // bug 1470339
--- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -152,16 +152,31 @@ let ACTORS = { events: { DOMWindowCreated: { capture: true }, }, }, matches: ["about:protections"], }, + AboutReader: { + parent: { + moduleURI: "resource:///actors/AboutReaderParent.jsm", + }, + child: { + moduleURI: "resource:///actors/AboutReaderChild.jsm", + events: { + DOMContentLoaded: {}, + pageshow: { mozSystemGroup: true }, + pagehide: { mozSystemGroup: true }, + }, + }, + messageManagerGroups: ["browsers"], + }, + AboutTabCrashed: { parent: { moduleURI: "resource:///actors/AboutTabCrashedParent.jsm", }, child: { moduleURI: "resource:///actors/AboutTabCrashedChild.jsm", events: { @@ -516,30 +531,16 @@ let ACTORS = { moduleURI: "resource:///actors/WebRTCChild.jsm", }, allFrames: true, }, }; let LEGACY_ACTORS = { - AboutReader: { - child: { - module: "resource:///actors/AboutReaderChild.jsm", - group: "browsers", - events: { - AboutReaderContentLoaded: { wantUntrusted: true }, - DOMContentLoaded: {}, - pageshow: { mozSystemGroup: true }, - pagehide: { mozSystemGroup: true }, - }, - messages: ["Reader:ToggleReaderMode", "Reader:PushState"], - }, - }, - URIFixup: { child: { module: "resource:///actors/URIFixupChild.jsm", group: "browsers", observers: ["keyword-uri-fixup"], }, }, }; @@ -695,17 +696,16 @@ XPCOMUtils.defineLazyModuleGetters(this, WindowsRegistry: "resource://gre/modules/WindowsRegistry.jsm", }); // eslint-disable-next-line no-unused-vars XPCOMUtils.defineLazyModuleGetters(this, { AboutLoginsParent: "resource:///modules/AboutLoginsParent.jsm", AsyncPrefs: "resource://gre/modules/AsyncPrefs.jsm", PluginManager: "resource:///actors/PluginParent.jsm", - ReaderParent: "resource:///modules/ReaderParent.jsm", }); /** * IF YOU ADD OR REMOVE FROM THIS LIST, PLEASE UPDATE THE LIST ABOVE AS WELL. * XXX Bug 1325373 is for making eslint detect these automatically. */ let initializedModules = {};
--- a/browser/components/extensions/parent/ext-browser.js +++ b/browser/components/extensions/parent/ext-browser.js @@ -20,16 +20,21 @@ ChromeUtils.defineModuleGetter( "BrowserWindowTracker", "resource:///modules/BrowserWindowTracker.jsm" ); ChromeUtils.defineModuleGetter( this, "PromiseUtils", "resource://gre/modules/PromiseUtils.jsm" ); +ChromeUtils.defineModuleGetter( + this, + "AboutReaderParent", + "resource:///actors/AboutReaderParent.jsm" +); var { ExtensionError } = ExtensionUtils; var { defineLazyGetter } = ExtensionCommon; const READER_MODE_PREFIX = "about:reader"; let tabTracker; @@ -347,17 +352,17 @@ class TabTracker extends TabTrackerBase windowTracker.addListener("TabClose", this); windowTracker.addListener("TabOpen", this); windowTracker.addListener("TabSelect", this); windowTracker.addListener("TabMultiSelect", this); windowTracker.addOpenListener(this._handleWindowOpen); windowTracker.addCloseListener(this._handleWindowClose); - Services.mm.addMessageListener("Reader:UpdateReaderButton", this); + AboutReaderParent.addMessageListener("Reader:UpdateReaderButton", this); /* eslint-disable mozilla/balanced-listeners */ this.on("tab-detached", this._handleTabDestroyed); this.on("tab-removed", this._handleTabDestroyed); /* eslint-enable mozilla/balanced-listeners */ } getId(nativeTab) {
--- a/browser/components/extensions/parent/ext-tabs.js +++ b/browser/components/extensions/parent/ext-tabs.js @@ -1484,18 +1484,21 @@ this.tabs = class extends ExtensionAPI { async toggleReaderMode(tabId) { let tab = await promiseTabWhenReady(tabId); if (!tab.isInReaderMode && !tab.isArticle) { throw new ExtensionError( "The specified tab cannot be placed into reader mode." ); } let nativeTab = getTabOrActive(tabId); - nativeTab.linkedBrowser.messageManager.sendAsyncMessage( - "Reader:ToggleReaderMode" + + nativeTab.linkedBrowser.sendMessageToActor( + "Reader:ToggleReaderMode", + {}, + "AboutReader" ); }, moveInSuccession(tabIds, tabId, options) { const { insert, append } = options || {}; const tabIdSet = new Set(tabIds); if (tabIdSet.size !== tabIds.length) { throw new ExtensionError(
--- a/browser/components/newtab/lib/ASRouterTriggerListeners.jsm +++ b/browser/components/newtab/lib/ASRouterTriggerListeners.jsm @@ -6,16 +6,17 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); XPCOMUtils.defineLazyModuleGetters(this, { PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm", EveryWindow: "resource:///modules/EveryWindow.jsm", + AboutReaderParent: "resource:///actors/AboutReaderParent.jsm", }); const FEW_MINUTES = 15 * 60 * 1000; // 15 mins const MATCH_PATTERN_OPTIONS = { ignorePath: true }; function isPrivateWindow(win) { return ( !(win instanceof Ci.nsIDOMWindow) || @@ -94,17 +95,17 @@ this.ASRouterTriggerListeners = new Map( _triggerHandler: null, _hosts: new Set(), _matchPatternSet: null, readerModeEvent: "Reader:UpdateReaderButton", init(triggerHandler, hosts, patterns) { if (!this._initialized) { this.receiveMessage = this.receiveMessage.bind(this); - Services.mm.addMessageListener(this.readerModeEvent, this); + AboutReaderParent.addMessageListener(this.readerModeEvent, this); this._triggerHandler = triggerHandler; this._initialized = true; } if (patterns) { this._matchPatternSet = createMatchPatternSet([ ...(this._matchPatternSet ? this._matchPatternSet.patterns : []), ...patterns, ]); @@ -123,17 +124,17 @@ this.ASRouterTriggerListeners = new Map( if (match) { this._triggerHandler(target, { id: this.id, param: match }); } } }, uninit() { if (this._initialized) { - Services.mm.removeMessageListener(this.readerModeEvent, this); + AboutReaderParent.removeMessageListener(this.readerModeEvent, this); this._initialized = false; this._triggerHandler = null; this._hosts = new Set(); this._matchPatternSet = null; } }, }, ],
--- a/browser/components/newtab/test/browser/browser_asrouter_trigger_listeners.js +++ b/browser/components/newtab/test/browser/browser_asrouter_trigger_listeners.js @@ -58,17 +58,20 @@ add_task(async function check_openArticl articleTrigger.init((browser, match) => resolve(match), ["example.com"]) ); const win = await BrowserTestUtils.openNewBrowserWindow(); await openURLInWindow(win, TEST_URL); // Send a message from the content page (the TEST_URL) to the parent // This should trigger the `receiveMessage` cb in the articleTrigger await ContentTask.spawn(win.gBrowser.selectedBrowser, null, async () => { - sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: true }); + let readerActor = content.windowGlobalChild.getActor("AboutReader"); + readerActor.sendAsyncMessage("Reader:UpdateReaderButton", { + isArticle: true, + }); }); await listenerTriggered.then(data => is( data.param.url, TEST_URL, "We should match on the TEST_URL as a website article" )
--- a/browser/components/newtab/test/unit/asrouter/ASRouterTriggerListeners.test.js +++ b/browser/components/newtab/test/unit/asrouter/ASRouterTriggerListeners.test.js @@ -97,71 +97,71 @@ describe("ASRouterTriggerListeners", () beforeEach(() => { globals.set( "MatchPatternSet", sandbox.stub().callsFake(patterns => ({ patterns, matches: url => patterns.has(url), })) ); - sandbox.stub(global.Services.mm, "addMessageListener"); - sandbox.stub(global.Services.mm, "removeMessageListener"); + sandbox.stub(global.AboutReaderParent, "addMessageListener"); + sandbox.stub(global.AboutReaderParent, "removeMessageListener"); }); afterEach(() => { openArticleURLListener.uninit(); }); it("setup an event listener on init", () => { openArticleURLListener.init(sandbox.stub(), hosts, hosts); - assert.calledOnce(global.Services.mm.addMessageListener); + assert.calledOnce(global.AboutReaderParent.addMessageListener); assert.calledWithExactly( - global.Services.mm.addMessageListener, + global.AboutReaderParent.addMessageListener, openArticleURLListener.readerModeEvent, sinon.match.object ); }); it("should call triggerHandler correctly for matches [host match]", () => { const stub = sandbox.stub(); const target = { currentURI: { host: hosts[0], spec: hosts[1] } }; openArticleURLListener.init(stub, hosts, hosts); const [ , { receiveMessage }, - ] = global.Services.mm.addMessageListener.firstCall.args; + ] = global.AboutReaderParent.addMessageListener.firstCall.args; receiveMessage({ data: { isArticle: true }, target }); assert.calledOnce(stub); assert.calledWithExactly(stub, target, { id: openArticleURLListener.id, param: { host: hosts[0], url: hosts[1] }, }); }); it("should call triggerHandler correctly for matches [pattern match]", () => { const stub = sandbox.stub(); const target = { currentURI: { host: null, spec: hosts[1] } }; openArticleURLListener.init(stub, hosts, hosts); const [ , { receiveMessage }, - ] = global.Services.mm.addMessageListener.firstCall.args; + ] = global.AboutReaderParent.addMessageListener.firstCall.args; receiveMessage({ data: { isArticle: true }, target }); assert.calledOnce(stub); assert.calledWithExactly(stub, target, { id: openArticleURLListener.id, param: { host: null, url: hosts[1] }, }); }); it("should remove the message listener", () => { openArticleURLListener.init(sandbox.stub(), hosts, hosts); openArticleURLListener.uninit(); - assert.calledOnce(global.Services.mm.removeMessageListener); + assert.calledOnce(global.AboutReaderParent.removeMessageListener); }); }); }); describe("frequentVisits", () => { let _triggerHandler; beforeEach(() => { _triggerHandler = sandbox.stub();
--- a/browser/components/newtab/test/unit/unit-entry.js +++ b/browser/components/newtab/test/unit/unit-entry.js @@ -40,16 +40,20 @@ const RemoteSettings = name => ({ return Promise.resolve([]); }, on: () => {}, off: () => {}, }); RemoteSettings.pollChanges = () => {}; const TEST_GLOBAL = { + AboutReaderParent: { + addMessageListener: (messageName, listener) => {}, + removeMessageListener: (messageName, listener) => {}, + }, AddonManager: { getActiveAddons() { return Promise.resolve({ addons: [], fullData: false }); }, }, AppConstants: { MOZILLA_OFFICIAL: true, MOZ_APP_VERSION: "69.0a1",
--- a/browser/components/pocket/content/SaveToPocket.jsm +++ b/browser/components/pocket/content/SaveToPocket.jsm @@ -24,16 +24,21 @@ ChromeUtils.defineModuleGetter( "Pocket", "chrome://pocket/content/Pocket.jsm" ); ChromeUtils.defineModuleGetter( this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm" ); +ChromeUtils.defineModuleGetter( + this, + "AboutReaderParent", + "resource:///actors/AboutReaderParent.jsm" +); var EXPORTED_SYMBOLS = ["SaveToPocket"]; XPCOMUtils.defineLazyGetter(this, "gStrings", () => { return Services.strings.createBundle( "chrome://global/locale/aboutReader.properties" ); }); @@ -245,18 +250,18 @@ var SaveToPocket = { if (this.prefEnabled) { PocketOverlay.startup(); } else { // We avoid calling onPrefChange or similar here, because we don't want to // shut down things that haven't started up, or broadcast unnecessary messages. this.updateElements(false); Services.obs.addObserver(this, "browser-delayed-startup-finished"); } - Services.mm.addMessageListener("Reader:OnSetup", this); - Services.mm.addMessageListener("Reader:Clicked-pocket-button", this); + AboutReaderParent.addMessageListener("Reader:OnSetup", this); + AboutReaderParent.addMessageListener("Reader:Clicked-pocket-button", this); }, observe(subject, topic, data) { if (topic == "browser-delayed-startup-finished") { // We only get here if pocket is disabled; the observer is removed when // we're enabled. this.updateElementsInWindow(subject, false); } @@ -269,29 +274,29 @@ var SaveToPocket = { ]), image: "chrome://global/skin/reader/pocket.svg", width: 16, height: 16, }, onPrefChange(pref, oldValue, newValue) { if (!newValue) { - Services.mm.broadcastAsyncMessage("Reader:RemoveButton", { + AboutReaderParent.broadcastAsyncMessage("Reader:RemoveButton", { id: "pocket-button", }); PocketOverlay.shutdown(); Services.obs.addObserver(this, "browser-delayed-startup-finished"); } else { Services.obs.removeObserver(this, "browser-delayed-startup-finished"); PocketOverlay.startup(); // The title for the button is extracted from browser.xhtml where it comes from a DTD. // If we don't have this, there's also no possibility of there being a reader // mode tab already loaded. We'll get an Reader:OnSetup message when that happens. if (this._readerButtonData.title) { - Services.mm.broadcastAsyncMessage( + AboutReaderParent.broadcastAsyncMessage( "Reader:AddButton", this._readerButtonData ); } } this.updateElements(newValue); }, @@ -323,19 +328,20 @@ var SaveToPocket = { // We must have a browser window; get the tooltip for the button if we don't // have it already. if (!this._readerButtonData.title) { let doc = message.target.ownerDocument; let button = doc.getElementById("pocket-button"); this._readerButtonData.title = button.getAttribute("tooltiptext"); } // Tell the reader about our button. - message.target.messageManager.sendAsyncMessage( + message.target.sendMessageToActor( "Reader:AddButton", - this._readerButtonData + this._readerButtonData, + "AboutReader" ); break; } case "Reader:Clicked-pocket-button": { PocketPageAction.pageAction.doCommand(message.target.ownerGlobal); break; } }
--- a/browser/components/uitour/UITour.jsm +++ b/browser/components/uitour/UITour.jsm @@ -41,18 +41,18 @@ ChromeUtils.defineModuleGetter( ); ChromeUtils.defineModuleGetter( this, "ProfileAge", "resource://gre/modules/ProfileAge.jsm" ); ChromeUtils.defineModuleGetter( this, - "ReaderParent", - "resource:///modules/ReaderParent.jsm" + "AboutReaderParent", + "resource:///actors/AboutReaderParent.jsm" ); ChromeUtils.defineModuleGetter( this, "ResetProfile", "resource://gre/modules/ResetProfile.jsm" ); ChromeUtils.defineModuleGetter( this, @@ -657,24 +657,24 @@ var UITour = { case "ping": { if (typeof data.callbackID == "string") { this.sendPageCallback(browser, data.callbackID); } break; } case "forceShowReaderIcon": { - ReaderParent.forceShowReaderIcon(browser); + AboutReaderParent.forceShowReaderIcon(browser); break; } case "toggleReaderMode": { let targetPromise = this.getTarget(window, "readerMode-urlBar"); targetPromise.then(target => { - ReaderParent.toggleReaderMode({ target: target.node }); + AboutReaderParent.toggleReaderMode({ target: target.node }); }); break; } case "closeTab": { // Find the <tabbrowser> element of the <browser> for which the event // was generated originally. If the browser where the UI tour is loaded // is windowless, just ignore the request to close the tab. The request
--- a/browser/modules/moz.build +++ b/browser/modules/moz.build @@ -74,19 +74,16 @@ with Files("OpenInTabsUtils.jsm"): BUG_COMPONENT = ("Firefox", "Tabbed Browser") with Files("PermissionUI.jsm"): BUG_COMPONENT = ("Firefox", "Site Permissions") with Files("ProcessHangMonitor.jsm"): BUG_COMPONENT = ("Core", "DOM: Content Processes") -with Files("ReaderParent.jsm"): - BUG_COMPONENT = ("Toolkit", "Reader Mode") - with Files("Sanitizer.jsm"): BUG_COMPONENT = ("Firefox", "Preferences") with Files("SelectionChangedMenulist.jsm"): BUG_COMPONENT = ("Firefox", "Preferences") with Files("SiteDataManager.jsm"): BUG_COMPONENT = ("Firefox", "Preferences") @@ -140,17 +137,16 @@ EXTRA_JS_MODULES += [ 'LaterRun.jsm', 'LiveBookmarkMigrator.jsm', 'NewTabPagePreloading.jsm', 'OpenInTabsUtils.jsm', 'PageActions.jsm', 'PermissionUI.jsm', 'PingCentre.jsm', 'ProcessHangMonitor.jsm', - 'ReaderParent.jsm', 'Sanitizer.jsm', 'SelectionChangedMenulist.jsm', 'SiteDataManager.jsm', 'SitePermissions.jsm', 'TabsList.jsm', 'TabUnloader.jsm', 'ThemeVariableMap.jsm', 'TransientPrefs.jsm',
--- a/toolkit/components/narrate/NarrateControls.jsm +++ b/toolkit/components/narrate/NarrateControls.jsm @@ -16,18 +16,17 @@ const { AsyncPrefs } = ChromeUtils.impor ); var EXPORTED_SYMBOLS = ["NarrateControls"]; var gStrings = Services.strings.createBundle( "chrome://global/locale/narrate.properties" ); -function NarrateControls(mm, win, languagePromise) { - this._mm = mm; +function NarrateControls(win, languagePromise) { this._winRef = Cu.getWeakReference(win); this._languagePromise = languagePromise; win.addEventListener("unload", this); // Append content style sheet in document head let style = win.document.createElement("link"); style.rel = "stylesheet";
--- a/toolkit/components/reader/AboutReader.jsm +++ b/toolkit/components/reader/AboutReader.jsm @@ -42,39 +42,33 @@ var gStrings = Services.strings.createBu const zoomOnCtrl = Services.prefs.getIntPref("mousewheel.with_control.action", 3) == 3; const zoomOnMeta = Services.prefs.getIntPref("mousewheel.with_meta.action", 1) == 3; const gIsFirefoxDesktop = Services.appinfo.ID == "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"; -var AboutReader = function(mm, win, articlePromise) { +var AboutReader = function(actor, articlePromise) { + let win = actor.contentWindow; let url = this._getOriginalUrl(win); if (!(url.startsWith("http://") || url.startsWith("https://"))) { let errorMsg = "Only http:// and https:// URLs can be loaded in about:reader."; if (Services.prefs.getBoolPref("reader.errors.includeURLs")) { errorMsg += " Tried to load: " + url + "."; } Cu.reportError(errorMsg); win.location.href = "about:blank"; return; } let doc = win.document; - this._mm = mm; - this._mm.addMessageListener("Reader:CloseDropdown", this); - this._mm.addMessageListener("Reader:AddButton", this); - this._mm.addMessageListener("Reader:RemoveButton", this); - this._mm.addMessageListener("Reader:GetStoredArticleData", this); - this._mm.addMessageListener("Reader:ZoomIn", this); - this._mm.addMessageListener("Reader:ZoomOut", this); - this._mm.addMessageListener("Reader:ResetZoom", this); + this._actor = actor; this._docRef = Cu.getWeakReference(doc); this._winRef = Cu.getWeakReference(win); this._innerWindowId = win.windowUtils.currentInnerWindowID; this._article = null; this._languagePromise = new Promise(resolve => { this._foundLanguage = resolve; @@ -134,17 +128,17 @@ var AboutReader = function(mm, win, arti this._setupButton( "close-button", this._onReaderClose.bind(this), "aboutReader.toolbar.close" ); if (gIsFirefoxDesktop) { // we're ready for any external setup, send a signal for that. - this._mm.sendAsyncMessage("Reader:OnSetup"); + this._actor.sendAsyncMessage("Reader:OnSetup"); } let colorSchemeValues = JSON.parse( Services.prefs.getCharPref("reader.color_scheme.values") ); let colorSchemeOptions = colorSchemeValues.map(value => { return { name: gStrings.GetStringFromName("aboutReader.colorScheme." + value), @@ -199,17 +193,17 @@ var AboutReader = function(mm, win, arti this._setupFontSizeButtons(); this._setupContentWidthButtons(); this._setupLineHeightButtons(); if (win.speechSynthesis && Services.prefs.getBoolPref("narrate.enabled")) { - new NarrateControls(mm, win, this._languagePromise); + new NarrateControls(win, this._languagePromise); } this._loadArticle(); let dropdown = this._toolbarElement; let elemL10nMap = { ".minus-button": "minus", @@ -307,36 +301,18 @@ AboutReader.prototype = { if (this._toolbarVertical !== undefined) { return this._toolbarVertical; } return (this._toolbarVertical = Services.prefs.getBoolPref( "reader.toolbar.vertical" )); }, - // Provides unique view Id. - get viewId() { - let _viewId = Cc["@mozilla.org/uuid-generator;1"] - .getService(Ci.nsIUUIDGenerator) - .generateUUID() - .toString(); - Object.defineProperty(this, "viewId", { value: _viewId }); - - return _viewId; - }, - receiveMessage(message) { switch (message.name) { - // Triggered by Android user pressing BACK while the banner font-dropdown is open. - case "Reader:CloseDropdown": { - // Just close it. - this._closeDropdowns(); - break; - } - case "Reader:AddButton": { if ( message.data.id && message.data.image && !this._doc.getElementsByClassName(message.data.id)[0] ) { let btn = this._doc.createElement("button"); btn.dataset.buttonid = message.data.id; @@ -350,39 +326,33 @@ AboutReader.prototype = { btn.textContent = message.data.text; } if (message.data.width && message.data.height) { btn.style.backgroundSize = `${message.data.width}px ${message.data.height}px`; } let tb = this._toolbarElement; tb.appendChild(btn); this._setupButton(message.data.id, button => { - this._mm.sendAsyncMessage( + this._actor.sendAsyncMessage( "Reader:Clicked-" + button.dataset.buttonid, { article: this._article } ); }); } break; } case "Reader:RemoveButton": { if (message.data.id) { let btn = this._doc.getElementsByClassName(message.data.id)[0]; if (btn) { btn.remove(); } } break; } - case "Reader:GetStoredArticleData": { - this._mm.sendAsyncMessage("Reader:StoredArticleData", { - article: this._article, - }); - break; - } case "Reader:ZoomIn": { this._changeFontSize(+1); break; } case "Reader:ZoomOut": { this._changeFontSize(-1); break; } @@ -423,17 +393,16 @@ AboutReader.prototype = { // hide the system UI and the "reader-toolbar" only if the dropdown is not opened let selector = ".dropdown.open"; let openDropdowns = this._doc.querySelectorAll(selector); if (openDropdowns.length) { break; } let isScrollingUp = this._scrollOffset > vv.pageTop; - this._setSystemUIVisibility(isScrollingUp); this._setToolbarVisibility(isScrollingUp); } this._scrollOffset = vv.pageTop; break; case "resize": this._updateImageMargins(); break; @@ -475,45 +444,28 @@ AboutReader.prototype = { case "visibilitychange": this._handleVisibilityChange(); break; case "pagehide": // Close the Banners Font-dropdown, cleanup Android BackPressListener. this._closeDropdowns(); - this._mm.removeMessageListener("Reader:CloseDropdown", this); - this._mm.removeMessageListener("Reader:AddButton", this); - this._mm.removeMessageListener("Reader:RemoveButton", this); - this._mm.removeMessageListener("Reader:GetStoredArticleData", this); - this._mm.removeMessageListener("Reader:ZoomIn", this); - this._mm.removeMessageListener("Reader:ZoomOut", this); - this._mm.removeMessageListener("Reader:ResetZoom", this); - this._windowUnloaded = true; + this._actor.readerModeHidden(); + this.clearActor(); break; } }, - observe(subject, topic, data) { - if ( - subject.QueryInterface(Ci.nsISupportsPRUint64).data != this._innerWindowId - ) { - return; - } - - Services.obs.removeObserver(this, "inner-window-destroyed"); - - this._mm.removeMessageListener("Reader:CloseDropdown", this); - this._mm.removeMessageListener("Reader:AddButton", this); - this._mm.removeMessageListener("Reader:RemoveButton", this); - this._windowUnloaded = true; + clearActor() { + this._actor = null; }, _onReaderClose() { - ReaderMode.leaveReaderMode(this._mm.docShell, this._win); + ReaderMode.leaveReaderMode(this._actor.docShell, this._win); }, async _resetFontSize() { await AsyncPrefs.reset("reader.font_size"); let currentSize = Services.prefs.getIntPref("reader.font_size"); this._setFontSize(currentSize); }, @@ -883,20 +835,16 @@ AboutReader.prototype = { } this._fontType = newFontType; bodyClasses.add(this._fontType); AsyncPrefs.set("reader.font_type", this._fontType); }, - _setSystemUIVisibility(visible) { - this._mm.sendAsyncMessage("Reader:SystemUIVisibility", { visible }); - }, - _setToolbarVisibility(visible) { let tb = this._toolbarElement; if (visible) { if (tb.style.opacity != "1") { tb.removeAttribute("hidden"); tb.style.opacity = "1"; } @@ -918,73 +866,50 @@ AboutReader.prototype = { let url = this._getOriginalUrl(); this._showProgressDelayed(); let article; if (this._articlePromise) { article = await this._articlePromise; } else { try { - article = await this._getArticle(url); + article = await ReaderMode.downloadAndParseDocument(url); } catch (e) { if (e && e.newURL) { let readerURL = "about:reader?url=" + encodeURIComponent(e.newURL); this._win.location.replace(readerURL); return; } } } - if (this._windowUnloaded) { + if (!this._actor) { return; } // Replace the loading message with an error message if there's a failure. // Users are supposed to navigate away by themselves (because we cannot // remove ourselves from session history.) if (!article) { this._showError(); return; } this._showContent(article); }, - _getArticle(url) { - if (this.PLATFORM_HAS_CACHE) { - return new Promise((resolve, reject) => { - let listener = message => { - this._mm.removeMessageListener("Reader:ArticleData", listener); - if (message.data.newURL) { - reject({ newURL: message.data.newURL }); - return; - } - resolve(message.data.article); - }; - this._mm.addMessageListener("Reader:ArticleData", listener); - this._mm.sendAsyncMessage("Reader:ArticleGet", { url }); - }); - } - return ReaderMode.downloadAndParseDocument(url); - }, - - _requestFavicon() { - let handleFaviconReturn = message => { - this._mm.removeMessageListener( - "Reader:FaviconReturn", - handleFaviconReturn - ); - this._loadFavicon(message.data.url, message.data.faviconUrl); - }; - - this._mm.addMessageListener("Reader:FaviconReturn", handleFaviconReturn); - this._mm.sendAsyncMessage("Reader:FaviconRequest", { + async _requestFavicon() { + let iconDetails = await this._actor.sendQuery("Reader:FaviconRequest", { url: this._article.url, preferredWidth: 16 * this._win.devicePixelRatio, }); + + if (iconDetails) { + this._loadFavicon(iconDetails.url, iconDetails.faviconUrl); + } }, _loadFavicon(url, faviconUrl) { if (this._article.url !== url) { return; } let doc = this._doc; @@ -1159,17 +1084,17 @@ AboutReader.prototype = { this._contentElement.classList.remove("reader-show-element"); }, _showProgressDelayed() { this._win.setTimeout(() => { // No need to show progress if the article has been loaded, // if the window has been unloaded, or if there was an error // trying to load the article. - if (this._article || this._windowUnloaded || this._error) { + if (this._article || !this._actor || this._error) { return; } this._headerElement.classList.remove("reader-show-element"); this._contentElement.classList.remove("reader-show-element"); this._messageElement.textContent = gStrings.GetStringFromName( "aboutReader.loading2" @@ -1330,17 +1255,16 @@ AboutReader.prototype = { this._closeDropdowns(); // Trigger BackPressListener initialization in Android. dropdown.classList.add("open"); let { windowUtils } = this._winRef.get(); let toggle = dropdown.querySelector(".dropdown-toggle"); let anchorWidth = windowUtils.getBoundsWithoutFlushing(toggle).width; dropdown.style.setProperty("--popup-anchor-width", anchorWidth + "px"); - this._mm.sendAsyncMessage("Reader:DropdownOpened", this.viewId); }, /* * If the ReaderView has open dropdowns, close them. If we are closing the * dropdowns because the page is scrolling, allow popups to stay open with * the keep-open class. */ _closeDropdowns(scrolling) { @@ -1348,21 +1272,16 @@ AboutReader.prototype = { if (scrolling) { selector += ":not(.keep-open)"; } let openDropdowns = this._doc.querySelectorAll(selector); for (let dropdown of openDropdowns) { dropdown.classList.remove("open"); } - - // Trigger BackPressListener cleanup in Android. - if (openDropdowns.length) { - this._mm.sendAsyncMessage("Reader:DropdownClosed", this.viewId); - } }, /* * Scroll reader view to a reference */ _goToReference(ref) { if (ref) { this._win.location.hash = ref;
--- a/toolkit/components/reader/content/aboutReader.html +++ b/toolkit/components/reader/content/aboutReader.html @@ -5,17 +5,16 @@ <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Security-Policy" content="default-src chrome:; img-src data: *; media-src *; object-src 'none'" /> <meta content="text/html; charset=UTF-8" http-equiv="content-type" /> <meta name="viewport" content="width=device-width; user-scalable=0" /> <link rel="stylesheet" href="chrome://global/skin/aboutReader.css" type="text/css"/> - <script src="chrome://global/content/reader/aboutReader.js"></script> </head> <body> <div class="container"> <div class="header reader-header"> <a class="domain reader-domain"></a> <div class="domain-border"></div> <h1 class="reader-title"></h1>
deleted file mode 100644 --- a/toolkit/components/reader/content/aboutReader.js +++ /dev/null @@ -1,11 +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/. */ - -"use strict"; - -window.addEventListener("DOMContentLoaded", function() { - document.dispatchEvent( - new CustomEvent("AboutReaderContentLoaded", { bubbles: true }) - ); -});
--- a/toolkit/components/reader/jar.mn +++ b/toolkit/components/reader/jar.mn @@ -1,7 +1,6 @@ # 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/. toolkit.jar: content/global/reader/aboutReader.html (content/aboutReader.html) - content/global/reader/aboutReader.js (content/aboutReader.js)
--- a/toolkit/components/reader/test/browser.ini +++ b/toolkit/components/reader/test/browser.ini @@ -3,16 +3,20 @@ support-files = head.js [browser_readerMode.js] support-files = readerModeNonArticle.html readerModeArticle.html readerModeArticleHiddenNodes.html [browser_readerMode_hidden_nodes.js] support-files = readerModeArticleHiddenNodes.html +[browser_readerMode_pocket.js] +support-files = + readerModeArticleShort.html + readerModeArticleMedium.html [browser_readerMode_with_anchor.js] skip-if = fission # Bug 1616805 support-files = readerModeArticle.html [browser_bug1124271_readerModePinnedTab.js] support-files = readerModeArticle.html [browser_bug1453818_samesite_cookie.js]
new file mode 100644 --- /dev/null +++ b/toolkit/components/reader/test/browser_readerMode_pocket.js @@ -0,0 +1,90 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// This test verifies that the Save To Pocket button appears in reader mode, +// and is toggled hidden and visible when pocket is disabled and enabled. + +const TEST_PATH = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "http://example.com" +); + +async function getPocketButtonsCount(browser) { + return SpecialPowers.spawn(browser, [], () => { + return content.document.getElementsByClassName("pocket-button").length; + }); +} + +add_task(async function() { + // set the pocket preference before beginning. + await SpecialPowers.pushPrefEnv({ + set: [["extensions.pocket.enabled", true]], + }); + + var readerButton = document.getElementById("reader-mode-button"); + + let tab1 = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PATH + "readerModeArticleShort.html" + ); + + let promiseTabLoad = promiseTabLoadEvent(tab1); + readerButton.click(); + await promiseTabLoad; + + let tab2 = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PATH + "readerModeArticleMedium.html" + ); + + promiseTabLoad = promiseTabLoadEvent(tab2); + readerButton.click(); + await promiseTabLoad; + + is( + await getPocketButtonsCount(tab1.linkedBrowser), + 1, + "tab 1 has a pocket button" + ); + is( + await getPocketButtonsCount(tab1.linkedBrowser), + 1, + "tab 2 has a pocket button" + ); + + // Turn off the pocket preference. The Save To Pocket buttons should disappear. + await SpecialPowers.pushPrefEnv({ + set: [["extensions.pocket.enabled", false]], + }); + + is( + await getPocketButtonsCount(tab1.linkedBrowser), + 0, + "tab 1 has no pocket button" + ); + is( + await getPocketButtonsCount(tab1.linkedBrowser), + 0, + "tab 2 has no pocket button" + ); + + // Turn on the pocket preference. The Save To Pocket buttons should reappear again. + await SpecialPowers.pushPrefEnv({ + set: [["extensions.pocket.enabled", true]], + }); + + is( + await getPocketButtonsCount(tab1.linkedBrowser), + 1, + "tab 1 has a pocket button again" + ); + is( + await getPocketButtonsCount(tab1.linkedBrowser), + 1, + "tab 2 has a pocket button again" + ); + + BrowserTestUtils.removeTab(tab1); + BrowserTestUtils.removeTab(tab2); +});