Skip to content
This repository has been archived by the owner on Sep 16, 2024. It is now read-only.

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
soundofspace committed Aug 21, 2024
1 parent 53250e7 commit e45c1a0
Show file tree
Hide file tree
Showing 25 changed files with 778 additions and 458 deletions.
10 changes: 10 additions & 0 deletions plugins/default-browser-emulator/injected-scripts/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ module.exports = {
files: ['**/*.ts'],
rules: {
'no-restricted-globals': 'off',
'no-restricted-properties': [
'error',
...Object.getOwnPropertyNames(Object).map(key => {
return { object: 'Object', property: key };
}),
...Object.getOwnPropertyNames(Reflect).map(key => {
return { object: 'Reflect', property: key };
}),
],
'no-proto': 'off',
'no-extend-native': 'off',
'no-inner-declarations': 'off',
Expand All @@ -21,6 +30,7 @@ module.exports = {
'prefer-rest-params': 'off',
'func-names': 'off',
'no-console': 'off',
'lines-around-directive': 'off',
},
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const cookieTrigger = (self[triggerName] as unknown as Function).bind(self);

delete self[triggerName];

proxySetter(Document.prototype, 'cookie', (target, thisArg, argArray) => {
replaceSetter(Document.prototype, 'cookie', (target, thisArg, argArray) => {
const cookie = argArray.at(0);
if (cookie) {
cookieTrigger(JSON.stringify({ cookie, origin: self.location.origin }));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export type Args = never;

proxyFunction(JSON, 'stringify', (target, thisArg, argArray) => {
replaceFunction(JSON, 'stringify', (target, thisArg, argArray) => {
const result = ReflectCached.apply(target, thisArg, [argArray.at(0), null, 2]);
console.log(result);
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ if (
kind: 'videoinput',
label: '',
};
proxyFunction(MediaDevices.prototype, 'enumerateDevices', (func, thisObj, ...args) => {
return func.apply(thisObj, args).then(list => {
replaceFunction(MediaDevices.prototype, 'enumerateDevices', (target, thisArg, argArray) => {
return (ReflectCached.apply(target, thisArg, argArray) as Promise<any>).then(list => {
if (list.find(x => x.kind === 'videoinput')) return list;
list.push(videoDevice);
return list;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const typedArgs = args as Args;
const { audioCodecs, videoCodecs } = typedArgs;

if ('RTCRtpSender' in self && RTCRtpSender.prototype) {
proxyFunction(RTCRtpSender, 'getCapabilities', function (target, thisArg, argArray) {
replaceFunction(RTCRtpSender, 'getCapabilities', function (target, thisArg, argArray) {
const kind = argArray && argArray.length ? argArray[0] : null;
const args = kind ? [kind] : undefined;
const capabilities = target.apply(thisArg, args);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,73 @@ if (typeof SharedWorker === 'undefined') {
return;
}

const OriginalSharedWorker = SharedWorker;
const originalSharedWorkerProperties = ObjectCached.getOwnPropertyDescriptors(SharedWorker);

ObjectCached.defineProperty(self, 'SharedWorker', {
// eslint-disable-next-line object-shorthand
value: function (this, scriptURL, options) {
// eslint-disable-next-line strict
'use strict';
let constructor;
try {
constructor = this && ObjectCached.getPrototypeOf(this).constructor === SharedWorker;
} catch {}

if (!constructor) {
return ReflectCached.apply(OriginalSharedWorker, this, [scriptURL, options]);
}

let isBlob = false;
try {
isBlob = scriptURL?.toString().startsWith('blob:');
} catch {}
if (!isBlob) {
return ReflectCached.construct(OriginalSharedWorker, [scriptURL, options]);
}

// read blob contents synchronously
const xhr = new XMLHttpRequest();
xhr.open('GET', scriptURL, false);
xhr.send();
const text = xhr.response;

const script = createScript(text);

const newBlob = new Blob([script]);
return ReflectCached.construct(OriginalSharedWorker, [URL.createObjectURL(newBlob), options]);
},
});

ObjectCached.defineProperties(SharedWorker, originalSharedWorkerProperties);
SharedWorker.prototype.constructor = SharedWorker;
toOriginalFn.set(SharedWorker, OriginalSharedWorker);

// shared workers created from blobs don't automatically pause in devtools, so we have to manipulate
proxyConstructor(self, 'SharedWorker', (target, argArray) => {
if (!argArray?.length) return ReflectCached.construct(target, argArray);
// proxyConstructor(self, 'SharedWorker', (target, argArray) => {
// if (!argArray?.length) return ReflectCached.construct(target, argArray);

// const [url] = argArray;
// if (!url?.toString().startsWith('blob:')) {
// return ReflectCached.construct(target, argArray);
// }

const [url] = argArray;
if (!url?.toString().startsWith('blob:')) {
return ReflectCached.construct(target, argArray);
}
// // read blob contents synchronously
// const xhr = new XMLHttpRequest();
// xhr.open('GET', url, false);
// xhr.send();
// const text = xhr.response;

// read blob contents synchronously
const xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.send();
const text = xhr.response;
// const script = createScript(text)

// const newBlob = new Blob([script]);
// return ReflectCached.construct(target, [URL.createObjectURL(newBlob)]);
// });

function createScript(originalScript: string) {
const script = `
function original() {
${text};
${originalScript};
}
(async function runWhenReady() {
Expand All @@ -45,7 +94,7 @@ proxyConstructor(self, 'SharedWorker', (target, argArray) => {
// See proxyUtils
function getSharedStorage() {
try {
return Function.prototype.toString.call('${sourceUrl}');
return Function.prototype.toString('${sourceUrl}');
} catch {
return undefined;
}
Expand All @@ -65,7 +114,5 @@ proxyConstructor(self, 'SharedWorker', (target, argArray) => {
}, 20);
})()
`;

const newBlob = new Blob([script]);
return ReflectCached.construct(target, [URL.createObjectURL(newBlob)]);
});
return script;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,24 @@ function preventDefault(event: ErrorEvent | PromiseRejectionEvent) {

// Hide this, but make sure if they hide it we mimic normal behaviour
let prevented = event.defaultPrevented;
proxyFunction(
replaceFunction(
event,
'preventDefault',
(originalFunction, thisArg, argArray) => {
(target, thisArg, argArray) => {
// Will raise correct error if 'thisArg' is wrong
ReflectCached.apply(originalFunction, thisArg, argArray);
ReflectCached.apply(target, thisArg, argArray);
prevented = true;
},
{ overrideOnlyForInstance: true },
{ onlyForInstance: true },
);
proxyGetter(
replaceGetter(
event,
'defaultPrevented',
(target, thisArg) => {
ReflectCached.get(target, thisArg);
return prevented;
},
{ overrideOnlyForInstance: true },
{ onlyForInstance: true },
);

if (!('console' in self)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,25 @@ const typedArgs = args as Args;

const activatedDebugInfo = new WeakSet<WebGL2RenderingContext | WebGLRenderingContext>();

const ReflectCachedHere = ReflectCached;
for (const context of [
self.WebGLRenderingContext.prototype,
self.WebGL2RenderingContext.prototype,
]) {
proxyFunction(context, 'getExtension', function (originalFunction, thisArg, argArray) {
const result = Reflect.apply(originalFunction, thisArg, argArray);
if (argArray?.[0] === 'WEBGL_debug_renderer_info') {
replaceFunction(context, 'getExtension', function (target, thisArg, argArray) {
const result = ReflectCachedHere.apply(target, thisArg, argArray) as any;
if (argArray.at(0) === 'WEBGL_debug_renderer_info') {
activatedDebugInfo.add(thisArg);
}

return result;
});

// eslint-disable-next-line @typescript-eslint/no-loop-func
proxyFunction(context, 'getParameter', function (originalFunction, thisArg, argArray) {
replaceFunction(context, 'getParameter', function (target, thisArg, argArray) {
const parameter = argArray && argArray.length ? argArray[0] : null;
// call api to make sure signature goes through
const result = ReflectCached.apply(originalFunction, thisArg, argArray);
const result = ReflectCached.apply(target, thisArg, argArray);
if (typedArgs[parameter]) {
if (!result && !activatedDebugInfo.has(context)) {
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ function newObjectConstructor(
if (typeof invocation === 'function') return invocation(...arguments);
return invocationReturnOrThrow(invocation, isAsync);
}
const props = Object.entries(newProps);
const props = ObjectCached.entries(newProps);
const obj = {};
if (!newProps._$protos) throw new Error('newProps._$protos undefined');
Object.setPrototypeOf(
ObjectCached.setPrototypeOf(
obj,
prototypesByPath[newProps._$protos[0]] ?? getObjectAtPath(newProps._$protos[0]),
);
Expand All @@ -58,7 +58,7 @@ function newObjectConstructor(
if (propName.startsWith('Symbol(')) {
propName = Symbol.for(propName.match(/Symbol\((.+)\)/)![1]);
}
Object.defineProperty(obj, propName, buildDescriptor(value, `${path}.${prop}`));
ObjectCached.defineProperty(obj, propName, buildDescriptor(value, `${path}.${prop}`));
}
return obj;
};
Expand All @@ -82,6 +82,7 @@ function buildDescriptor(entry: IDescriptor, path: string): PropertyDescriptor {
},
});
overriddenFns.set(attrs.get!, entry._$get);
toOriginalFn.set(attrs.get!, entry._$get);
} else if (entry['_$$value()']) {
attrs.value = entry['_$$value()']();
} else if (entry._$value !== undefined) {
Expand All @@ -93,6 +94,7 @@ function buildDescriptor(entry: IDescriptor, path: string): PropertyDescriptor {
apply() {},
});
overriddenFns.set(attrs.set!, entry._$set);
toOriginalFn.set(attrs.set!, entry._$set);
}

let prototypeDescriptor: PropertyDescriptor | undefined;
Expand All @@ -104,6 +106,7 @@ function buildDescriptor(entry: IDescriptor, path: string): PropertyDescriptor {
}
if (entry._$function) {
overriddenFns.set(prototypeDescriptor.value.constructor, entry._$function);
toOriginalFn.set(attrs.set!, entry._$set);
}
prototypesByPath[`${path}.prototype`] = prototypeDescriptor.value;
}
Expand All @@ -114,14 +117,15 @@ function buildDescriptor(entry: IDescriptor, path: string): PropertyDescriptor {
if (newProps) {
attrs.value = newObjectConstructor(newProps, path, entry._$invocation, entry._$isAsync);
} else {
Object.keys(entry)
ObjectCached.keys(entry)
.filter((key): key is OtherInvocationKey => key.startsWith('_$otherInvocation'))
// Not supported currently
.filter(key => !key.includes('new()'))
.forEach(key => OtherInvocationsTracker.addOtherInvocation(path, key, entry[key]));

// use function call just to get a function that doesn't create prototypes on new
// bind to an empty object so we don't modify the original
// TODO remove proxy
attrs.value = new Proxy(function () {}, {
apply(_target, thisArg) {
const invocation =
Expand All @@ -132,30 +136,31 @@ function buildDescriptor(entry: IDescriptor, path: string): PropertyDescriptor {
});
}
if (entry._$invocation !== undefined) {
Object.setPrototypeOf(attrs.value, Function.prototype);
ObjectCached.setPrototypeOf(attrs.value, Function.prototype);
delete attrs.value.prototype;
delete attrs.value.constructor;
}

if (prototypeDescriptor && newProps) {
Object.defineProperty(prototypeDescriptor.value, 'constructor', {
ObjectCached.defineProperty(prototypeDescriptor.value, 'constructor', {
value: attrs.value,
writable: true,
enumerable: false,
configurable: true,
});
}
overriddenFns.set(attrs.value, entry._$function);
toOriginalFn.set(attrs.value, entry._$function);
}

if (typeof entry === 'object') {
const props = Object.entries(entry).filter(([prop]) => !prop.startsWith('_$'));
const props = ObjectCached.entries(entry).filter(([prop]) => !prop.startsWith('_$'));
if (!attrs.value && (props.length || entry._$protos)) {
attrs.value = {};
}
if (entry._$protos) {
const proto = prototypesByPath[entry._$protos[0]] ?? getObjectAtPath(entry._$protos[0]);
attrs.value = Object.setPrototypeOf(attrs.value, proto);
attrs.value = ObjectCached.setPrototypeOf(attrs.value, proto);
}

for (const [prop, value] of props) {
Expand All @@ -175,7 +180,7 @@ function buildDescriptor(entry: IDescriptor, path: string): PropertyDescriptor {
} else {
descriptor = buildDescriptor(value, `${path}.${prop}`);
}
Object.defineProperty(attrs.value, propName, descriptor);
ObjectCached.defineProperty(attrs.value, propName, descriptor);
}
}

Expand Down
Loading

0 comments on commit e45c1a0

Please sign in to comment.