Skip to content

Commit ed36a2c

Browse files
committed
First steps on Manifest v3 conversion #69
Enabled Manifest v3 for Firefox (not SW-based) Switched code to modules Switched to using webextension-polyfill
1 parent f37e702 commit ed36a2c

11 files changed

+1488
-236
lines changed

source/js/background.js

+32-12
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,43 @@
1-
/* global onStorageChange, loadFromStorage, getCountersFromHTTP, openOurTab, onMessage, onExtensionUpdate, onNotificationClick, startupInject */
2-
// synchronize settings
3-
chrome.storage.onChanged.addListener(onStorageChange);
4-
loadFromStorage();
1+
import "./lib/browser-polyfill.js";
2+
import { getCountersFromHTTP, openOurTab, onMessage, onExtensionUpdate, onNotificationClick, startupInject, onAlarm } from "./functions.js";
3+
import { onStorageChange, loadFromStorage } from "./storage.js";
4+
import { toggleContentMenus, onContextMenuClick } from "./menu.js";
55

6-
// run first counter refresh
7-
getCountersFromHTTP();
6+
// synchronize settings
7+
browser.storage.onChanged.addListener(onStorageChange);
88

99
// initialize button click event
10-
chrome.browserAction.onClicked.addListener(function(tab) {
10+
browser.action.onClicked.addListener(function(tab) {
1111
openOurTab(tab.windowId);
1212
});
1313

1414
// listen to injected scripts
15-
chrome.runtime.onMessage.addListener(onMessage);
15+
browser.runtime.onMessage.addListener(onMessage);
1616

1717
// alert about new features, if any
18-
chrome.runtime.onInstalled.addListener(onExtensionUpdate);
18+
browser.runtime.onInstalled.addListener(onExtensionUpdate);
19+
20+
// react to notification clicks
21+
browser.notifications.onClicked.addListener(onNotificationClick);
22+
23+
// react to notification timeout alarm
24+
browser.alarms.onAlarm.addListener(onAlarm);
1925

20-
chrome.notifications.onClicked.addListener(onNotificationClick);
26+
// react to context menu clicks
27+
browser.contextMenus.onClicked.addListener(onContextMenuClick);
2128

22-
// initially inject content scripts
23-
startupInject();
29+
// do some startup initialization
30+
// this is reentrant (calling it multiple times isn't harmful)
31+
async function onStartup() {
32+
// Update local settings from sync storage
33+
await loadFromStorage();
34+
// turn on context menus
35+
await toggleContentMenus(localStorage.context_menu);
36+
// initially inject content scripts
37+
await startupInject();
38+
// run first counter refresh
39+
getCountersFromHTTP();
40+
}
41+
// On first install / subsequent updates, onStartup isn't guaranteed to fire
42+
browser.runtime.onStartup.addListener(onStartup);
43+
browser.runtime.onInstalled.addListener(onStartup);

source/js/bookmark-cancel-script.js

-7
This file was deleted.

source/js/functions.js

+95-73
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
1-
/* globals saveToStorage, toggleContentMenus */
1+
import "./lib/browser-polyfill.js";
2+
import { saveToStorage } from "./storage.js";
3+
import { toggleContentMenus } from "./menu.js";
4+
25
const BADGE_BACKGROUND_COLOR = '#d51b15';
3-
const BADGE_TEXT_COLOR = '#ffffff'
6+
const BADGE_TEXT_COLOR = '#ffffff';
47
const OPTIONS_VERSION = 3; // Increment when there are new options
58

6-
let refreshTimeout;
79
let last_unread_count = 0;
8-
let notificationTimeout;
910
let retryCount = 0;
1011
let lastError = "";
1112

1213
function getBrowserName() {
13-
if (typeof browser !== 'undefined') {
14+
// Credit to https://github.com/mozilla/webextension-polyfill/issues/55#issuecomment-329676034 since polyfill makes old "check if browser is defined impossible"
15+
if (browser.runtime.id === 'theoldreader@knyar') {
1416
return 'Mozilla';
1517
} else {
1618
return 'Chrome';
1719
}
1820
}
1921

20-
function showNotification(title, body, id = "theoldreader") {
22+
async function showNotification(title, body, id = "theoldreader") {
2123
if (id != "theoldreader-newOptions" && localStorage.show_notifications != 'yes') {
2224
return;
2325
}
@@ -35,80 +37,99 @@ function showNotification(title, body, id = "theoldreader") {
3537
options.isClickable = true;
3638
}
3739

38-
chrome.notifications.create(id, options);
40+
browser.notifications.create(id, options);
3941

4042
if (id == "theoldreader" && localStorage.notification_timeout > 0) {
41-
window.clearTimeout(notificationTimeout); // If updating a notification, reset timeout
42-
notificationTimeout = window.setTimeout(
43-
function() { chrome.notifications.clear("theoldreader"); },
44-
localStorage.notification_timeout * 1000
43+
let timeout = localStorage.notification_timeout;
44+
45+
// Chrome does not allow alarms shorter than 30 seconds
46+
if (getBrowserName() === "Chrome" && timeout < 30) {
47+
timeout = 30;
48+
}
49+
50+
await browser.alarms.clear("notification-clear");
51+
browser.alarms.create(
52+
"notification-clear",
53+
{
54+
delayInMinutes: timeout / 60
55+
}
4556
);
4657
}
4758
}
4859

49-
/* exported onNotificationClick */
50-
function onNotificationClick(id) {
60+
// Listener for browser.alarms.onAlarm
61+
export function onAlarm(alarm) {
62+
switch (alarm.name) {
63+
case "notification-clear":
64+
browser.notifications.clear("theoldreader");
65+
break;
66+
case "server-refresh":
67+
getCountersFromHTTP();
68+
break;
69+
}
70+
}
71+
72+
export async function onNotificationClick(id) {
5173
switch (id) {
5274
case "theoldreader":
5375
openOurTab();
5476
break;
5577
case "theoldreader-newOptions":
56-
chrome.runtime.openOptionsPage();
78+
await browser.runtime.openOptionsPage();
5779
break;
5880
}
59-
chrome.notifications.clear(id);
81+
await browser.notifications.clear(id);
6082
}
6183

62-
function baseUrl() {
84+
export function baseUrl() {
6385
return (localStorage.force_http == 'yes' ? 'http://theoldreader.com/' : 'https://theoldreader.com/');
6486
}
6587

66-
function findOurTab(callback, windowId) {
67-
chrome.tabs.query(
88+
async function findOurTab(windowId) {
89+
const tabs = await chrome.tabs.query(
6890
{
6991
url: "*://theoldreader.com/*",
7092
windowId: windowId
71-
},
72-
function(tabs) {
73-
callback(tabs[0]);
7493
}
7594
);
95+
96+
return tabs[0];
7697
}
7798

78-
function openOurTab(windowId) {
79-
findOurTab(function(tab) {
80-
if (tab) {
81-
chrome.tabs.update(tab.id, {active: true});
82-
} else {
83-
let url = baseUrl();
84-
const pinned = (localStorage.prefer_pinned_tab == 'yes' ? true : false);
85-
if (localStorage.click_page == 'all_items') { url += 'posts/all'; }
86-
chrome.tabs.create({url: url, pinned: pinned, windowId: windowId});
87-
}
88-
}, windowId);
99+
export async function openOurTab(windowId) {
100+
const maybeTab = await findOurTab(windowId);
101+
102+
if (maybeTab) {
103+
browser.tabs.update(maybeTab.id, {active: true});
104+
} else {
105+
let url = baseUrl();
106+
const pinned = (localStorage.prefer_pinned_tab == 'yes' ? true : false);
107+
if (localStorage.click_page == 'all_items') { url += 'posts/all'; }
108+
browser.tabs.create({url: url, pinned: pinned, windowId: windowId});
109+
}
89110
}
90111

91112
function reportError(details) {
92113
console.warn(details.errorText);
93114

94-
chrome.browserAction.setIcon({
115+
browser.action.setIcon({
95116
path: {
96117
19: 'img/icon-inactive.png',
97118
38: 'img/icon-inactive-scale2.png'
98119
}
99120
});
100121

101122
if (details.loggedOut) {
102-
chrome.browserAction.setBadgeText({text: '!'});
103-
chrome.browserAction.setTitle({title: chrome.i18n.getMessage('button_title_loggedOut')});
123+
browser.action.setBadgeText({text: '!'});
124+
browser.action.setTitle({title: browser.i18n.getMessage('button_title_loggedOut')});
104125
if (lastError != details.errorText) { // Suppress repeat notifications about the same error
105-
showNotification(chrome.i18n.getMessage('notification_loggedOut_title'), chrome.i18n.getMessage('notification_loggedOut_body'));
126+
showNotification(browser.i18n.getMessage('notification_loggedOut_title'), browser.i18n.getMessage('notification_loggedOut_body'));
106127
}
107128
} else {
108-
chrome.browserAction.setBadgeText({text: ''});
109-
chrome.browserAction.setTitle({title: chrome.i18n.getMessage('button_title_fetchError')});
129+
browser.action.setBadgeText({text: ''});
130+
browser.action.setTitle({title: browser.i18n.getMessage('button_title_fetchError')});
110131
if (lastError != details.errorText) { // Suppress repeat notifications about the same error
111-
showNotification(chrome.i18n.getMessage('notification_fetchError_title'), chrome.i18n.getMessage('notification_fetchError_body') + details.errorText);
132+
showNotification(browser.i18n.getMessage('notification_fetchError_title'), browser.i18n.getMessage('notification_fetchError_body') + details.errorText);
112133
}
113134
}
114135

@@ -128,29 +149,29 @@ function updateIcon(count) {
128149
} else {
129150
count = countInt.toString();
130151
}
131-
chrome.browserAction.setIcon({
152+
browser.action.setIcon({
132153
path: {
133154
19: 'img/icon-active.png',
134155
38: 'img/icon-active-scale2.png'
135156
}
136157
});
137-
chrome.browserAction.setBadgeBackgroundColor({color: BADGE_BACKGROUND_COLOR});
138-
if (typeof(chrome.browserAction.setBadgeTextColor) === "function") {
139-
chrome.browserAction.setBadgeTextColor({color: BADGE_TEXT_COLOR});
140-
}
141-
chrome.browserAction.setBadgeText({text: count});
142-
chrome.browserAction.setTitle({title: 'The Old Reader' + title_suffix});
158+
159+
browser.action.setBadgeBackgroundColor({color: BADGE_BACKGROUND_COLOR});
160+
// Not supported in Firefox
161+
browser.action.setBadgeTextColor?.({color: BADGE_TEXT_COLOR});
162+
browser.action.setBadgeText({text: count});
163+
browser.action.setTitle({title: 'The Old Reader' + title_suffix});
143164

144165
lastError = ""; // Clear last remembered error
145166

146167
if (countInt > last_unread_count) {
147168
const text = 'You have ' + countInt + ' unread post' + (countInt > 1 ? 's' : '') + '.';
148-
showNotification(chrome.i18n.getMessage('notification_newPosts_title'), text);
169+
showNotification(browser.i18n.getMessage('notification_newPosts_title'), text);
149170
}
150171
last_unread_count = countInt;
151172
}
152173

153-
function getCountersFromHTTP() {
174+
export function getCountersFromHTTP() {
154175
// If request times out or if we get unexpected output, report error and reschedule
155176
function refreshFailed(details) {
156177
window.clearTimeout(requestTimeout);
@@ -210,17 +231,28 @@ function getCountersFromHTTP() {
210231
}
211232

212233
function scheduleRefresh() {
213-
let interval = (localStorage.refresh_interval || 15) * 60 * 1000;
214-
window.clearTimeout(refreshTimeout);
234+
let intervalMinutes = Number(localStorage.refresh_interval) || 15;
235+
215236
if (retryCount) { // There was an error
216-
interval = Math.min(interval, 5 * 1000 * Math.pow(2, retryCount - 1));
217-
// 0:05 -> 0:10 -> 0:20 -> 0:40 -> 1:20 -> 2:40 -> 5:20 -> ...
237+
intervalMinutes = Math.min(intervalMinutes, 0.5 * Math.pow(2, retryCount - 1));
238+
// 0.5m -> 1m -> 2m -> 4m -> 8m -> 16m -> ...
218239
}
219-
refreshTimeout = window.setTimeout(getCountersFromHTTP, interval);
240+
241+
console.debug(`Scheduled refresh for ${intervalMinutes} minutes`);
242+
243+
browser.alarms.clear("server-refresh");
244+
245+
browser.alarms.create(
246+
"server-refresh",
247+
{
248+
delayInMinutes: intervalMinutes
249+
}
250+
);
220251
}
221252

222-
/* exported onMessage */
223-
function onMessage(request, sender, callback) {
253+
export function onMessage(request, sender, callback) {
254+
console.debug(request);
255+
224256
if (typeof request.count !== 'undefined') {
225257
setCountFromObserver(request.count);
226258
}
@@ -232,45 +264,35 @@ function onMessage(request, sender, callback) {
232264
toggleContentMenus(localStorage.context_menu);
233265
}
234266
if (request.openInBackground) {
235-
chrome.tabs.create({
267+
browser.tabs.create({
236268
url: request.url,
237269
active: false
238270
});
239271
}
240-
if (request.type == 'close-this-tab' && typeof sender.tab !== 'undefined') {
241-
chrome.tabs.remove(sender.tab.id);
242-
}
243272
}
244273

245274
function setCountFromObserver(count) {
246275
updateIcon(count);
247276
scheduleRefresh();
248277
}
249278

250-
/* exported onExtensionUpdate */
251-
function onExtensionUpdate(details) {
279+
export function onExtensionUpdate(details) {
252280
if (details.reason == "update" && localStorage.options_version < OPTIONS_VERSION) {
253281
showNotification(
254-
chrome.i18n.getMessage('notification_newOptions_title'),
255-
chrome.i18n.getMessage('notification_newOptions_body'),
282+
browser.i18n.getMessage('notification_newOptions_title'),
283+
browser.i18n.getMessage('notification_newOptions_body'),
256284
"theoldreader-newOptions"
257285
);
258286
}
259287
localStorage.options_version = OPTIONS_VERSION;
260288
saveToStorage();
261289
}
262290

263-
/* exported startupInject */
264-
function startupInject() {
291+
export async function startupInject() {
265292
// At this point, all old content scripts, if any, cannot communicate with the extension anymore
266293
// Old instances of content scripts have a "kill-switch" to terminate their event listeners
267294
// Here we inject new instances in existing tabs
268-
chrome.tabs.query(
269-
{url: "*://theoldreader.com/*"},
270-
function(tabs) {
271-
for (let tab of tabs) {
272-
chrome.tabs.executeScript(tab.id, {file: "js/observer.js"});
273-
}
274-
}
275-
);
295+
for await (const tab of browser.tabs.query({url: "*://theoldreader.com/*"})) {
296+
await browser.scripting.executeScript({files: ["js/observer.js"], target: { tabId: tab.id }});
297+
}
276298
}

0 commit comments

Comments
 (0)