Bug 1371801 - Detect if a page can be viewed in reader mode, r?mixedpuppy
This adds a hasReaderMode function to the tabs API which reports back
true or false.
MozReview-Commit-ID: 9yrgLcMMsjD
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -907,13 +907,19 @@ this.tabs = class extends ExtensionAPI {
resolve(retval == 0 ? "saved" : "replaced");
} else {
// Cancel clicked (retval == 1)
resolve("canceled");
}
});
});
},
+
+ async hasReaderMode(tabId) {
+ let tab = await promiseTabWhenReady(tabId);
+
+ return tab.sendMessage(context, "Extension:IsTabReaderable");
+ },
},
};
return self;
}
};
--- a/browser/components/extensions/schemas/tabs.json
+++ b/browser/components/extensions/schemas/tabs.json
@@ -891,16 +891,31 @@
"name": "language",
"description": "An ISO language code such as <code>en</code> or <code>fr</code>. For a complete list of languages supported by this method, see <a href='http://src.chromium.org/viewvc/chrome/trunk/src/third_party/cld/languages/internal/languages.cc'>kLanguageInfoTable</a>. The 2nd to 4th columns will be checked and the first non-NULL value will be returned except for Simplified Chinese for which zh-CN will be returned. For an unknown language, <code>und</code> will be returned."
}
]
}
]
},
{
+ "name": "hasReaderMode",
+ "type": "function",
+ "description": "Reports whether the document in the tab is likely able to be rendered in reader mode.",
+ "async": true,
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "tabId",
+ "minimum": 0,
+ "optional": true,
+ "description": "Defaults to the active tab of the $(topic:current-window)[current window]."
+ }
+ ]
+ },
+ {
"name": "captureVisibleTab",
"type": "function",
"description": "Captures the visible area of the currently active tab in the specified window. You must have $(topic:declare_permissions)[<all_urls>] permission to use this method.",
"permissions": ["<all_urls>"],
"async": "callback",
"parameters": [
{
"type": "integer",
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -26,16 +26,18 @@ support-files =
locale/chrome.manifest
webNav_createdTarget.html
webNav_createdTargetSource.html
webNav_createdTargetSource_subframe.html
serviceWorker.js
searchSuggestionEngine.xml
searchSuggestionEngine.sjs
../../../../../toolkit/components/extensions/test/mochitest/head_webrequest.js
+ ../../../../../toolkit/components/reader/test/readerModeNonArticle.html
+ ../../../../../toolkit/components/reader/test/readerModeArticle.html
[browser_ext_browserAction_area.js]
[browser_ext_browserAction_context.js]
[browser_ext_browserAction_contextMenu.js]
# bug 1369197
skip-if = os == 'linux'
[browser_ext_browserAction_disabled.js]
[browser_ext_browserAction_pageAction_icon.js]
@@ -145,16 +147,17 @@ skip-if = debug || asan # Bug 1354681
[browser_ext_tabs_move_window.js]
[browser_ext_tabs_move_window_multiple.js]
[browser_ext_tabs_move_window_pinned.js]
[browser_ext_tabs_onHighlighted.js]
[browser_ext_tabs_onUpdated.js]
[browser_ext_tabs_opener.js]
[browser_ext_tabs_printPreview.js]
[browser_ext_tabs_query.js]
+[browser_ext_tabs_readerMode.js]
[browser_ext_tabs_reload.js]
[browser_ext_tabs_reload_bypass_cache.js]
[browser_ext_tabs_sendMessage.js]
[browser_ext_tabs_cookieStoreId.js]
[browser_ext_tabs_update.js]
[browser_ext_tabs_zoom.js]
[browser_ext_tabs_update_url.js]
[browser_ext_themes_icons.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_readerMode.js
@@ -0,0 +1,37 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(async function test_has_reader_mode() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ async background() {
+ browser.test.onMessage.addListener(msg => {
+ browser.tabs.update({url: msg});
+ });
+
+ browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
+ if (changeInfo.status === "complete") {
+ let isReaderable = await browser.tabs.hasReaderMode(tabId);
+ browser.test.sendMessage("updated", isReaderable);
+ }
+ });
+ },
+ });
+
+ const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "http://example.com");
+
+ await extension.startup();
+ extension.sendMessage(`${TEST_PATH}readerModeArticle.html`);
+ let isReaderable = await extension.awaitMessage("updated");
+ ok(isReaderable, "Tab is readerable.");
+
+ extension.sendMessage(`${TEST_PATH}readerModeNonArticle.html`);
+ isReaderable = await extension.awaitMessage("updated");
+ ok(!isReaderable, "Tab is not readerable.");
+
+ await extension.unload();
+});
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -11,16 +11,17 @@ this.EXPORTED_SYMBOLS = ["ExtensionConte
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
LanguageDetector: "resource:///modules/translation/LanguageDetector.jsm",
MessageChannel: "resource://gre/modules/MessageChannel.jsm",
+ ReaderMode: "resource://gre/modules/ReaderMode.jsm",
Schemas: "resource://gre/modules/Schemas.jsm",
TelemetryStopwatch: "resource://gre/modules/TelemetryStopwatch.jsm",
WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.jsm",
});
XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",
"@mozilla.org/content/style-sheet-service;1",
"nsIStyleSheetService");
@@ -736,16 +737,20 @@ this.ExtensionContent = {
const fileName = js.length ? js[js.length - 1] : "<anonymous code>";
const message = `Script '${fileName}' result is non-structured-clonable data`;
return Promise.reject({message, fileName});
}
return result;
},
+ handleIsTabReaderable(global) {
+ return ReaderMode.isProbablyReaderable(global.content.document);
+ },
+
handleWebNavigationGetFrame(global, {frameId}) {
return WebNavigationFrames.getFrame(global.docShell, frameId);
},
handleWebNavigationGetAllFrames(global) {
return WebNavigationFrames.getAllFrames(global.docShell);
},
--- a/toolkit/components/extensions/extension-process-script.js
+++ b/toolkit/components/extensions/extension-process-script.js
@@ -90,16 +90,17 @@ class ExtensionGlobal {
this.global = global;
this.global.addMessageListener("Extension:SetFrameData", this);
this.frameData = null;
MessageChannel.addListener(global, "Extension:Capture", this);
MessageChannel.addListener(global, "Extension:DetectLanguage", this);
MessageChannel.addListener(global, "Extension:Execute", this);
+ MessageChannel.addListener(global, "Extension:IsTabReaderable", this);
MessageChannel.addListener(global, "WebNavigation:GetFrame", this);
MessageChannel.addListener(global, "WebNavigation:GetAllFrames", this);
}
get messageFilterStrict() {
return {
innerWindowID: getInnerWindowID(this.global.content),
};
@@ -142,16 +143,18 @@ class ExtensionGlobal {
cssOrigin: data.options.css_origin,
cssCode: data.options.cssCode,
jsCode: data.options.jsCode,
});
let script = contentScripts.get(matcher);
return ExtensionContent.handleExtensionExecute(this.global, target, data.options, script);
+ case "Extension:IsTabReaderable":
+ return ExtensionContent.handleIsTabReaderable(this.global);
case "WebNavigation:GetFrame":
return ExtensionContent.handleWebNavigationGetFrame(this.global, data.options);
case "WebNavigation:GetAllFrames":
return ExtensionContent.handleWebNavigationGetAllFrames(this.global);
}
}
}