--- a/browser/components/loop/MozLoopAPI.jsm
+++ b/browser/components/loop/MozLoopAPI.jsm
@@ -38,23 +38,33 @@ XPCOMUtils.defineLazyServiceGetter(this,
"nsIExternalProtocolService");
this.EXPORTED_SYMBOLS = ["injectLoopAPI"];
/**
* Trying to clone an Error object into a different container will yield an error.
* We can work around this by copying the properties we care about onto a regular
* object.
*
- * @param {Error} error Error object to copy
- * @param {nsIDOMWindow} targetWindow The content window to attach the API
+ * @param {Error|nsIException} error Error object to copy
+ * @param {nsIDOMWindow} targetWindow The content window to clone into
*/
const cloneErrorObject = function(error, targetWindow) {
let obj = new targetWindow.Error();
- for (let prop of Object.getOwnPropertyNames(error)) {
+ let props = Object.getOwnPropertyNames(error);
+ // nsIException properties are not enumerable, so we'll try to copy the most
+ // common and useful ones.
+ if (!props.length) {
+ props.push("message", "filename", "lineNumber", "columnNumber", "stack");
+ }
+ for (let prop of props) {
let value = error[prop];
+ // for nsIException objects, the property may not be defined.
+ if (typeof value == "undefined") {
+ continue;
+ }
if (typeof value != "string" && typeof value != "number") {
value = String(value);
}
Object.defineProperty(Cu.waiveXrays(obj), prop, {
configurable: false,
enumerable: true,
value: value,
@@ -73,26 +83,31 @@ const cloneErrorObject = function(error,
* @param {any} value Value or object to copy
* @param {nsIDOMWindow} targetWindow The content window to copy to
*/
const cloneValueInto = function(value, targetWindow) {
if (!value || typeof value != "object") {
return value;
}
+ // HAWK request errors contain an nsIException object inside `value`.
+ if (("error" in value) && (value.error instanceof Ci.nsIException)) {
+ value = value.error;
+ }
+
// Strip Function properties, since they can not be cloned across boundaries
// like this.
for (let prop of Object.getOwnPropertyNames(value)) {
if (typeof value[prop] == "function") {
delete value[prop];
}
}
// Inspect for an error this way, because the Error object is special.
- if (value.constructor.name == "Error") {
+ if (value.constructor.name == "Error" || value instanceof Ci.nsIException) {
return cloneErrorObject(value, targetWindow);
}
let clone;
try {
clone = Cu.cloneInto(value, targetWindow);
} catch (ex) {
MozLoopService.log.debug("Failed to clone value:", value);
--- a/browser/components/loop/content/shared/js/feedbackViews.js
+++ b/browser/components/loop/content/shared/js/feedbackViews.js
@@ -71,17 +71,17 @@ loop.shared.views.FeedbackView = (functi
},
_getCategories: function() {
return {
audio_quality: l10n.get("feedback_category_audio_quality"),
video_quality: l10n.get("feedback_category_video_quality"),
disconnected : l10n.get("feedback_category_was_disconnected"),
confusing: l10n.get("feedback_category_confusing"),
- other: l10n.get("feedback_category_other")
+ other: l10n.get("feedback_category_other2")
};
},
_getCategoryFields: function() {
var categories = this._getCategories();
return Object.keys(categories).map(function(category, key) {
return (
React.createElement("label", {key: key, className: "feedback-category-label"},
@@ -114,56 +114,48 @@ loop.shared.views.FeedbackView = (functi
return false;
}
return true;
},
handleCategoryChange: function(event) {
var category = event.target.value;
this.setState({
- category: category,
- description: category == "other" ? "" : this._getCategories()[category]
+ category: category
});
if (category == "other") {
this.refs.description.getDOMNode().focus();
}
},
handleDescriptionFieldChange: function(event) {
this.setState({description: event.target.value});
},
- handleDescriptionFieldFocus: function(event) {
- this.setState({category: "other", description: ""});
- },
-
handleFormSubmit: function(event) {
event.preventDefault();
// XXX this feels ugly, we really want a feedbackActions object here.
this.props.feedbackStore.dispatchAction(new sharedActions.SendFeedback({
happy: false,
category: this.state.category,
description: this.state.description
}));
},
render: function() {
- var descriptionDisplayValue = this.state.category === "other" ?
- this.state.description : "";
return (
React.createElement(FeedbackLayout, {title: l10n.get("feedback_what_makes_you_sad"),
reset: this.props.reset},
React.createElement("form", {onSubmit: this.handleFormSubmit},
this._getCategoryFields(),
React.createElement("p", null,
React.createElement("input", {type: "text", ref: "description", name: "description",
className: "feedback-description",
onChange: this.handleDescriptionFieldChange,
- onFocus: this.handleDescriptionFieldFocus,
- value: descriptionDisplayValue,
+ value: this.state.description,
placeholder:
l10n.get("feedback_custom_category_text_placeholder")})
),
React.createElement("button", {type: "submit", className: "btn btn-success",
disabled: !this._isFormReady()},
l10n.get("feedback_submit_button")
)
)
--- a/browser/components/loop/content/shared/js/feedbackViews.jsx
+++ b/browser/components/loop/content/shared/js/feedbackViews.jsx
@@ -71,17 +71,17 @@ loop.shared.views.FeedbackView = (functi
},
_getCategories: function() {
return {
audio_quality: l10n.get("feedback_category_audio_quality"),
video_quality: l10n.get("feedback_category_video_quality"),
disconnected : l10n.get("feedback_category_was_disconnected"),
confusing: l10n.get("feedback_category_confusing"),
- other: l10n.get("feedback_category_other")
+ other: l10n.get("feedback_category_other2")
};
},
_getCategoryFields: function() {
var categories = this._getCategories();
return Object.keys(categories).map(function(category, key) {
return (
<label key={key} className="feedback-category-label">
@@ -114,56 +114,48 @@ loop.shared.views.FeedbackView = (functi
return false;
}
return true;
},
handleCategoryChange: function(event) {
var category = event.target.value;
this.setState({
- category: category,
- description: category == "other" ? "" : this._getCategories()[category]
+ category: category
});
if (category == "other") {
this.refs.description.getDOMNode().focus();
}
},
handleDescriptionFieldChange: function(event) {
this.setState({description: event.target.value});
},
- handleDescriptionFieldFocus: function(event) {
- this.setState({category: "other", description: ""});
- },
-
handleFormSubmit: function(event) {
event.preventDefault();
// XXX this feels ugly, we really want a feedbackActions object here.
this.props.feedbackStore.dispatchAction(new sharedActions.SendFeedback({
happy: false,
category: this.state.category,
description: this.state.description
}));
},
render: function() {
- var descriptionDisplayValue = this.state.category === "other" ?
- this.state.description : "";
return (
<FeedbackLayout title={l10n.get("feedback_what_makes_you_sad")}
reset={this.props.reset}>
<form onSubmit={this.handleFormSubmit}>
{this._getCategoryFields()}
<p>
<input type="text" ref="description" name="description"
className="feedback-description"
onChange={this.handleDescriptionFieldChange}
- onFocus={this.handleDescriptionFieldFocus}
- value={descriptionDisplayValue}
+ value={this.state.description}
placeholder={
l10n.get("feedback_custom_category_text_placeholder")} />
</p>
<button type="submit" className="btn btn-success"
disabled={!this._isFormReady()}>
{l10n.get("feedback_submit_button")}
</button>
</form>
--- a/browser/components/loop/standalone/content/l10n/en-US/loop.properties
+++ b/browser/components/loop/standalone/content/l10n/en-US/loop.properties
@@ -69,17 +69,17 @@ fxos_app_needed=Please install the {{fxo
feedback_call_experience_heading2=How was your conversation?
feedback_what_makes_you_sad=What makes you sad?
feedback_thank_you_heading=Thank you for your feedback!
feedback_category_audio_quality=Audio quality
feedback_category_video_quality=Video quality
feedback_category_was_disconnected=Was disconnected
feedback_category_confusing=Confusing
-feedback_category_other=Other:
+feedback_category_other2=Other
feedback_custom_category_text_placeholder=What went wrong?
feedback_submit_button=Submit
feedback_back_button=Back
## LOCALIZATION NOTE (feedback_window_will_close_in2):
## Gaia l10n format; see https://github.com/mozilla-b2g/gaia/blob/f108c706fae43cd61628babdd9463e7695b2496e/apps/email/locales/email.en-US.properties#L387
## In this item, don't translate the part between {{..}}
feedback_window_will_close_in2={[ plural(countdown) ]}
feedback_window_will_close_in2[one] = This window will close in {{countdown}} second
--- a/browser/components/loop/test/shared/feedbackViews_test.js
+++ b/browser/components/loop/test/shared/feedbackViews_test.js
@@ -128,26 +128,16 @@ describe("loop.shared.views.FeedbackView
"chosen and a description is entered",
function() {
clickSadFace(comp);
fillSadFeedbackForm(comp, "other", "fake");
expect(comp.getDOMNode().querySelector("form button").disabled).eql(false);
});
- it("should empty the description field when a predefined category is " +
- "chosen",
- function() {
- clickSadFace(comp);
-
- fillSadFeedbackForm(comp, "confusing");
-
- expect(comp.getDOMNode().querySelector(".feedback-description").value).eql("");
- });
-
it("should enable the form submit button once a predefined category is " +
"chosen",
function() {
clickSadFace(comp);
fillSadFeedbackForm(comp, "confusing");
expect(comp.getDOMNode().querySelector("form button").disabled).eql(false);
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_599725_response_headers.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_599725_response_headers.js
@@ -11,22 +11,16 @@
const INIT_URI = "data:text/plain;charset=utf8,hello world";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-599725-response-headers.sjs";
let loads = 0;
function performTest(aRequest, aConsole)
{
let deferred = promise.defer();
- loads++;
- ok(aRequest, "page load was logged");
- if (loads != 2) {
- return;
- }
-
let headers = null;
function readHeader(aName)
{
for (let header of headers) {
if (header.name == aName) {
return header.value;
}
@@ -59,16 +53,21 @@ function performTest(aRequest, aConsole)
HUDService.lastFinishedRequest.callback = null;
return deferred.promise;
}
function waitForRequest() {
let deferred = promise.defer();
HUDService.lastFinishedRequest.callback = (req, console) => {
+ loads++;
+ ok(req, "page load was logged");
+ if (loads != 2) {
+ return;
+ }
performTest(req, console).then(deferred.resolve);
};
return deferred.promise;
}
let test = asyncTest(function* () {
let { browser } = yield loadTab(INIT_URI);
--- a/browser/locales/en-US/chrome/browser/loop/loop.properties
+++ b/browser/locales/en-US/chrome/browser/loop/loop.properties
@@ -266,17 +266,17 @@ powered_by_afterLogo=
feedback_call_experience_heading2=How was your conversation?
feedback_what_makes_you_sad=What makes you sad?
feedback_thank_you_heading=Thank you for your feedback!
feedback_category_audio_quality=Audio quality
feedback_category_video_quality=Video quality
feedback_category_was_disconnected=Was disconnected
feedback_category_confusing=Confusing
-feedback_category_other=Other:
+feedback_category_other2=Other
feedback_custom_category_text_placeholder=What went wrong?
feedback_submit_button=Submit
feedback_back_button=Back
## LOCALIZATION NOTE (feedback_window_will_close_in2):
## Semicolon-separated list of plural forms. See:
## http://developer.mozilla.org/en/docs/Localization_and_Plurals
## In this item, don't translate the part between {{..}}
feedback_window_will_close_in2=This window will close in {{countdown}} second;This window will close in {{countdown}} seconds
--- a/mobile/android/base/tests/robocop.ini
+++ b/mobile/android/base/tests/robocop.ini
@@ -72,16 +72,18 @@ skip-if = processor == "x86"
[testPromptGridInput]
# bug 957185 for x86, bug 1001657 for 2.3
skip-if = android_version == "10" || processor == "x86"
# [testReaderMode] # see bug 913254, 936224
[testReadingListCache]
[testReadingListProvider]
[testSearchHistoryProvider]
[testSearchSuggestions]
+# disabled on 2.3; bug 907768
+skip-if = android_version == "10"
[testSessionOOMSave]
# disabled on x86 and 2.3; bug 945395
skip-if = android_version == "10" || processor == "x86"
[testSessionOOMRestore]
# disabled on Android 2.3; bug 979600
skip-if = android_version == "10"
[testSettingsMenuItems]
# disabled on Android 2.3; bug 979552
--- a/mobile/android/chrome/content/Reader.js
+++ b/mobile/android/chrome/content/Reader.js
@@ -1,34 +1,138 @@
// -*- 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";
-const { utils: Cu } = Components;
-
-Cu.import("resource://gre/modules/ReaderMode.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm");
let Reader = {
// These values should match those defined in BrowserContract.java.
STATUS_UNFETCHED: 0,
STATUS_FETCH_FAILED_TEMPORARY: 1,
STATUS_FETCH_FAILED_PERMANENT: 2,
STATUS_FETCH_FAILED_UNSUPPORTED_FORMAT: 3,
STATUS_FETCHED_ARTICLE: 4,
- get isEnabledForParseOnLoad() {
- delete this.isEnabledForParseOnLoad;
+ observe: function Reader_observe(aMessage, aTopic, aData) {
+ switch (aTopic) {
+ case "Reader:Added": {
+ let mm = window.getGroupMessageManager("browsers");
+ mm.broadcastAsyncMessage("Reader:Added", { url: aData });
+ break;
+ }
+ case "Reader:Removed": {
+ let uri = Services.io.newURI(aData, null, null);
+ ReaderMode.removeArticleFromCache(uri).catch(e => Cu.reportError("Error removing article from cache: " + e));
+
+ let mm = window.getGroupMessageManager("browsers");
+ mm.broadcastAsyncMessage("Reader:Removed", { url: aData });
+ break;
+ }
+ case "Gesture:DoubleTap": {
+ // Ideally, we would just do this all with web APIs in AboutReader.jsm (bug 1118487)
+ if (!BrowserApp.selectedBrowser.currentURI.spec.startsWith("about:reader")) {
+ return;
+ }
+
+ let win = BrowserApp.selectedBrowser.contentWindow;
+ let scrollBy;
+ // Arbitrary choice of innerHeight (50) to give some context after scroll.
+ if (JSON.parse(aData).y < (win.innerHeight / 2)) {
+ scrollBy = - win.innerHeight + 50;
+ } else {
+ scrollBy = win.innerHeight - 50;
+ }
+
+ let viewport = BrowserApp.selectedTab.getViewport();
+ let newY = Math.min(Math.max(viewport.cssY + scrollBy, viewport.cssPageTop), viewport.cssPageBottom);
+ let newRect = new Rect(viewport.cssX, newY, viewport.cssWidth, viewport.cssHeight);
+ ZoomHelper.zoomToRect(newRect, -1);
+ break;
+ }
+ }
+ },
+
+ receiveMessage: function(message) {
+ switch (message.name) {
+ case "Reader:AddToList":
+ this.addArticleToReadingList(message.data.article);
+ break;
+
+ case "Reader:ArticleGet":
+ this._getArticle(message.data.url, message.target).then((article) => {
+ message.target.messageManager.sendAsyncMessage("Reader:ArticleData", { article: article });
+ });
+ break;
- // Listen for future pref changes.
- Services.prefs.addObserver("reader.parse-on-load.", this, false);
+ case "Reader:FaviconRequest": {
+ let observer = (s, t, d) => {
+ Services.obs.removeObserver(observer, "Reader:FaviconReturn", false);
+ message.target.messageManager.sendAsyncMessage("Reader:FaviconReturn", JSON.parse(d));
+ };
+ Services.obs.addObserver(observer, "Reader:FaviconReturn", false);
+ Messaging.sendRequest({
+ type: "Reader:FaviconRequest",
+ url: message.data.url
+ });
+ break;
+ }
+
+ case "Reader:ListStatusRequest":
+ Messaging.sendRequestForResult({
+ type: "Reader:ListStatusRequest",
+ url: message.data.url
+ }).then((data) => {
+ message.target.messageManager.sendAsyncMessage("Reader:ListStatusData", JSON.parse(data));
+ });
+ break;
+
+ case "Reader:RemoveFromList":
+ Messaging.sendRequest({
+ type: "Reader:RemoveFromList",
+ url: message.data.url
+ });
+ break;
- return this.isEnabledForParseOnLoad = this._getStateForParseOnLoad();
+ case "Reader:Share":
+ Messaging.sendRequest({
+ type: "Reader:Share",
+ url: message.data.url,
+ title: message.data.title
+ });
+ break;
+
+ case "Reader:ShowToast":
+ NativeWindow.toast.show(message.data.toast, "short");
+ break;
+
+ case "Reader:SystemUIVisibility":
+ Messaging.sendRequest({
+ type: "SystemUI:Visibility",
+ visible: message.data.visible
+ });
+ break;
+
+ case "Reader:ToolbarVisibility":
+ Messaging.sendRequest({
+ type: "BrowserToolbar:Visibility",
+ visible: message.data.visible
+ });
+ break;
+
+ case "Reader:UpdateIsArticle": {
+ let tab = BrowserApp.getTabForBrowser(message.target);
+ tab.isArticle = message.data.isArticle;
+ this.updatePageAction(tab);
+ break;
+ }
+ }
},
pageAction: {
readerModeCallback: function(tabID) {
Messaging.sendRequest({
type: "Reader:Toggle",
tabID: tabID
});
@@ -62,53 +166,35 @@ let Reader = {
// not track background reader viewers.
UITelemetry.startSession("reader.1", null);
return;
}
// Only stop a reader session if the foreground viewer is not visible.
UITelemetry.stopSession("reader.1", "", null);
- if (tab.savedArticle) {
+ if (tab.isArticle) {
this.pageAction.id = PageActions.add({
title: Strings.browser.GetStringFromName("readerMode.enter"),
icon: "drawable://reader",
clickCallback: () => this.pageAction.readerModeCallback(tab.id),
longClickCallback: () => this.pageAction.readerModeActiveCallback(tab.id),
important: true
});
}
},
- observe: function(aMessage, aTopic, aData) {
- switch(aTopic) {
- case "Reader:Removed": {
- let uri = Services.io.newURI(aData, null, null);
- ReaderMode.removeArticleFromCache(uri).catch(e => Cu.reportError("Error removing article from cache: " + e));
- break;
- }
-
- case "nsPref:changed":
- if (aData.startsWith("reader.parse-on-load.")) {
- this.isEnabledForParseOnLoad = this._getStateForParseOnLoad();
- }
- break;
- }
- },
-
_addTabToReadingList: Task.async(function* (tabID) {
let tab = BrowserApp.getTabForId(tabID);
if (!tab) {
throw new Error("Can't add tab to reading list because no tab found for ID: " + tabID);
}
- let uri = tab.browser.currentURI;
- let urlWithoutRef = uri.specIgnoringRef;
-
- let article = yield this.getArticle(urlWithoutRef, tabID).catch(e => {
+ let urlWithoutRef = tab.browser.currentURI.specIgnoringRef;
+ let article = yield this._getArticle(urlWithoutRef, tab.browser).catch(e => {
Cu.reportError("Error getting article for tab: " + e);
return null;
});
if (!article) {
// If there was a problem getting the article, just store the
// URL and title from the tab.
article = {
url: urlWithoutRef,
@@ -135,55 +221,56 @@ let Reader = {
length: article.length || 0,
excerpt: article.excerpt || "",
status: article.status,
});
ReaderMode.storeArticleInCache(article).catch(e => Cu.reportError("Error storing article in cache: " + e));
},
- _getStateForParseOnLoad: function () {
- let isEnabled = Services.prefs.getBoolPref("reader.parse-on-load.enabled");
- let isForceEnabled = Services.prefs.getBoolPref("reader.parse-on-load.force-enabled");
- // For low-memory devices, don't allow reader mode since it takes up a lot of memory.
- // See https://bugzilla.mozilla.org/show_bug.cgi?id=792603 for details.
- return isForceEnabled || (isEnabled && !BrowserApp.isOnLowMemoryPlatform);
- },
-
/**
* Gets an article for a given URL. This method will download and parse a document
* if it does not find the article in the tab data or the cache.
*
* @param url The article URL.
- * @param tabId (optional) The id of the tab where we can look for a saved article.
+ * @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.
*/
- getArticle: Task.async(function* (url, tabId) {
- // First, look for an article object stored on the tab.
- let tab = BrowserApp.getTabForId(tabId);
- if (tab) {
- let article = tab.savedArticle;
- if (article && article.url == url) {
- return article;
- }
+ _getArticle: Task.async(function* (url, browser) {
+ // First, look for a saved article.
+ let article = yield this._getSavedArticle(browser);
+ if (article && article.url == url) {
+ return article;
}
// Next, try to find a parsed article in the cache.
let uri = Services.io.newURI(url, null, null);
- let article = yield ReaderMode.getArticleFromCache(uri);
+ article = yield ReaderMode.getArticleFromCache(uri);
if (article) {
return article;
}
// Article hasn't been found in the cache, we need to
// download the page and parse the article out of it.
return yield ReaderMode.downloadAndParseDocument(url);
}),
+ _getSavedArticle: function(browser) {
+ return new Promise((resolve, reject) => {
+ let mm = browser.messageManager;
+ let listener = (message) => {
+ mm.removeMessageListener("Reader:SavedArticleData", listener);
+ resolve(message.data.article);
+ };
+ mm.addMessageListener("Reader:SavedArticleData", listener);
+ mm.sendAsyncMessage("Reader:SavedArticleGet");
+ });
+ },
+
/**
* Migrates old indexedDB reader mode cache to new JSON cache.
*/
migrateCache: Task.async(function* () {
let cacheDB = yield new Promise((resolve, reject) => {
let request = window.indexedDB.open("about:reader", 1);
request.onsuccess = event => resolve(event.target.result);
request.onerror = event => reject(request.error);
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -104,27 +104,23 @@ XPCOMUtils.defineLazyModuleGetter(this,
"resource://gre/modules/PermissionsUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SharedPreferences",
"resource://gre/modules/SharedPreferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Notifications",
"resource://gre/modules/Notifications.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
- "resource://gre/modules/ReaderMode.jsm");
-
XPCOMUtils.defineLazyModuleGetter(this, "GMPInstallManager",
"resource://gre/modules/GMPInstallManager.jsm");
// Lazily-loaded browser scripts:
[
["SelectHelper", "chrome://browser/content/SelectHelper.js"],
["InputWidgetHelper", "chrome://browser/content/InputWidgetHelper.js"],
- ["AboutReader", "chrome://global/content/reader/aboutReader.js"],
["MasterPassword", "chrome://browser/content/MasterPassword.js"],
["PluginHelper", "chrome://browser/content/PluginHelper.js"],
["OfflineApps", "chrome://browser/content/OfflineApps.js"],
["Linkifier", "chrome://browser/content/Linkify.js"],
["ZoomHelper", "chrome://browser/content/ZoomHelper.js"],
["CastingApps", "chrome://browser/content/CastingApps.js"],
#ifdef NIGHTLY_BUILD
["WebcompatReporter", "chrome://browser/content/WebcompatReporter.js"],
@@ -145,17 +141,17 @@ XPCOMUtils.defineLazyModuleGetter(this,
["MemoryObserver", ["memory-pressure", "Memory:Dump"], "chrome://browser/content/MemoryObserver.js"],
["ConsoleAPI", ["console-api-log-event"], "chrome://browser/content/ConsoleAPI.js"],
["FindHelper", ["FindInPage:Opened", "FindInPage:Closed", "Tab:Selected"], "chrome://browser/content/FindHelper.js"],
["PermissionsHelper", ["Permissions:Get", "Permissions:Clear"], "chrome://browser/content/PermissionsHelper.js"],
["FeedHandler", ["Feeds:Subscribe"], "chrome://browser/content/FeedHandler.js"],
["Feedback", ["Feedback:Show"], "chrome://browser/content/Feedback.js"],
["SelectionHandler", ["TextSelection:Get"], "chrome://browser/content/SelectionHandler.js"],
["EmbedRT", ["GeckoView:ImportScript"], "chrome://browser/content/EmbedRT.js"],
- ["Reader", ["Reader:Removed"], "chrome://browser/content/Reader.js"],
+ ["Reader", ["Reader:Added", "Reader:Removed", "Gesture:DoubleTap"], "chrome://browser/content/Reader.js"],
].forEach(function (aScript) {
let [name, notifications, script] = aScript;
XPCOMUtils.defineLazyGetter(window, name, function() {
let sandbox = {};
Services.scriptloader.loadSubScript(script, sandbox);
return sandbox[name];
});
let observer = (s, t, d) => {
@@ -163,16 +159,49 @@ XPCOMUtils.defineLazyModuleGetter(this,
Services.obs.addObserver(window[name], t, false);
window[name].observe(s, t, d); // Explicitly notify new observer
};
notifications.forEach((notification) => {
Services.obs.addObserver(observer, notification, false);
});
});
+// Lazily-loaded browser scripts that use message listeners.
+[
+ ["Reader", [
+ "Reader:AddToList",
+ "Reader:ArticleGet",
+ "Reader:FaviconRequest",
+ "Reader:ListStatusRequest",
+ "Reader:RemoveFromList",
+ "Reader:Share",
+ "Reader:ShowToast",
+ "Reader:ToolbarVisibility",
+ "Reader:SystemUIVisibility",
+ "Reader:UpdateIsArticle",
+ ], "chrome://browser/content/Reader.js"],
+].forEach(aScript => {
+ let [name, messages, script] = aScript;
+ XPCOMUtils.defineLazyGetter(window, name, function() {
+ let sandbox = {};
+ Services.scriptloader.loadSubScript(script, sandbox);
+ return sandbox[name];
+ });
+
+ let mm = window.getGroupMessageManager("browsers");
+ let listener = (message) => {
+ mm.removeMessageListener(message.name, listener);
+ mm.addMessageListener(message.name, window[name]);
+ window[name].receiveMessage(message);
+ };
+ messages.forEach((message) => {
+ mm.addMessageListener(message, listener);
+ });
+});
+
// Lazily-loaded JS modules that use observer notifications
[
["Home", ["HomeBanner:Get", "HomePanels:Get", "HomePanels:Authenticate", "HomePanels:RefreshView",
"HomePanels:Installed", "HomePanels:Uninstalled"], "resource://gre/modules/Home.jsm"],
].forEach(module => {
let [name, notifications, resource] = module;
XPCOMUtils.defineLazyModuleGetter(this, name, resource);
let observer = (s, t, d) => {
@@ -501,16 +530,19 @@ var BrowserApp = {
// Set the tiles click observer only if tiles reporting is enabled (that
// is, a report URL is set in prefs).
gTilesReportURL = Services.prefs.getCharPref("browser.tiles.reportURL");
Services.obs.addObserver(this, "Tiles:Click", false);
} catch (e) {
// Tiles reporting is disabled.
}
+ let mm = window.getGroupMessageManager("browsers");
+ mm.loadFrameScript("chrome://browser/content/content.js", true);
+
// Notify Java that Gecko has loaded.
Messaging.sendRequest({ type: "Gecko:Ready" });
},
get _startupStatus() {
delete this._startupStatus;
let savedMilestone = null;
@@ -3230,17 +3262,17 @@ function Tab(aURL, aParams) {
this.viewportMeasureCallback = null;
this.lastPageSizeAfterViewportRemeasure = { width: 0, height: 0 };
this.contentDocumentIsDisplayed = true;
this.pluginDoorhangerTimeout = null;
this.shouldShowPluginDoorhanger = true;
this.clickToPlayPluginsActivated = false;
this.desktopMode = false;
this.originalURI = null;
- this.savedArticle = null;
+ this.isArticle = false;
this.hasTouchListener = false;
this.browserWidth = 0;
this.browserHeight = 0;
this.tilesData = null;
this.create(aURL, aParams);
}
@@ -3281,16 +3313,17 @@ Tab.prototype = {
create: function(aURL, aParams) {
if (this.browser)
return;
aParams = aParams || {};
this.browser = document.createElement("browser");
this.browser.setAttribute("type", "content-targetable");
+ this.browser.setAttribute("messagemanagergroup", "browsers");
this.setBrowserSize(kDefaultCSSViewportWidth, kDefaultCSSViewportHeight);
// Make sure the previously selected panel remains selected. The selected panel of a deck is
// not stable when panels are added.
let selectedPanel = BrowserApp.deck.selectedPanel;
BrowserApp.deck.insertBefore(this.browser, aParams.sibling || null);
BrowserApp.deck.selectedPanel = selectedPanel;
@@ -3575,17 +3608,16 @@ Tab.prototype = {
// Make sure the previously selected panel remains selected. The selected panel of a deck is
// not stable when panels are removed.
let selectedPanel = BrowserApp.deck.selectedPanel;
BrowserApp.deck.removeChild(this.browser);
BrowserApp.deck.selectedPanel = selectedPanel;
this.browser = null;
- this.savedArticle = null;
},
// This should be called to update the browser when the tab gets selected/unselected
setActive: function setActive(aActive) {
if (!this.browser || !this.browser.docShell)
return;
this.lastTouchedAt = Date.now();
@@ -3971,24 +4003,16 @@ Tab.prototype = {
this.browser.removeEventListener("click", ErrorPageEventHandler, true);
this.browser.removeEventListener("pagehide", listener, true);
}.bind(this);
this.browser.addEventListener("pagehide", listener, true);
}
if (docURI.startsWith("about:reader")) {
- // During browser restart / recovery, duplicate "DOMContentLoaded" messages are received here
- // For the visible tab ... where more than one tab is being reloaded, the inital "DOMContentLoaded"
- // Message can be received before the document body is available ... so we avoid instantiating an
- // AboutReader object, expecting that an eventual valid message will follow.
- let contentDocument = this.browser.contentDocument;
- if (contentDocument.body) {
- new AboutReader(contentDocument, this.browser.contentWindow);
- }
// Update the page action to show the "reader active" icon.
Reader.updatePageAction(this);
}
break;
}
case "DOMFormHasPassword": {
@@ -4283,41 +4307,16 @@ Tab.prototype = {
type: "Robocop:TilesResponse",
response: this.response
});
}
};
xhr.send(this.tilesData);
this.tilesData = null;
}
-
- // Don't try to parse the document if reader mode is disabled,
- // or if the page is already in reader mode.
- if (!Reader.isEnabledForParseOnLoad || this.readerActive) {
- return;
- }
-
- // Reader mode is disabled until proven enabled.
- this.savedArticle = null;
- Reader.updatePageAction(this);
-
- // Once document is fully loaded, parse it
- ReaderMode.parseDocumentFromBrowser(this.browser).then(article => {
- // The loaded page may have changed while we were parsing the document.
- // Make sure we've got the current one.
- let currentURL = this.browser.currentURI.specIgnoringRef;
-
- // Do nothing if there's no article or the page in this tab has changed.
- if (article == null || (article.url != currentURL)) {
- return;
- }
-
- this.savedArticle = article;
- Reader.updatePageAction(this);
- }).catch(e => Cu.reportError("Error parsing document from tab: " + e));
}
}
},
onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
let contentWin = aWebProgress.DOMWindow;
if (contentWin != contentWin.top)
return;
new file mode 100644
--- /dev/null
+++ b/mobile/android/chrome/content/content.js
@@ -0,0 +1,86 @@
+/* -*- 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/. */
+
+let { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AboutReader", "resource://gre/modules/AboutReader.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm");
+
+let dump = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.d.bind(null, "Content");
+
+let global = this;
+
+let AboutReaderListener = {
+ _savedArticle: null,
+
+ init: function() {
+ addEventListener("AboutReaderContentLoaded", this, false, true);
+ addEventListener("pageshow", this, false);
+ addMessageListener("Reader:SavedArticleGet", this);
+ },
+
+ receiveMessage: function(message) {
+ switch (message.name) {
+ case "Reader:SavedArticleGet":
+ sendAsyncMessage("Reader:SavedArticleData", { article: this._savedArticle });
+ break;
+ }
+ },
+
+ get isAboutReader() {
+ return content.document.documentURI.startsWith("about:reader");
+ },
+
+ handleEvent: function(event) {
+ if (event.originalTarget.defaultView != content) {
+ return;
+ }
+
+ switch (event.type) {
+ case "AboutReaderContentLoaded":
+ if (!this.isAboutReader) {
+ return;
+ }
+
+ // If we are restoring multiple reader mode tabs during session restore, duplicate "DOMContentLoaded"
+ // events may be fired for the visible tab. The inital "DOMContentLoaded" may be received before the
+ // document body is available, so we avoid instantiating an AboutReader object, expecting that a
+ // valid message will follow. See bug 925983.
+ if (content.document.body) {
+ new AboutReader(global, content);
+ }
+ break;
+
+ case "pageshow":
+ if (!ReaderMode.isEnabledForParseOnLoad || this.isAboutReader) {
+ return;
+ }
+
+ // Reader mode is disabled until proven enabled.
+ this._savedArticle = null;
+ sendAsyncMessage("Reader:UpdateIsArticle", { isArticle: false });
+
+ ReaderMode.parseDocument(content.document).then(article => {
+ // The loaded page may have changed while we were parsing the document.
+ // Make sure we've got the current one.
+ let currentURL = Services.io.newURI(content.document.documentURI, null, null).specIgnoringRef;
+
+ // Do nothing if there's no article or the page in this tab has changed.
+ if (article == null || (article.url != currentURL)) {
+ return;
+ }
+
+ this._savedArticle = article;
+ sendAsyncMessage("Reader:UpdateIsArticle", { isArticle: true });
+
+ }).catch(e => Cu.reportError("Error parsing document: " + e));
+ break;
+ }
+ }
+};
+AboutReaderListener.init();
--- a/mobile/android/chrome/jar.mn
+++ b/mobile/android/chrome/jar.mn
@@ -5,16 +5,17 @@
chrome.jar:
% content browser %content/ contentaccessible=yes
* content/about.xhtml (content/about.xhtml)
content/config.xhtml (content/config.xhtml)
content/config.js (content/config.js)
+ content/content.js (content/content.js)
content/aboutAddons.xhtml (content/aboutAddons.xhtml)
content/aboutAddons.js (content/aboutAddons.js)
content/aboutCertError.xhtml (content/aboutCertError.xhtml)
content/aboutDownloads.xhtml (content/aboutDownloads.xhtml)
content/aboutDownloads.js (content/aboutDownloads.js)
content/aboutFeedback.xhtml (content/aboutFeedback.xhtml)
content/aboutFeedback.js (content/aboutFeedback.js)
content/aboutPrivateBrowsing.xhtml (content/aboutPrivateBrowsing.xhtml)
copy from toolkit/components/reader/content/aboutReader.js
copy to toolkit/components/reader/AboutReader.jsm
--- a/toolkit/components/reader/content/aboutReader.js
+++ b/toolkit/components/reader/AboutReader.jsm
@@ -1,50 +1,43 @@
/* 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";
+
let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;
-Cu.import("resource://gre/modules/Messaging.jsm");
-Cu.import("resource://gre/modules/Services.jsm")
+this.EXPORTED_SYMBOLS = [ "AboutReader" ];
+
+Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
- "resource://gre/modules/UITelemetry.jsm");
-
-XPCOMUtils.defineLazyGetter(window, "gChromeWin", function ()
- window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShellTreeItem)
- .rootTreeItem
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindow)
- .QueryInterface(Ci.nsIDOMChromeWindow));
+XPCOMUtils.defineLazyModuleGetter(this, "Rect", "resource://gre/modules/Geometry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry", "resource://gre/modules/UITelemetry.jsm");
function dump(s) {
Services.console.logStringMessage("AboutReader: " + s);
}
let gStrings = Services.strings.createBundle("chrome://global/locale/aboutReader.properties");
-let AboutReader = function(doc, win) {
- dump("Init()");
+let AboutReader = function(mm, win) {
+ let doc = win.document;
+
+ this._mm = mm;
+ this._mm.addMessageListener("Reader:Added", this);
+ this._mm.addMessageListener("Reader:Removed", this);
this._docRef = Cu.getWeakReference(doc);
this._winRef = Cu.getWeakReference(win);
- Services.obs.addObserver(this, "Reader:FaviconReturn", false);
- Services.obs.addObserver(this, "Reader:Added", false);
- Services.obs.addObserver(this, "Reader:Removed", false);
- Services.obs.addObserver(this, "Gesture:DoubleTap", false);
-
this._article = null;
- dump("Feching toolbar, header and content notes from about:reader");
this._headerElementRef = Cu.getWeakReference(doc.getElementById("reader-header"));
this._domainElementRef = Cu.getWeakReference(doc.getElementById("reader-domain"));
this._titleElementRef = Cu.getWeakReference(doc.getElementById("reader-title"));
this._creditsElementRef = Cu.getWeakReference(doc.getElementById("reader-credits"));
this._contentElementRef = Cu.getWeakReference(doc.getElementById("reader-content"));
this._toolbarElementRef = Cu.getWeakReference(doc.getElementById("reader-toolbar"));
this._messageElementRef = Cu.getWeakReference(doc.getElementById("reader-message"));
@@ -115,26 +108,23 @@ let AboutReader = function(doc, win) {
value: 5,
linkClass: "font-size5-sample" }
];
let fontSize = Services.prefs.getIntPref("reader.font_size");
this._setupSegmentedButton("font-size-buttons", fontSizeOptions, fontSize, this._setFontSize.bind(this));
this._setFontSize(fontSize);
- dump("Decoding query arguments");
let queryArgs = this._decodeQueryString(win.location.href);
// Track status of reader toolbar add/remove toggle button
this._isReadingListItem = -1;
this._updateToggleButton();
- let url = queryArgs.url;
- let tabId = queryArgs.tabId;
- this._loadArticle(url, tabId);
+ this._loadArticle(queryArgs.url);
}
AboutReader.prototype = {
_BLOCK_IMAGES_SELECTOR: ".content p > img:only-child, " +
".content p > a:only-child > img:only-child, " +
".content .wp-caption img, " +
".content figure img",
@@ -169,79 +159,53 @@ AboutReader.prototype = {
get _toolbarElement() {
return this._toolbarElementRef.get();
},
get _messageElement() {
return this._messageElementRef.get();
},
- observe: function Reader_observe(aMessage, aTopic, aData) {
- switch(aTopic) {
- case "Reader:FaviconReturn": {
- let args = JSON.parse(aData);
- this._loadFavicon(args.url, args.faviconUrl);
- Services.obs.removeObserver(this, "Reader:FaviconReturn");
- break;
- }
-
+ receiveMessage: function (message) {
+ switch (message.name) {
case "Reader:Added": {
// Page can be added by long-press pageAction, or by tap on banner icon.
- if (aData == this._article.url) {
+ if (message.data.url == this._article.url) {
if (this._isReadingListItem != 1) {
this._isReadingListItem = 1;
this._updateToggleButton();
}
}
break;
}
-
case "Reader:Removed": {
- if (aData == this._article.url) {
+ if (message.data.url == this._article.url) {
if (this._isReadingListItem != 0) {
this._isReadingListItem = 0;
this._updateToggleButton();
}
}
break;
}
-
- case "Gesture:DoubleTap": {
- let args = JSON.parse(aData);
- let scrollBy;
- // Arbitary choice of innerHeight - 50 to give some context after scroll
- if (args.y < (this._win.innerHeight / 2)) {
- scrollBy = -this._win.innerHeight + 50;
- } else {
- scrollBy = this._win.innerHeight - 50;
- }
- this._scrollPage(scrollBy);
- break;
- }
}
},
handleEvent: function Reader_handleEvent(aEvent) {
if (!aEvent.isTrusted)
return;
switch (aEvent.type) {
- case "touchstart":
- this._scrolled = false;
- break;
case "click":
- if (!this._scrolled)
- this._toggleToolbarVisibility();
+ // XXX: Don't toggle the toolbar on double click. (See the "Gesture:DoubleTap" handler in Reader.js)
+ this._toggleToolbarVisibility();
break;
case "scroll":
- if (!this._scrolled) {
- let isScrollingUp = this._scrollOffset > aEvent.pageY;
- this._setToolbarVisibility(isScrollingUp);
- this._scrollOffset = aEvent.pageY;
- }
+ let isScrollingUp = this._scrollOffset > aEvent.pageY;
+ this._setToolbarVisibility(isScrollingUp);
+ this._scrollOffset = aEvent.pageY;
break;
case "popstate":
if (!aEvent.state)
this._closeAllDropdowns();
break;
case "resize":
this._updateImageMargins();
break;
@@ -250,95 +214,76 @@ AboutReader.prototype = {
this._handleDeviceLight(aEvent.value);
break;
case "visibilitychange":
this._handleVisibilityChange();
break;
case "unload":
- Services.obs.removeObserver(this, "Reader:Added");
- Services.obs.removeObserver(this, "Reader:Removed");
- Services.obs.removeObserver(this, "Gesture:DoubleTap");
+ this._mm.removeMessageListener("Reader:Added", this);
+ this._mm.removeMessageListener("Reader:Removed", this);
break;
}
},
- _scrollPage: function Reader_scrollPage(scrollByPixels) {
- let viewport = BrowserApp.selectedTab.getViewport();
- let newY = Math.min(Math.max(viewport.cssY + scrollByPixels, viewport.cssPageTop), viewport.cssPageBottom);
- let newRect = new Rect(viewport.cssX, newY, viewport.cssWidth, viewport.cssHeight);
-
- this._setToolbarVisibility(false);
- this._setBrowserToolbarVisiblity(false);
- this._scrolled = true;
- ZoomHelper.zoomToRect(newRect, -1);
- },
-
_updateToggleButton: function Reader_updateToggleButton() {
let classes = this._doc.getElementById("toggle-button").classList;
if (this._isReadingListItem == 1) {
classes.add("on");
} else {
classes.remove("on");
}
},
_requestReadingListStatus: function Reader_requestReadingListStatus() {
- Messaging.sendRequestForResult({
- type: "Reader:ListStatusRequest",
- url: this._article.url
- }).then((data) => {
- let args = JSON.parse(data);
+ let handleListStatusData = (message) => {
+ this._mm.removeMessageListener("Reader:ListStatusData", handleListStatusData);
+
+ let args = message.data;
if (args.url == this._article.url) {
if (this._isReadingListItem != args.inReadingList) {
let isInitialStateChange = (this._isReadingListItem == -1);
this._isReadingListItem = args.inReadingList;
this._updateToggleButton();
// Display the toolbar when all its initial component states are known
if (isInitialStateChange) {
this._setToolbarVisibility(true);
}
}
}
- });
+ };
+
+ this._mm.addMessageListener("Reader:ListStatusData", handleListStatusData);
+ this._mm.sendAsyncMessage("Reader:ListStatusRequest", { url: this._article.url });
},
_onReaderToggle: function Reader_onToggle() {
if (!this._article)
return;
if (this._isReadingListItem == 0) {
- // If we're in reader mode, we must have fetched the article.
- this._article.status = gChromeWin.Reader.STATUS_FETCHED_ARTICLE;
- gChromeWin.Reader.addArticleToReadingList(this._article);
-
+ this._mm.sendAsyncMessage("Reader:AddToList", { article: this._article });
UITelemetry.addEvent("save.1", "button", null, "reader");
} else {
- Messaging.sendRequest({
- type: "Reader:RemoveFromList",
- url: this._article.url
- });
-
+ this._mm.sendAsyncMessage("Reader:RemoveFromList", { url: this._article.url });
UITelemetry.addEvent("unsave.1", "button", null, "reader");
}
},
_onShare: function Reader_onShare() {
if (!this._article)
return;
- Messaging.sendRequest({
- type: "Reader:Share",
+ this._mm.sendAsyncMessage("Reader:Share", {
url: this._article.url,
title: this._article.title
});
-
UITelemetry.addEvent("share.1", "list", null);
},
_setFontSize: function Reader_setFontSize(newFontSize) {
let bodyClasses = this._doc.body.classList;
if (this._fontSize > 0)
bodyClasses.remove("font-size" + this._fontSize);
@@ -475,61 +420,65 @@ AboutReader.prototype = {
return;
this._toolbarElement.classList.toggle("toolbar-hidden");
this._setSystemUIVisibility(visible);
if (!visible && !this._hasUsedToolbar) {
this._hasUsedToolbar = Services.prefs.getBoolPref("reader.has_used_toolbar");
if (!this._hasUsedToolbar) {
- gChromeWin.NativeWindow.toast.show(gStrings.GetStringFromName("aboutReader.toolbarTip"), "short");
-
+ this._mm.sendAsyncMessage("Reader:ShowToast", { toast: gStrings.GetStringFromName("aboutReader.toolbarTip") });
Services.prefs.setBoolPref("reader.has_used_toolbar", true);
this._hasUsedToolbar = true;
}
}
},
_toggleToolbarVisibility: function Reader_toggleToolbarVisibility() {
this._setToolbarVisibility(!this._getToolbarVisibility());
},
_setBrowserToolbarVisiblity: function Reader_setBrowserToolbarVisiblity(visible) {
- Messaging.sendRequest({
- type: "BrowserToolbar:Visibility",
- visible: visible
- });
+ this._mm.sendAsyncMessage("Reader:ToolbarVisibility", { visible: visible });
},
_setSystemUIVisibility: function Reader_setSystemUIVisibility(visible) {
- Messaging.sendRequest({
- type: "SystemUI:Visibility",
- visible: visible
- });
+ this._mm.sendAsyncMessage("Reader:SystemUIVisibility", { visible: visible });
},
- _loadArticle: Task.async(function* (url, tabId) {
+ _loadArticle: Task.async(function* (url) {
this._showProgressDelayed();
- let article = yield gChromeWin.Reader.getArticle(url, tabId).catch(e => {
- Cu.reportError("Error loading article: " + e);
- return null;
- });
- if (article) {
+ let article = yield this._getArticle(url);
+ if (article && article.url == url) {
this._showContent(article);
} else {
this._win.location.href = url;
}
}),
+ _getArticle: function(url) {
+ return new Promise((resolve, reject) => {
+ let listener = (message) => {
+ this._mm.removeMessageListener("Reader:ArticleData", listener);
+ resolve(message.data.article);
+ };
+ this._mm.addMessageListener("Reader:ArticleData", listener);
+ this._mm.sendAsyncMessage("Reader:ArticleGet", { url: url });
+ });
+ },
+
_requestFavicon: function Reader_requestFavicon() {
- Messaging.sendRequest({
- type: "Reader:FaviconRequest",
- url: this._article.url
- });
+ 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", { url: this._article.url });
},
_loadFavicon: function Reader_loadFavicon(url, faviconUrl) {
if (this._article.url !== url)
return;
let doc = this._doc;
@@ -781,17 +730,17 @@ AboutReader.prototype = {
dropdownArrow.style.left = arrowLeft + "px";
};
win.addEventListener("resize", function(aEvent) {
if (!aEvent.isTrusted)
return;
// Wait for reflow before calculating the new position of the popup.
- setTimeout(updatePopupPosition, 0);
+ win.setTimeout(updatePopupPosition, 0);
}, true);
dropdownToggle.addEventListener("click", function(aEvent) {
if (!aEvent.isTrusted)
return;
aEvent.stopPropagation();
--- a/toolkit/components/reader/ReaderMode.jsm
+++ b/toolkit/components/reader/ReaderMode.jsm
@@ -22,39 +22,71 @@ let ReaderMode = {
CACHE_VERSION: 1,
DEBUG: 0,
// Don't try to parse the page if it has too many elements (for memory and
// performance reasons)
MAX_ELEMS_TO_PARSE: 3000,
+ get isEnabledForParseOnLoad() {
+ delete this.isEnabledForParseOnLoad;
+
+ // Listen for future pref changes.
+ Services.prefs.addObserver("reader.parse-on-load.", this, false);
+
+ return this.isEnabledForParseOnLoad = this._getStateForParseOnLoad();
+ },
+
+ get isOnLowMemoryPlatform() {
+ let memory = Cc["@mozilla.org/xpcom/memory-service;1"].getService(Ci.nsIMemory);
+ delete this.isOnLowMemoryPlatform;
+ return this.isOnLowMemoryPlatform = memory.isLowMemoryPlatform();
+ },
+
+ _getStateForParseOnLoad: function () {
+ let isEnabled = Services.prefs.getBoolPref("reader.parse-on-load.enabled");
+ let isForceEnabled = Services.prefs.getBoolPref("reader.parse-on-load.force-enabled");
+ // For low-memory devices, don't allow reader mode since it takes up a lot of memory.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=792603 for details.
+ return isForceEnabled || (isEnabled && !this.isOnLowMemoryPlatform);
+ },
+
+ observe: function(aMessage, aTopic, aData) {
+ switch(aTopic) {
+ case "nsPref:changed":
+ if (aData.startsWith("reader.parse-on-load.")) {
+ this.isEnabledForParseOnLoad = this._getStateForParseOnLoad();
+ }
+ break;
+ }
+ },
+
/**
* Gets an article from a loaded browser's document. This method will parse the document
* if it does not find the article in the cache.
*
- * @param browser A browser with a loaded page.
+ * @param doc A document to parse.
* @return {Promise}
* @resolves JS object representing the article, or null if no article is found.
*/
- parseDocumentFromBrowser: Task.async(function* (browser) {
- let uri = browser.currentURI;
+ parseDocument: Task.async(function* (doc) {
+ let uri = Services.io.newURI(doc.documentURI, null, null);
if (!this._shouldCheckUri(uri)) {
this.log("Reader mode disabled for URI");
return null;
}
// First, try to find a parsed article in the cache.
let article = yield this.getArticleFromCache(uri);
if (article) {
this.log("Page found in cache, return article immediately");
return article;
}
- let doc = browser.contentWindow.document;
return yield this._readerParse(uri, doc);
}),
/**
* Downloads and parses a document from a URL.
*
* @param url URL to download and parse.
* @return {Promise}
--- a/toolkit/components/reader/content/aboutReader.html
+++ b/toolkit/components/reader/content/aboutReader.html
@@ -1,16 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<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 type="text/javascript;version=1.8" src="chrome://global/content/reader/aboutReader.js"></script>
</head>
<body>
<div id="reader-header" class="header">
<a id="reader-domain" class="domain"></a>
<div class="domain-border"></div>
<h1 id="reader-title"></h1>
<div id="reader-credits" class="credits"></div>
--- a/toolkit/components/reader/content/aboutReader.js
+++ b/toolkit/components/reader/content/aboutReader.js
@@ -1,843 +1,9 @@
/* 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 Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;
-
-Cu.import("resource://gre/modules/Messaging.jsm");
-Cu.import("resource://gre/modules/Services.jsm")
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
- "resource://gre/modules/UITelemetry.jsm");
-
-XPCOMUtils.defineLazyGetter(window, "gChromeWin", function ()
- window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShellTreeItem)
- .rootTreeItem
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindow)
- .QueryInterface(Ci.nsIDOMChromeWindow));
-
-function dump(s) {
- Services.console.logStringMessage("AboutReader: " + s);
-}
-
-let gStrings = Services.strings.createBundle("chrome://global/locale/aboutReader.properties");
-
-let AboutReader = function(doc, win) {
- dump("Init()");
-
- this._docRef = Cu.getWeakReference(doc);
- this._winRef = Cu.getWeakReference(win);
-
- Services.obs.addObserver(this, "Reader:FaviconReturn", false);
- Services.obs.addObserver(this, "Reader:Added", false);
- Services.obs.addObserver(this, "Reader:Removed", false);
- Services.obs.addObserver(this, "Gesture:DoubleTap", false);
-
- this._article = null;
-
- dump("Feching toolbar, header and content notes from about:reader");
- this._headerElementRef = Cu.getWeakReference(doc.getElementById("reader-header"));
- this._domainElementRef = Cu.getWeakReference(doc.getElementById("reader-domain"));
- this._titleElementRef = Cu.getWeakReference(doc.getElementById("reader-title"));
- this._creditsElementRef = Cu.getWeakReference(doc.getElementById("reader-credits"));
- this._contentElementRef = Cu.getWeakReference(doc.getElementById("reader-content"));
- this._toolbarElementRef = Cu.getWeakReference(doc.getElementById("reader-toolbar"));
- this._messageElementRef = Cu.getWeakReference(doc.getElementById("reader-message"));
-
- this._toolbarEnabled = false;
-
- this._scrollOffset = win.pageYOffset;
-
- let body = doc.body;
- body.addEventListener("touchstart", this, false);
- body.addEventListener("click", this, false);
-
- win.addEventListener("unload", this, false);
- win.addEventListener("scroll", this, false);
- win.addEventListener("popstate", this, false);
- win.addEventListener("resize", this, false);
-
- doc.addEventListener("visibilitychange", this, false);
-
- this._setupAllDropdowns();
- this._setupButton("toggle-button", this._onReaderToggle.bind(this));
- this._setupButton("share-button", this._onShare.bind(this));
-
- let colorSchemeOptions = [
- { name: gStrings.GetStringFromName("aboutReader.colorSchemeDark"),
- value: "dark"},
- { name: gStrings.GetStringFromName("aboutReader.colorSchemeLight"),
- value: "light"},
- { name: gStrings.GetStringFromName("aboutReader.colorSchemeAuto"),
- value: "auto"}
- ];
-
- let colorScheme = Services.prefs.getCharPref("reader.color_scheme");
- this._setupSegmentedButton("color-scheme-buttons", colorSchemeOptions, colorScheme, this._setColorSchemePref.bind(this));
- this._setColorSchemePref(colorScheme);
-
- let fontTypeSample = gStrings.GetStringFromName("aboutReader.fontTypeSample");
- let fontTypeOptions = [
- { name: fontTypeSample,
- description: gStrings.GetStringFromName("aboutReader.fontTypeSerif"),
- value: "serif",
- linkClass: "serif" },
- { name: fontTypeSample,
- description: gStrings.GetStringFromName("aboutReader.fontTypeSansSerif"),
- value: "sans-serif",
- linkClass: "sans-serif"
- },
- ];
-
- let fontType = Services.prefs.getCharPref("reader.font_type");
- this._setupSegmentedButton("font-type-buttons", fontTypeOptions, fontType, this._setFontType.bind(this));
- this._setFontType(fontType);
-
- let fontSizeSample = gStrings.GetStringFromName("aboutReader.fontSizeSample");
- let fontSizeOptions = [
- { name: fontSizeSample,
- value: 1,
- linkClass: "font-size1-sample" },
- { name: fontSizeSample,
- value: 2,
- linkClass: "font-size2-sample" },
- { name: fontSizeSample,
- value: 3,
- linkClass: "font-size3-sample" },
- { name: fontSizeSample,
- value: 4,
- linkClass: "font-size4-sample" },
- { name: fontSizeSample,
- value: 5,
- linkClass: "font-size5-sample" }
- ];
-
- let fontSize = Services.prefs.getIntPref("reader.font_size");
- this._setupSegmentedButton("font-size-buttons", fontSizeOptions, fontSize, this._setFontSize.bind(this));
- this._setFontSize(fontSize);
-
- dump("Decoding query arguments");
- let queryArgs = this._decodeQueryString(win.location.href);
-
- // Track status of reader toolbar add/remove toggle button
- this._isReadingListItem = -1;
- this._updateToggleButton();
-
- let url = queryArgs.url;
- let tabId = queryArgs.tabId;
- this._loadArticle(url, tabId);
-}
-
-AboutReader.prototype = {
- _BLOCK_IMAGES_SELECTOR: ".content p > img:only-child, " +
- ".content p > a:only-child > img:only-child, " +
- ".content .wp-caption img, " +
- ".content figure img",
-
- get _doc() {
- return this._docRef.get();
- },
-
- get _win() {
- return this._winRef.get();
- },
-
- get _headerElement() {
- return this._headerElementRef.get();
- },
-
- get _domainElement() {
- return this._domainElementRef.get();
- },
-
- get _titleElement() {
- return this._titleElementRef.get();
- },
-
- get _creditsElement() {
- return this._creditsElementRef.get();
- },
-
- get _contentElement() {
- return this._contentElementRef.get();
- },
-
- get _toolbarElement() {
- return this._toolbarElementRef.get();
- },
-
- get _messageElement() {
- return this._messageElementRef.get();
- },
-
- observe: function Reader_observe(aMessage, aTopic, aData) {
- switch(aTopic) {
- case "Reader:FaviconReturn": {
- let args = JSON.parse(aData);
- this._loadFavicon(args.url, args.faviconUrl);
- Services.obs.removeObserver(this, "Reader:FaviconReturn");
- break;
- }
-
- case "Reader:Added": {
- // Page can be added by long-press pageAction, or by tap on banner icon.
- if (aData == this._article.url) {
- if (this._isReadingListItem != 1) {
- this._isReadingListItem = 1;
- this._updateToggleButton();
- }
- }
- break;
- }
-
- case "Reader:Removed": {
- if (aData == this._article.url) {
- if (this._isReadingListItem != 0) {
- this._isReadingListItem = 0;
- this._updateToggleButton();
- }
- }
- break;
- }
-
- case "Gesture:DoubleTap": {
- let args = JSON.parse(aData);
- let scrollBy;
- // Arbitary choice of innerHeight - 50 to give some context after scroll
- if (args.y < (this._win.innerHeight / 2)) {
- scrollBy = -this._win.innerHeight + 50;
- } else {
- scrollBy = this._win.innerHeight - 50;
- }
- this._scrollPage(scrollBy);
- break;
- }
- }
- },
-
- handleEvent: function Reader_handleEvent(aEvent) {
- if (!aEvent.isTrusted)
- return;
-
- switch (aEvent.type) {
- case "touchstart":
- this._scrolled = false;
- break;
- case "click":
- if (!this._scrolled)
- this._toggleToolbarVisibility();
- break;
- case "scroll":
- if (!this._scrolled) {
- let isScrollingUp = this._scrollOffset > aEvent.pageY;
- this._setToolbarVisibility(isScrollingUp);
- this._scrollOffset = aEvent.pageY;
- }
- break;
- case "popstate":
- if (!aEvent.state)
- this._closeAllDropdowns();
- break;
- case "resize":
- this._updateImageMargins();
- break;
-
- case "devicelight":
- this._handleDeviceLight(aEvent.value);
- break;
-
- case "visibilitychange":
- this._handleVisibilityChange();
- break;
-
- case "unload":
- Services.obs.removeObserver(this, "Reader:Added");
- Services.obs.removeObserver(this, "Reader:Removed");
- Services.obs.removeObserver(this, "Gesture:DoubleTap");
- break;
- }
- },
-
- _scrollPage: function Reader_scrollPage(scrollByPixels) {
- let viewport = BrowserApp.selectedTab.getViewport();
- let newY = Math.min(Math.max(viewport.cssY + scrollByPixels, viewport.cssPageTop), viewport.cssPageBottom);
- let newRect = new Rect(viewport.cssX, newY, viewport.cssWidth, viewport.cssHeight);
-
- this._setToolbarVisibility(false);
- this._setBrowserToolbarVisiblity(false);
- this._scrolled = true;
- ZoomHelper.zoomToRect(newRect, -1);
- },
-
- _updateToggleButton: function Reader_updateToggleButton() {
- let classes = this._doc.getElementById("toggle-button").classList;
-
- if (this._isReadingListItem == 1) {
- classes.add("on");
- } else {
- classes.remove("on");
- }
- },
-
- _requestReadingListStatus: function Reader_requestReadingListStatus() {
- Messaging.sendRequestForResult({
- type: "Reader:ListStatusRequest",
- url: this._article.url
- }).then((data) => {
- let args = JSON.parse(data);
- if (args.url == this._article.url) {
- if (this._isReadingListItem != args.inReadingList) {
- let isInitialStateChange = (this._isReadingListItem == -1);
- this._isReadingListItem = args.inReadingList;
- this._updateToggleButton();
-
- // Display the toolbar when all its initial component states are known
- if (isInitialStateChange) {
- this._setToolbarVisibility(true);
- }
- }
- }
- });
- },
-
- _onReaderToggle: function Reader_onToggle() {
- if (!this._article)
- return;
-
- if (this._isReadingListItem == 0) {
- // If we're in reader mode, we must have fetched the article.
- this._article.status = gChromeWin.Reader.STATUS_FETCHED_ARTICLE;
- gChromeWin.Reader.addArticleToReadingList(this._article);
-
- UITelemetry.addEvent("save.1", "button", null, "reader");
- } else {
- Messaging.sendRequest({
- type: "Reader:RemoveFromList",
- url: this._article.url
- });
-
- UITelemetry.addEvent("unsave.1", "button", null, "reader");
- }
- },
-
- _onShare: function Reader_onShare() {
- if (!this._article)
- return;
-
- Messaging.sendRequest({
- type: "Reader:Share",
- url: this._article.url,
- title: this._article.title
- });
-
- UITelemetry.addEvent("share.1", "list", null);
- },
-
- _setFontSize: function Reader_setFontSize(newFontSize) {
- let bodyClasses = this._doc.body.classList;
-
- if (this._fontSize > 0)
- bodyClasses.remove("font-size" + this._fontSize);
-
- this._fontSize = newFontSize;
- bodyClasses.add("font-size" + this._fontSize);
-
- Services.prefs.setIntPref("reader.font_size", this._fontSize);
- },
-
- _handleDeviceLight: function Reader_handleDeviceLight(newLux) {
- // Desired size of the this._luxValues array.
- let luxValuesSize = 10;
- // Add new lux value at the front of the array.
- this._luxValues.unshift(newLux);
- // Add new lux value to this._totalLux for averaging later.
- this._totalLux += newLux;
-
- // Don't update when length of array is less than luxValuesSize except when it is 1.
- if (this._luxValues.length < luxValuesSize) {
- // Use the first lux value to set the color scheme until our array equals luxValuesSize.
- if (this._luxValues.length == 1) {
- this._updateColorScheme(newLux);
- }
- return;
- }
- // Holds the average of the lux values collected in this._luxValues.
- let averageLuxValue = this._totalLux/luxValuesSize;
-
- this._updateColorScheme(averageLuxValue);
- // Pop the oldest value off the array.
- let oldLux = this._luxValues.pop();
- // Subtract oldLux since it has been discarded from the array.
- this._totalLux -= oldLux;
- },
-
- _handleVisibilityChange: function Reader_handleVisibilityChange() {
- let colorScheme = Services.prefs.getCharPref("reader.color_scheme");
- if (colorScheme != "auto") {
- return;
- }
-
- // Turn off the ambient light sensor if the page is hidden
- this._enableAmbientLighting(!this._doc.hidden);
- },
-
- // Setup or teardown the ambient light tracking system.
- _enableAmbientLighting: function Reader_enableAmbientLighting(enable) {
- if (enable) {
- this._win.addEventListener("devicelight", this, false);
- this._luxValues = [];
- this._totalLux = 0;
- } else {
- this._win.removeEventListener("devicelight", this, false);
- delete this._luxValues;
- delete this._totalLux;
- }
- },
-
- _updateColorScheme: function Reader_updateColorScheme(luxValue) {
- // Upper bound value for "dark" color scheme beyond which it changes to "light".
- let upperBoundDark = 50;
- // Lower bound value for "light" color scheme beyond which it changes to "dark".
- let lowerBoundLight = 10;
- // Threshold for color scheme change.
- let colorChangeThreshold = 20;
-
- // Ignore changes that are within a certain threshold of previous lux values.
- if ((this._colorScheme === "dark" && luxValue < upperBoundDark) ||
- (this._colorScheme === "light" && luxValue > lowerBoundLight))
- return;
-
- if (luxValue < colorChangeThreshold)
- this._setColorScheme("dark");
- else
- this._setColorScheme("light");
- },
+ * 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/. */
- _setColorScheme: function Reader_setColorScheme(newColorScheme) {
- // "auto" is not a real color scheme
- if (this._colorScheme === newColorScheme || newColorScheme === "auto")
- return;
-
- let bodyClasses = this._doc.body.classList;
-
- if (this._colorScheme)
- bodyClasses.remove(this._colorScheme);
-
- this._colorScheme = newColorScheme;
- bodyClasses.add(this._colorScheme);
- },
-
- // Pref values include "dark", "light", and "auto", which automatically switches
- // between light and dark color schemes based on the ambient light level.
- _setColorSchemePref: function Reader_setColorSchemePref(colorSchemePref) {
- this._enableAmbientLighting(colorSchemePref === "auto");
- this._setColorScheme(colorSchemePref);
-
- Services.prefs.setCharPref("reader.color_scheme", colorSchemePref);
- },
-
- _setFontType: function Reader_setFontType(newFontType) {
- if (this._fontType === newFontType)
- return;
-
- let bodyClasses = this._doc.body.classList;
-
- if (this._fontType)
- bodyClasses.remove(this._fontType);
-
- this._fontType = newFontType;
- bodyClasses.add(this._fontType);
-
- Services.prefs.setCharPref("reader.font_type", this._fontType);
- },
-
- _getToolbarVisibility: function Reader_getToolbarVisibility() {
- return !this._toolbarElement.classList.contains("toolbar-hidden");
- },
-
- _setToolbarVisibility: function Reader_setToolbarVisibility(visible) {
- let win = this._win;
- if (win.history.state)
- win.history.back();
-
- if (!this._toolbarEnabled)
- return;
-
- // Don't allow visible toolbar until banner state is known
- if (this._isReadingListItem == -1)
- return;
-
- if (this._getToolbarVisibility() === visible)
- return;
-
- this._toolbarElement.classList.toggle("toolbar-hidden");
- this._setSystemUIVisibility(visible);
-
- if (!visible && !this._hasUsedToolbar) {
- this._hasUsedToolbar = Services.prefs.getBoolPref("reader.has_used_toolbar");
- if (!this._hasUsedToolbar) {
- gChromeWin.NativeWindow.toast.show(gStrings.GetStringFromName("aboutReader.toolbarTip"), "short");
-
- Services.prefs.setBoolPref("reader.has_used_toolbar", true);
- this._hasUsedToolbar = true;
- }
- }
- },
-
- _toggleToolbarVisibility: function Reader_toggleToolbarVisibility() {
- this._setToolbarVisibility(!this._getToolbarVisibility());
- },
-
- _setBrowserToolbarVisiblity: function Reader_setBrowserToolbarVisiblity(visible) {
- Messaging.sendRequest({
- type: "BrowserToolbar:Visibility",
- visible: visible
- });
- },
-
- _setSystemUIVisibility: function Reader_setSystemUIVisibility(visible) {
- Messaging.sendRequest({
- type: "SystemUI:Visibility",
- visible: visible
- });
- },
-
- _loadArticle: Task.async(function* (url, tabId) {
- this._showProgressDelayed();
-
- let article = yield gChromeWin.Reader.getArticle(url, tabId).catch(e => {
- Cu.reportError("Error loading article: " + e);
- return null;
- });
- if (article) {
- this._showContent(article);
- } else {
- this._win.location.href = url;
- }
- }),
-
- _requestFavicon: function Reader_requestFavicon() {
- Messaging.sendRequest({
- type: "Reader:FaviconRequest",
- url: this._article.url
- });
- },
-
- _loadFavicon: function Reader_loadFavicon(url, faviconUrl) {
- if (this._article.url !== url)
- return;
-
- let doc = this._doc;
-
- let link = doc.createElement('link');
- link.rel = 'shortcut icon';
- link.href = faviconUrl;
-
- doc.getElementsByTagName('head')[0].appendChild(link);
- },
-
- _updateImageMargins: function Reader_updateImageMargins() {
- let windowWidth = this._win.innerWidth;
- let contentWidth = this._contentElement.offsetWidth;
- let maxWidthStyle = windowWidth + "px !important";
-
- let setImageMargins = function(img) {
- if (!img._originalWidth)
- img._originalWidth = img.offsetWidth;
-
- let imgWidth = img._originalWidth;
-
- // If the image is taking more than half of the screen, just make
- // it fill edge-to-edge.
- if (imgWidth < contentWidth && imgWidth > windowWidth * 0.55)
- imgWidth = windowWidth;
-
- let sideMargin = Math.max((contentWidth - windowWidth) / 2,
- (contentWidth - imgWidth) / 2);
-
- let imageStyle = sideMargin + "px !important";
- let widthStyle = imgWidth + "px !important";
-
- let cssText = "max-width: " + maxWidthStyle + ";" +
- "width: " + widthStyle + ";" +
- "margin-left: " + imageStyle + ";" +
- "margin-right: " + imageStyle + ";";
-
- img.style.cssText = cssText;
- }
-
- let imgs = this._doc.querySelectorAll(this._BLOCK_IMAGES_SELECTOR);
- for (let i = imgs.length; --i >= 0;) {
- let img = imgs[i];
-
- if (img.width > 0) {
- setImageMargins(img);
- } else {
- img.onload = function() {
- setImageMargins(img);
- }
- }
- }
- },
-
- _maybeSetTextDirection: function Read_maybeSetTextDirection(article){
- if(!article.dir)
- return;
-
- //Set "dir" attribute on content
- this._contentElement.setAttribute("dir", article.dir);
- this._headerElement.setAttribute("dir", article.dir);
- },
-
- _showError: function Reader_showError(error) {
- this._headerElement.style.display = "none";
- this._contentElement.style.display = "none";
-
- this._messageElement.innerHTML = error;
- this._messageElement.style.display = "block";
-
- this._doc.title = error;
- },
-
- // This function is the JS version of Java's StringUtils.stripCommonSubdomains.
- _stripHost: function Reader_stripHost(host) {
- if (!host)
- return host;
-
- let start = 0;
-
- if (host.startsWith("www."))
- start = 4;
- else if (host.startsWith("m."))
- start = 2;
- else if (host.startsWith("mobile."))
- start = 7;
-
- return host.substring(start);
- },
-
- _showContent: function Reader_showContent(article) {
- this._messageElement.style.display = "none";
-
- this._article = article;
-
- this._domainElement.href = article.url;
- let articleUri = Services.io.newURI(article.url, null, null);
- this._domainElement.innerHTML = this._stripHost(articleUri.host);
+"use strict";
- this._creditsElement.innerHTML = article.byline;
-
- this._titleElement.textContent = article.title;
- this._doc.title = article.title;
-
- this._headerElement.style.display = "block";
-
- let parserUtils = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils);
- let contentFragment = parserUtils.parseFragment(article.content, Ci.nsIParserUtils.SanitizerDropForms,
- false, articleUri, this._contentElement);
- this._contentElement.innerHTML = "";
- this._contentElement.appendChild(contentFragment);
- this._updateImageMargins();
- this._maybeSetTextDirection(article);
-
- this._contentElement.style.display = "block";
- this._requestReadingListStatus();
-
- this._toolbarEnabled = true;
- this._setToolbarVisibility(true);
-
- this._requestFavicon();
- },
-
- _hideContent: function Reader_hideContent() {
- this._headerElement.style.display = "none";
- this._contentElement.style.display = "none";
- },
-
- _showProgressDelayed: function Reader_showProgressDelayed() {
- this._win.setTimeout(function() {
- // Article has already been loaded, no need to show
- // progress anymore.
- if (this._article)
- return;
-
- this._headerElement.style.display = "none";
- this._contentElement.style.display = "none";
-
- this._messageElement.innerHTML = gStrings.GetStringFromName("aboutReader.loading");
- this._messageElement.style.display = "block";
- }.bind(this), 300);
- },
-
- _decodeQueryString: function Reader_decodeQueryString(url) {
- let result = {};
- let query = url.split("?")[1];
- if (query) {
- let pairs = query.split("&");
- for (let i = 0; i < pairs.length; i++) {
- let [name, value] = pairs[i].split("=");
- result[name] = decodeURIComponent(value);
- }
- }
-
- return result;
- },
-
- _setupSegmentedButton: function Reader_setupSegmentedButton(id, options, initialValue, callback) {
- let doc = this._doc;
- let segmentedButton = doc.getElementById(id);
-
- for (let i = 0; i < options.length; i++) {
- let option = options[i];
-
- let item = doc.createElement("li");
- let link = doc.createElement("a");
- link.textContent = option.name;
- item.appendChild(link);
-
- if (option.linkClass !== undefined)
- link.classList.add(option.linkClass);
-
- if (option.description !== undefined) {
- let description = doc.createElement("div");
- description.textContent = option.description;
- item.appendChild(description);
- }
-
- link.style.MozUserSelect = 'none';
- segmentedButton.appendChild(item);
-
- link.addEventListener("click", function(aEvent) {
- if (!aEvent.isTrusted)
- return;
-
- aEvent.stopPropagation();
-
- // Just pass the ID of the button as an extra and hope the ID doesn't change
- // unless the context changes
- UITelemetry.addEvent("action.1", "button", null, id);
-
- let items = segmentedButton.children;
- for (let j = items.length - 1; j >= 0; j--) {
- items[j].classList.remove("selected");
- }
-
- item.classList.add("selected");
- callback(option.value);
- }.bind(this), true);
-
- if (option.value === initialValue)
- item.classList.add("selected");
- }
- },
-
- _setupButton: function Reader_setupButton(id, callback) {
- let button = this._doc.getElementById(id);
-
- button.addEventListener("click", function(aEvent) {
- if (!aEvent.isTrusted)
- return;
-
- aEvent.stopPropagation();
- callback();
- }, true);
- },
-
- _setupAllDropdowns: function Reader_setupAllDropdowns() {
- let doc = this._doc;
- let win = this._win;
-
- let dropdowns = doc.getElementsByClassName("dropdown");
-
- for (let i = dropdowns.length - 1; i >= 0; i--) {
- let dropdown = dropdowns[i];
-
- let dropdownToggle = dropdown.getElementsByClassName("dropdown-toggle")[0];
- let dropdownPopup = dropdown.getElementsByClassName("dropdown-popup")[0];
-
- if (!dropdownToggle || !dropdownPopup)
- continue;
-
- let dropdownArrow = doc.createElement("div");
- dropdownArrow.className = "dropdown-arrow";
- dropdownPopup.appendChild(dropdownArrow);
-
- let updatePopupPosition = function() {
- let popupWidth = dropdownPopup.offsetWidth + 30;
- let arrowWidth = dropdownArrow.offsetWidth;
- let toggleWidth = dropdownToggle.offsetWidth;
- let toggleLeft = dropdownToggle.offsetLeft;
-
- let popupShift = (toggleWidth - popupWidth) / 2;
- let popupLeft = Math.max(0, Math.min(win.innerWidth - popupWidth, toggleLeft + popupShift));
- dropdownPopup.style.left = popupLeft + "px";
-
- let arrowShift = (toggleWidth - arrowWidth) / 2;
- let arrowLeft = toggleLeft - popupLeft + arrowShift;
- dropdownArrow.style.left = arrowLeft + "px";
- };
-
- win.addEventListener("resize", function(aEvent) {
- if (!aEvent.isTrusted)
- return;
-
- // Wait for reflow before calculating the new position of the popup.
- setTimeout(updatePopupPosition, 0);
- }, true);
-
- dropdownToggle.addEventListener("click", function(aEvent) {
- if (!aEvent.isTrusted)
- return;
-
- aEvent.stopPropagation();
-
- if (!this._getToolbarVisibility())
- return;
-
- let dropdownClasses = dropdown.classList;
-
- if (dropdownClasses.contains("open")) {
- win.history.back();
- } else {
- updatePopupPosition();
- if (!this._closeAllDropdowns())
- this._pushDropdownState();
-
- dropdownClasses.add("open");
- }
- }.bind(this), true);
- }
- },
-
- _pushDropdownState: function Reader_pushDropdownState() {
- // FIXME: We're getting a NS_ERROR_UNEXPECTED error when we try
- // to do win.history.pushState() here (see bug 682296). This is
- // a workaround that allows us to push history state on the target
- // content document.
-
- let doc = this._doc;
- let body = doc.body;
-
- if (this._pushStateScript)
- body.removeChild(this._pushStateScript);
-
- this._pushStateScript = doc.createElement('script');
- this._pushStateScript.type = "text/javascript";
- this._pushStateScript.innerHTML = 'history.pushState({ dropdown: 1 }, document.title);';
-
- body.appendChild(this._pushStateScript);
- },
-
- _closeAllDropdowns : function Reader_closeAllDropdowns() {
- let dropdowns = this._doc.querySelectorAll(".dropdown.open");
- for (let i = dropdowns.length - 1; i >= 0; i--) {
- dropdowns[i].classList.remove("open");
- }
-
- return (dropdowns.length > 0)
- }
-};
+window.addEventListener("DOMContentLoaded", function () {
+ document.dispatchEvent(new CustomEvent("AboutReaderContentLoaded", { bubbles: true }));
+});
--- a/toolkit/components/reader/moz.build
+++ b/toolkit/components/reader/moz.build
@@ -2,10 +2,11 @@
# vim: set filetype=python:
# 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/.
JAR_MANIFESTS += ['jar.mn']
EXTRA_JS_MODULES += [
+ 'AboutReader.jsm',
'ReaderMode.jsm'
]