Skip to content

Commit 4e93670

Browse files
authored
Merge pull request #38 from pendo-io/ai-angular-zone-fix
Use alternative angular zone detection in getUntaintedPrototype
2 parents 4d750f4 + a95dd18 commit 4e93670

File tree

1 file changed

+39
-5
lines changed

1 file changed

+39
-5
lines changed

packages/utils/src/index.ts

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,48 @@ const testableMethods = {
2828

2929
const untaintedBasePrototype: Partial<BasePrototypeCache> = {};
3030

31+
type WindowWithZone = typeof globalThis & {
32+
Zone?: {
33+
__symbol__?: (key: string) => string;
34+
};
35+
};
36+
37+
type WindowWithUnpatchedSymbols = typeof globalThis &
38+
Record<string, TypeofPrototypeOwner>;
39+
40+
/*
41+
Angular zone patches many things and can pass the untainted checks below, causing performance issues
42+
Angular zone, puts the unpatched originals on the window, and the names for hose on the zone object.
43+
So, we get the unpatched versions from the window object if they exist.
44+
You can rename Zone, but this is a good enough proxy to avoid going to an iframe to get the untainted versions.
45+
see: https://github.com/angular/angular/issues/26948
46+
*/
47+
function angularZoneUnpatchedAlternative(key: keyof BasePrototypeCache) {
48+
const angularUnpatchedVersionSymbol = (
49+
globalThis as WindowWithZone
50+
)?.Zone?.__symbol__?.(key);
51+
if (
52+
angularUnpatchedVersionSymbol &&
53+
(globalThis as WindowWithUnpatchedSymbols)[angularUnpatchedVersionSymbol]
54+
) {
55+
return (globalThis as WindowWithUnpatchedSymbols)[
56+
angularUnpatchedVersionSymbol
57+
];
58+
} else {
59+
return undefined;
60+
}
61+
}
62+
3163
export function getUntaintedPrototype<T extends keyof BasePrototypeCache>(
3264
key: T,
3365
): BasePrototypeCache[T] {
3466
if (untaintedBasePrototype[key])
3567
return untaintedBasePrototype[key] as BasePrototypeCache[T];
3668

37-
const defaultObj = globalThis[key] as TypeofPrototypeOwner;
38-
const defaultPrototype = defaultObj.prototype as BasePrototypeCache[T];
69+
const candidate =
70+
angularZoneUnpatchedAlternative(key) ||
71+
(globalThis[key] as TypeofPrototypeOwner);
72+
const defaultPrototype = candidate.prototype as BasePrototypeCache[T];
3973

4074
// use list of testable accessors to check if the prototype is tainted
4175
const accessorNames =
@@ -64,15 +98,15 @@ export function getUntaintedPrototype<T extends keyof BasePrototypeCache>(
6498
);
6599

66100
if (isUntaintedAccessors && isUntaintedMethods) {
67-
untaintedBasePrototype[key] = defaultObj.prototype as BasePrototypeCache[T];
68-
return defaultObj.prototype as BasePrototypeCache[T];
101+
untaintedBasePrototype[key] = candidate.prototype as BasePrototypeCache[T];
102+
return candidate.prototype as BasePrototypeCache[T];
69103
}
70104

71105
try {
72106
const iframeEl = document.createElement('iframe');
73107
document.body.appendChild(iframeEl);
74108
const win = iframeEl.contentWindow;
75-
if (!win) return defaultObj.prototype as BasePrototypeCache[T];
109+
if (!win) return candidate.prototype as BasePrototypeCache[T];
76110

77111
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
78112
const untaintedObject = (win as any)[key]

0 commit comments

Comments
 (0)