├── .gitignore
├── auto-reader-view
├── icons
│ ├── miu-book-icon-16.png
│ ├── miu-book-icon-32.png
│ ├── miu-book-icon-48.png
│ └── miu-book-icon-64.png
├── popup
│ ├── panel.css
│ ├── panel.html
│ └── panel.js
├── test
│ └── test-url-utils.js
├── options
│ ├── options.html
│ └── options.js
├── manifest.json
├── lib
│ └── url-utils.js
├── data
│ └── panel.js
├── index.js
└── background.js
├── TODO.md
├── LICENSE.md
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | *.xpi
2 |
--------------------------------------------------------------------------------
/auto-reader-view/icons/miu-book-icon-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmarchwiak/auto-reader-view/HEAD/auto-reader-view/icons/miu-book-icon-16.png
--------------------------------------------------------------------------------
/auto-reader-view/icons/miu-book-icon-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmarchwiak/auto-reader-view/HEAD/auto-reader-view/icons/miu-book-icon-32.png
--------------------------------------------------------------------------------
/auto-reader-view/icons/miu-book-icon-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmarchwiak/auto-reader-view/HEAD/auto-reader-view/icons/miu-book-icon-48.png
--------------------------------------------------------------------------------
/auto-reader-view/icons/miu-book-icon-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmarchwiak/auto-reader-view/HEAD/auto-reader-view/icons/miu-book-icon-64.png
--------------------------------------------------------------------------------
/auto-reader-view/popup/panel.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | /*width: 100px;*/
3 | }
4 |
5 | div {
6 | margin: 10px;
7 | }
8 |
9 | .hidden {
10 | display: none;
11 | }
12 |
13 | .button {
14 | margin: 3% auto;
15 | padding: 4px;
16 | text-align: center;
17 | /*font-size: 1em;*/
18 | cursor: pointer;
19 | }
20 |
--------------------------------------------------------------------------------
/auto-reader-view/popup/panel.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
16 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/auto-reader-view/test/test-url-utils.js:
--------------------------------------------------------------------------------
1 | var uu = require("../lib/url-utils");
2 |
3 | exports["test domainFromUrl"] = function(assert) {
4 | assert.ok(uu.domainFromUrl("http://google.com") == "google.com");
5 | assert.ok(uu.domainFromUrl("http://google.com/blah") == "google.com");
6 | assert.ok(uu.domainFromUrl("https://google.com/blah") == "google.com");
7 | };
8 |
9 | exports["test isUrlHomePage"] = function(assert) {
10 | assert.ok(uu.isUrlHomePage("http://google.com"));
11 | assert.ok(uu.isUrlHomePage("http://google.com/"));
12 | assert.ok(!uu.isUrlHomePage("http://google.com/search"));
13 | assert.ok(!uu.isUrlHomePage("https://google.com/search"));
14 | };
15 |
16 |
17 | require("sdk/test").run(exports);
18 |
--------------------------------------------------------------------------------
/auto-reader-view/options/options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # TODO
2 |
3 | ## Bugs
4 | - [ ] Fix "Error: The specified tab cannot be placed into reader mode." when opening enabled tabs in background
5 | - [ ] Prevent loop when reader view doesn't work
6 | - [ ] Fix whitelisted domain, readerView -> exit -> readerView -> exit in same tab
7 | - [ ] Allow enabling for a domain when a page is already in reader view
8 | - [ ] Prevent addition of special pages such as about:addons
9 | - [x] Badge doesn't update to red minus occasionally
10 |
11 | ## Features
12 | - [ ] Add help docs
13 | - [ ] Add dialog for viewing saved domains
14 | - [ ] Add dialog for modifying saved domains
15 | - [ ] Support (regex?) patterns for urls
16 | - [ ] Add unit tests
17 | - [ ] Don't use global variables
18 | - [ ] Firefox on Android support?
19 | - [ ] Add hotkey - Cmd-Opt-R?
20 | - [ ] Disable on site home pages, this will happen automatically?
21 | - [x] Allow exiting reader view from whitelisted domains
22 |
--------------------------------------------------------------------------------
/auto-reader-view/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "auto-reader-view",
4 | "description": "Enable Reader Mode automatically on chosen websites",
5 | "version": "1.0.3",
6 | "homepage_url": "https://github.com/pmarchwiak/auto-reader-view",
7 | "applications": {
8 | "gecko": {
9 | "id": "@auto-reader-view"
10 | }
11 | },
12 |
13 | "icons": {
14 | "48": "icons/miu-book-icon-48.png"
15 | },
16 |
17 | "permissions": ["storage", "tabs"],
18 |
19 | "background": {
20 | "scripts": ["background.js"]
21 | },
22 |
23 | "browser_action": {
24 | "browser_style": true,
25 | "default_icon": {
26 | "16": "icons/miu-book-icon-16.png",
27 | "32": "icons/miu-book-icon-32.png",
28 | "64": "icons/miu-book-icon-64.png"
29 | },
30 | "default_title": "Auto Reader Mode",
31 | "default_popup": "popup/panel.html"
32 | },
33 |
34 | "options_ui": {
35 | "page": "options/options.html",
36 | "browser_style": true
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/auto-reader-view/lib/url-utils.js:
--------------------------------------------------------------------------------
1 | var ABOUT_READER_PREFIX = "about:reader?url=";
2 |
3 | // Strip reader prefix from url
4 | function urlWithoutReader(url) {
5 | if (url.startsWith(ABOUT_READER_PREFIX)) {
6 | return url.substring(ABOUT_READER_PREFIX.length);
7 | }
8 | return url;
9 | }
10 |
11 | // Extract domain from a url
12 | function domainFromUrl(url) {
13 | if (url.startsWith(ABOUT_READER_PREFIX)) {
14 | url = urlWithoutReader(url);
15 | }
16 | var r = /:\/\/(.[^/]+)/;
17 | var matches = url.match(r);
18 | if (matches && matches.length >= 2) {
19 | return matches[1];
20 | }
21 | return null;
22 | }
23 |
24 | function isUrlHomePage(url) {
25 | var domain = domainFromUrl(url);
26 | var endOfDomainPartIdx = url.indexOf(domain) + domain.length;
27 | var pathPart = url.substr(endOfDomainPartIdx);
28 |
29 | return pathPart.length < 2; // 2 in case of trailing '/'
30 | }
31 |
32 | exports.domainFromUrl = domainFromUrl;
33 | exports.isUrlHomePage = isUrlHomePage;
34 | exports.ABOUT_READER_PREFIX = ABOUT_READER_PREFIX;
35 |
--------------------------------------------------------------------------------
/auto-reader-view/options/options.js:
--------------------------------------------------------------------------------
1 |
2 | function handleSaveButtonClick(e) {
3 | var text = document.querySelector("#domainsInput").value;
4 | var domainsList = text.split("\n");
5 |
6 | browser.runtime.getBackgroundPage().then(
7 | (backgroundPage) => {
8 | backgroundPage.saveDomainsList(domainsList);
9 | document.querySelector("#saveMessage").innerText = "List saved successfully."
10 | },
11 | (error) => {
12 | console.log("Error getting backgroundPage", error)
13 | }
14 | )
15 |
16 | e.preventDefault();
17 | }
18 |
19 | function restoreOptions() {
20 | // TODO refresh after a tab switch
21 | console.log("Reading domains");
22 | browser.storage.local.get("enabledDomains").then(storageObject => {
23 | console.log("retrieved domains", storageObject);
24 | var domainsString = storageObject.enabledDomains.join("\n");
25 | document.querySelector("#domainsInput").value = domainsString;
26 | });
27 | }
28 |
29 | document.addEventListener('DOMContentLoaded', restoreOptions);
30 | document.querySelector("form").addEventListener("submit", handleSaveButtonClick);
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Patrick Marchwiak
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Auto Reader View
2 | A Firefox add-on for loading Reader View automatically on chosen websites.
3 |
4 | Get it here: https://addons.mozilla.org/en-US/firefox/addon/auto-reader-view/
5 |
6 | ## Usage
7 | Firefox's Reader View feature strips away clutter from web pages for improved readabilty. In order to switch to this view, users must manually click the Reader View icon in the address bar. This can be a minor inconvenience when Reader View is consistently used for the same web sites.
8 |
9 | This add-on allows users to automatically open Reader View for chosen websites. When the add-on button is clicked, a panel will open that allows the user to change the preference for the current domain.
10 |
11 | To enable Auto Reader View For a new site, click the Auto Reader View button [!book icon](auto-reader-view/data/miu-book-icon-32.png) (it looks like a book) to open the panel, then click "Enable". When clicked, this will save the preference for that domain and a green checkbox will appear on the add-on button. Any future tabs opened for that domain will switch to Reader View automatically. Note that the Close Reader View button continues to work if the user needs to switch back to the normal view for that page.
12 |
13 | To remove the preference for a domain, navigate to a page from that site. Click the Auto Reader View button to open the panel, then click "Disable".
14 |
15 | ## Issues
16 |
17 | Please report any issues at https://github.com/pmarchwiak/auto-reader-view/issues and thank you for using this add-on!
18 |
19 | ## Debugging
20 | Logging for SDK add-ons is disabled by default. Set it info level by setting the `extensions.sdk.console.logLevel` Firefox preference to `info` in `about:config`. See more details at https://developer.mozilla.org/en-US/Add-ons/SDK/Tools/console#Logging_Levels .
21 |
22 | ## Credits
23 | All code by Patrick Marchwiak.
24 |
25 | Icon from Miu theme by Linh Pham Thi Dieu
26 | http://linhpham.me/
27 |
--------------------------------------------------------------------------------
/auto-reader-view/data/panel.js:
--------------------------------------------------------------------------------
1 | document.getElementById("submitButton").addEventListener("click", submitClicked);
2 |
3 | var btn = document.getElementById("submitButton");
4 | var prompt = document.getElementById("panelPrompt");
5 | var domain = "";
6 |
7 | self.port.on("panelOpened", function(state) {
8 | console.log("panelOpened event received");
9 | console.log(state);
10 | domain = state.domain;
11 | if (domain) {
12 | if (state.enabled) {
13 | setEnabledState(domain, btn, prompt);
14 | }
15 | else {
16 | setDisabledState(domain, btn, prompt);
17 | }
18 | }
19 | else {
20 | setUnsupportedState(btn, prompt);
21 | }
22 | });
23 |
24 | function submitClicked(event) {
25 | var btn = event.target;
26 | var isEnabled = (btn.value == "Enable");
27 |
28 | if (isEnabled) {
29 | setEnabledState(domain, btn, prompt);
30 | }
31 | else {
32 | setDisabledState(domain, btn, prompt);
33 | }
34 |
35 | self.port.emit("domainChange", {
36 | domain: domain,
37 | enabled: isEnabled
38 | });
39 | }
40 |
41 | function setEnabledState(domain, btn, prompt) {
42 | btn.style.display = '';
43 | btn.value = "Disable";
44 | replacePromptText(prompt, "Pages from ", domain,
45 | " will automatically open in Reader View.");
46 | }
47 |
48 | function setDisabledState(domain, btn, prompt) {
49 | btn.style.display = '';
50 | btn.value = "Enable";
51 | replacePromptText(prompt, "Always open pages from ", domain, " in Reader View?")
52 | }
53 |
54 | function setUnsupportedState(btn, prompt) {
55 | btn.style.display = 'none';
56 | replacePromptText(prompt, "Reader View not available on this page.", "", "");
57 | }
58 |
59 | function replacePromptText(prompt, part1, domain, part2) {
60 | // clear out prompt text
61 | while (prompt.firstChild) prompt.removeChild(prompt.firstChild);
62 |
63 | // TODO use a library / templating here instead
64 |
65 | var text1 = document.createTextNode(part1);
66 |
67 | var b = document.createElement("b");
68 | var domainText = document.createTextNode(domain);
69 | b.appendChild(domainText);
70 |
71 | var text2 = document.createTextNode(part2);
72 |
73 | prompt.appendChild(text1);
74 | prompt.appendChild(b);
75 | prompt.appendChild(text2);
76 | }
77 |
--------------------------------------------------------------------------------
/auto-reader-view/popup/panel.js:
--------------------------------------------------------------------------------
1 | function getButton() {
2 | return document.getElementById("submitButton");
3 | }
4 |
5 | function getPrompt() {
6 | return document.getElementById("panelPrompt");
7 | }
8 |
9 | function getDomainInput() {
10 | return document.getElementById("domainInput");
11 | }
12 |
13 | function submitClicked(event) {
14 | console.log("submitButton clicked");
15 | var btn = event.target;
16 | var isEnabled = (btn.value == "Enable");
17 | var domain = getDomainInput().value;
18 | updatePanelUi(true, domain, isEnabled);
19 |
20 | browser.runtime.sendMessage({
21 | type: "domainChange",
22 | enabled: isEnabled,
23 | "domain": domain
24 | });
25 | }
26 |
27 | function updatePanelUi(isValid, domain, isEnabled) {
28 | if (!isValid) {
29 | setInvalidState();
30 | }
31 | else if (isEnabled) {
32 | setEnabledState(domain);
33 | }
34 | else {
35 | setDisabledState(domain);
36 | }
37 | }
38 |
39 | function setInvalidState() {
40 | var btn = getButton();
41 | btn.value = "Enable";
42 | btn.setAttribute("disabled", "");
43 | replacePromptText(`"about:" pages cannot be opened in Reader View`);
44 | }
45 |
46 | function setEnabledState(domain) {
47 | var btn = getButton();
48 | // btn.style.display = '';
49 | btn.value = "Disable";
50 | btn.removeAttribute("disabled");
51 | getDomainInput().value = domain;
52 | replacePromptText("Pages from ", domain, " will automatically open in Reader View");
53 | }
54 |
55 | function setDisabledState(domain) {
56 | var btn = getButton();
57 | // btn.style.display = '';
58 | btn.value = "Enable";
59 | btn.removeAttribute("disabled");
60 | getDomainInput().value = domain;
61 | replacePromptText("Always open pages from ", domain, " in Reader View?");
62 | }
63 |
64 | function replacePromptText(part1, domain = null, part2 = null) {
65 | var prompt = getPrompt();
66 | // clear out prompt text
67 | while (prompt.firstChild) prompt.removeChild(prompt.firstChild);
68 |
69 | // TODO use a library / templating here instead
70 |
71 | var text1 = document.createTextNode(part1);
72 | prompt.appendChild(text1);
73 |
74 | if (domain) {
75 | var b = document.createElement("b");
76 | var domainText = document.createTextNode(domain);
77 | b.appendChild(domainText);
78 | prompt.appendChild(b);
79 | }
80 |
81 | if (part2) {
82 | var text2 = document.createTextNode(part2);
83 | prompt.appendChild(text2);
84 | }
85 | }
86 |
87 | function handlePanelOpened(msg) {
88 | console.log("Received message", msg);
89 | if (msg.type === "browserActionClicked") {
90 | updatePanelUi(msg.domain, msg.isEnabled);
91 | }
92 | }
93 |
94 | console.log("Loaded panel.js");
95 |
96 | getButton().addEventListener("click", submitClicked);
97 | browser.runtime.onMessage.addListener(handlePanelOpened);
98 |
99 | browser.runtime.sendMessage({"type": "domainState"}).then(resp => {
100 | console.log("received resp", resp);
101 | var domain = null;
102 | var isEnabled = null;
103 | if (resp.valid) {
104 | domain = resp.domain;
105 | isEnabled = resp.enabled;
106 | }
107 | updatePanelUi(resp.valid, resp.domain, resp.enabled);
108 | });
109 |
--------------------------------------------------------------------------------
/auto-reader-view/index.js:
--------------------------------------------------------------------------------
1 | var buttons = require('sdk/ui/button/action');
2 | var pageMod = require("sdk/page-mod");
3 | var panels = require("sdk/panel");
4 | var self = require("sdk/self");
5 | var ss = require("sdk/simple-storage");
6 | var tabs = require("sdk/tabs");
7 | var uu = require("./lib/url-utils.js");
8 |
9 | var ABOUT_READER_PREFIX = uu.ABOUT_READER_PREFIX;
10 |
11 | var button = buttons.ActionButton({
12 | id: "auto-reader-view-link",
13 | label: "Auto Reader View",
14 | icon: {
15 | "16": "./miu-book-icon-16.png",
16 | "32": "./miu-book-icon-32.png",
17 | "64": "./miu-book-icon-64.png"
18 | },
19 | onClick: openPanel,
20 | });
21 |
22 | var tabPast = new Set();
23 |
24 | var panel = panels.Panel({
25 | contentURL: self.data.url("panel.html"),
26 | height: 100,
27 | width: 250,
28 | contentScriptFile: self.data.url("panel.js")
29 | });
30 |
31 | // When tabs load check if the domain is enabled and if so, switch view.
32 | tabs.on('load', function(tab) {
33 | console.log("tab load: " + tab.url);
34 | if (tab.url && isDomainEnabled(tab.url)) {
35 | console.log("Auto reader enabled for " + tab.url);
36 | setEnabledButtonState(button, tab);
37 | if (!uu.isUrlHomePage(tab.url)) {
38 | redirectToReaderView(tab);
39 | }
40 | }
41 | else {
42 | setDisabledButtonState(button, tab);
43 | }
44 | });
45 |
46 | // Send state for current tab when the button is clicked
47 | function openPanel(btnState) {
48 | panel.port.emit("panelOpened", {
49 | enabled: isDomainEnabled(tabs.activeTab.url),
50 | domain: uu.domainFromUrl(tabs.activeTab.url)
51 | });
52 | panel.show({
53 | position: button
54 | });
55 | }
56 |
57 | // Save the preference when the enable/disable button is clicked
58 | panel.port.on("domainChange", function(data) {
59 | console.log("change event received");
60 | console.log(data);
61 | if (data.enabled) {
62 | addDomain(data.domain);
63 | setEnabledButtonState(button, tabs.activeTab);
64 | if (!uu.isUrlHomePage(tabs.activeTab.url)) {
65 | redirectToReaderView(tabs.activeTab);
66 | }
67 | }
68 | else {
69 | removeDomain(data.domain);
70 | setDisabledButtonState(button, tabs.activeTab);
71 | }
72 | });
73 |
74 | function redirectToReaderView(tab) {
75 | console.log("Tab past: " + setToString(tabPast));
76 | var origUrl = tab.url;
77 |
78 | // Detect user exiting reader view temporarily, don't do a redirect
79 | if (!origUrl.startsWith(ABOUT_READER_PREFIX) &&
80 | tabPast.has(ABOUT_READER_PREFIX + origUrl)) {
81 | console.log("Was already in Reader View, exit");
82 | tabPast.delete(ABOUT_READER_PREFIX + origUrl);
83 | }
84 | // Already in reader view or another about page
85 | else if (origUrl.startsWith("about:")) {
86 | // do nothing
87 | }
88 | // Redirect to reader view
89 | else {
90 | var newUrl = ABOUT_READER_PREFIX + tab.url;
91 | console.log("Setting new url to " + newUrl);
92 | tab.url = newUrl;
93 | }
94 |
95 | // Store the previous urls in order to detect reader view "exits"
96 | tabPast.add(tab.url);
97 |
98 | // Housekeeping to prevent unbounded memory use
99 | if (tabPast.size > 50) {
100 | // TODO use an LRU cache instead
101 | console.log("tabPast size is " + tabPast.size + ". Clearing entries.");
102 | freeCache(tabPast, 5);
103 | }
104 | console.log("New tab past: " + setToString(tabPast));
105 | }
106 |
107 | function freeCache(set, numToRemove) {
108 | // remove least recently inserted entries
109 | // TODO use an LRU cache instead
110 | var iter = tabPast.values()
111 | for (var i = 0; i < numToRemove; i++) {
112 | var val = iter.next().value;
113 | set.delete(val);
114 | }
115 | }
116 |
117 | // No built-in pretty printing for Set :(
118 | function setToString(s) {
119 | return JSON.stringify([...tabPast]);
120 | }
121 |
122 |
123 | // Add a checkmark badge to indicate the reader view is enabled
124 | function setEnabledButtonState(button, tab, panel) {
125 | button.state(tab, {
126 | "badge" : "✓",
127 | "badgeColor" : "green"
128 | });
129 | }
130 |
131 | // Clear the badge
132 | function setDisabledButtonState(button, tab) {
133 | button.state(tab, {
134 | badge : "",
135 | badgeColor : ""
136 | });
137 | }
138 |
139 |
140 |
141 | // Check storage for the domain
142 | function isDomainEnabled(url) {
143 | initStorage();
144 | var domain = uu.domainFromUrl(url);
145 | return ss.storage.domains.indexOf(domain) != -1;
146 | }
147 |
148 | // Add a domain to storage
149 | function addDomain(domain) {
150 | initStorage();
151 | console.log("Adding domain " + domain);
152 | ss.storage.domains.push(domain);
153 | console.log("Stored domains:");
154 | console.log(ss.storage.domains);
155 | }
156 |
157 | // Remove a domain from storage
158 | function removeDomain(domain) {
159 | initStorage();
160 | console.log("Removing domain " + domain);
161 | var i = ss.storage.domains.indexOf(domain);
162 | delete ss.storage.domains[i];
163 | console.log(ss.storage.domains);
164 | }
165 |
166 | // Initialize storage if not already done so.
167 | function initStorage() {
168 | if (!ss.storage.domains) {
169 | ss.storage.domains = [];
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/auto-reader-view/background.js:
--------------------------------------------------------------------------------
1 | // Track previous tab URLs
2 | var tabPast = new Set();
3 |
4 | /*
5 | * Updates the browserAction icon.
6 | */
7 | function updateIcon(enabled) {
8 | console.log("Updating icon");
9 | if (enabled) {
10 | browser.browserAction.setBadgeText({text: "✓"});
11 | browser.browserAction.setBadgeBackgroundColor({color: "green"});
12 | }
13 | else {
14 | browser.browserAction.setBadgeText({text: ""});
15 | browser.browserAction.setBadgeBackgroundColor({color: null});
16 | }
17 | }
18 |
19 | function handleMessage(msg) {
20 | console.log("received message", msg);
21 | if (msg.type == 'domainState') {
22 | // Sent by the panel when it loads to determine the current domain and
23 | // its state.
24 | return browser.tabs.query({active: true, windowId: browser.windows.WINDOW_ID_CURRENT})
25 | .then(tabs => browser.tabs.get(tabs[0].id))
26 | .then(tab => {
27 | if (isNonReaderAboutPage(tab.url)) {
28 | return {"valid": false};
29 | }
30 | var domain = domainFromUrl(tab.url);
31 | console.log(`Checking enabled status for ${domain}`);
32 | return isDomainEnabled(domain).then(enabled => {
33 | updateIcon(enabled);
34 | return {"enabled": enabled, "domain": domain, "valid": true};
35 | });
36 | });
37 | }
38 | else if (msg.type == 'domainChange') {
39 | // Sent by the panel to indicate the new state of the given domain.
40 | browser.tabs.query({active: true, windowId: browser.windows.WINDOW_ID_CURRENT})
41 | .then(tabs => browser.tabs.get(tabs[0].id))
42 | .then(tab => {
43 | if (msg.enabled) {
44 | addDomain(msg.domain);
45 | tryToggleReaderView(tab);
46 | }
47 | else {
48 | removeDomain(msg.domain);
49 | }
50 | updateIcon(msg.enabled);
51 | });
52 | }
53 | }
54 |
55 | function handleTabSwitch(activeInfo) {
56 | console.log(`Tab ${activeInfo.tabId} was activated`);
57 | if (activeInfo.tabId) {
58 | return browser.tabs.get(activeInfo.tabId)
59 | .then(tab => {
60 | var domain = domainFromUrl(tab.url);
61 | console.log(`Checking enabled status for ${domain}`);
62 | return isDomainEnabled(domain).then(enabled => {
63 | updateIcon(enabled);
64 | return enabled;
65 | });
66 | });
67 | }
68 | }
69 |
70 |
71 | // Check storage for the domain
72 | // @return {Promise}
73 | function isDomainEnabled(domain) {
74 | return getStorage().get("enabledDomains").then(result => {
75 | console.log("Enabled domains are:", result.enabledDomains);
76 | var isEnabled = result.enabledDomains.indexOf(domain) >= 0;
77 | console.log(`${domain} enabled: ${isEnabled}`);
78 | return isEnabled;
79 | });
80 | }
81 |
82 | // Add a domain to storage
83 | function addDomain(domain) {
84 | console.log("Adding domain " + domain);
85 | getStorage().get("enabledDomains").then(domains => {
86 | console.log("retrieved domains", domains);
87 | if (domains.enabledDomains.indexOf(domain) === -1) {
88 | domains.enabledDomains.push(domain);
89 | }
90 | getStorage().set({"enabledDomains": domains.enabledDomains});
91 | console.log("Stored domains:", domains.enabledDomains);
92 | });
93 | }
94 |
95 | // Remove a domain from storage
96 | function removeDomain(domain) {
97 | console.log("Removing domain " + domain);
98 | getStorage().get("enabledDomains").then(result => {
99 | var i = result.enabledDomains.indexOf(domain);
100 | delete result.enabledDomains[i];
101 | result.enabledDomains = result.enabledDomains.filter(Boolean);
102 | getStorage().set({"enabledDomains": result.enabledDomains});
103 | console.log("Updated domains:");
104 | console.log(result.enabledDomains);
105 | });
106 | }
107 |
108 | function saveDomainsList(domainsList) {
109 | console.log("Saving domains list", domainsList);
110 | getStorage().set({"enabledDomains": domainsList});
111 | console.log("Saved domains list")
112 | }
113 |
114 | function getStorage() {
115 | return browser.storage.local;
116 | }
117 |
118 | // Initialize storage if not already done so.
119 | // @return {Promise}
120 | function initStorage() {
121 | var store = getStorage();
122 | return store.get("enabledDomains").then(domains => {
123 | if (isObjectEmpty(domains)) {
124 | console.log("Initializing storage");
125 | store.set({"enabledDomains": new Array()});
126 | }
127 | else {
128 | console.log("Storage already intialized");
129 | }
130 | });
131 | }
132 |
133 | function isObjectEmpty(obj) {
134 | return Object.keys(obj).length === 0;
135 | }
136 |
137 | // Extract domain from a url
138 | function domainFromUrl(url) {
139 | if (url.startsWith("about:reader?")) {
140 | url = decodeURIComponent(url.substr("about:reader?url=".length));
141 | }
142 | var r = /:\/\/(.[^/]+)/;
143 | var matches = url.match(r);
144 | if (matches && matches.length >= 2) {
145 | return matches[1];
146 | }
147 | return null;
148 | }
149 |
150 | function handleTabUpdate(tabId, changeInfo, tab) {
151 | // console.log(`Handling tab update for tab ${tabId} ${tab.url}, status: ${changeInfo.status}`, changeInfo);
152 | if ((changeInfo && changeInfo.isArticle) ||
153 | (tab && tab.isArticle)) {
154 | var domain = domainFromUrl(tab.url);
155 | console.log(`Domain for updated tab ${tab.id} is ${domain}`);
156 | isDomainEnabled(domain).then(isEnabled => {
157 | updateIcon(isEnabled);
158 | if(isEnabled) {
159 | console.log(`Auto reader enabled for ${domain}`);
160 | tryToggleReaderView(tab);
161 | }
162 | })
163 | }
164 | }
165 |
166 | function tryToggleReaderView(tab) {
167 | // Detect user exiting reader view temporarily, don't toggle back
168 | if (!tab.isInReaderMode && tabPast.has(normalToReaderUrl(tab.url))) {
169 | console.log("Was previously in Reader View");
170 | tabPast.delete(normalToReaderUrl(tab.url));
171 | }
172 | // Already in reader view
173 | else if (tab.isInReaderMode) {
174 | // do nothing
175 | }
176 | else {
177 | console.log(`Toggling reader mode for ${tab.id} ${tab.url} (isArticle? ${tab.isArticle})`);
178 | browser.tabs.toggleReaderMode(tab.id).catch(onError);
179 | }
180 |
181 | // Store the previous urls in order to detect reader view "exits"
182 | tabPast.add(normalToReaderUrl(tab.url));
183 |
184 | // Housekeeping to prevent unbounded memory use
185 | if (tabPast.size > 50) {
186 | // TODO use an LRU cache instead
187 | console.log("tabPast size is " + tabPast.size + ". Clearing entries.");
188 | freeCache(tabPast, 5);
189 | }
190 | console.log("New tab past: " + setToString(tabPast));
191 | }
192 |
193 | function freeCache(set, numToRemove) {
194 | // remove least recently inserted entries
195 | // TODO use an LRU cache instead
196 | var iter = tabPast.values()
197 | for (var i = 0; i < numToRemove; i++) {
198 | var val = iter.next().value;
199 | set.delete(val);
200 | }
201 | }
202 |
203 | // No built-in pretty printing for Set :(
204 | function setToString(s) {
205 | return JSON.stringify([...tabPast]);
206 | }
207 |
208 | function normalToReaderUrl(url) {
209 | if (!url.startsWith("about:reader")) {
210 | url = "about:reader?url=" + encodeURIComponent(url);
211 | }
212 | return url;
213 | }
214 |
215 | function readerToNormalUrl(readerUrl) {
216 | return decodeURIComponent(readerUrl.substr("about:reader?url=".length));
217 | }
218 |
219 | function isNonReaderAboutPage(url) {
220 | return url.startsWith("about:") && !url.startsWith("about:reader");
221 | }
222 |
223 | function isUrlHomePage(url) {
224 | var domain = domainFromUrl(url);
225 | var endOfDomainPartIdx = url.indexOf(domain) + domain.length;
226 | var pathPart = url.substr(endOfDomainPartIdx);
227 |
228 | return pathPart.length < 2; // 2 in case of trailing '/'
229 | }
230 |
231 | function onError(err) {
232 | console.log(err);
233 | }
234 |
235 | console.log("background script started");
236 | console.log("add on click");
237 |
238 | updateIcon();
239 | initStorage().then(() => {
240 | // Listen to messages sent from the panel
241 | browser.runtime.onMessage.addListener(handleMessage);
242 |
243 | // Listen to tab URL changes
244 | browser.tabs.onUpdated.addListener(handleTabUpdate);
245 |
246 | // listen to tab switching
247 | browser.tabs.onActivated.addListener(handleTabSwitch);
248 |
249 | // listen for window switching
250 | browser.windows.onFocusChanged.addListener(handleTabSwitch);
251 | });
252 |
--------------------------------------------------------------------------------