diff --git a/common.js b/common.js index b568c0f..8b727ba 100644 --- a/common.js +++ b/common.js @@ -2,27 +2,134 @@ '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 = { ua: '', blacklist: [], whitelist: [], custom: {}, - mode: 'blacklist' + mode: 'blacklist', + color: '#ffa643' }; - chrome.storage.local.get(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 => { Object.keys(ps).forEach(key => prefs[key] = ps[key].newValue); 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) { const s = url.indexOf('//') + 2; if (s > 1) { @@ -45,7 +152,7 @@ function hostname(url) { } } // 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.blacklist.length) { const h = hostname(url); @@ -69,34 +176,22 @@ function match(url) { s = s[Math.floor(Math.random() * s.length)]; } if (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; + return ua.parse(s); } 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}) => { if (type === 'main_frame') { - cache[tabId] = match(url); + cache[tabId] = match({url, tabId}); } if (cache[tabId] === true) { return; } - const str = (cache[tabId] || ua).userAgent; + const str = (cache[tabId] || ua.object(tabId)).userAgent; if (str) { for (let i = 0, name = requestHeaders[0].name; i < requestHeaders.length; i += 1, name = requestHeaders[i].name) { if (name === 'User-Agent' || name === 'user-agent') { @@ -114,7 +209,7 @@ var onCommitted = ({frameId, url, tabId}) => { if (cache[tabId] === true) { return; } - const o = cache[tabId] || ua; + const o = cache[tabId] || ua.object(tabId); if (o.userAgent) { chrome.tabs.executeScript(tabId, { runAt: 'document_start', @@ -128,52 +223,17 @@ var onCommitted = ({frameId, url, tabId}) => { navigator.__defineGetter__('vendor', () => '${o.vendor}'); }\`; document.documentElement.appendChild(script); + script.remove(); }` }, () => 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 chrome.storage.local.get({ 'version': null, diff --git a/data/popup/index.css b/data/popup/index.css index abb1ebe..0019eb4 100644 --- a/data/popup/index.css +++ b/data/popup/index.css @@ -156,6 +156,7 @@ select { margin-left: 2px; margin-right: 2px; } +[data-cmd="window"], [data-cmd="apply"] { color: #fff; background-color: #3c923c; @@ -170,6 +171,7 @@ select { border: solid 1px #ec9730; } +[data-cmd="reload"], [data-cmd="options"], [data-cmd="refresh"] { color: #000; @@ -177,6 +179,5 @@ select { } #explore:not([data-loaded="true"]) { - margin-top: -8px; height: 16px; } diff --git a/data/popup/index.html b/data/popup/index.html index 60b1212..eae5093 100644 --- a/data/popup/index.html +++ b/data/popup/index.html @@ -146,16 +146,18 @@
- + +
- User-Agent String:  - - + User-Agent String:  + - + +
+
diff --git a/data/popup/index.js b/data/popup/index.js index 7737c05..152d507 100644 --- a/data/popup/index.js +++ b/data/popup/index.js @@ -96,6 +96,7 @@ document.addEventListener('change', ({target}) => { } if (target.type === 'radio') { 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 => { if (prefs.ua) { document.getElementById('ua').value = prefs.ua.newValue || navigator.userAgent; + document.getElementById('ua').dispatchEvent(new Event('input')); } }); window.addEventListener('load', () => { @@ -145,7 +147,7 @@ window.addEventListener('load', () => { function msg(msg) { const info = document.getElementById('info'); info.textContent = msg; - window.setTimeout(() => info.textContent = '', 750); + window.setTimeout(() => info.textContent = 'User-Agent String:', 2000); } // commands @@ -155,7 +157,7 @@ document.addEventListener('click', ({target}) => { if (cmd === 'apply') { const value = document.getElementById('ua').value; if (value === navigator.userAgent) { - msg('Default user-agent'); + msg('Default UA, press the reset button instead'); } else { msg('user-agent is set'); @@ -164,6 +166,13 @@ document.addEventListener('click', ({target}) => { 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') { const input = document.querySelector('#list :checked'); if (input) { @@ -185,5 +194,13 @@ document.addEventListener('click', ({target}) => { else if (cmd === 'options') { 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 === ''; +});