Browse Source

fix #2150: expose existing userAgentData in GM_info

* fix Vivaldi detection
* simplify UA: remove browserBrand and short aliases
tophf 1 year ago
parent
commit
e5a9f063a3

+ 1 - 1
package.json

@@ -35,7 +35,7 @@
     "@types/jest": "^29.5.3",
     "@typescript-eslint/eslint-plugin": "^6.4.0",
     "@typescript-eslint/parser": "^6.4.0",
-    "@violentmonkey/types": "0.1.5",
+    "@violentmonkey/types": "0.1.9",
     "@vue/compiler-sfc": "^3.3.4",
     "@vue/eslint-config-typescript": "^11.0.3",
     "amo-upload": "^0.5.5",

+ 1 - 6
src/background/utils/db.js

@@ -291,7 +291,6 @@ export function getScriptsByURL(url, isTop, errors, prevIds) {
   /** @type {VMInjection.EnvDelayed} */
   let envDelayed;
   let clipboardChecked = isDelayed || !IS_FIREFOX;
-  let xhrChecked = isDelayed;
   testerBatch(errors || true);
   for (const script of aliveScripts) {
     const {
@@ -326,15 +325,11 @@ export function getScriptsByURL(url, isTop, errors, prevIds) {
     if (meta.grant.some(GMVALUES_RE.test, GMVALUES_RE)) {
       env[VALUE_IDS].push(id);
     }
-    if (!clipboardChecked || !xhrChecked) {
+    if (!clipboardChecked) {
       for (const g of meta.grant) {
         if (!clipboardChecked && (g === 'GM_setClipboard' || g === 'GM.setClipboard')) {
           clipboardChecked = envStart.clipFF = true;
         }
-        if (!xhrChecked && (g === 'GM_xmlhttpRequest' || g === 'GM.xmlHttpRequest'
-        || g === 'GM_download' || g === 'GM.download')) {
-          xhrChecked = envStart.xhr = true;
-        }
       }
     }
     for (const [list, name, dataUriDecoder] of [

+ 0 - 9
src/background/utils/preinject.js

@@ -28,7 +28,6 @@ let isApplied;
 let injectInto;
 let ffInject;
 let xhrInject = false; // must be initialized for proper comparison when toggling
-let vivaldiChecked = IS_FIREFOX;
 
 const sessionId = getUniqId();
 const API_HEADERS_RECEIVED = browser.webRequest.onHeadersReceived;
@@ -148,7 +147,6 @@ addPublicCommands({
     const frameDoc = getFrameDocId(isTop, src[kDocumentId], frameId);
     const tabId = tab.id;
     if (!url) url = src.url || tab.url;
-    if (!vivaldiChecked) checkVivaldi(tab);
     clearFrameData(tabId, frameDoc);
     let skip = skippedTabs[tabId];
     if (skip > 0) { // first time loading the tab after skipScripts was invoked
@@ -658,13 +656,6 @@ function clearFrameData(tabId, frameId, tabRemoved) {
   clearNotifications(tabId, frameId, tabRemoved);
 }
 
-function checkVivaldi(tab) {
-  vivaldiChecked = true;
-  if (tab.vivExtData/*new*/ || tab.extData/*old*/) {
-    ua.brand = ua.browserBrand = 'Vivaldi';
-  }
-}
-
 function sendPopupShown(tabId, frameDoc) {
   setTimeout(sendTabCmd, 0, tabId, 'PopupShown', true, getFrameDocIdAsObj(frameDoc));
 }

+ 15 - 28
src/background/utils/ua.js

@@ -1,38 +1,22 @@
+import { browserWindows } from '@/common';
 import { addOwnCommands, init } from './init';
 
-export const navUA = navigator.userAgent;
-export const navUAD = navigator.userAgentData;
-const info = (() => {
-  let brand, ver, full;
-  if (navUAD) {
-    full = navUAD.getHighEntropyValues(['uaFullVersion']);
-    [brand, ver] = navUAD.brands.map(({ brand: b, version }) => `${
-      /Not[^a-z]*A[^a-z]*Brand/i.test(b) ? '4' :
-        b === 'Chromium' ? '3' + b :
-          b === 'Google Chrome' ? '2' + b :
-            '1' + b
-    }\n${version}`).sort()[0]?.slice(1).split('\n') || [];
-  } else {
-    ver = navUA.match(/\s(?:Chrom(?:e|ium)|Firefox)\/(\d+[.0-9]*)|$/i)[1];
-  }
-  return {
-    [IS_FIREFOX ? 'FF' : 'CH']: parseFloat(ver) || 1,
-    brand,
-    full,
-    ver,
-  };
-})();
+export const {
+  userAgent: navUA,
+  userAgentData: navUAD,
+} = navigator;
+const uaVer = navUA.match(/\s(?:Chrom(?:e|ium)|Firefox)\/(\d+[.0-9]*)|$/i)[1];
 
 /** @type {VMScriptGMInfoPlatform} */
 export const ua = {};
 /** @type {number|void} This value can be trusted because the only way to spoof it in Chrome/ium
  * is to manually open devtools for the background page in device emulation mode.
  * Using `void` for numeric comparisons like CHROME < 100 to be false in Firefox */
-export const CHROME = info.CH;
+export const CHROME = !IS_FIREFOX ? parseFloat(uaVer) || 1 : undefined;
 /** @type {number|void} DANGER! Until init is done the only sure thing about this value
  * is whether it's truthy, because UA can be overridden by about:config.
  * Using `void` for numeric comparisons like FIREFOX < 100 to be false in Chrome */
-export let FIREFOX = info.FF;
+export let FIREFOX = IS_FIREFOX ? parseFloat(uaVer) || 1 : undefined;
 
 addOwnCommands({
   UA: () => ua,
@@ -42,17 +26,20 @@ init.deps.push(
   Promise.all([
     browser.runtime.getPlatformInfo(),
     browser.runtime.getBrowserInfo?.(),
-    info.full, // Getting the real version in new Chrome that simplifies its UA as ###.0.0.0
+    navUAD?.getHighEntropyValues(['uaFullVersion']),
+    !IS_FIREFOX && browserWindows.getCurrent(),
   ]).then(([
     { os, arch },
     { name, version } = {},
     { uaFullVersion } = {},
+    wnd,
   ]) => {
     ua.arch = arch;
     ua.os = os;
-    ua.brand = ua.browserBrand = info.brand || '';
-    ua.name = ua.browserName = name?.toLowerCase() || 'chrome';
-    ua.version = ua.browserVersion = uaFullVersion || version || info.ver;
+    ua.browserName = wnd && (wnd.vivExtData/*new*/ || wnd.extData/*old*/)
+      ? 'vivaldi'
+      : name?.toLowerCase() || 'chrome';
+    ua.browserVersion = uaFullVersion || version || uaVer;
     if (FIREFOX) FIREFOX = parseFloat(version);
   })
 );

+ 22 - 16
src/injected/content/requests.js

@@ -20,23 +20,18 @@ const LOADEND = 'loadend';
 const isBlobXhr = req => req[kXhrType] === 'blob';
 /** @type {GMReq.Content} */
 const requests = createNullObj();
-let navigator, getUAData, getUAProps;
+let navigator, getUAData, getUAProps, getHighEntropyValues;
 
-onScripts.push(data => {
-  if (data.xhr) {
-    // The tab may have a different UA due to a devtools override or about:config
-    navigator = global.navigator;
-    getUAProps = UA_PROPS;
-    for (let p = getPrototypeOf(navigator), i = 0; i < getUAProps.length; i++) {
-      getUAProps[i] = p && describeProperty(p, getUAProps[i]).get;
-      if (!i) {
-        if ((p = describeProperty(p, 'userAgentData'))) {
-          getUAData = p.get;
-          p = global.NavigatorUAData[PROTO];
-        } else {
-          getUAProps.length = 1;
-        }
-      }
+onScripts.push(() => {
+  // The tab may have a different UA due to a devtools override or about:config
+  navigator = global.navigator;
+  getUAProps = [];
+  for (let p = getPrototypeOf(navigator), i = 0; p && i < UA_PROPS.length; i++) {
+    getUAProps[i] = describeProperty(p, UA_PROPS[i]).get;
+    if (!i && (p = describeProperty(p, 'userAgentData'))) {
+      getUAData = p.get;
+      p = getPrototypeOf(navigator::getUAData());
+      getHighEntropyValues = p.getHighEntropyValues;
     }
   }
 });
@@ -72,6 +67,17 @@ addHandlers({
     return sendCmd('HttpRequest', msg);
   },
   AbortRequest: true,
+  UA() {
+    if (getUAData) {
+      const res = createNullObj();
+      const uaData = navigator::getUAData();
+      for (let i = 1; i < getUAProps.length; i++) {
+        res[UA_PROPS[i]] = uaData::getUAProps[i]();
+      }
+      return res;
+    }
+  },
+  UAH: hints => (navigator::getUAData())::getHighEntropyValues(hints),
 });
 
 addBackgroundHandlers({

+ 3 - 1
src/injected/web/gm-api-wrapper.js

@@ -9,6 +9,8 @@ const COPY_SCRIPT_PROPS = [
   'id',
 ];
 const componentUtils = makeComponentUtils();
+const getUAHints = hints => bridge.send('UAH', hints);
+const getUAData = () => setOwnProp(bridge.call('UA'), 'getHighEntropyValues', getUAHints);
 const sendTabClose = () => bridge.post('TabClose');
 const sendTabFocus = () => bridge.post('TabFocus');
 
@@ -83,6 +85,7 @@ function makeGmInfo(gmInfo, meta, resources) {
   // No __proto__:null because these are standard objects for userscripts
   meta.resources = resourcesArr;
   safeAssign(gmInfo, bridge.gmi);
+  setOwnProp(gmInfo, 'userAgentData', getUAData, true, 'get');
   return safeAssign(gmInfo, {
     [INJECT_INTO]: bridge.mode,
     platform: safeAssign({}, bridge.ua),
@@ -91,4 +94,3 @@ function makeGmInfo(gmInfo, meta, resources) {
     version: process.env.VM_VER,
   });
 }
-

+ 2 - 1
src/options/views/tab-settings/vm-export.vue

@@ -38,6 +38,7 @@ import { downloadBlob } from '@/common/download';
 import loadZip from '@/common/zip';
 import VmDateInfo from './vm-date-info';
 
+/** @type {VMScriptGMInfoPlatform} */
 let ua;
 
 const tpl = ref();
@@ -64,7 +65,7 @@ function download(blob) {
    * v61 in MacOS https://bugzil.la/1385403
    * v63 in Linux https://bugzil.la/1357487 */
   // TODO: remove when strict_min_version >= 63
-  const FF = IS_FIREFOX && parseFloat(ua.version);
+  const FF = IS_FIREFOX && parseFloat(ua.browserVersion);
   const name = fileName.value;
   if (FF && (ua.os === 'win' ? FF < 56 : ua.os === 'mac' ? FF < 61 : FF < 63)) {
     const reader = new FileReader();

+ 0 - 1
src/types.d.ts

@@ -235,7 +235,6 @@ declare interface VMInjectionDisabled {
 declare interface VMInjectionFlags {
   clipFF?: boolean;
   forceContent?: boolean;
-  xhr?: boolean;
 }
 
 /**

+ 3 - 0
test/mock/polyfill.js

@@ -26,6 +26,9 @@ global.browser = {
     onReplaced: { addListener: () => {} },
     onUpdated: { addListener: () => {} },
   },
+  windows: {
+    getCurrent: () => ({}),
+  },
 };
 if (!window.Response) window.Response = { prototype: {} };
 const domProps = Object.getOwnPropertyDescriptors(window);

+ 4 - 4
yarn.lock

@@ -2220,10 +2220,10 @@
   dependencies:
     "@babel/runtime" "^7.16.7"
 
-"@violentmonkey/[email protected].5":
-  version "0.1.5"
-  resolved "https://registry.yarnpkg.com/@violentmonkey/types/-/types-0.1.5.tgz#264f062082cb4543da54abe3adcf4973d1fd0461"
-  integrity sha512-pDj3TndOfXRWuyRLVlGCZbXDqh7L/Ca3qO8TDUWTtSuUPF+PkNKbMwuxPGrqmRFXn3V0hXp4wjCwzrDWyJCYiw==
+"@violentmonkey/[email protected].9":
+  version "0.1.9"
+  resolved "https://registry.yarnpkg.com/@violentmonkey/types/-/types-0.1.9.tgz#dd77c8d8c031716175229b418c88e3dff20d89ef"
+  integrity sha512-mBEHd8IOIrJW9NTSFf9474et5eduoq16Pmddg5O3MC/RElCxXay//Go0jpSz20LEpA9t0H44EHDRcXJYMBBtNg==
 
 "@volar/[email protected]", "@volar/language-core@~1.10.0":
   version "1.10.1"