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

fix(closure): patchOnProperty with exact eventNames as possible #768

Merged
merged 2 commits into from
May 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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