-
Notifications
You must be signed in to change notification settings - Fork 382
/
polyfill.ts
156 lines (148 loc) · 5.32 KB
/
polyfill.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/*
* Copyright (c) 2018, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
import { defineProperties, isUndefined, ArraySlice } from '@lwc/shared';
import {
addCustomElementEventListener,
removeCustomElementEventListener,
} from '../../faux-shadow/events';
import {
isHostElement,
getHost,
SyntheticShadowRootInterface,
} from '../../faux-shadow/shadow-root';
import { eventCurrentTargetGetter, eventTargetGetter } from '../../env/dom';
import { isNodeShadowed } from '../../shared/node-ownership';
import { contains } from '../../env/node';
// this method returns true if an event can be seen by a particular eventTarget,
// otherwise it returns false, which means the listener will never be invoked.
function isQualifyingEventTarget(evt: Event): boolean {
const currentTarget = eventCurrentTargetGetter.call(evt);
const originalTarget = eventTargetGetter.call(evt);
if (originalTarget === currentTarget) {
// short-circuit when dispatched on the target directly
return true;
}
const { composed, bubbles } = evt;
if (!composed) {
if (bubbles) {
// bubbles true, composed false: only propagate up to the originalTarget's shadowRoot
if (originalTarget instanceof Node) {
if (isNodeShadowed(originalTarget)) {
// if the original target is shadowed, every node in the path to its shadowRoot
// will have visibility into the event, including slots since the target might
// be slotted.
const sr = originalTarget.getRootNode() as SyntheticShadowRootInterface;
return contains.call(getHost(sr), currentTarget as Node);
}
// else it is a global node or document
}
// else it is window
} else {
// bubbles false, composed false: only dispatch it on the original target
return currentTarget === originalTarget;
}
} else if (!bubbles) {
// bubble false, composed true: only propagate via host elements
return isHostElement(currentTarget as Node);
}
// bubbles true, composed true
return true;
}
const EventListenerToWrapperMap: WeakMap<
EventListenerOrEventListenerObject,
EventListener
> = new WeakMap();
function getEventListenerWrapper(fnOrObj: EventListenerOrEventListenerObject): EventListener {
let wrapperFn: EventListener | undefined = EventListenerToWrapperMap.get(fnOrObj);
if (isUndefined(wrapperFn)) {
wrapperFn = function(this: EventTarget, e: Event) {
if (isQualifyingEventTarget(e)) {
if (typeof fnOrObj === 'function') {
fnOrObj.call(this, e);
} else {
fnOrObj.handleEvent && fnOrObj.handleEvent(e);
}
}
};
EventListenerToWrapperMap.set(fnOrObj, wrapperFn);
}
return wrapperFn;
}
// These methods are usually from EventTarget.prototype, but that's not available in IE11, the next best thing
// is Node.prototype, which is an EventTarget as well.
const {
addEventListener: superAddEventListener,
removeEventListener: superRemoveEventListener,
} = Node.prototype;
function addEventListenerPatched(
this: Element,
_type: string,
listener: EventListenerOrEventListenerObject | null,
_options?: boolean | AddEventListenerOptions
) {
if (listener == null) {
return; /* nullish */
}
const args = ArraySlice.call(arguments);
args[1] = getEventListenerWrapper(listener);
if (isHostElement(this)) {
addCustomElementEventListener.apply(this, args as [
string,
EventListener,
(EventListenerOptions | boolean | undefined)?
]);
} else {
superAddEventListener.apply(this, args as [
string,
EventListener,
(EventListenerOptions | boolean | undefined)?
]);
}
}
function removeEventListenerPatched(
this: Element,
_type: string,
listener: EventListenerOrEventListenerObject | null,
_options?: EventListenerOptions | boolean
) {
if (listener == null) {
return; /* nullish */
}
const args = ArraySlice.call(arguments);
args[1] = getEventListenerWrapper(listener);
if (isHostElement(this)) {
removeCustomElementEventListener.apply(this, args as [
string,
EventListener,
(EventListenerOptions | boolean | undefined)?
]);
} else {
superRemoveEventListener.apply(this, args as [
string,
EventListener,
(EventListenerOptions | boolean | undefined)?
]);
}
}
// IE11 doesn't have EventTarget, so we have to patch it conditionally
// on the wrong prototypes.
const protoToBePatched =
typeof EventTarget !== 'undefined' ? EventTarget.prototype : Node.prototype;
defineProperties(protoToBePatched, {
addEventListener: {
value: addEventListenerPatched,
enumerable: true,
writable: true,
configurable: true,
},
removeEventListener: {
value: removeEventListenerPatched,
enumerable: true,
writable: true,
configurable: true,
},
});