Browse Source

fix: workaround for defineProperty, fixes #1418

tophf 4 years ago
parent
commit
3eb994a04f

+ 1 - 0
src/injected/content/inject.js

@@ -20,6 +20,7 @@ let VMInitInjection = window[INIT_FUNC_NAME];
 /** Avoid running repeatedly due to new `documentElement` or with declarativeContent in Chrome.
  * The prop's mode is overridden to be unforgeable by a userscript in content mode. */
 defineProperty(window, INIT_FUNC_NAME, {
+  __proto__: null,
   value: 1,
   configurable: false,
   enumerable: false,

+ 14 - 1
src/injected/safe-globals-injected.js

@@ -30,6 +30,7 @@ export const getOwnProp = (obj, key) => (
  * its length or from an unassigned `hole`. */
 export const setOwnProp = (obj, key, value) => (
   defineProperty(obj, key, {
+    __proto__: null,
     value,
     configurable: true,
     enumerable: true,
@@ -45,7 +46,10 @@ export const createNullObj = () => ({ __proto__: null });
 export const promiseResolve = () => (async () => {})();
 
 export const vmOwnFunc = (func, toString) => (
-  defineProperty(func, 'toString', { value: toString || vmOwnFuncToString })
+  defineProperty(func, 'toString', {
+    __proto__: null,
+    value: toString || vmOwnFuncToString,
+  })
 );
 
 /** @param {Window} wnd */
@@ -80,3 +84,12 @@ export function pickIntoThis(obj, keys) {
   }
   return this;
 }
+
+/**
+ * Object.defineProperty seems to be inherently broken: it reads inherited props from desc
+ * (even though the purpose of this API is to define own props) and then complains when it finds
+ * invalid props like an inherited setter when you only provide `{value}`.
+ */
+export const safeDefineProperty = (obj, key, desc) => (
+  defineProperty(obj, key, assign(createNullObj(), desc))
+);

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

@@ -196,7 +196,7 @@ function webAddElement(parent, tag, attrs, context) {
      but we keep it for compatibility with GM_addStyle in VM of 2017-2019
      https://github.com/violentmonkey/violentmonkey/issues/217
      as well as for GM_addElement in Tampermonkey. */
-  defineProperty(el, 'then', {
+  safeDefineProperty(el, 'then', {
     configurable: true,
     value(callback) {
       // prevent infinite resolve loop

+ 12 - 9
src/injected/web/gm-global-wrapper.js

@@ -134,15 +134,17 @@ const boundMethods = {
 
 for (const name in unforgeables) { /* proto is null */// eslint-disable-line guard-for-in
   let thisObj;
-  const info = (
+  let info = (
     describeProperty(thisObj = global, name)
     || describeProperty(thisObj = window, name)
   );
+  let fn;
   if (info) {
+    info = assign(createNullObj(), info);
     // currently only `document` and `window`
-    if (info.get) info.get = info.get::bind(thisObj);
+    if ((fn = info.get)) info.get = fn::bind(thisObj);
     // currently only `location`
-    if (info.set) info.set = info.set::bind(thisObj);
+    if ((fn = info.set)) info.set = fn::bind(thisObj);
     unforgeables[name] = info;
   } else {
     delete unforgeables[name];
@@ -164,12 +166,12 @@ export function makeGlobalWrapper(local) {
   /* Browsers may return [object Object] for Object.prototype.toString(window)
      on our `window` proxy so jQuery libs see it as a plain object and throw
      when trying to clone its recursive properties like `self` and `window`. */
-  defineProperty(local, toStringTag, { get: () => 'Window' });
+  safeDefineProperty(local, toStringTag, { get: () => 'Window' });
   const wrapper = new ProxySafe(local, {
     defineProperty(_, name, desc) {
       const isString = typeof name === 'string';
       if (!isFrameIndex(name, isString)) {
-        defineProperty(local, name, desc);
+        safeDefineProperty(local, name, desc);
         if (isString) setEventHandler(name);
         delete readonlys[name];
       }
@@ -196,14 +198,14 @@ export function makeGlobalWrapper(local) {
       const ownDesc = describeProperty(local, name);
       const desc = ownDesc || globals.has(name) && describeProperty(global, name);
       if (!desc) return;
-      if (desc.value === window) {
+      if (getOwnProp(desc, 'value') === window) {
         desc.value = wrapper;
       }
       // preventing spec violation - we must mirror an unknown unforgeable prop
-      if (!ownDesc && !desc.configurable) {
-        const { get } = desc;
+      if (!ownDesc && !getOwnProp(desc, 'configurable')) {
+        const get = getOwnProp(desc, 'get');
         if (get) desc.get = get::bind(global);
-        defineProperty(local, name, desc);
+        safeDefineProperty(local, name, desc);
       }
       return desc;
     },
@@ -231,6 +233,7 @@ export function makeGlobalWrapper(local) {
       delete desc.set;
       desc.value = wrapper;
     }
+    /* proto is already null */// eslint-disable-next-line no-restricted-syntax
     defineProperty(local, name, desc);
   }
   return wrapper;

+ 1 - 1
src/injected/web/index.js

@@ -103,7 +103,7 @@ function createScriptData(item) {
   if (window[dataKey]) { // executeScript ran before GetInjected response
     onCodeSet(item, window[dataKey]);
   } else {
-    defineProperty(window, dataKey, {
+    safeDefineProperty(window, dataKey, {
       configurable: true,
       set: fn => onCodeSet(item, fn),
     });

+ 2 - 1
src/injected/web/requests.js

@@ -63,9 +63,10 @@ function callback(req, msg) {
       req.raw = response;
     }
     defineProperty(data, 'response', {
+      __proto__: null,
       get() {
         const value = 'raw' in req ? parseData(req, msg) : req.response;
-        defineProperty(this, 'response', { value });
+        defineProperty(this, 'response', { value, __proto__: null });
         return value;
       },
     });

+ 2 - 2
src/injected/web/safe-globals-web.js

@@ -1,5 +1,5 @@
-/* eslint-disable camelcase, one-var, one-var-declaration-per-line, no-unused-vars,
-   prefer-const, import/no-mutable-exports */
+/* eslint-disable one-var, one-var-declaration-per-line, no-unused-vars,
+   prefer-const, import/no-mutable-exports, no-restricted-syntax */
 
 /**
  * `safeCall` is used by our modified babel-plugin-safe-bind.js.