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

Commit

Permalink
fix(closure): patchOnProperty with exact eventNames as possible (#768)
Browse files Browse the repository at this point in the history
* fix(closure): patchOnProperty with exact eventNames as possible

* fix(patch): only patch property which exists
  • Loading branch information
JiaLiPassion authored and mhevery committed May 19, 2017
1 parent 7ad3070 commit 582ff7b
Show file tree
Hide file tree
Showing 4 changed files with 307 additions and 20 deletions.
256 changes: 244 additions & 12 deletions lib/browser/property-descriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,221 @@ import {isBrowser, isMix, isNode, patchClass, patchOnProperties, zoneSymbol} fro

import * as webSocketPatch from './websocket';

const eventNames =
'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'
.split(' ');
const globalEventHandlersEventNames = [
'abort',
'animationcancel',
'animationend',
'animationiteration',
'auxclick',
'beforeinput',
'blur',
'cancel',
'canplay',
'canplaythrough',
'change',
'compositionstart',
'compositionupdate',
'compositionend',
'cuechange',
'click',
'close',
'contextmenu',
'curechange',
'dblclick',
'drag',
'dragend',
'dragenter',
'dragexit',
'dragleave',
'dragover',
'drop',
'durationchange',
'emptied',
'ended',
'error',
'focus',
'focusin',
'focusout',
'gotpointercapture',
'input',
'invalid',
'keydown',
'keypress',
'keyup',
'load',
'loadstart',
'loadeddata',
'loadedmetadata',
'lostpointercapture',
'mousedown',
'mouseenter',
'mouseleave',
'mousemove',
'mouseout',
'mouseover',
'mouseup',
'mousewheel',
'pause',
'play',
'playing',
'pointercancel',
'pointerdown',
'pointerenter',
'pointerleave',
'pointerlockchange',
'mozpointerlockchange',
'webkitpointerlockerchange',
'pointerlockerror',
'mozpointerlockerror',
'webkitpointerlockerror',
'pointermove',
'pointout',
'pointerover',
'pointerup',
'progress',
'ratechange',
'reset',
'resize',
'scroll',
'seeked',
'seeking',
'select',
'selectionchange',
'selectstart',
'show',
'sort',
'stalled',
'submit',
'suspend',
'timeupdate',
'volumechange',
'touchcancel',
'touchmove',
'touchstart',
'transitioncancel',
'transitionend',
'waiting',
'wheel'
];
const documentEventNames = [
'afterscriptexecute', 'beforescriptexecute', 'DOMContentLoaded', 'fullscreenchange',
'mozfullscreenchange', 'webkitfullscreenchange', 'msfullscreenchange', 'fullscreenerror',
'mozfullscreenerror', 'webkitfullscreenerror', 'msfullscreenerror', 'readystatechange'
];
const windowEventNames = [
'absolutedeviceorientation',
'afterinput',
'afterprint',
'appinstalled',
'beforeinstallprompt',
'beforeprint',
'beforeunload',
'devicelight',
'devicemotion',
'deviceorientation',
'deviceorientationabsolute',
'deviceproximity',
'hashchange',
'languagechange',
'message',
'mozbeforepaint',
'offline',
'online',
'paint',
'pageshow',
'pagehide',
'popstate',
'rejectionhandled',
'storage',
'unhandledrejection',
'unload',
'userproximity',
'vrdisplyconnected',
'vrdisplaydisconnected',
'vrdisplaypresentchange'
];
const htmlElementEventNames = [
'beforecopy', 'beforecut', 'beforepaste', 'copy', 'cut', 'paste', 'dragstart', 'loadend',
'animationstart', 'search', 'transitionrun', 'transitionstart', 'webkitanimationend',
'webkitanimationiteration', 'webkitanimationstart', 'webkittransitionend'
];
const mediaElementEventNames =
['encrypted', 'waitingforkey', 'msneedkey', 'mozinterruptbegin', 'mozinterruptend'];
const ieElementEventNames = [
'activate',
'afterupdate',
'ariarequest',
'beforeactivate',
'beforedeactivate',
'beforeeditfocus',
'beforeupdate',
'cellchange',
'controlselect',
'dataavailable',
'datasetchanged',
'datasetcomplete',
'errorupdate',
'filterchange',
'layoutcomplete',
'losecapture',
'move',
'moveend',
'movestart',
'propertychange',
'resizeend',
'resizestart',
'rowenter',
'rowexit',
'rowsdelete',
'rowsinserted',
'command',
'compassneedscalibration',
'deactivate',
'help',
'mscontentzoom',
'msmanipulationstatechanged',
'msgesturechange',
'msgesturedoubletap',
'msgestureend',
'msgesturehold',
'msgesturestart',
'msgesturetap',
'msgotpointercapture',
'msinertiastart',
'mslostpointercapture',
'mspointercancel',
'mspointerdown',
'mspointerenter',
'mspointerhover',
'mspointerleave',
'mspointermove',
'mspointerout',
'mspointerover',
'mspointerup',
'pointerout',
'mssitemodejumplistitemremoved',
'msthumbnailclick',
'stop',
'storagecommit'
];
const webglEventNames = ['webglcontextrestored', 'webglcontextlost', 'webglcontextcreationerror'];
const formEventNames = ['autocomplete', 'autocompleteerror'];
const detailEventNames = ['toggle'];
const frameEventNames = ['load'];
const frameSetEventNames = ['blur', 'error', 'focus', 'load', 'resize', 'scroll'];
const marqueeEventNames = ['bounce', 'finish', 'start'];

const XMLHttpRequestEventNames = [
'loadstart', 'progress', 'abort', 'error', 'load', 'progress', 'timeout', 'loadend',
'readystatechange'
];
const IDBIndexEventNames =
['upgradeneeded', 'complete', 'abort', 'success', 'error', 'blocked', 'versionchange', 'close'];
const websocketEventNames = ['close', 'error', 'open', 'message'];

const eventNames = globalEventHandlersEventNames.concat(
webglEventNames, formEventNames, detailEventNames, documentEventNames, windowEventNames,
htmlElementEventNames, ieElementEventNames);

export function propertyDescriptorPatch(_global: any) {
if (isNode && !isMix) {
Expand All @@ -23,24 +235,44 @@ export function propertyDescriptorPatch(_global: any) {
if (canPatchViaPropertyDescriptor()) {
// for browsers that we can patch the descriptor: Chrome & Firefox
if (isBrowser) {
patchOnProperties(window, eventNames.concat(['resize']));
// in IE/Edge, onProp not exist in window object, but in WindowPrototype
// so we need to pass WindowPrototype to check onProp exist or not
patchOnProperties(window, eventNames, Object.getPrototypeOf(window));
patchOnProperties(Document.prototype, eventNames);

if (typeof(<any>window)['SVGElement'] !== 'undefined') {
patchOnProperties((<any>window)['SVGElement'].prototype, eventNames);
}
patchOnProperties(Element.prototype, eventNames);
patchOnProperties(HTMLElement.prototype, eventNames);
patchOnProperties(HTMLMediaElement.prototype, mediaElementEventNames);
patchOnProperties(HTMLFrameSetElement.prototype, windowEventNames.concat(frameSetEventNames));
patchOnProperties(HTMLBodyElement.prototype, windowEventNames.concat(frameSetEventNames));
patchOnProperties(HTMLFrameElement.prototype, frameEventNames);
patchOnProperties(HTMLIFrameElement.prototype, frameEventNames);

const HTMLMarqueeElement = (window as any)['HTMLMarqueeElement'];
if (HTMLMarqueeElement) {
patchOnProperties(HTMLMarqueeElement.prototype, marqueeEventNames);
}
}
patchOnProperties(XMLHttpRequest.prototype, XMLHttpRequestEventNames);
const XMLHttpRequestEventTarget = _global['XMLHttpRequestEventTarget'];
if (XMLHttpRequestEventTarget) {
patchOnProperties(
XMLHttpRequestEventTarget && XMLHttpRequestEventTarget.prototype,
XMLHttpRequestEventNames);
}
patchOnProperties(XMLHttpRequest.prototype, null);
if (typeof IDBIndex !== 'undefined') {
patchOnProperties(IDBIndex.prototype, null);
patchOnProperties(IDBRequest.prototype, null);
patchOnProperties(IDBOpenDBRequest.prototype, null);
patchOnProperties(IDBDatabase.prototype, null);
patchOnProperties(IDBTransaction.prototype, null);
patchOnProperties(IDBCursor.prototype, null);
patchOnProperties(IDBIndex.prototype, IDBIndexEventNames);
patchOnProperties(IDBRequest.prototype, IDBIndexEventNames);
patchOnProperties(IDBOpenDBRequest.prototype, IDBIndexEventNames);
patchOnProperties(IDBDatabase.prototype, IDBIndexEventNames);
patchOnProperties(IDBTransaction.prototype, IDBIndexEventNames);
patchOnProperties(IDBCursor.prototype, IDBIndexEventNames);
}
if (supportsWebSocket) {
patchOnProperties(WebSocket.prototype, null);
patchOnProperties(WebSocket.prototype, websocketEventNames);
}
} else {
// Safari, Android browsers (Jelly Bean)
Expand Down
21 changes: 14 additions & 7 deletions lib/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,18 @@ export const isMix: boolean = typeof process !== 'undefined' &&
{}.toString.call(process) === '[object process]' && !isWebWorker &&
!!(typeof window !== 'undefined' && (window as any)['HTMLElement']);

export function patchProperty(obj: any, prop: string) {
const desc = Object.getOwnPropertyDescriptor(obj, prop) || {enumerable: true, configurable: true};
// if the descriptor is not configurable
export function patchProperty(obj: any, prop: string, prototype?: any) {
let desc = Object.getOwnPropertyDescriptor(obj, prop);
if (!desc && prototype) {
// when patch window object, use prototype to check prop exist or not
const prototypeDesc = Object.getOwnPropertyDescriptor(prototype, prop);
if (prototypeDesc) {
desc = {enumerable: true, configurable: true};
}
}
// if the descriptor not exists or is not configurable
// just return
if (!desc.configurable) {
if (!desc || !desc.configurable) {
return;
}

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

export function patchOnProperties(obj: any, properties: string[]) {
export function patchOnProperties(obj: any, properties: string[], prototype?: any) {
if (properties) {
for (let i = 0; i < properties.length; i++) {
patchProperty(obj, 'on' + properties[i]);
patchProperty(obj, 'on' + properties[i], prototype);
}
} else {
const onProperties = [];
Expand All @@ -161,7 +168,7 @@ export function patchOnProperties(obj: any, properties: string[]) {
}
}
for (let j = 0; j < onProperties.length; j++) {
patchProperty(obj, onProperties[j]);
patchProperty(obj, onProperties[j], prototype);
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion test/browser/XMLHttpRequest.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ describe('XMLHttpRequest', function() {
});

const supportsOnProgress = function() {
return 'onprogress' in new XMLHttpRequest();
return 'onprogress' in (new XMLHttpRequest());
};

(<any>supportsOnProgress).message = 'XMLHttpRequest.onprogress';

describe('onprogress', ifEnvSupports(supportsOnProgress, function() {
Expand Down
47 changes: 47 additions & 0 deletions test/browser/browser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,53 @@ describe('Zone', function() {
eventListenerSpy = jasmine.createSpy('eventListener');
});

function checkIsOnPropertiesPatched(target: any) {
for (let prop in target) {
if (prop.substr(0, 2) === 'on') {
target[prop] = noop;
expect(target[Zone.__symbol__('_' + prop)]).toBeTruthy();
target[prop] = null;
expect(!target[Zone.__symbol__('_' + prop)]).toBeTruthy();
}
}
}

it('should patch all possbile on properties on element', function() {
const htmlElementTagNames: string[] = [
'a', 'area', 'audio', 'base', 'basefont', 'blockquote', 'br',
'button', 'canvas', 'caption', 'col', 'colgroup', 'data', 'datalist',
'del', 'dir', 'div', 'dl', 'embed', 'fieldset', 'font',
'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4',
'h5', 'h6', 'head', 'hr', 'html', 'iframe', 'img',
'input', 'ins', 'isindex', 'label', 'legend', 'li', 'link',
'listing', 'map', 'marquee', 'menu', 'meta', 'meter', 'nextid',
'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture',
'pre', 'progress', 'q', 'script', 'select', 'source', 'span',
'style', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot',
'th', 'thead', 'time', 'title', 'tr', 'track', 'ul',
'video'
];
htmlElementTagNames.forEach(tagName => {
checkIsOnPropertiesPatched(document.createElement(tagName));
});
});

it('should patch all possbile on properties on body', function() {
checkIsOnPropertiesPatched(document.body);
});

it('should patch all possbile on properties on Document', function() {
checkIsOnPropertiesPatched(document);
});

it('should patch all possbile on properties on Window', function() {
checkIsOnPropertiesPatched(window);
});

it('should patch all possbile on properties on xhr', function() {
checkIsOnPropertiesPatched(new XMLHttpRequest());
});

it('window onclick should be in zone',
ifEnvSupports(canPatchOnProperty(window, 'onmousedown'), function() {
zone.run(function() {
Expand Down

0 comments on commit 582ff7b

Please sign in to comment.