supporting custom ua string per container

This commit is contained in:
Ray Lothian 2020-08-04 16:11:00 +02:00
parent bf16cb694a
commit 2a317e5aec
5 changed files with 112 additions and 67 deletions

View file

@ -5,8 +5,16 @@
const cache = {}; const cache = {};
const tabs = {}; const tabs = {};
chrome.tabs.onRemoved.addListener(id => delete cache[id]); const cookieStoreIds = {};
chrome.tabs.onCreated.addListener(tab => tabs[tab.id] = tab.windowId); 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;
});
const prefs = { const prefs = {
ua: '', ua: '',
@ -50,7 +58,10 @@ chrome.storage.local.get(prefs, ps => {
} }
expand(); expand();
chrome.tabs.query({}, ts => { chrome.tabs.query({}, ts => {
ts.forEach(t => tabs[t.id] = t.windowId); ts.forEach(t => {
tabs[t.id] = t.windowId;
cookieStoreIds[t.id] = t.cookieStoreId;
});
ua.update(); ua.update();
}); });
}); });
@ -84,7 +95,6 @@ chrome.storage.local.get(prefs, ps => {
}, () => chrome.runtime.lastError); }, () => chrome.runtime.lastError);
}); });
chrome.storage.onChanged.addListener(ps => { chrome.storage.onChanged.addListener(ps => {
console.log(ps);
Object.keys(ps).forEach(key => prefs[key] = ps[key].newValue); Object.keys(ps).forEach(key => prefs[key] = ps[key].newValue);
if (ps.ua || ps.mode) { if (ps.ua || ps.mode) {
ua.update(); ua.update();
@ -95,17 +105,19 @@ chrome.storage.onChanged.addListener(ps => {
}); });
const ua = { const ua = {
_obj: { _obj: {},
'global': {} diff(tabId, cookieStoreId = 'default-container') { // returns true if there is per window object
}, log('ua.diff is called', tabId, cookieStoreId);
diff(tabId) { // returns true if there is per window object
log('ua.diff is called', tabId);
const windowId = tabs[tabId]; const windowId = tabs[tabId];
return windowId in this._obj; return windowId in (this._obj[cookieStoreId] || this._obj['default-container'] || {});
}, },
get windows() { get windows() {
log('ua.windows is called'); log('ua.windows is called');
return Object.keys(this._obj).filter(id => id !== 'global').map(s => Number(s)); 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);
}, },
parse: s => { parse: s => {
log('ua.parse is called', s); log('ua.parse is called', s);
@ -159,18 +171,23 @@ const ua = {
} }
return o; return o;
}, },
object(tabId, windowId) { object(tabId, windowId, cookieStoreId = 'default-container') {
log('ua.object is called', tabId, windowId);
windowId = windowId || (tabId ? tabs[tabId] : 'global'); windowId = windowId || (tabId ? tabs[tabId] : 'global');
return this._obj[windowId] || this._obj.global; log('ua.object is called', tabId, windowId, cookieStoreId);
if (this._obj[cookieStoreId]) {
return this._obj[cookieStoreId][windowId] || this._obj[cookieStoreId].global || this._obj['default-container'].global;
}
return (this._obj['default-container'] || {}).global;
}, },
string(str, windowId) { string(str, windowId, cookieStoreId) {
log('ua.string is called', str, windowId); log('ua.string is called', str, windowId);
this._obj[cookieStoreId] = this._obj[cookieStoreId] || {};
if (str) { if (str) {
this._obj[windowId] = this.parse(str); this._obj[cookieStoreId][windowId] = this.parse(str);
} }
else { else {
this._obj[windowId] = {}; this._obj[cookieStoreId][windowId] = {};
} }
}, },
tooltip(title, tabId) { tooltip(title, tabId) {
@ -195,7 +212,7 @@ const ua = {
} }
}); });
}, },
toolbar: ({windowId, tabId}) => { toolbar: ({windowId, tabId, cookieStoreId}) => {
log('ua.toolbar is called', windowId, tabId); log('ua.toolbar is called', windowId, tabId);
if (windowId) { if (windowId) {
chrome.tabs.query({ chrome.tabs.query({
@ -204,20 +221,19 @@ const ua = {
const tabId = tab.id; const tabId = tab.id;
chrome.browserAction.setBadgeText({ chrome.browserAction.setBadgeText({
tabId, tabId,
text: ua.object(null, windowId).platform.substr(0, 3) text: ua.object(null, windowId, tab.cookieStoreId).platform.substr(0, 3)
}); });
})); }));
} }
else if (tabId) { else if (tabId) {
chrome.browserAction.setBadgeText({ chrome.browserAction.setBadgeText({
tabId, tabId,
text: ua.object(tabId).platform.substr(0, 3) text: ua.object(tabId, undefined, cookieStoreId).platform.substr(0, 3)
}); });
} }
}, },
update(str = prefs.ua, windowId = 'global') { update(str = prefs.ua, windowId = 'global', cookieStoreId = 'default-container') {
console.log(str); log('ua.update is called', str, windowId, cookieStoreId);
log('ua.update is called', str, windowId);
// clear caching // clear caching
Object.keys(cache).forEach(key => delete cache[key]); Object.keys(cache).forEach(key => delete cache[key]);
// remove old listeners // remove old listeners
@ -225,7 +241,7 @@ const ua = {
chrome.webNavigation.onCommitted.removeListener(onCommitted); chrome.webNavigation.onCommitted.removeListener(onCommitted);
// apply new ones // apply new ones
if (str || prefs.mode === 'custom' || this.windows.length) { if (str || prefs.mode === 'custom' || this.windows.length) {
ua.string(str, windowId); ua.string(str, windowId, cookieStoreId);
chrome.webRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, { chrome.webRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, {
'urls': ['*://*/*'] 'urls': ['*://*/*']
}, ['blocking', 'requestHeaders']); }, ['blocking', 'requestHeaders']);
@ -250,7 +266,11 @@ const ua = {
window.ua = ua; // using from popup window.ua = ua; // using from popup
// make sure to clean on window removal // make sure to clean on window removal
if (chrome.windows) { // FF on Android if (chrome.windows) { // FF on Android
chrome.windows.onRemoved.addListener(windowId => delete ua._obj[windowId]); chrome.windows.onRemoved.addListener(windowId => {
Object.keys(ua._obj).forEach(cookieStoreId => {
delete ua._obj[cookieStoreId][windowId];
});
});
} }
function hostname(url) { function hostname(url) {
@ -276,8 +296,8 @@ function hostname(url) {
} }
} }
// returns true, false or an object; true: ignore, false: use from ua object. // returns true, false or an object; true: ignore, false: use from ua object.
function match({url, tabId}) { function match({url, tabId, cookieStoreId = 'default-container'}) {
log('match', url, tabId); log('match', url, tabId, cookieStoreId);
const h = hostname(url); const h = hostname(url);
if (prefs.mode === 'blacklist') { if (prefs.mode === 'blacklist') {
@ -307,55 +327,57 @@ function match({url, tabId}) {
return true; return true;
} }
} }
else { const [hh] = h.split(':');
const [hh] = h.split(':'); const key = Object.keys(expand.rules).filter(s => {
const key = Object.keys(expand.rules).filter(s => { if (s === h || s === hh) {
if (s === h || s === hh) { return true;
return true; }
else if (prefs.exactMatch === false) {
return s.endsWith('.' + h) || h.endsWith('.' + s) || s.endsWith('.' + hh) || hh.endsWith('.' + s);
}
}).shift();
let s = expand.rules[key] || expand.rules['*'];
// 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;
} }
else if (prefs.exactMatch === false) { else if (expand.rules._.indexOf('*') !== -1) {
return s.endsWith('.' + h) || h.endsWith('.' + s) || s.endsWith('.' + hh) || hh.endsWith('.' + s); if (expand.rules[key]) {
}
}).shift();
let s = expand.rules[key] || expand.rules['*'];
// 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; expand.rules[key] = s;
} }
else if (expand.rules._.indexOf('*') !== -1) { else if (expand.rules['*']) {
if (expand.rules[key]) { expand.rules['*'] = s;
expand.rules[key] = s;
}
else if (expand.rules['*']) {
expand.rules['*'] = s;
}
} }
} }
} }
if (s) { }
return ua.parse(s); if (s) {
} return ua.parse(s);
else { }
return !ua.object(tabId).userAgent; else {
} const o = ua.object(tabId, undefined, cookieStoreId);
return o ? !o.userAgent : true;
} }
} }
const onBeforeSendHeaders = ({tabId, url, requestHeaders, type}) => { const onBeforeSendHeaders = d => {
const {tabId, url, requestHeaders, type, cookieStoreId} = d;
if (type === 'main_frame' || prefs.cache === false) { if (type === 'main_frame' || prefs.cache === false) {
cache[tabId] = match({url, tabId}); cache[tabId] = match({url, tabId, cookieStoreId});
} }
console.log(cache[tabId]);
if (cache[tabId] === true) { if (cache[tabId] === true) {
return {}; return {};
} }
if (prefs.protected.some(s => url.indexOf(s) !== -1)) { if (prefs.protected.some(s => url.indexOf(s) !== -1)) {
return {}; return {};
} }
const str = (cache[tabId] || ua.object(tabId)).userAgent; const str = (cache[tabId] || ua.object(tabId, undefined, d.cookieStoreId)).userAgent;
if (str) { if (str) {
for (let i = 0, name = requestHeaders[0].name; i < requestHeaders.length; i += 1, name = (requestHeaders[i] || {}).name) { for (let i = 0, name = requestHeaders[0].name; i < requestHeaders.length; i += 1, name = (requestHeaders[i] || {}).name) {
if (name === 'User-Agent' || name === 'user-agent') { if (name === 'User-Agent' || name === 'user-agent') {
@ -368,12 +390,15 @@ const onBeforeSendHeaders = ({tabId, url, requestHeaders, type}) => {
} }
}; };
const onCommitted = ({frameId, url, tabId}) => { const onCommitted = d => {
const {frameId, url, tabId} = d;
const cookieStoreId = cookieStoreIds[tabId] || 'default-container';
if (url && (url.startsWith('http') || url.startsWith('ftp')) || url === 'about:blank') { if (url && (url.startsWith('http') || url.startsWith('ftp')) || url === 'about:blank') {
if (cache[tabId] === true) { if (cache[tabId] === true) {
return; return;
} }
const o = cache[tabId] || ua.object(tabId); const o = cache[tabId] || ua.object(tabId, undefined, cookieStoreId);
if (o.userAgent) { if (o.userAgent) {
chrome.tabs.executeScript(tabId, { chrome.tabs.executeScript(tabId, {
runAt: 'document_start', runAt: 'document_start',
@ -416,8 +441,8 @@ const onCommitted = ({frameId, url, tabId}) => {
} }
} }
// change the toolbar icon if there is a per window UA setting // change the toolbar icon if there is a per window UA setting
if (frameId === 0 && ua.diff(tabId)) { if (frameId === 0 && ua.diff(tabId, cookieStoreId)) {
ua.toolbar({tabId}); ua.toolbar({tabId, cookieStoreId});
} }
}; };
// context menu // context menu

View file

@ -190,6 +190,7 @@ select {
background-color: #4d72b7; background-color: #4d72b7;
color: #fff; color: #fff;
} }
[data-cmd="container"],
[data-cmd="window"], [data-cmd="window"],
[data-cmd="apply"] { [data-cmd="apply"] {
color: #fff; color: #fff;

View file

@ -127,7 +127,7 @@
<input type="button" value="Refresh Tab" title="Refresh the current page" data-cmd="refresh"> <input type="button" value="Refresh Tab" title="Refresh the current page" data-cmd="refresh">
<input type="button" value="Reset" title="Reset User-Agent string to the default one. This will not reset window-based UA strings. To reset them, use the 'Restart' button" data-cmd="reset"> <input type="button" value="Reset" title="Reset User-Agent string to the default one. This will not reset window-based UA strings. To reset them, use the 'Restart' button" data-cmd="reset">
<input type="button" value="Test" title="Test your user-agent string" data-cmd="test"> <input type="button" value="Test" title="Test your user-agent string" data-cmd="test">
<span></span> <input type="button" value="Container" title="Set this string as this container's User-Agent string" data-cmd="container">
<input type="button" value="Window" title="Set this string as this window's User-Agent string" data-cmd="window"> <input type="button" value="Window" title="Set this string as this window's User-Agent string" data-cmd="window">
<input type="button" value="Apply" title="Set this string as the browser's User-Agent string" data-cmd="apply"> <input type="button" value="Apply" title="Set this string as the browser's User-Agent string" data-cmd="apply">
</div> </div>

View file

@ -237,12 +237,27 @@ document.addEventListener('click', ({target}) => {
} }
}); });
} }
else if (cmd === 'window') { else if (cmd === 'window' || cmd === 'container') {
const value = document.getElementById('ua').value; const value = document.getElementById('ua').value;
chrome.tabs.query({ const next = () => chrome.tabs.query({
active: true, active: true,
currentWindow: true currentWindow: true
}, ([tab]) => chrome.runtime.getBackgroundPage(bg => bg.ua.update(value, tab.windowId))); }, ([tab]) => {
if (cmd === 'window') {
chrome.runtime.getBackgroundPage(bg => bg.ua.update(value, tab.windowId, tab.cookieStoreId));
}
else {
chrome.runtime.getBackgroundPage(bg => bg.ua.update(value, undefined, tab.cookieStoreId));
}
});
if (cmd === 'container') {
chrome.permissions.request({
permissions: ['cookies']
}, granted => granted && next());
}
else {
next();
}
} }
else if (cmd === 'reset') { else if (cmd === 'reset') {
const input = document.querySelector('#list :checked'); const input = document.querySelector('#list :checked');

View file

@ -13,8 +13,12 @@
"webNavigation", "webNavigation",
"webRequest", "webRequest",
"webRequestBlocking", "webRequestBlocking",
"contextualIdentities",
"contextMenus" "contextMenus"
], ],
"optional_permissions": [
"cookies"
],
"icons": { "icons": {
"16": "data/icons/active/16.png", "16": "data/icons/active/16.png",