This commit is contained in:
Ray Lothian 2018-08-16 14:44:11 +04:30
parent 5d9b0e6b12
commit d1b29b805a
4 changed files with 151 additions and 71 deletions

186
common.js
View file

@ -2,27 +2,134 @@
'use strict'; 'use strict';
var ua = {}; var cache = {};
var tabs = {};
chrome.tabs.onRemoved.addListener(id => delete cache[id]);
chrome.tabs.onCreated.addListener(tab => tabs[tab.id] = tab.windowId);
var prefs = { var prefs = {
ua: '', ua: '',
blacklist: [], blacklist: [],
whitelist: [], whitelist: [],
custom: {}, custom: {},
mode: 'blacklist' mode: 'blacklist',
color: '#ffa643'
}; };
chrome.storage.local.get(prefs, ps => { chrome.storage.local.get(prefs, ps => {
Object.assign(prefs, ps); Object.assign(prefs, ps);
update(); chrome.tabs.query({}, ts => {
ts.forEach(t => tabs[t.id] = t.windowId);
ua.update();
});
chrome.browserAction.setBadgeBackgroundColor({
color: prefs.color
});
}); });
chrome.storage.onChanged.addListener(ps => { chrome.storage.onChanged.addListener(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) {
update(); ua.update();
} }
}); });
var ua = {
_obj: {
'global': {}
},
diff(tabId) { // returns true if there is per window object
const windowId = tabs[tabId];
return windowId in this._obj;
},
get windows() {
return Object.keys(this._obj).filter(id => id !== 'global').map(s => Number(s));
},
parse: s => {
const o = {};
o.userAgent = s;
o.appVersion = s
.replace(/^Mozilla\//, '')
.replace(/^Opera\//, '');
const p = new UAParser(s);
o.platform = p.getOS().name || '';
o.vendor = p.getDevice().vendor || '';
return o;
},
object(tabId, windowId) {
windowId = windowId || (tabId ? tabs[tabId] : 'global');
return this._obj[windowId] || this._obj.global;
},
string(str, windowId) {
if (str) {
this._obj[windowId] = this.parse(str);
}
else {
this._obj[windowId] = {};
}
},
toolbar: ({windowId, tabId, str = ua.object(tabId, windowId).userAgent}) => {
console.log(windowId, tabId, str);
const icon = {
path: {
16: 'data/icons/' + (str ? 'active/' : '') + '16.png',
32: 'data/icons/' + (str ? 'active/' : '') + '32.png',
48: 'data/icons/' + (str ? 'active/' : '') + '48.png',
64: 'data/icons/' + (str ? 'active/' : '') + '64.png'
}
};
const custom = 'Mapped from user\'s JSON object if found, otherwise uses "' + (str || navigator.userAgent) + '"';
const title = {
title: `UserAgent Switcher (${str ? 'enabled' : 'disabled'})
User-Agent String: ${prefs.mode === 'custom' ? custom : str || navigator.userAgent}`
};
if (windowId) {
chrome.tabs.query({
windowId
}, tabs => tabs.forEach(tab => {
const tabId = tab.id;
chrome.browserAction.setTitle(Object.assign({tabId}, title));
chrome.browserAction.setBadgeText({
tabId,
text: ua.object(null, windowId).platform.substr(0, 3)
});
}));
}
else if (tabId) {
chrome.browserAction.setTitle(Object.assign({tabId}, title));
chrome.browserAction.setBadgeText({
tabId,
text: ua.object(tabId).platform.substr(0, 3)
});
}
else {
chrome.browserAction.setIcon(icon);
chrome.browserAction.setTitle(title);
}
},
update(str = prefs.ua, windowId = 'global') {
chrome.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders);
chrome.webNavigation.onCommitted.removeListener(onCommitted);
if (str || prefs.mode === 'custom' || this.windows.length) {
ua.string(str, windowId);
chrome.webRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, {
'urls': ['*://*/*']
}, ['blocking', 'requestHeaders']);
chrome.webNavigation.onCommitted.addListener(onCommitted);
}
if (windowId === 'global') {
this.toolbar({str});
}
// update per window
else {
this.windows.forEach(windowId => this.toolbar({windowId}));
}
}
};
// make sure to clean on window removal
chrome.windows.onRemoved.addListener(windowId => delete ua._obj[windowId]);
function hostname(url) { function hostname(url) {
const s = url.indexOf('//') + 2; const s = url.indexOf('//') + 2;
if (s > 1) { if (s > 1) {
@ -45,7 +152,7 @@ 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) { function match({url, tabId}) {
if (prefs.mode === 'blacklist') { if (prefs.mode === 'blacklist') {
if (prefs.blacklist.length) { if (prefs.blacklist.length) {
const h = hostname(url); const h = hostname(url);
@ -69,34 +176,22 @@ function match(url) {
s = s[Math.floor(Math.random() * s.length)]; s = s[Math.floor(Math.random() * s.length)];
} }
if (s) { if (s) {
const o = {}; return ua.parse(s);
o.userAgent = s;
o.appVersion = s
.replace(/^Mozilla\//, '')
.replace(/^Opera\//, '');
const p = new UAParser(s);
o.platform = p.getOS().name || '';
o.vendor = p.getDevice().vendor || '';
return o;
} }
else { else {
return !ua.userAgent; return !ua.object(tabId).userAgent;
} }
} }
} }
var cache = {};
chrome.tabs.onRemoved.addListener(id => delete cache[id]);
var onBeforeSendHeaders = ({tabId, url, requestHeaders, type}) => { var onBeforeSendHeaders = ({tabId, url, requestHeaders, type}) => {
if (type === 'main_frame') { if (type === 'main_frame') {
cache[tabId] = match(url); cache[tabId] = match({url, tabId});
} }
if (cache[tabId] === true) { if (cache[tabId] === true) {
return; return;
} }
const str = (cache[tabId] || ua).userAgent; const str = (cache[tabId] || ua.object(tabId)).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') {
@ -114,7 +209,7 @@ var onCommitted = ({frameId, url, tabId}) => {
if (cache[tabId] === true) { if (cache[tabId] === true) {
return; return;
} }
const o = cache[tabId] || ua; const o = cache[tabId] || ua.object(tabId);
if (o.userAgent) { if (o.userAgent) {
chrome.tabs.executeScript(tabId, { chrome.tabs.executeScript(tabId, {
runAt: 'document_start', runAt: 'document_start',
@ -128,52 +223,17 @@ var onCommitted = ({frameId, url, tabId}) => {
navigator.__defineGetter__('vendor', () => '${o.vendor}'); navigator.__defineGetter__('vendor', () => '${o.vendor}');
}\`; }\`;
document.documentElement.appendChild(script); document.documentElement.appendChild(script);
script.remove();
}` }`
}, () => chrome.runtime.lastError); }, () => chrome.runtime.lastError);
} }
} }
// change the toolbar icon if there is a per window UA setting
if (frameId === 0 && ua.diff(tabId)) {
ua.toolbar({tabId});
}
}; };
function update() {
if (prefs.ua || prefs.mode === 'custom') {
if (prefs.ua) {
ua.userAgent = prefs.ua;
ua.appVersion = ua.userAgent
.replace(/^Mozilla\//, '')
.replace(/^Opera\//, '');
const p = new UAParser(prefs.ua);
ua.platform = p.getOS().name || '';
ua.vendor = p.getDevice().vendor || '';
}
else {
ua = {};
}
chrome.webRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, {
'urls' : ['*://*/*']
}, ['blocking', 'requestHeaders']);
chrome.webNavigation.onCommitted.addListener(onCommitted);
}
else {
chrome.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders);
chrome.webNavigation.onCommitted.removeListener(onCommitted);
}
chrome.browserAction.setIcon({
path: {
16: 'data/icons/' + (prefs.ua ? 'active/' : '') + '16.png',
32: 'data/icons/' + (prefs.ua ? 'active/' : '') + '32.png',
48: 'data/icons/' + (prefs.ua ? 'active/' : '') + '48.png',
64: 'data/icons/' + (prefs.ua ? 'active/' : '') + '64.png'
}
});
const custom = 'Mapped from user\'s JSON object if found, otherwise uses "' + (prefs.ua || navigator.userAgent) + '"';
chrome.browserAction.setTitle({
title: `UserAgent Switcher (${prefs.ua ? 'enabled' : 'disabled'})
User-Agent String: ${prefs.mode === 'custom' ? custom : prefs.ua || navigator.userAgent}`
});
}
// FAQs & Feedback // FAQs & Feedback
chrome.storage.local.get({ chrome.storage.local.get({
'version': null, 'version': null,

View file

@ -156,6 +156,7 @@ select {
margin-left: 2px; margin-left: 2px;
margin-right: 2px; margin-right: 2px;
} }
[data-cmd="window"],
[data-cmd="apply"] { [data-cmd="apply"] {
color: #fff; color: #fff;
background-color: #3c923c; background-color: #3c923c;
@ -170,6 +171,7 @@ select {
border: solid 1px #ec9730; border: solid 1px #ec9730;
} }
[data-cmd="reload"],
[data-cmd="options"], [data-cmd="options"],
[data-cmd="refresh"] { [data-cmd="refresh"] {
color: #000; color: #000;
@ -177,6 +179,5 @@ select {
} }
#explore:not([data-loaded="true"]) { #explore:not([data-loaded="true"]) {
margin-top: -8px;
height: 16px; height: 16px;
} }

View file

@ -146,16 +146,18 @@
</div> </div>
<div hbox> <div hbox>
<input type="search" id="custom" placeholder="Filter items"> <input type="search" id="custom" placeholder="Filter items">
<input type="button" value="Refresh Tab" title="Refresh the current page" data-cmd="refresh">
<input type="button" value="Options" title="Open options page" style="margin-left: 2px;" data-cmd="options"> <input type="button" value="Options" title="Open options page" style="margin-left: 2px;" data-cmd="options">
<input type="button" value="Refresh Tab" title="Refresh the current page" data-cmd="refresh">
<input type="button" value="Restart" title="Click to reload the extension. This will cause all the window-based user-agent strings to be cleared" data-cmd="reload">
</div> </div>
<div hbox id="agent" pack="center" align="center"> <div hbox id="agent" pack="center" align="center">
User-Agent String:&nbsp; <span id="info">User-Agent String:</span>&nbsp;
<input id="ua" type="text" placeholder="Your preferred user-agent string"> <span flex="1"></span>
<span id="info"></span>
<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">
<input type="button" value="Reset" title="Reset User-Agent string to the default one" style="margin-left: 2px;" data-cmd="reset"> <input type="button" value="Window" title="Set this string as this window's User-Agent string" data-cmd="window">
<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" style="margin-left: 2px;" data-cmd="reset">
</div> </div>
<input id="ua" type="text" placeholder="Your preferred user-agent string">
<div id="explore"></div> <div id="explore"></div>
<script src="ua-parser.min.js"></script> <script src="ua-parser.min.js"></script>
<script src="index.js"></script> <script src="index.js"></script>

View file

@ -96,6 +96,7 @@ document.addEventListener('change', ({target}) => {
} }
if (target.type === 'radio') { if (target.type === 'radio') {
document.getElementById('ua').value = target.closest('tr').querySelector('td:nth-child(4)').textContent; document.getElementById('ua').value = target.closest('tr').querySelector('td:nth-child(4)').textContent;
document.getElementById('ua').dispatchEvent(new Event('input'));
} }
}); });
@ -127,6 +128,7 @@ chrome.storage.local.get({
chrome.storage.onChanged.addListener(prefs => { chrome.storage.onChanged.addListener(prefs => {
if (prefs.ua) { if (prefs.ua) {
document.getElementById('ua').value = prefs.ua.newValue || navigator.userAgent; document.getElementById('ua').value = prefs.ua.newValue || navigator.userAgent;
document.getElementById('ua').dispatchEvent(new Event('input'));
} }
}); });
window.addEventListener('load', () => { window.addEventListener('load', () => {
@ -145,7 +147,7 @@ window.addEventListener('load', () => {
function msg(msg) { function msg(msg) {
const info = document.getElementById('info'); const info = document.getElementById('info');
info.textContent = msg; info.textContent = msg;
window.setTimeout(() => info.textContent = '', 750); window.setTimeout(() => info.textContent = 'User-Agent String:', 2000);
} }
// commands // commands
@ -155,7 +157,7 @@ document.addEventListener('click', ({target}) => {
if (cmd === 'apply') { if (cmd === 'apply') {
const value = document.getElementById('ua').value; const value = document.getElementById('ua').value;
if (value === navigator.userAgent) { if (value === navigator.userAgent) {
msg('Default user-agent'); msg('Default UA, press the reset button instead');
} }
else { else {
msg('user-agent is set'); msg('user-agent is set');
@ -164,6 +166,13 @@ document.addEventListener('click', ({target}) => {
ua: value === navigator.userAgent ? '' : value ua: value === navigator.userAgent ? '' : value
}); });
} }
else if (cmd === 'window') {
const value = document.getElementById('ua').value;
chrome.tabs.query({
active: true,
currentWindow: true
}, ([tab]) => chrome.runtime.getBackgroundPage(bg => bg.ua.update(value, tab.windowId)));
}
else if (cmd === 'reset') { else if (cmd === 'reset') {
const input = document.querySelector('#list :checked'); const input = document.querySelector('#list :checked');
if (input) { if (input) {
@ -185,5 +194,13 @@ document.addEventListener('click', ({target}) => {
else if (cmd === 'options') { else if (cmd === 'options') {
chrome.runtime.openOptionsPage(); chrome.runtime.openOptionsPage();
} }
else if (cmd === 'reload') {
chrome.runtime.reload();
}
} }
}); });
document.getElementById('ua').addEventListener('input', e => {
document.querySelector('[data-cmd=apply]').disabled = e.target.value === '';
document.querySelector('[data-cmd=window]').disabled = e.target.value === '';
});