2019-05-11 01:20:07 -07:00
|
|
|
/* globals UAParser */
|
2017-11-19 01:32:13 -08:00
|
|
|
|
2017-09-13 03:18:12 -07:00
|
|
|
'use strict';
|
|
|
|
|
2020-08-05 03:13:33 -07:00
|
|
|
const DCSI = 'firefox-default';
|
2019-11-05 22:19:42 -08:00
|
|
|
|
2020-08-04 23:29:57 -07:00
|
|
|
const cache = {}; // cache how a tab's request get handled (true, false, object)
|
2019-07-31 23:53:48 -07:00
|
|
|
const tabs = {};
|
2020-08-04 07:11:00 -07:00
|
|
|
const cookieStoreIds = {};
|
|
|
|
chrome.tabs.onRemoved.addListener(id => {
|
|
|
|
delete cache[id];
|
|
|
|
delete tabs[id];
|
|
|
|
delete cookieStoreIds[id];
|
|
|
|
});
|
|
|
|
chrome.tabs.onCreated.addListener(tab => {
|
|
|
|
tabs[tab.id] = tab.windowId;
|
|
|
|
cookieStoreIds[tab.id] = tab.cookieStoreId;
|
|
|
|
});
|
2017-09-13 03:18:12 -07:00
|
|
|
|
2019-07-31 23:53:48 -07:00
|
|
|
const prefs = {
|
2020-09-06 04:26:09 -07:00
|
|
|
'ua': '',
|
|
|
|
'blacklist': [],
|
|
|
|
'whitelist': [],
|
|
|
|
'custom': {},
|
|
|
|
'siblings': {}, // a list of domains that are considered siblings (use same index for all)
|
|
|
|
'mode': 'blacklist',
|
|
|
|
'color': '#777',
|
|
|
|
'cache': true,
|
|
|
|
'exactMatch': false,
|
2022-11-13 21:45:44 -08:00
|
|
|
'userAgentData': true,
|
2021-05-02 04:07:10 -07:00
|
|
|
'protected': [
|
|
|
|
'google.com/recaptcha',
|
|
|
|
'gstatic.com/recaptcha',
|
|
|
|
'accounts.google.com',
|
|
|
|
'accounts.youtube.com',
|
|
|
|
'gitlab.com/users/sign_in'
|
|
|
|
],
|
2020-09-06 04:26:09 -07:00
|
|
|
'parser': {}, // maps ua string to a ua object,
|
|
|
|
'log': false,
|
|
|
|
'json-guid': 'na'
|
2017-11-22 04:35:22 -08:00
|
|
|
};
|
2020-08-04 01:22:18 -07:00
|
|
|
window.prefs = prefs; // access from popup
|
2019-11-05 22:19:42 -08:00
|
|
|
|
|
|
|
const log = (...args) => prefs.log && console.log(...args);
|
|
|
|
|
2020-09-06 00:36:28 -07:00
|
|
|
// expand comma-separated keys of prefs.custom and add missing keys
|
2019-11-05 06:04:18 -08:00
|
|
|
const expand = () => {
|
2019-11-05 22:19:42 -08:00
|
|
|
log('expanding custom rules');
|
2019-11-05 06:04:18 -08:00
|
|
|
expand.rules = {};
|
|
|
|
for (const key of Object.keys(prefs.custom)) {
|
|
|
|
for (const k of key.split(/\s*,\s*/)) {
|
|
|
|
if (k) {
|
|
|
|
expand.rules[k] = prefs.custom[key];
|
2020-09-06 00:36:28 -07:00
|
|
|
// make sure all siblings have the same expanded rule
|
|
|
|
const i = prefs.siblings[key];
|
|
|
|
if (i !== undefined) {
|
|
|
|
for (const [hostname, j] of Object.entries(prefs.siblings)) {
|
|
|
|
if (i === j) {
|
|
|
|
expand.rules[hostname] = expand.rules[hostname] || prefs.custom[key];
|
|
|
|
if (expand.rules._) {
|
|
|
|
const x = expand.rules._.indexOf(key);
|
|
|
|
const y = expand.rules._.indexOf(hostname);
|
|
|
|
if (x !== -1 && y === -1) {
|
|
|
|
expand.rules._.push(hostname);
|
|
|
|
}
|
|
|
|
if (x === -1 && y !== -1) {
|
|
|
|
expand.rules._.push(key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-11-05 06:04:18 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
expand.rules = {};
|
|
|
|
|
2020-08-04 23:29:57 -07:00
|
|
|
const currentCookieStoreId = () => new Promise(resolve => chrome.tabs.query({
|
|
|
|
active: true,
|
|
|
|
currentWindow: true
|
|
|
|
}, tbs => {
|
2020-08-05 03:13:33 -07:00
|
|
|
resolve((tbs.length ? tbs[0].cookieStoreId : '') || DCSI);
|
2020-08-04 23:29:57 -07:00
|
|
|
}));
|
|
|
|
|
2017-11-22 04:35:22 -08:00
|
|
|
chrome.storage.local.get(prefs, ps => {
|
|
|
|
Object.assign(prefs, ps);
|
2020-08-04 23:29:57 -07:00
|
|
|
expand();
|
|
|
|
|
|
|
|
chrome.tabs.query({}, ts => {
|
|
|
|
ts.forEach(t => {
|
|
|
|
tabs[t.id] = t.windowId;
|
|
|
|
cookieStoreIds[t.id] = t.cookieStoreId;
|
|
|
|
});
|
|
|
|
|
|
|
|
// update prefs.ua from the managed storage
|
2020-09-06 00:57:47 -07:00
|
|
|
try {
|
|
|
|
chrome.storage.managed.get({
|
2020-09-06 04:26:09 -07:00
|
|
|
'ua': '',
|
|
|
|
'json': ''
|
2020-09-06 00:57:47 -07:00
|
|
|
}, rps => {
|
2020-09-06 04:26:09 -07:00
|
|
|
if (!chrome.runtime.lastError) {
|
|
|
|
const p = {};
|
|
|
|
if (rps.json) {
|
|
|
|
try {
|
|
|
|
const j = JSON.parse(rps.json);
|
|
|
|
if (prefs['json-guid'] !== j['json-guid'] || j['json-forced']) {
|
|
|
|
Object.assign(p, j);
|
|
|
|
console.warn('preferences are updated by an admin');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (e) {
|
|
|
|
console.warn('cannot parse remote JSON', e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (rps.ua) {
|
|
|
|
p.ua = rps.ua;
|
|
|
|
console.warn('user-agent string is updated by an admin');
|
|
|
|
}
|
|
|
|
chrome.storage.local.set(p, () => {
|
|
|
|
ua.update(undefined, undefined, DCSI);
|
2020-09-06 00:57:47 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
ua.update(undefined, undefined, DCSI);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
catch (e) {
|
|
|
|
ua.update(undefined, undefined, DCSI);
|
|
|
|
}
|
2018-08-16 03:14:11 -07:00
|
|
|
});
|
2020-08-04 01:22:18 -07:00
|
|
|
|
2018-08-16 23:16:10 -07:00
|
|
|
if (chrome.browserAction.setBadgeBackgroundColor) { // FF for Android
|
|
|
|
chrome.browserAction.setBadgeBackgroundColor({
|
|
|
|
color: prefs.color
|
|
|
|
});
|
|
|
|
}
|
2018-11-04 01:56:34 -08:00
|
|
|
// context menu
|
|
|
|
chrome.contextMenus.create({
|
|
|
|
id: 'blacklist',
|
|
|
|
title: 'Switch to "black-list" mode',
|
|
|
|
contexts: ['browser_action'],
|
|
|
|
type: 'radio',
|
|
|
|
checked: prefs.mode === 'blacklist'
|
2019-03-13 01:27:20 -07:00
|
|
|
}, () => chrome.runtime.lastError);
|
2018-11-04 01:56:34 -08:00
|
|
|
chrome.contextMenus.create({
|
|
|
|
id: 'whitelist',
|
|
|
|
title: 'Switch to "white-list" mode',
|
|
|
|
contexts: ['browser_action'],
|
|
|
|
type: 'radio',
|
|
|
|
checked: prefs.mode === 'whitelist'
|
2019-03-13 01:27:20 -07:00
|
|
|
}, () => chrome.runtime.lastError);
|
2018-11-04 01:56:34 -08:00
|
|
|
chrome.contextMenus.create({
|
|
|
|
id: 'custom',
|
|
|
|
title: 'Switch to "custom" mode',
|
|
|
|
contexts: ['browser_action'],
|
|
|
|
type: 'radio',
|
|
|
|
checked: prefs.mode === 'custom'
|
2019-03-13 01:27:20 -07:00
|
|
|
}, () => chrome.runtime.lastError);
|
2017-11-22 04:35:22 -08:00
|
|
|
});
|
|
|
|
chrome.storage.onChanged.addListener(ps => {
|
|
|
|
Object.keys(ps).forEach(key => prefs[key] = ps[key].newValue);
|
2020-08-04 23:29:57 -07:00
|
|
|
|
2022-11-13 21:45:44 -08:00
|
|
|
if (ps.ua || ps.mode || ps.userAgentData) {
|
2020-08-04 23:29:57 -07:00
|
|
|
currentCookieStoreId().then(cookieStoreId => {
|
|
|
|
if (ps.ua) {
|
|
|
|
if (ps.ua.newValue === '') {
|
|
|
|
delete ua._obj[cookieStoreId];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ua.update(undefined, undefined, cookieStoreId);
|
|
|
|
});
|
2017-12-13 04:45:54 -08:00
|
|
|
}
|
2020-03-15 02:44:52 -07:00
|
|
|
if (ps.custom) {
|
2019-11-05 06:04:18 -08:00
|
|
|
expand();
|
|
|
|
}
|
2017-11-22 04:35:22 -08:00
|
|
|
});
|
|
|
|
|
2019-07-31 23:53:48 -07:00
|
|
|
const ua = {
|
2020-08-04 07:11:00 -07:00
|
|
|
_obj: {},
|
2020-08-05 03:13:33 -07:00
|
|
|
diff(tabId, cookieStoreId = DCSI) { // returns true if there is per window object
|
2020-08-04 07:11:00 -07:00
|
|
|
log('ua.diff is called', tabId, cookieStoreId);
|
2018-08-16 03:14:11 -07:00
|
|
|
const windowId = tabs[tabId];
|
2020-08-05 03:13:33 -07:00
|
|
|
return windowId in (this._obj[cookieStoreId] || this._obj[DCSI] || {});
|
2018-08-16 03:14:11 -07:00
|
|
|
},
|
|
|
|
get windows() {
|
2019-11-05 22:19:42 -08:00
|
|
|
log('ua.windows is called');
|
2020-08-04 07:11:00 -07:00
|
|
|
const ids = [];
|
|
|
|
for (const cookieStoreId of Object.keys(this._obj)) {
|
|
|
|
ids.push(...Object.keys(this._obj[cookieStoreId]).filter(id => id !== 'global').map(s => Number(s)));
|
|
|
|
}
|
|
|
|
return ids.filter((n, i, l) => n && l.indexOf(n) === i);
|
2018-08-16 03:14:11 -07:00
|
|
|
},
|
|
|
|
parse: s => {
|
2019-11-05 22:19:42 -08:00
|
|
|
log('ua.parse is called', s);
|
2019-11-05 03:02:26 -08:00
|
|
|
if (prefs.parser[s]) {
|
2019-11-05 22:19:42 -08:00
|
|
|
log('ua.parse is resolved using parser');
|
2019-11-05 03:02:26 -08:00
|
|
|
return Object.assign({
|
|
|
|
userAgent: s
|
|
|
|
}, prefs.parser[s]);
|
|
|
|
}
|
2020-09-12 00:33:53 -07:00
|
|
|
// build ua string from the navigator object or from a custom UAParser;
|
|
|
|
// examples: ${platform}, ${browser.version|ua-parser}
|
|
|
|
s = s.replace(/\${([^}]+)}/g, (a, b) => {
|
|
|
|
const key = (parent, keys) => {
|
|
|
|
for (const key of keys) {
|
|
|
|
parent = parent[key] || {};
|
|
|
|
}
|
|
|
|
|
|
|
|
return parent;
|
|
|
|
};
|
|
|
|
|
|
|
|
let [childs, object] = b.split('|');
|
|
|
|
object = object || 'navigator';
|
|
|
|
|
|
|
|
let v;
|
|
|
|
if (object.startsWith('ua-parser')) {
|
|
|
|
const [a, b] = object.split('@');
|
|
|
|
object = a;
|
|
|
|
|
|
|
|
v = key((new UAParser(b || navigator.userAgent)).getResult(), childs.split('.'));
|
|
|
|
}
|
|
|
|
v = v || key(navigator, childs.split('.'));
|
|
|
|
return typeof v === 'string' ? v : 'cannot parse your ${...} replacements.';
|
|
|
|
});
|
2018-08-16 03:14:11 -07:00
|
|
|
const o = {};
|
|
|
|
o.userAgent = s;
|
|
|
|
o.appVersion = s
|
|
|
|
.replace(/^Mozilla\//, '')
|
|
|
|
.replace(/^Opera\//, '');
|
2019-11-05 03:02:26 -08:00
|
|
|
|
|
|
|
const isFF = /Firefox/.test(s);
|
|
|
|
const isCH = /Chrome/.test(s);
|
|
|
|
const isSF = /Safari/.test(s) && isCH === false;
|
|
|
|
|
2022-11-13 21:45:44 -08:00
|
|
|
|
2019-11-05 03:02:26 -08:00
|
|
|
if (isFF) {
|
|
|
|
o.appVersion = '5.0 ' + o.appVersion.replace('5.0 ', '').split(/[\s;]/)[0] + ')';
|
|
|
|
}
|
2019-05-11 01:20:07 -07:00
|
|
|
const p = (new UAParser(s)).getResult();
|
|
|
|
o.platform = p.os.name || '';
|
2020-12-29 01:39:24 -08:00
|
|
|
if (o.platform === 'Mac OS') {
|
|
|
|
o.platform = 'MacIntel';
|
|
|
|
}
|
2019-05-11 01:20:07 -07:00
|
|
|
o.vendor = p.device.vendor || '';
|
2019-11-05 03:02:26 -08:00
|
|
|
if (isSF) {
|
|
|
|
o.vendor = 'Apple Computer, Inc.';
|
|
|
|
}
|
|
|
|
else if (isFF === false) {
|
|
|
|
o.vendor = 'Google Inc.';
|
|
|
|
}
|
2019-05-11 01:20:07 -07:00
|
|
|
o.product = p.engine.name || '';
|
2019-11-05 03:02:26 -08:00
|
|
|
if (s.indexOf('Gecko') !== -1) {
|
|
|
|
o.product = 'Gecko';
|
|
|
|
}
|
2022-11-13 21:45:44 -08:00
|
|
|
o.userAgentData = '[delete]';
|
2019-11-05 03:02:26 -08:00
|
|
|
if (isFF) {
|
|
|
|
o.oscpu = ((p.os.name || '') + ' ' + (p.os.version || '')).trim();
|
2021-05-02 04:07:10 -07:00
|
|
|
o.productSub = '20100101';
|
|
|
|
o.buildID = '20181001000000';
|
2019-11-05 03:02:26 -08:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
o.oscpu = '[delete]';
|
2021-05-02 04:07:10 -07:00
|
|
|
o.buildID = '[delete]';
|
|
|
|
o.productSub = '20030107';
|
2022-11-13 02:08:36 -08:00
|
|
|
|
2022-11-13 21:45:44 -08:00
|
|
|
if (prefs.userAgentData && p.browser && p.browser.major) {
|
2022-11-13 22:31:04 -08:00
|
|
|
if (['Opera', 'Chrome', 'Edge'].includes(p.browser.name)) {
|
2022-11-13 21:45:44 -08:00
|
|
|
o.userAgentDataBuilder = {p, ua: s};
|
|
|
|
delete o.userAgentData;
|
2022-11-13 03:26:32 -08:00
|
|
|
}
|
2021-05-02 04:07:10 -07:00
|
|
|
}
|
2019-11-05 03:02:26 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (o.userAgent === 'empty') {
|
|
|
|
Object.keys(o).forEach(key => {
|
|
|
|
if (key !== 'userAgent') {
|
|
|
|
o[key] = '';
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2018-08-16 03:14:11 -07:00
|
|
|
return o;
|
|
|
|
},
|
2020-08-05 03:13:33 -07:00
|
|
|
object(tabId, windowId, cookieStoreId = DCSI) {
|
2018-08-16 03:14:11 -07:00
|
|
|
windowId = windowId || (tabId ? tabs[tabId] : 'global');
|
2020-08-04 07:11:00 -07:00
|
|
|
log('ua.object is called', tabId, windowId, cookieStoreId);
|
|
|
|
|
|
|
|
if (this._obj[cookieStoreId]) {
|
2020-08-04 23:29:57 -07:00
|
|
|
return this._obj[cookieStoreId][windowId] || this._obj[cookieStoreId].global;
|
2020-08-04 07:11:00 -07:00
|
|
|
}
|
2018-08-16 03:14:11 -07:00
|
|
|
},
|
2020-08-04 07:11:00 -07:00
|
|
|
string(str, windowId, cookieStoreId) {
|
2019-11-05 22:19:42 -08:00
|
|
|
log('ua.string is called', str, windowId);
|
2020-08-04 07:11:00 -07:00
|
|
|
this._obj[cookieStoreId] = this._obj[cookieStoreId] || {};
|
2018-08-16 03:14:11 -07:00
|
|
|
if (str) {
|
2020-08-04 07:11:00 -07:00
|
|
|
this._obj[cookieStoreId][windowId] = this.parse(str);
|
2018-08-16 03:14:11 -07:00
|
|
|
}
|
|
|
|
else {
|
2020-08-04 07:11:00 -07:00
|
|
|
this._obj[cookieStoreId][windowId] = {};
|
2018-08-16 03:14:11 -07:00
|
|
|
}
|
|
|
|
},
|
2019-07-31 23:53:48 -07:00
|
|
|
tooltip(title, tabId) {
|
2019-11-05 22:19:42 -08:00
|
|
|
log('ua.tooltip is called', title, tabId);
|
2019-07-31 23:53:48 -07:00
|
|
|
chrome.browserAction.setTitle({
|
|
|
|
title,
|
|
|
|
tabId
|
|
|
|
});
|
|
|
|
},
|
|
|
|
icon(mode, tabId) {
|
2019-11-05 22:19:42 -08:00
|
|
|
log('ua.icon is called', mode, tabId);
|
2019-07-31 23:53:48 -07:00
|
|
|
chrome.browserAction.setIcon({
|
|
|
|
tabId,
|
2018-08-16 03:14:11 -07:00
|
|
|
path: {
|
2019-07-31 23:53:48 -07:00
|
|
|
'16': 'data/icons/' + (mode ? mode + '/' : '') + '16.png',
|
|
|
|
'18': 'data/icons/' + (mode ? mode + '/' : '') + '18.png',
|
|
|
|
'19': 'data/icons/' + (mode ? mode + '/' : '') + '19.png',
|
|
|
|
'32': 'data/icons/' + (mode ? mode + '/' : '') + '32.png',
|
|
|
|
'36': 'data/icons/' + (mode ? mode + '/' : '') + '36.png',
|
|
|
|
'38': 'data/icons/' + (mode ? mode + '/' : '') + '38.png',
|
|
|
|
'48': 'data/icons/' + (mode ? mode + '/' : '') + '48.png'
|
2018-08-16 03:14:11 -07:00
|
|
|
}
|
2019-07-31 23:53:48 -07:00
|
|
|
});
|
|
|
|
},
|
2020-08-04 07:11:00 -07:00
|
|
|
toolbar: ({windowId, tabId, cookieStoreId}) => {
|
2019-11-05 22:19:42 -08:00
|
|
|
log('ua.toolbar is called', windowId, tabId);
|
2018-08-16 03:14:11 -07:00
|
|
|
if (windowId) {
|
|
|
|
chrome.tabs.query({
|
|
|
|
windowId
|
|
|
|
}, tabs => tabs.forEach(tab => {
|
|
|
|
const tabId = tab.id;
|
2020-08-04 23:29:57 -07:00
|
|
|
const o = ua.object(null, windowId, tab.cookieStoreId);
|
2018-08-16 03:14:11 -07:00
|
|
|
chrome.browserAction.setBadgeText({
|
|
|
|
tabId,
|
2022-02-20 10:30:58 -08:00
|
|
|
text: o && o.platform ? o.platform.slice(0, 3) : ''
|
2018-08-16 03:14:11 -07:00
|
|
|
});
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
else if (tabId) {
|
2020-08-04 23:29:57 -07:00
|
|
|
const o = ua.object(tabId, undefined, cookieStoreId);
|
2018-08-16 03:14:11 -07:00
|
|
|
chrome.browserAction.setBadgeText({
|
|
|
|
tabId,
|
2022-02-20 10:30:58 -08:00
|
|
|
text: o.platform ? o.platform.slice(0, 3) : 'BOT'
|
2018-08-16 03:14:11 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2020-08-05 03:13:33 -07:00
|
|
|
update(str = prefs.ua, windowId = 'global', cookieStoreId = DCSI) {
|
2020-08-04 07:11:00 -07:00
|
|
|
log('ua.update is called', str, windowId, cookieStoreId);
|
2019-11-05 22:19:42 -08:00
|
|
|
// clear caching
|
|
|
|
Object.keys(cache).forEach(key => delete cache[key]);
|
|
|
|
// remove old listeners
|
2018-08-16 03:14:11 -07:00
|
|
|
chrome.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders);
|
|
|
|
chrome.webNavigation.onCommitted.removeListener(onCommitted);
|
2019-11-05 22:19:42 -08:00
|
|
|
// apply new ones
|
2020-08-04 23:29:57 -07:00
|
|
|
if (str || prefs.mode === 'custom' || this.windows.length || Object.keys(this._obj).length) {
|
|
|
|
if (str) {
|
|
|
|
ua.string(str, windowId, cookieStoreId);
|
|
|
|
}
|
2018-08-16 03:14:11 -07:00
|
|
|
chrome.webRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, {
|
2020-09-11 22:41:00 -07:00
|
|
|
'urls': ['*://*/*', 'ws://*/*', 'wss://*/*']
|
2018-08-16 03:14:11 -07:00
|
|
|
}, ['blocking', 'requestHeaders']);
|
|
|
|
chrome.webNavigation.onCommitted.addListener(onCommitted);
|
2019-07-31 23:53:48 -07:00
|
|
|
ua.tooltip('[Default] ' + navigator.userAgent);
|
|
|
|
ua.icon('ignored');
|
2018-08-16 03:14:11 -07:00
|
|
|
}
|
2019-07-31 23:53:48 -07:00
|
|
|
else {
|
|
|
|
ua.icon('');
|
|
|
|
ua.tooltip('[Disabled] to enable, use the popup window');
|
|
|
|
}
|
|
|
|
|
2018-08-16 03:14:11 -07:00
|
|
|
if (windowId === 'global') {
|
|
|
|
this.toolbar({str});
|
|
|
|
}
|
|
|
|
// update per window
|
|
|
|
else {
|
|
|
|
this.windows.forEach(windowId => this.toolbar({windowId}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2019-07-31 23:53:48 -07:00
|
|
|
window.ua = ua; // using from popup
|
2018-08-16 03:14:11 -07:00
|
|
|
// make sure to clean on window removal
|
2018-08-16 23:16:10 -07:00
|
|
|
if (chrome.windows) { // FF on Android
|
2020-08-04 07:11:00 -07:00
|
|
|
chrome.windows.onRemoved.addListener(windowId => {
|
2020-08-04 23:29:57 -07:00
|
|
|
let update = false;
|
2020-08-04 07:11:00 -07:00
|
|
|
Object.keys(ua._obj).forEach(cookieStoreId => {
|
2020-08-04 23:29:57 -07:00
|
|
|
if (windowId in ua._obj[cookieStoreId]) {
|
|
|
|
delete ua._obj[cookieStoreId][windowId];
|
|
|
|
// delete the entire object if it is empty
|
|
|
|
if (Object.keys(ua._obj[cookieStoreId]).length === 0) {
|
|
|
|
delete ua._obj[cookieStoreId];
|
|
|
|
}
|
|
|
|
update = true;
|
|
|
|
}
|
2020-08-04 07:11:00 -07:00
|
|
|
});
|
2020-08-04 23:29:57 -07:00
|
|
|
// if nothing is left to monitor, disable the extension
|
|
|
|
if (update) {
|
|
|
|
currentCookieStoreId().then(cookieStoreId => ua.update(undefined, undefined, cookieStoreId));
|
|
|
|
}
|
2020-08-04 07:11:00 -07:00
|
|
|
});
|
2018-08-16 23:16:10 -07:00
|
|
|
}
|
2018-08-16 03:14:11 -07:00
|
|
|
|
2017-11-22 04:35:22 -08:00
|
|
|
function hostname(url) {
|
2019-11-05 22:19:42 -08:00
|
|
|
log('hostname', url);
|
2017-11-22 04:35:22 -08:00
|
|
|
const s = url.indexOf('//') + 2;
|
|
|
|
if (s > 1) {
|
|
|
|
let o = url.indexOf('/', s);
|
|
|
|
if (o > 0) {
|
|
|
|
return url.substring(s, o);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
o = url.indexOf('?', s);
|
|
|
|
if (o > 0) {
|
|
|
|
return url.substring(s, o);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return url.substring(s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return url;
|
|
|
|
}
|
|
|
|
}
|
2018-04-10 00:50:58 -07:00
|
|
|
// returns true, false or an object; true: ignore, false: use from ua object.
|
2020-08-05 03:13:33 -07:00
|
|
|
function match({url, tabId, cookieStoreId = DCSI}) {
|
2020-08-04 07:11:00 -07:00
|
|
|
log('match', url, tabId, cookieStoreId);
|
2019-11-05 06:04:18 -08:00
|
|
|
const h = hostname(url);
|
|
|
|
|
2021-05-02 04:07:10 -07:00
|
|
|
if (prefs.protected.some(s => url.indexOf(s) !== -1)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-11-22 04:35:22 -08:00
|
|
|
if (prefs.mode === 'blacklist') {
|
|
|
|
if (prefs.blacklist.length) {
|
2019-03-13 02:24:03 -07:00
|
|
|
return prefs.blacklist.some(s => {
|
2018-11-04 01:37:09 -08:00
|
|
|
if (s === h) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (prefs.exactMatch === false) {
|
2019-03-13 02:29:37 -07:00
|
|
|
return s.endsWith('.' + h) || h.endsWith('.' + s);
|
2018-11-04 01:37:09 -08:00
|
|
|
}
|
|
|
|
});
|
2017-11-22 04:35:22 -08:00
|
|
|
}
|
|
|
|
}
|
2017-12-13 04:45:54 -08:00
|
|
|
else if (prefs.mode === 'whitelist') {
|
2017-12-13 00:45:52 -08:00
|
|
|
if (prefs.whitelist.length) {
|
2018-11-04 01:37:09 -08:00
|
|
|
return prefs.whitelist.some(s => {
|
|
|
|
if (s === h) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (prefs.exactMatch === false) {
|
2019-03-13 02:29:37 -07:00
|
|
|
return s.endsWith('.' + h) || h.endsWith('.' + s);
|
2018-11-04 01:37:09 -08:00
|
|
|
}
|
|
|
|
}) === false;
|
2017-11-22 04:35:22 -08:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2020-08-04 07:11:00 -07:00
|
|
|
const [hh] = h.split(':');
|
|
|
|
const key = Object.keys(expand.rules).filter(s => {
|
|
|
|
if (s === h || s === hh) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (prefs.exactMatch === false) {
|
|
|
|
return s.endsWith('.' + h) || h.endsWith('.' + s) || s.endsWith('.' + hh) || hh.endsWith('.' + s);
|
|
|
|
}
|
|
|
|
}).shift();
|
2020-09-06 00:36:28 -07:00
|
|
|
let s;
|
|
|
|
// try to use an already resolved sibling hostname
|
|
|
|
const i = prefs.siblings[key];
|
|
|
|
if (i !== undefined) {
|
|
|
|
for (const [hostname, j] of Object.entries(prefs.siblings)) {
|
|
|
|
if (j === i && expand.rules[hostname] && typeof expand.rules[hostname] === 'string') {
|
|
|
|
s = expand.rules[hostname];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s = s || expand.rules[key];
|
|
|
|
// use '*' when the hostname specific key is not found
|
|
|
|
s = s || expand.rules['*'];
|
2020-08-04 07:11:00 -07:00
|
|
|
// if s is an array select a random string
|
|
|
|
if (Array.isArray(s)) {
|
|
|
|
s = s[Math.floor(Math.random() * s.length)];
|
|
|
|
// set session mode if key is either on _[key] or _['*'] lists
|
|
|
|
if (expand.rules._ && Array.isArray(expand.rules._)) {
|
|
|
|
if (expand.rules._.indexOf(key) !== -1) {
|
|
|
|
expand.rules[key] = s;
|
2018-11-04 02:16:25 -08:00
|
|
|
}
|
2020-08-04 07:11:00 -07:00
|
|
|
else if (expand.rules._.indexOf('*') !== -1) {
|
|
|
|
if (expand.rules[key]) {
|
2019-11-05 06:04:18 -08:00
|
|
|
expand.rules[key] = s;
|
2019-05-11 01:57:29 -07:00
|
|
|
}
|
2020-08-04 07:11:00 -07:00
|
|
|
else if (expand.rules['*']) {
|
|
|
|
expand.rules['*'] = s;
|
2019-05-11 01:57:29 -07:00
|
|
|
}
|
|
|
|
}
|
2018-05-15 00:54:51 -07:00
|
|
|
}
|
2020-08-04 07:11:00 -07:00
|
|
|
}
|
|
|
|
if (s) {
|
|
|
|
return ua.parse(s);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
const o = ua.object(tabId, undefined, cookieStoreId);
|
|
|
|
return o ? !o.userAgent : true;
|
2017-12-13 04:45:54 -08:00
|
|
|
}
|
2017-11-22 04:35:22 -08:00
|
|
|
}
|
|
|
|
|
2020-08-04 07:11:00 -07:00
|
|
|
const onBeforeSendHeaders = d => {
|
2020-08-05 03:13:33 -07:00
|
|
|
const {tabId, url, requestHeaders, type} = d;
|
|
|
|
const cookieStoreId = d.cookieStoreId || cookieStoreIds[tabId] || DCSI;
|
|
|
|
|
2018-08-20 04:41:47 -07:00
|
|
|
if (type === 'main_frame' || prefs.cache === false) {
|
2020-08-05 03:13:33 -07:00
|
|
|
cookieStoreIds[tabId] = cookieStoreId;
|
2020-08-04 07:11:00 -07:00
|
|
|
cache[tabId] = match({url, tabId, cookieStoreId});
|
2017-11-22 04:35:22 -08:00
|
|
|
}
|
2020-08-04 07:11:00 -07:00
|
|
|
|
2017-12-13 04:45:54 -08:00
|
|
|
if (cache[tabId] === true) {
|
2019-07-31 23:53:48 -07:00
|
|
|
return {};
|
2017-11-22 04:35:22 -08:00
|
|
|
}
|
2019-06-25 00:43:08 -07:00
|
|
|
if (prefs.protected.some(s => url.indexOf(s) !== -1)) {
|
|
|
|
return {};
|
|
|
|
}
|
2020-08-05 03:13:33 -07:00
|
|
|
const o = (cache[tabId] || ua.object(tabId, undefined, cookieStoreId));
|
2020-12-28 05:17:45 -08:00
|
|
|
|
2020-08-04 23:29:57 -07:00
|
|
|
const str = o ? o.userAgent : '';
|
2020-09-06 00:36:28 -07:00
|
|
|
if (str && requestHeaders.length) {
|
2021-05-02 04:07:10 -07:00
|
|
|
for (
|
|
|
|
let i = 0, name = requestHeaders[0].name;
|
|
|
|
i < requestHeaders.length;
|
|
|
|
i += 1, name = (requestHeaders[i] || {}).name
|
|
|
|
) {
|
2022-11-13 03:36:17 -08:00
|
|
|
name = name.toLowerCase();
|
|
|
|
if (name === 'user-agent') {
|
2018-11-04 01:18:40 -08:00
|
|
|
requestHeaders[i].value = str === 'empty' ? '' : str;
|
2022-11-13 03:36:17 -08:00
|
|
|
}
|
|
|
|
// https://github.com/ray-lothian/UserAgent-Switcher/issues/160
|
|
|
|
else if (name.startsWith('sec-ch-')) {
|
2022-11-13 22:31:04 -08:00
|
|
|
if (o.userAgentDataBuilder) {
|
|
|
|
if (name === 'sec-ch-ua-platform') {
|
|
|
|
let platform = o.userAgentDataBuilder.p?.os?.name || 'Windows';
|
|
|
|
|
|
|
|
if (platform.toLowerCase().includes('mac')) {
|
|
|
|
platform = 'macOS';
|
|
|
|
}
|
|
|
|
else if (name.includes('debian')) {
|
|
|
|
platform = 'Linux';
|
|
|
|
}
|
|
|
|
|
|
|
|
requestHeaders[i].value = '"' + platform + '"';
|
|
|
|
}
|
|
|
|
else if (name === 'sec-ch-ua') {
|
|
|
|
const version = o.userAgentDataBuilder.p?.browser?.major || 107;
|
|
|
|
let name = o.userAgentDataBuilder.p?.browser?.name || 'Google Chrome';
|
|
|
|
if (name === 'Chrome') {
|
|
|
|
name = 'Google Chrome';
|
|
|
|
}
|
|
|
|
requestHeaders[i].value = `"${name}";v="${version}", "Chromium";v="${version}", "Not=A?Brand";v="24"`;
|
|
|
|
}
|
|
|
|
else if (name === 'sec-ch-ua-mobile') {
|
|
|
|
requestHeaders[i].value =
|
|
|
|
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(o.userAgent) ? '?1' : '?0';
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
requestHeaders[i] = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
requestHeaders[i] = null;
|
|
|
|
}
|
2017-12-13 04:45:54 -08:00
|
|
|
}
|
2017-09-13 03:18:12 -07:00
|
|
|
}
|
|
|
|
}
|
2022-11-13 03:36:17 -08:00
|
|
|
return {
|
|
|
|
requestHeaders: requestHeaders.filter(a => a)
|
|
|
|
};
|
2017-09-13 03:18:12 -07:00
|
|
|
};
|
|
|
|
|
2020-08-04 07:11:00 -07:00
|
|
|
const onCommitted = d => {
|
|
|
|
const {frameId, url, tabId} = d;
|
2020-08-05 03:13:33 -07:00
|
|
|
const cookieStoreId = d.cookieStoreId || cookieStoreIds[tabId] || DCSI;
|
2020-08-04 07:11:00 -07:00
|
|
|
|
2018-04-10 00:50:58 -07:00
|
|
|
if (url && (url.startsWith('http') || url.startsWith('ftp')) || url === 'about:blank') {
|
|
|
|
if (cache[tabId] === true) {
|
2017-11-22 04:35:22 -08:00
|
|
|
return;
|
|
|
|
}
|
2020-08-04 07:11:00 -07:00
|
|
|
const o = cache[tabId] || ua.object(tabId, undefined, cookieStoreId);
|
2020-08-05 03:13:33 -07:00
|
|
|
if (o && o.userAgent) {
|
2022-11-13 03:26:32 -08:00
|
|
|
const s = btoa(unescape(encodeURIComponent(JSON.stringify(o))));
|
|
|
|
|
2018-04-10 00:50:58 -07:00
|
|
|
chrome.tabs.executeScript(tabId, {
|
|
|
|
runAt: 'document_start',
|
|
|
|
frameId,
|
|
|
|
code: `{
|
|
|
|
const script = document.createElement('script');
|
2020-12-29 01:39:24 -08:00
|
|
|
script.textContent = \`{
|
|
|
|
document.currentScript.dataset.injected = true;
|
2022-11-13 03:26:32 -08:00
|
|
|
const o = JSON.parse(decodeURIComponent(escape(atob('${s}'))));
|
2021-05-02 04:07:10 -07:00
|
|
|
|
2022-11-13 21:45:44 -08:00
|
|
|
if (o.userAgentDataBuilder) {
|
2022-11-13 22:31:04 -08:00
|
|
|
const v = new class NavigatorUAData {
|
2022-11-13 21:45:44 -08:00
|
|
|
#p;
|
|
|
|
|
|
|
|
constructor({p, ua}) {
|
|
|
|
this.#p = p;
|
|
|
|
|
|
|
|
const version = p.browser.major;
|
|
|
|
const name = p.browser.name === 'Chrome' ? 'Google Chrome' : p.browser.name;
|
|
|
|
|
|
|
|
this.brands = [{
|
|
|
|
brand: name,
|
|
|
|
version
|
|
|
|
}, {
|
|
|
|
brand: 'Chromium',
|
|
|
|
version
|
|
|
|
}, {
|
|
|
|
brand: 'Not=A?Brand',
|
|
|
|
version: '24'
|
|
|
|
}];
|
|
|
|
|
2022-11-13 22:31:04 -08:00
|
|
|
this.mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua);
|
2022-11-13 21:45:44 -08:00
|
|
|
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Platform
|
|
|
|
this.platform = 'Unknown';
|
|
|
|
if (p.os && p.os.name) {
|
|
|
|
const name = p.os.name.toLowerCase();
|
|
|
|
if (name.includes('mac')) {
|
|
|
|
this.platform = 'macOS';
|
|
|
|
}
|
|
|
|
else if (name.includes('debian')) {
|
|
|
|
this.platform = 'Linux';
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this.platform = p.os.name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
toJSON() {
|
|
|
|
return {
|
|
|
|
brands: this.brands,
|
|
|
|
mobile: this.mobile,
|
|
|
|
platform: this.platform
|
|
|
|
};
|
|
|
|
}
|
|
|
|
getHighEntropyValues(hints) {
|
|
|
|
if (!hints || Array.isArray(hints) === false) {
|
|
|
|
return Promise.reject(Error("Failed to execute 'getHighEntropyValues' on 'NavigatorUAData'"));
|
|
|
|
}
|
|
|
|
|
|
|
|
const r = this.toJSON();
|
|
|
|
|
|
|
|
if (hints.includes('architecture')) {
|
|
|
|
r.architecture = this.#p?.cpu?.architecture || 'x86';
|
|
|
|
}
|
|
|
|
if (hints.includes('bitness')) {
|
|
|
|
r.bitness = '64';
|
|
|
|
}
|
|
|
|
if (hints.includes('model')) {
|
|
|
|
r.model = '';
|
|
|
|
}
|
|
|
|
if (hints.includes('platformVersion')) {
|
|
|
|
r.platformVersion = this.#p?.os?.version || '10.0.0';
|
|
|
|
}
|
|
|
|
if (hints.includes('uaFullVersion')) {
|
|
|
|
r.uaFullVersion = this.brands[0].version;
|
|
|
|
}
|
|
|
|
if (hints.includes('fullVersionList')) {
|
|
|
|
r.fullVersionList = this.brands;
|
|
|
|
}
|
|
|
|
return Promise.resolve(r);
|
|
|
|
}
|
|
|
|
}(o.userAgentDataBuilder);
|
2022-11-13 22:31:04 -08:00
|
|
|
|
|
|
|
navigator.__defineGetter__('userAgentData', () => {
|
|
|
|
return v;
|
|
|
|
});
|
2022-11-13 21:45:44 -08:00
|
|
|
}
|
|
|
|
delete o.userAgentDataBuilder;
|
|
|
|
|
2019-11-05 03:02:26 -08:00
|
|
|
for (const key of Object.keys(o)) {
|
2021-05-02 04:07:10 -07:00
|
|
|
if (o[key] === '[delete]') {
|
|
|
|
delete Object.getPrototypeOf(window.navigator)[key];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
navigator.__defineGetter__(key, () => {
|
|
|
|
if (o[key] === 'empty') {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
return o[key];
|
|
|
|
});
|
|
|
|
}
|
2019-11-05 03:02:26 -08:00
|
|
|
}
|
2020-12-29 01:39:24 -08:00
|
|
|
}\`;
|
2018-04-10 00:50:58 -07:00
|
|
|
document.documentElement.appendChild(script);
|
2020-12-29 01:39:24 -08:00
|
|
|
if (script.dataset.injected !== 'true') {
|
|
|
|
const s = document.createElement('script');
|
|
|
|
s.src = 'data:text/javascript;charset=utf-8;base64,' + btoa(script.textContent);
|
|
|
|
document.documentElement.appendChild(s);
|
|
|
|
s.remove();
|
|
|
|
}
|
2018-08-16 03:14:11 -07:00
|
|
|
script.remove();
|
2018-04-10 00:50:58 -07:00
|
|
|
}`
|
2020-03-15 01:01:29 -07:00
|
|
|
}, () => {
|
2019-11-05 22:19:42 -08:00
|
|
|
const lastError = chrome.runtime.lastError;
|
|
|
|
if (lastError &&
|
|
|
|
lastError.message !== 'No matching message handler' && // Firefox on Windows
|
|
|
|
lastError.message !== 'document.documentElement is null' // Firefox on Windows
|
|
|
|
) {
|
2019-07-31 23:53:48 -07:00
|
|
|
if (frameId === 0) {
|
2019-11-05 06:04:18 -08:00
|
|
|
ua.tooltip('[Default] ' + navigator.userAgent, tabId);
|
2019-07-31 23:53:48 -07:00
|
|
|
ua.icon('ignored', tabId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (frameId === 0) {
|
2019-11-05 06:04:18 -08:00
|
|
|
ua.tooltip('[Custom] ' + o.userAgent, tabId);
|
2019-07-31 23:53:48 -07:00
|
|
|
ua.icon('active', tabId);
|
|
|
|
}
|
|
|
|
});
|
2018-04-10 00:50:58 -07:00
|
|
|
}
|
2017-09-13 03:18:12 -07:00
|
|
|
}
|
2018-08-16 03:14:11 -07:00
|
|
|
// change the toolbar icon if there is a per window UA setting
|
2020-08-04 07:11:00 -07:00
|
|
|
if (frameId === 0 && ua.diff(tabId, cookieStoreId)) {
|
|
|
|
ua.toolbar({tabId, cookieStoreId});
|
2017-09-13 03:18:12 -07:00
|
|
|
}
|
2018-08-16 03:14:11 -07:00
|
|
|
};
|
2018-11-04 01:56:34 -08:00
|
|
|
// context menu
|
|
|
|
chrome.contextMenus.onClicked.addListener(info => chrome.storage.local.set({
|
|
|
|
mode: info.menuItemId
|
|
|
|
}));
|
2017-09-13 03:18:12 -07:00
|
|
|
|
2020-08-05 03:13:33 -07:00
|
|
|
// restore container agents
|
|
|
|
chrome.storage.local.get({
|
|
|
|
'container-uas': {}
|
|
|
|
}, prefs => {
|
|
|
|
for (const cookieStoreId of Object.keys(prefs['container-uas'])) {
|
|
|
|
ua.string(prefs['container-uas'][cookieStoreId], 'global', cookieStoreId);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-12-28 05:17:45 -08:00
|
|
|
/* message passing */
|
|
|
|
chrome.runtime.onMessage.addListener((request, sender, response) => {
|
|
|
|
if (request.method === 'parse-ua') {
|
|
|
|
response(ua.parse(request.value));
|
|
|
|
}
|
|
|
|
else if (request.method === 'get-ua') {
|
|
|
|
response(prefs.ua || '' || navigator.userAgent);
|
|
|
|
}
|
|
|
|
else if (request.method === 'request-update') {
|
|
|
|
if (request.delete) {
|
|
|
|
delete ua._obj[request.cookieStoreId];
|
|
|
|
}
|
2021-05-02 06:06:20 -07:00
|
|
|
ua.update(request.value, request.windowId, request.cookieStoreId);
|
2020-12-28 05:17:45 -08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-03-15 01:01:29 -07:00
|
|
|
/* FAQs & Feedback */
|
2019-05-11 01:20:07 -07:00
|
|
|
{
|
2020-08-03 21:47:39 -07:00
|
|
|
const {management, runtime: {onInstalled, setUninstallURL, getManifest}, storage, tabs} = chrome;
|
2020-03-15 01:01:29 -07:00
|
|
|
if (navigator.webdriver !== true) {
|
2020-08-03 21:47:39 -07:00
|
|
|
const page = getManifest().homepage_url;
|
|
|
|
const {name, version} = getManifest();
|
2020-03-15 01:01:29 -07:00
|
|
|
onInstalled.addListener(({reason, previousVersion}) => {
|
2020-08-03 21:47:39 -07:00
|
|
|
management.getSelf(({installType}) => installType === 'normal' && storage.local.get({
|
2020-03-15 01:01:29 -07:00
|
|
|
'faqs': true,
|
|
|
|
'last-update': 0
|
|
|
|
}, prefs => {
|
|
|
|
if (reason === 'install' || (prefs.faqs && reason === 'update')) {
|
|
|
|
const doUpdate = (Date.now() - prefs['last-update']) / 1000 / 60 / 60 / 24 > 45;
|
|
|
|
if (doUpdate && previousVersion !== version) {
|
2020-12-28 05:43:19 -08:00
|
|
|
tabs.query({active: true, currentWindow: true}, tbs => tabs.create({
|
2020-08-03 21:47:39 -07:00
|
|
|
url: page + '?version=' + version + (previousVersion ? '&p=' + previousVersion : '') + '&type=' + reason,
|
2020-12-28 05:43:19 -08:00
|
|
|
active: reason === 'install',
|
2020-12-29 01:39:24 -08:00
|
|
|
...(tbs && tbs.length && {index: tbs[0].index + 1})
|
2020-12-28 05:43:19 -08:00
|
|
|
}));
|
2020-08-03 21:47:39 -07:00
|
|
|
storage.local.set({'last-update': Date.now()});
|
2020-03-15 01:01:29 -07:00
|
|
|
}
|
2019-05-11 01:20:07 -07:00
|
|
|
}
|
2020-08-03 21:47:39 -07:00
|
|
|
}));
|
2017-09-14 05:50:53 -07:00
|
|
|
});
|
2020-03-15 01:01:29 -07:00
|
|
|
setUninstallURL(page + '?rd=feedback&name=' + encodeURIComponent(name) + '&version=' + version);
|
|
|
|
}
|
2017-09-14 05:50:53 -07:00
|
|
|
}
|