Skip to content
This repository was archived by the owner on Feb 26, 2024. It is now read-only.

Commit 582ff7b

Browse files
JiaLiPassionmhevery
authored andcommitted
fix(closure): patchOnProperty with exact eventNames as possible (#768)
* fix(closure): patchOnProperty with exact eventNames as possible * fix(patch): only patch property which exists
1 parent 7ad3070 commit 582ff7b

File tree

4 files changed

+307
-20
lines changed

4 files changed

+307
-20
lines changed

lib/browser/property-descriptor.ts

Lines changed: 244 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,221 @@ import {isBrowser, isMix, isNode, patchClass, patchOnProperties, zoneSymbol} fro
1010

1111
import * as webSocketPatch from './websocket';
1212

13-
const eventNames =
14-
'copy cut paste abort blur focus canplay canplaythrough change click contextmenu dblclick drag dragend dragenter dragleave dragover dragstart drop durationchange emptied ended input invalid keydown keypress keyup load loadeddata loadedmetadata loadstart message mousedown mouseenter mouseleave mousemove mouseout mouseover mouseup pause play playing progress ratechange reset scroll seeked seeking select show stalled submit suspend timeupdate volumechange waiting mozfullscreenchange mozfullscreenerror mozpointerlockchange mozpointerlockerror error webglcontextrestored webglcontextlost webglcontextcreationerror'
15-
.split(' ');
13+
const globalEventHandlersEventNames = [
14+
'abort',
15+
'animationcancel',
16+
'animationend',
17+
'animationiteration',
18+
'auxclick',
19+
'beforeinput',
20+
'blur',
21+
'cancel',
22+
'canplay',
23+
'canplaythrough',
24+
'change',
25+
'compositionstart',
26+
'compositionupdate',
27+
'compositionend',
28+
'cuechange',
29+
'click',
30+
'close',
31+
'contextmenu',
32+
'curechange',
33+
'dblclick',
34+
'drag',
35+
'dragend',
36+
'dragenter',
37+
'dragexit',
38+
'dragleave',
39+
'dragover',
40+
'drop',
41+
'durationchange',
42+
'emptied',
43+
'ended',
44+
'error',
45+
'focus',
46+
'focusin',
47+
'focusout',
48+
'gotpointercapture',
49+
'input',
50+
'invalid',
51+
'keydown',
52+
'keypress',
53+
'keyup',
54+
'load',
55+
'loadstart',
56+
'loadeddata',
57+
'loadedmetadata',
58+
'lostpointercapture',
59+
'mousedown',
60+
'mouseenter',
61+
'mouseleave',
62+
'mousemove',
63+
'mouseout',
64+
'mouseover',
65+
'mouseup',
66+
'mousewheel',
67+
'pause',
68+
'play',
69+
'playing',
70+
'pointercancel',
71+
'pointerdown',
72+
'pointerenter',
73+
'pointerleave',
74+
'pointerlockchange',
75+
'mozpointerlockchange',
76+
'webkitpointerlockerchange',
77+
'pointerlockerror',
78+
'mozpointerlockerror',
79+
'webkitpointerlockerror',
80+
'pointermove',
81+
'pointout',
82+
'pointerover',
83+
'pointerup',
84+
'progress',
85+
'ratechange',
86+
'reset',
87+
'resize',
88+
'scroll',
89+
'seeked',
90+
'seeking',
91+
'select',
92+
'selectionchange',
93+
'selectstart',
94+
'show',
95+
'sort',
96+
'stalled',
97+
'submit',
98+
'suspend',
99+
'timeupdate',
100+
'volumechange',
101+
'touchcancel',
102+
'touchmove',
103+
'touchstart',
104+
'transitioncancel',
105+
'transitionend',
106+
'waiting',
107+
'wheel'
108+
];
109+
const documentEventNames = [
110+
'afterscriptexecute', 'beforescriptexecute', 'DOMContentLoaded', 'fullscreenchange',
111+
'mozfullscreenchange', 'webkitfullscreenchange', 'msfullscreenchange', 'fullscreenerror',
112+
'mozfullscreenerror', 'webkitfullscreenerror', 'msfullscreenerror', 'readystatechange'
113+
];
114+
const windowEventNames = [
115+
'absolutedeviceorientation',
116+
'afterinput',
117+
'afterprint',
118+
'appinstalled',
119+
'beforeinstallprompt',
120+
'beforeprint',
121+
'beforeunload',
122+
'devicelight',
123+
'devicemotion',
124+
'deviceorientation',
125+
'deviceorientationabsolute',
126+
'deviceproximity',
127+
'hashchange',
128+
'languagechange',
129+
'message',
130+
'mozbeforepaint',
131+
'offline',
132+
'online',
133+
'paint',
134+
'pageshow',
135+
'pagehide',
136+
'popstate',
137+
'rejectionhandled',
138+
'storage',
139+
'unhandledrejection',
140+
'unload',
141+
'userproximity',
142+
'vrdisplyconnected',
143+
'vrdisplaydisconnected',
144+
'vrdisplaypresentchange'
145+
];
146+
const htmlElementEventNames = [
147+
'beforecopy', 'beforecut', 'beforepaste', 'copy', 'cut', 'paste', 'dragstart', 'loadend',
148+
'animationstart', 'search', 'transitionrun', 'transitionstart', 'webkitanimationend',
149+
'webkitanimationiteration', 'webkitanimationstart', 'webkittransitionend'
150+
];
151+
const mediaElementEventNames =
152+
['encrypted', 'waitingforkey', 'msneedkey', 'mozinterruptbegin', 'mozinterruptend'];
153+
const ieElementEventNames = [
154+
'activate',
155+
'afterupdate',
156+
'ariarequest',
157+
'beforeactivate',
158+
'beforedeactivate',
159+
'beforeeditfocus',
160+
'beforeupdate',
161+
'cellchange',
162+
'controlselect',
163+
'dataavailable',
164+
'datasetchanged',
165+
'datasetcomplete',
166+
'errorupdate',
167+
'filterchange',
168+
'layoutcomplete',
169+
'losecapture',
170+
'move',
171+
'moveend',
172+
'movestart',
173+
'propertychange',
174+
'resizeend',
175+
'resizestart',
176+
'rowenter',
177+
'rowexit',
178+
'rowsdelete',
179+
'rowsinserted',
180+
'command',
181+
'compassneedscalibration',
182+
'deactivate',
183+
'help',
184+
'mscontentzoom',
185+
'msmanipulationstatechanged',
186+
'msgesturechange',
187+
'msgesturedoubletap',
188+
'msgestureend',
189+
'msgesturehold',
190+
'msgesturestart',
191+
'msgesturetap',
192+
'msgotpointercapture',
193+
'msinertiastart',
194+
'mslostpointercapture',
195+
'mspointercancel',
196+
'mspointerdown',
197+
'mspointerenter',
198+
'mspointerhover',
199+
'mspointerleave',
200+
'mspointermove',
201+
'mspointerout',
202+
'mspointerover',
203+
'mspointerup',
204+
'pointerout',
205+
'mssitemodejumplistitemremoved',
206+
'msthumbnailclick',
207+
'stop',
208+
'storagecommit'
209+
];
210+
const webglEventNames = ['webglcontextrestored', 'webglcontextlost', 'webglcontextcreationerror'];
211+
const formEventNames = ['autocomplete', 'autocompleteerror'];
212+
const detailEventNames = ['toggle'];
213+
const frameEventNames = ['load'];
214+
const frameSetEventNames = ['blur', 'error', 'focus', 'load', 'resize', 'scroll'];
215+
const marqueeEventNames = ['bounce', 'finish', 'start'];
216+
217+
const XMLHttpRequestEventNames = [
218+
'loadstart', 'progress', 'abort', 'error', 'load', 'progress', 'timeout', 'loadend',
219+
'readystatechange'
220+
];
221+
const IDBIndexEventNames =
222+
['upgradeneeded', 'complete', 'abort', 'success', 'error', 'blocked', 'versionchange', 'close'];
223+
const websocketEventNames = ['close', 'error', 'open', 'message'];
224+
225+
const eventNames = globalEventHandlersEventNames.concat(
226+
webglEventNames, formEventNames, detailEventNames, documentEventNames, windowEventNames,
227+
htmlElementEventNames, ieElementEventNames);
16228

17229
export function propertyDescriptorPatch(_global: any) {
18230
if (isNode && !isMix) {
@@ -23,24 +235,44 @@ export function propertyDescriptorPatch(_global: any) {
23235
if (canPatchViaPropertyDescriptor()) {
24236
// for browsers that we can patch the descriptor: Chrome & Firefox
25237
if (isBrowser) {
26-
patchOnProperties(window, eventNames.concat(['resize']));
238+
// in IE/Edge, onProp not exist in window object, but in WindowPrototype
239+
// so we need to pass WindowPrototype to check onProp exist or not
240+
patchOnProperties(window, eventNames, Object.getPrototypeOf(window));
27241
patchOnProperties(Document.prototype, eventNames);
242+
28243
if (typeof(<any>window)['SVGElement'] !== 'undefined') {
29244
patchOnProperties((<any>window)['SVGElement'].prototype, eventNames);
30245
}
246+
patchOnProperties(Element.prototype, eventNames);
31247
patchOnProperties(HTMLElement.prototype, eventNames);
248+
patchOnProperties(HTMLMediaElement.prototype, mediaElementEventNames);
249+
patchOnProperties(HTMLFrameSetElement.prototype, windowEventNames.concat(frameSetEventNames));
250+
patchOnProperties(HTMLBodyElement.prototype, windowEventNames.concat(frameSetEventNames));
251+
patchOnProperties(HTMLFrameElement.prototype, frameEventNames);
252+
patchOnProperties(HTMLIFrameElement.prototype, frameEventNames);
253+
254+
const HTMLMarqueeElement = (window as any)['HTMLMarqueeElement'];
255+
if (HTMLMarqueeElement) {
256+
patchOnProperties(HTMLMarqueeElement.prototype, marqueeEventNames);
257+
}
258+
}
259+
patchOnProperties(XMLHttpRequest.prototype, XMLHttpRequestEventNames);
260+
const XMLHttpRequestEventTarget = _global['XMLHttpRequestEventTarget'];
261+
if (XMLHttpRequestEventTarget) {
262+
patchOnProperties(
263+
XMLHttpRequestEventTarget && XMLHttpRequestEventTarget.prototype,
264+
XMLHttpRequestEventNames);
32265
}
33-
patchOnProperties(XMLHttpRequest.prototype, null);
34266
if (typeof IDBIndex !== 'undefined') {
35-
patchOnProperties(IDBIndex.prototype, null);
36-
patchOnProperties(IDBRequest.prototype, null);
37-
patchOnProperties(IDBOpenDBRequest.prototype, null);
38-
patchOnProperties(IDBDatabase.prototype, null);
39-
patchOnProperties(IDBTransaction.prototype, null);
40-
patchOnProperties(IDBCursor.prototype, null);
267+
patchOnProperties(IDBIndex.prototype, IDBIndexEventNames);
268+
patchOnProperties(IDBRequest.prototype, IDBIndexEventNames);
269+
patchOnProperties(IDBOpenDBRequest.prototype, IDBIndexEventNames);
270+
patchOnProperties(IDBDatabase.prototype, IDBIndexEventNames);
271+
patchOnProperties(IDBTransaction.prototype, IDBIndexEventNames);
272+
patchOnProperties(IDBCursor.prototype, IDBIndexEventNames);
41273
}
42274
if (supportsWebSocket) {
43-
patchOnProperties(WebSocket.prototype, null);
275+
patchOnProperties(WebSocket.prototype, websocketEventNames);
44276
}
45277
} else {
46278
// Safari, Android browsers (Jelly Bean)

lib/common/utils.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,18 @@ export const isMix: boolean = typeof process !== 'undefined' &&
5959
{}.toString.call(process) === '[object process]' && !isWebWorker &&
6060
!!(typeof window !== 'undefined' && (window as any)['HTMLElement']);
6161

62-
export function patchProperty(obj: any, prop: string) {
63-
const desc = Object.getOwnPropertyDescriptor(obj, prop) || {enumerable: true, configurable: true};
64-
// if the descriptor is not configurable
62+
export function patchProperty(obj: any, prop: string, prototype?: any) {
63+
let desc = Object.getOwnPropertyDescriptor(obj, prop);
64+
if (!desc && prototype) {
65+
// when patch window object, use prototype to check prop exist or not
66+
const prototypeDesc = Object.getOwnPropertyDescriptor(prototype, prop);
67+
if (prototypeDesc) {
68+
desc = {enumerable: true, configurable: true};
69+
}
70+
}
71+
// if the descriptor not exists or is not configurable
6572
// just return
66-
if (!desc.configurable) {
73+
if (!desc || !desc.configurable) {
6774
return;
6875
}
6976

@@ -148,10 +155,10 @@ export function patchProperty(obj: any, prop: string) {
148155
Object.defineProperty(obj, prop, desc);
149156
}
150157

151-
export function patchOnProperties(obj: any, properties: string[]) {
158+
export function patchOnProperties(obj: any, properties: string[], prototype?: any) {
152159
if (properties) {
153160
for (let i = 0; i < properties.length; i++) {
154-
patchProperty(obj, 'on' + properties[i]);
161+
patchProperty(obj, 'on' + properties[i], prototype);
155162
}
156163
} else {
157164
const onProperties = [];
@@ -161,7 +168,7 @@ export function patchOnProperties(obj: any, properties: string[]) {
161168
}
162169
}
163170
for (let j = 0; j < onProperties.length; j++) {
164-
patchProperty(obj, onProperties[j]);
171+
patchProperty(obj, onProperties[j], prototype);
165172
}
166173
}
167174
}

test/browser/XMLHttpRequest.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ describe('XMLHttpRequest', function() {
6767
});
6868

6969
const supportsOnProgress = function() {
70-
return 'onprogress' in new XMLHttpRequest();
70+
return 'onprogress' in (new XMLHttpRequest());
7171
};
72+
7273
(<any>supportsOnProgress).message = 'XMLHttpRequest.onprogress';
7374

7475
describe('onprogress', ifEnvSupports(supportsOnProgress, function() {

test/browser/browser.spec.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,53 @@ describe('Zone', function() {
108108
eventListenerSpy = jasmine.createSpy('eventListener');
109109
});
110110

111+
function checkIsOnPropertiesPatched(target: any) {
112+
for (let prop in target) {
113+
if (prop.substr(0, 2) === 'on') {
114+
target[prop] = noop;
115+
expect(target[Zone.__symbol__('_' + prop)]).toBeTruthy();
116+
target[prop] = null;
117+
expect(!target[Zone.__symbol__('_' + prop)]).toBeTruthy();
118+
}
119+
}
120+
}
121+
122+
it('should patch all possbile on properties on element', function() {
123+
const htmlElementTagNames: string[] = [
124+
'a', 'area', 'audio', 'base', 'basefont', 'blockquote', 'br',
125+
'button', 'canvas', 'caption', 'col', 'colgroup', 'data', 'datalist',
126+
'del', 'dir', 'div', 'dl', 'embed', 'fieldset', 'font',
127+
'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4',
128+
'h5', 'h6', 'head', 'hr', 'html', 'iframe', 'img',
129+
'input', 'ins', 'isindex', 'label', 'legend', 'li', 'link',
130+
'listing', 'map', 'marquee', 'menu', 'meta', 'meter', 'nextid',
131+
'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture',
132+
'pre', 'progress', 'q', 'script', 'select', 'source', 'span',
133+
'style', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot',
134+
'th', 'thead', 'time', 'title', 'tr', 'track', 'ul',
135+
'video'
136+
];
137+
htmlElementTagNames.forEach(tagName => {
138+
checkIsOnPropertiesPatched(document.createElement(tagName));
139+
});
140+
});
141+
142+
it('should patch all possbile on properties on body', function() {
143+
checkIsOnPropertiesPatched(document.body);
144+
});
145+
146+
it('should patch all possbile on properties on Document', function() {
147+
checkIsOnPropertiesPatched(document);
148+
});
149+
150+
it('should patch all possbile on properties on Window', function() {
151+
checkIsOnPropertiesPatched(window);
152+
});
153+
154+
it('should patch all possbile on properties on xhr', function() {
155+
checkIsOnPropertiesPatched(new XMLHttpRequest());
156+
});
157+
111158
it('window onclick should be in zone',
112159
ifEnvSupports(canPatchOnProperty(window, 'onmousedown'), function() {
113160
zone.run(function() {

0 commit comments

Comments
 (0)