Skip to content

Commit faec383

Browse files
Add overloading of property descriptors
1 parent aff1438 commit faec383

File tree

3 files changed

+112
-2
lines changed

3 files changed

+112
-2
lines changed

src/features/runtime-checks.js

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import ContentFeature from '../content-feature.js'
44
import { DDGProxy, getStackTraceOrigins, getStack, matchHostname, injectGlobalStyles, createStyleElement, postDebugMessage, taintSymbol, hasTaintedMethod, taintedOrigins, getTabHostname, isBeingFramed } from '../utils.js'
5-
import { defineProperty } from '../wrapper-utils.js'
5+
import { defineProperty, wrapFunction } from '../wrapper-utils.js'
66
import { wrapScriptCodeOverload } from './runtime-checks/script-overload.js'
77
import { findClosestBreakpoint } from './runtime-checks/helpers.js'
88
import { Reflect } from '../captured-globals.js'
@@ -473,6 +473,86 @@ function isInterrogatingDebugMessage (matchType, matchedStackDomain, stack, scri
473473
})
474474
}
475475

476+
function isRuntimeElement (element) {
477+
try {
478+
return element instanceof DDGRuntimeChecks
479+
} catch {}
480+
return false
481+
}
482+
483+
function overloadGetOwnPropertyDescriptor () {
484+
const capturedDescriptors = {
485+
HTMLScriptElement: Object.getOwnPropertyDescriptors(HTMLScriptElement),
486+
HTMLScriptElementPrototype: Object.getOwnPropertyDescriptors(HTMLScriptElement.prototype)
487+
}
488+
/**
489+
* @param {any} value
490+
* @returns {string | undefined}
491+
*/
492+
function getInterfaceName (value) {
493+
let interfaceName
494+
if (value === HTMLScriptElement) {
495+
interfaceName = 'HTMLScriptElement'
496+
}
497+
if (value === HTMLScriptElement.prototype) {
498+
interfaceName = 'HTMLScriptElementPrototype'
499+
}
500+
return interfaceName
501+
}
502+
// TODO: Consoldiate with wrapProperty code
503+
function getInterfaceDescriptor (interfaceValue, interfaceName, propertyName) {
504+
const capturedInterface = capturedDescriptors[interfaceName] && capturedDescriptors[interfaceName][propertyName]
505+
const capturedInterfaceOut = { ...capturedInterface }
506+
if (capturedInterface.get) {
507+
capturedInterfaceOut.get = wrapFunction(function () {
508+
if (isRuntimeElement(this)) {
509+
return DDGRuntimeChecks[propertyName]
510+
}
511+
return capturedInterface.get.call(this)
512+
}, capturedInterface.get)
513+
}
514+
if (capturedInterface.set) {
515+
capturedInterfaceOut.set = wrapFunction(function (value) {
516+
if (isRuntimeElement(this)) {
517+
DDGRuntimeChecks[interfaceName] = value
518+
return
519+
}
520+
return capturedInterface.set.call(this, [value])
521+
}, capturedInterface.set)
522+
}
523+
return capturedInterfaceOut
524+
}
525+
const proxy = new DDGProxy(featureName, Object, 'getOwnPropertyDescriptor', {
526+
apply (fn, scope, args) {
527+
const interfaceValue = args[0]
528+
const interfaceName = getInterfaceName(interfaceValue)
529+
const propertyName = args[1]
530+
const capturedInterface = capturedDescriptors[interfaceName] && capturedDescriptors[interfaceName][propertyName]
531+
if (interfaceName && capturedInterface) {
532+
return getInterfaceDescriptor(interfaceValue, interfaceName, propertyName)
533+
}
534+
return Reflect.apply(fn, scope, args)
535+
}
536+
})
537+
proxy.overload()
538+
const proxy2 = new DDGProxy(featureName, Object, 'getOwnPropertyDescriptors', {
539+
apply (fn, scope, args) {
540+
const interfaceValue = args[0]
541+
const interfaceName = getInterfaceName(interfaceValue)
542+
const capturedInterface = capturedDescriptors[interfaceName]
543+
if (interfaceName && capturedInterface) {
544+
const out = {}
545+
for (const propertyName of Object.getOwnPropertyNames(capturedInterface)) {
546+
out[propertyName] = getInterfaceDescriptor(interfaceValue, interfaceName, propertyName)
547+
}
548+
return out
549+
}
550+
return Reflect.apply(fn, scope, args)
551+
}
552+
})
553+
proxy2.overload()
554+
}
555+
476556
function overrideCreateElement (debug) {
477557
const proxy = new DDGProxy(featureName, Document.prototype, 'createElement', {
478558
apply (fn, scope, args) {
@@ -576,6 +656,9 @@ export default class RuntimeChecks extends ContentFeature {
576656
if (this.getFeatureSettingEnabled('overloadReplaceChild')) {
577657
overloadReplaceChild()
578658
}
659+
if (this.getFeatureSettingEnabled('overloadGetOwnPropertyDescriptor')) {
660+
overloadGetOwnPropertyDescriptor()
661+
}
579662
}
580663

581664
injectGenericOverloads () {

src/wrapper-utils.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,33 @@ function wrapToString (newFn, origFn) {
7373
}
7474
}
7575

76+
/**
77+
* Wrap functions to fix toString but also behave as closely to their real function as possible like .name and .length etc.
78+
* TODO: validate with firefox non runtimeChecks context and also consolidate with wrapToString
79+
* @param {*} functionValue
80+
* @param {*} realTarget
81+
* @returns {Proxy} a proxy for the function
82+
*/
83+
export function wrapFunction (functionValue, realTarget) {
84+
return new Proxy(realTarget, {
85+
get (target, prop, receiver) {
86+
if (prop === 'toString') {
87+
const method = Reflect.get(target, prop, receiver).bind(target)
88+
Object.defineProperty(method, 'toString', {
89+
value: functionToString.bind(functionToString),
90+
enumerable: false
91+
})
92+
return method
93+
}
94+
return Reflect.get(target, prop, receiver)
95+
},
96+
apply (target, thisArg, argumentsList) {
97+
// This is where we call our real function
98+
return Reflect.apply(functionValue, thisArg, argumentsList)
99+
}
100+
})
101+
}
102+
76103
/**
77104
* Wrap a get/set or value property descriptor. Only for data properties. For methods, use wrapMethod(). For constructors, use wrapConstructor().
78105
* @param {any} object - object whose property we are wrapping (most commonly a prototype)

unit-test/verify-artifacts.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { cwd } from '../scripts/script-utils.js'
66
const ROOT = join(cwd(import.meta.url), '..')
77
const BUILD = join(ROOT, 'build')
88
const APPLE_BUILD = join(ROOT, 'Sources/ContentScopeScripts/dist')
9-
let CSS_OUTPUT_SIZE = 590_000
9+
let CSS_OUTPUT_SIZE = 600_000
1010
const CSS_OUTPUT_SIZE_CHROME = CSS_OUTPUT_SIZE * 1.45 // 45% larger for Chrome MV2 due to base64 encoding
1111
if (process.platform === 'win32') {
1212
CSS_OUTPUT_SIZE = CSS_OUTPUT_SIZE * 1.1 // 10% larger for Windows due to line endings

0 commit comments

Comments
 (0)