Skip to content

Commit 0a54878

Browse files
authored
Merge pull request #38 from leor-gh/master
Migrate the extension from MV2 to MV3.
2 parents 35f9fce + 6438eda commit 0a54878

File tree

5 files changed

+172
-35
lines changed

5 files changed

+172
-35
lines changed

background.js

+52-21
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
1+
var localStorage = {};
22

33
function setProxyIcon() {
44

@@ -10,35 +10,36 @@ function setProxyIcon() {
1010
{'incognito': false},
1111
function(config) {
1212
if (config["value"]["mode"] == "system") {
13-
chrome.browserAction.setIcon(icon);
13+
chrome.action.setIcon(icon);
1414
} else if (config["value"]["mode"] == "direct") {
15-
chrome.browserAction.setIcon(icon);
15+
chrome.action.setIcon(icon);
1616
} else {
1717
icon["path"] = "images/on.png";
18-
chrome.browserAction.setIcon(icon);
18+
chrome.action.setIcon(icon);
1919
}
2020
}
2121
);
2222
}
2323

2424
function gotoPage(url) {
2525

26-
var fulurl = chrome.extension.getURL(url);
27-
chrome.tabs.getAllInWindow(undefined, function(tabs) {
28-
for (var i in tabs) {
29-
tab = tabs[i];
30-
if (tab.url == fulurl) {
31-
chrome.tabs.update(tab.id, { selected: true });
32-
return;
33-
}
26+
var fulurl = chrome.runtime.getURL(url);
27+
chrome.tabs.query({ url: fulurl }, function(tabs) {
28+
if (tabs.length) {
29+
chrome.tabs.update(tabs[0].id, { selected: true });
30+
chrome.windows.update(tabs[0].windowId, { focused: true });
31+
return;
3432
}
35-
chrome.tabs.getSelected(null, function(tab) {
36-
chrome.tabs.create({url: url,index: tab.index + 1});
37-
});
33+
chrome.tabs.create({url: url, active: true});
3834
});
3935
}
4036

41-
function callbackFn(details) {
37+
async function callbackFn(details, cb) {
38+
console.log("%s onAuthRequiredCB", new Date(Date.now()).toISOString());
39+
40+
if (localStorage.proxySetting == undefined)
41+
await getLocalStorage();
42+
4243
var proxySetting = JSON.parse(localStorage.proxySetting);
4344

4445
if (proxySetting){
@@ -49,15 +50,29 @@ function callbackFn(details) {
4950

5051
if (proxySetting['auth']['user'] == '' &&
5152
proxySetting['auth']['pass'] == '')
52-
return {};
53+
cb({});
5354

54-
return { authCredentials: {username: username, password: password} };
55+
cb({ authCredentials: {username: username, password: password} });
5556
}
5657

5758
chrome.webRequest.onAuthRequired.addListener(
5859
callbackFn,
5960
{urls: ["<all_urls>"]},
60-
['blocking'] );
61+
['asyncBlocking'] );
62+
63+
chrome.runtime.onMessage.addListener(async function(msg, sender, res) {
64+
if (msg.action != "authUpdate")
65+
return;
66+
67+
console.log("%s onMessage listener", new Date(Date.now()).toISOString());
68+
if (localStorage.proxySetting == undefined)
69+
await getLocalStorage();
70+
71+
var proxySetting = JSON.parse(localStorage.proxySetting);
72+
proxySetting['auth'] = msg.data;
73+
localStorage.proxySetting = JSON.stringify(proxySetting);
74+
chrome.storage.local.set(localStorage);
75+
});
6176

6277
var proxySetting = {
6378
'pac_script_url' : {'http': '', 'https': '', 'file' : ''},
@@ -96,9 +111,13 @@ function getBypass() {
96111
}
97112
}
98113

99-
chrome.runtime.onInstalled.addListener(function(details){
100-
if(details.reason == "install") {
114+
chrome.runtime.onInstalled.addListener(async details => {
115+
var store = await getLocalStorage();
116+
if (store.proxySetting == undefined) {
101117
localStorage.proxySetting = JSON.stringify(proxySetting);
118+
await chrome.storage.local.set(localStorage);
119+
}
120+
if(details.reason == "install") {
102121
gotoPage('options.html');
103122
}
104123
/*
@@ -108,6 +127,17 @@ chrome.runtime.onInstalled.addListener(function(details){
108127
*/
109128
});
110129

130+
function getLocalStorage() {
131+
console.trace("%s getLocalStorage", new Date(Date.now()).toISOString());
132+
return chrome.storage.local.get(null).then(result => {
133+
console.log("%s getLocalStorage: result = %O", new Date(Date.now()).toISOString(), result);
134+
if (result.proxySetting != undefined) {
135+
Object.assign(localStorage, result);
136+
}
137+
return result;
138+
});
139+
}
140+
111141

112142
chrome.commands.onCommand.addListener(function(command) {
113143
if (command == 'open-option')
@@ -126,6 +156,7 @@ chrome.proxy.onProxyError.addListener(function(details) {
126156
console.log("details: ", details.details)
127157
});
128158

159+
console.log("%s service worker initialized", new Date(Date.now()).toISOString());
129160
setProxyIcon();
130161

131162
// sync bypass list from github.com

doc/migration

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
Migration from Manifest v2 (MV2) to v3 (MV3)
2+
3+
In MV2, the scripts included from options.html and popup.html and the
4+
background script all share the same persistent storage,
5+
window.localStorage, which is initialized when the JS pages are
6+
loaded. Changes to the localStorage object are persisted automatically.
7+
8+
In MV3, the background script is migrated to service worker, and it
9+
does not share the localStorage with the other scripts. Instead,
10+
persistent data storage is managed separately by the chrome.storage
11+
API, which must be loaded and saved explicitly via the async API
12+
calls. Additionally, the service worker lifecycle is managed by the
13+
browser with the browser terminating the instance after some amount of
14+
inactivity, and re-creating when needed.
15+
16+
17+
Extension storage design
18+
19+
In order to make the migration simple, for understanding and reviewing
20+
the changes, the persistent storage strategy is chosen for minimal
21+
changes from the existing design.
22+
23+
In MV2, the extension's main logic and data storage is handled by
24+
options.html and popup.html, and the background script only deals with
25+
supplying the Proxy Authorization credentials when asked by the
26+
browser. In MV3, this is still the case, and so the service worker
27+
must be notified of changes to the credential information that is
28+
managed by the user via options.html. This credential information
29+
must be persisted, and used by the service worker when the browser
30+
needs to access a proxy that requires authorization.
31+
32+
As a matter of fact, this information is all that is needed to be
33+
managed by the service worker with the chrome.storage API. However,
34+
since this information is part of the proxySetting object, the service
35+
worker will save the whole object. The only data that is relevant for
36+
actual usage, thus, is the 'auth' property.
37+
38+
When the extension is first installed, the background.js in MV2
39+
creates a proxySetting object from its template and store in the
40+
shared localStorage. The options.js and popup.js use this initialized
41+
object for their operations. In MV3, the service worker still
42+
initializes this object from its template, but this object is not
43+
shared automatically with options.js and popup.js. Instead, it
44+
persists this template into chrome.storage. When options.js first
45+
runs, it retrieves this initial object from chrome.storage and loads
46+
it into the shared localStorage. This is the only time where the
47+
non-service workers JS scripts interacts with the chrome.storage API.
48+
This is another reason of persisting the whole proxySetting object into
49+
chrome.storage.
50+
51+
52+
Storage usage scenario
53+
54+
When the extension is first installed:
55+
- both chrome.storage and localStorage are empty
56+
- service worker initializes, creates, and persists the default
57+
proxySetting into chrome.storage
58+
- options.js starts, retrieves the initial proxySetting from
59+
chrome.storage, and populates to localStorage
60+
- options.js and other JS scripts use localStorage as normal
61+
62+
When the extension restarts (due to browser restarts) or reloads
63+
(extension update):
64+
- both chrome.storage and localStorage are populated. No special
65+
handling needed
66+
67+
During normal operation:
68+
- options.js notifies the service worker of any changes to the
69+
proxy authorization credentials by message passing. Service worker
70+
persists this into chrome.storage
71+
- When asked by the browser, service worker provides the
72+
credentials to access the proxy in use
73+
- Service worker comes and goes, and credential information is
74+
lazy-loaded only when needed
75+
76+
After migration of installed MV2 extension to MV3, on the first run:
77+
- localStorage is populated with valid data
78+
- chrome.storage is empty, and the service worker populates the
79+
template proxySetting into chrome.storage, but this does not have
80+
valid proxy authorization credentials
81+
- proxy authorization will fail, and the browser presents a popup
82+
dialog to prompt user to enter proxy credentials
83+
- the option page still displays the correct credentials
84+
- this migration issue can be fixed by deleting and re-entering the
85+
credential information (both the username and password) in the
86+
options page to force the service worker receive and persist the
87+
correct data for its use

javascripts/options.js

+24-5
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ function sysProxy() {
340340
{value: config, scope: 'regular'},
341341
function() {});
342342

343-
chrome.browserAction.setIcon(icon);
343+
chrome.action.setIcon(icon);
344344
}
345345

346346
/**
@@ -363,6 +363,10 @@ function save() {
363363
proxySetting['bypasslist'] = $('#bypasslist').val() || "";
364364
proxySetting['proxy_rule'] = $('#proxy-rule').val() || "";
365365
//proxySetting['rules_mode'] = $('#rules-mode').val() || "";
366+
367+
var authInfo = {};
368+
authInfo.user = proxySetting['auth']['user'];
369+
authInfo.pass = proxySetting['auth']['pass'];
366370
proxySetting['auth']['user'] = $('#username').val() || "";
367371
proxySetting['auth']['pass'] = $('#password').val() || "";
368372

@@ -405,6 +409,11 @@ try {
405409
reloadProxy();
406410
loadProxyData();
407411

412+
if (authInfo['user'] != proxySetting['auth']['user'] ||
413+
authInfo['pass'] != proxySetting['auth']['pass']) {
414+
chrome.runtime.sendMessage({ action: 'authUpdate', data: proxySetting['auth'] });
415+
}
416+
408417
// sync settings to google cloud
409418
//chrome.storage.sync.set({'proxySetting' : settings}, function() {});
410419
}
@@ -549,9 +558,19 @@ function readSingleFile(e) {
549558
reader.readAsText(file);
550559
}
551560

552-
if (!localStorage.firstime)
561+
if (!localStorage.firstime) {
553562
loadOldInfo();
554-
else
555-
loadProxyData();
556563

557-
getProxyInfo();
564+
var clone = obj => JSON.parse(JSON.stringify(obj));
565+
chrome.storage.local.get(null).then((result) => {
566+
localStorage.chinaList = result.chinaList;
567+
localStorage.proxySetting = result.proxySetting;
568+
console.log("Setting localStorage from service worker storage");
569+
console.log("%s localStorage:", new Date(Date.now()).toISOString(), clone(localStorage));
570+
571+
getProxyInfo();
572+
});
573+
} else {
574+
loadProxyData();
575+
getProxyInfo();
576+
}

javascripts/popup.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ function iconSet(str) {
139139
if (str == 'off') {
140140
icon['path'] = 'images/off.png';
141141
}
142-
chrome.browserAction.setIcon(icon);
142+
chrome.action.setIcon(icon);
143143
}
144144

145145
function proxySelected(str) {

manifest.json

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"version": "1.4.6",
3-
"manifest_version": 2,
2+
"version": "2.0.0",
3+
"manifest_version": 3,
44
"default_locale": "en",
55
"description": "__MSG_appDesc__",
66
"name": "__MSG_appName__",
@@ -10,17 +10,17 @@
1010
"16": "images/on16.png"
1111
},
1212
"permissions": [
13-
"file://*/*",
1413
"proxy",
1514
"tabs",
1615
"storage",
17-
"unlimitedStorage",
18-
"<all_urls>",
1916
"webRequest",
20-
"webRequestBlocking"
17+
"webRequestAuthProvider"
18+
],
19+
"host_permissions": [
20+
"*://*/*"
2121
],
2222
"background": {
23-
"scripts": ["background.js"]
23+
"service_worker": "background.js"
2424
},
2525
"commands": {
2626
"open-option": {
@@ -30,7 +30,7 @@
3030
}
3131
},
3232
"options_page": "options.html",
33-
"browser_action": {
33+
"action": {
3434
"default_icon": "images/off.png",
3535
"default_title": "__MSG_title__",
3636
"default_popup": "popup.html"

0 commit comments

Comments
 (0)