Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(user-interaction): EventTarget is undefined in IE #627

Merged
merged 13 commits into from Oct 12, 2021
Expand Up @@ -330,6 +330,24 @@ export class UserInteractionInstrumentation extends InstrumentationBase<unknown>
};
}

/**
* Most browser provide event listener api via EventTarget in prototype chain.
* Exception to this is IE 11 which has it on the prototypes closest to EventTarget:
*
* * - has addEventListener in IE
* ** - has addEventListener in all other browsers
* ! - missing in IE
*
* HTMLElement -> Element -> Node * -> EventTarget **! -> Object
* Document -> Node * -> EventTarget **! -> Object
* Window * -> WindowProperties ! -> EventTarget **! -> Object
*/
private _getPatchableEventTargets(): EventTarget[] {
return window.EventTarget
? [EventTarget.prototype]
: [Node.prototype, Window.prototype];
}

/**
* Patches the history api
*/
Expand Down Expand Up @@ -571,26 +589,27 @@ export class UserInteractionInstrumentation extends InstrumentationBase<unknown>
);
} else {
this._zonePatched = false;
if (isWrapped(EventTarget.prototype.addEventListener)) {
this._unwrap(EventTarget.prototype, 'addEventListener');
api.diag.debug('removing previous patch from method addEventListener');
}
if (isWrapped(EventTarget.prototype.removeEventListener)) {
this._unwrap(EventTarget.prototype, 'removeEventListener');
api.diag.debug(
'removing previous patch from method removeEventListener'
const targets = this._getPatchableEventTargets();
targets.forEach(target => {
if (isWrapped(target.addEventListener)) {
this._unwrap(target, 'addEventListener');
api.diag.debug(
'removing previous patch from method addEventListener'
);
}
if (isWrapped(target.removeEventListener)) {
this._unwrap(target, 'removeEventListener');
api.diag.debug(
'removing previous patch from method removeEventListener'
);
}
this._wrap(target, 'addEventListener', this._patchAddEventListener());
this._wrap(
target,
'removeEventListener',
this._patchRemoveEventListener()
);
}
this._wrap(
EventTarget.prototype,
'addEventListener',
this._patchAddEventListener()
);
this._wrap(
EventTarget.prototype,
'removeEventListener',
this._patchRemoveEventListener()
);
});
}

this._patchHistoryApi();
Expand Down Expand Up @@ -619,12 +638,15 @@ export class UserInteractionInstrumentation extends InstrumentationBase<unknown>
this._unwrap(ZoneWithPrototype.prototype, 'cancelTask');
}
} else {
if (isWrapped(HTMLElement.prototype.addEventListener)) {
this._unwrap(HTMLElement.prototype, 'addEventListener');
}
if (isWrapped(HTMLElement.prototype.removeEventListener)) {
this._unwrap(HTMLElement.prototype, 'removeEventListener');
}
const targets = this._getPatchableEventTargets();
targets.forEach(target => {
if (isWrapped(target.addEventListener)) {
this._unwrap(target, 'addEventListener');
}
if (isWrapped(target.removeEventListener)) {
this._unwrap(target, 'removeEventListener');
}
});
}
this._unpatchHistoryApi();
}
Expand Down
Expand Up @@ -604,5 +604,42 @@ describe('UserInteractionInstrumentation', () => {
'go should be unwrapped'
);
});

describe('simulate IE', () => {
// Save window.EventTarget reference (including enumerable state)
const EventTargetDesc = Object.getOwnPropertyDescriptor(
window,
'EventTarget'
)!;
before(() => {
// @ts-expect-error window.EventTarget not optional
delete window.EventTarget;
});
after(() => {
Object.defineProperty(window, 'EventTarget', EventTargetDesc);
obecny marked this conversation as resolved.
Show resolved Hide resolved
// Undo unwrap putting originals back on it's targets
// @ts-expect-error event listener API not optional
delete Node.prototype.addEventListener;
// @ts-expect-error copy
delete Node.prototype.removeEventListener;
// @ts-expect-error copy
delete Window.prototype.addEventListener;
// @ts-expect-error copy
delete Window.prototype.removeEventListener;
});

it('works with missing EventTarget', () => {
/*
* Would already error out with:
* "before each" hook for "works with missing EventTarget"
* ReferenceError: EventTarget is not defined
*/

fakeInteraction();
assert.equal(exportSpy.args.length, 1, 'should export one span');
const spanClick = exportSpy.args[0][0][0];
assertClickSpan(spanClick);
});
});
});
});