mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(zone.js): remove onProp eventNames array to reduce the bundle size (#40962)
Zone.js supports the google closure compiler in the advance optimization mode, to prevent closure compiler modify the `onProperty` name such as `Element.prototype.onclick`, Zone.js implements the `onProperty` patch logic by declaring all the event names in the source code, this increases the bundle size and also require updating the event names array to keep the information updated. Now google closure compiler has the required event names defined in the built-in externs files, so zone.js can use more simple implementation and decrease the bundle size of zone.js (about 4k). PR Close #40962
This commit is contained in:
committed by
Jessica Janiuk
parent
c9679760b2
commit
0f298a13db
@@ -10,9 +10,12 @@ import {globalSources, patchEventPrototype, patchEventTarget, zoneSymbolEventNam
|
||||
import {ADD_EVENT_LISTENER_STR, ArraySlice, attachOriginToPatched, bindArguments, FALSE_STR, isBrowser, isIEOrEdge, isMix, isNode, ObjectCreate, ObjectDefineProperty, ObjectGetOwnPropertyDescriptor, patchClass, patchMacroTask, patchMethod, patchOnProperties, REMOVE_EVENT_LISTENER_STR, TRUE_STR, wrapWithCurrentZone, ZONE_SYMBOL_PREFIX} from '../common/utils';
|
||||
|
||||
import {patchCallbacks} from './browser-util';
|
||||
import {eventNames, filterProperties} from './property-descriptor';
|
||||
import {filterProperties, getOnEventNames} from './property-descriptor';
|
||||
|
||||
Zone.__load_patch('util', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
|
||||
// Collect native event names by looking at properties
|
||||
// on the global namespace, e.g. 'onclick'.
|
||||
const eventNames: string[] = getOnEventNames(global);
|
||||
api.patchOnProperties = patchOnProperties;
|
||||
api.patchMethod = patchMethod;
|
||||
api.bindArguments = bindArguments;
|
||||
|
||||
@@ -12,226 +12,6 @@
|
||||
|
||||
import {isBrowser, isIE, isMix, isNode, ObjectGetPrototypeOf, patchOnProperties} from '../common/utils';
|
||||
|
||||
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',
|
||||
'orientationchange',
|
||||
'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',
|
||||
'touchend',
|
||||
'transitioncancel',
|
||||
'transitionend',
|
||||
'waiting',
|
||||
'wheel'
|
||||
];
|
||||
const documentEventNames = [
|
||||
'afterscriptexecute', 'beforescriptexecute', 'DOMContentLoaded', 'freeze', 'fullscreenchange',
|
||||
'mozfullscreenchange', 'webkitfullscreenchange', 'msfullscreenchange', 'fullscreenerror',
|
||||
'mozfullscreenerror', 'webkitfullscreenerror', 'msfullscreenerror', 'readystatechange',
|
||||
'visibilitychange', 'resume'
|
||||
];
|
||||
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',
|
||||
'vrdisplayconnected',
|
||||
'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', 'messageerror'];
|
||||
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 workerEventNames = ['error', 'message'];
|
||||
|
||||
export const eventNames = globalEventHandlersEventNames.concat(
|
||||
webglEventNames, formEventNames, detailEventNames, documentEventNames, windowEventNames,
|
||||
htmlElementEventNames, ieElementEventNames);
|
||||
|
||||
export interface IgnoreProperty {
|
||||
target: any;
|
||||
ignoreProperties: string[];
|
||||
@@ -263,6 +43,16 @@ export function patchFilteredProperties(
|
||||
patchOnProperties(target, filteredProperties, prototype);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all event name properties which the event name startsWith `on`
|
||||
* from the target object itself, inherited properties are not considered.
|
||||
*/
|
||||
export function getOnEventNames(target: Object) {
|
||||
return Object.getOwnPropertyNames(target)
|
||||
.filter(name => name.startsWith('on') && name.length > 2)
|
||||
.map(name => name.substring(2));
|
||||
}
|
||||
|
||||
export function propertyDescriptorPatch(api: _ZonePrivate, _global: any) {
|
||||
if (isNode && !isMix) {
|
||||
return;
|
||||
@@ -271,64 +61,32 @@ export function propertyDescriptorPatch(api: _ZonePrivate, _global: any) {
|
||||
// events are already been patched by legacy patch.
|
||||
return;
|
||||
}
|
||||
const supportsWebSocket = typeof WebSocket !== 'undefined';
|
||||
const ignoreProperties: IgnoreProperty[] = _global['__Zone_ignore_on_properties'];
|
||||
// for browsers that we can patch the descriptor: Chrome & Firefox
|
||||
let patchTargets: string[] = [];
|
||||
if (isBrowser) {
|
||||
const internalWindow: any = window;
|
||||
patchTargets = patchTargets.concat([
|
||||
'Document', 'SVGElement', 'Element', 'HTMLElement', 'HTMLBodyElement', 'HTMLMediaElement',
|
||||
'HTMLFrameSetElement', 'HTMLFrameElement', 'HTMLIFrameElement', 'HTMLMarqueeElement', 'Worker'
|
||||
]);
|
||||
const ignoreErrorProperties =
|
||||
isIE() ? [{target: internalWindow, ignoreProperties: ['error']}] : [];
|
||||
// in IE/Edge, onProp not exist in window object, but in WindowPrototype
|
||||
// so we need to pass WindowPrototype to check onProp exist or not
|
||||
patchFilteredProperties(
|
||||
internalWindow, eventNames.concat(['messageerror']),
|
||||
internalWindow, getOnEventNames(internalWindow),
|
||||
ignoreProperties ? ignoreProperties.concat(ignoreErrorProperties) : ignoreProperties,
|
||||
ObjectGetPrototypeOf(internalWindow));
|
||||
patchFilteredProperties(Document.prototype, eventNames, ignoreProperties);
|
||||
|
||||
if (typeof internalWindow['SVGElement'] !== 'undefined') {
|
||||
patchFilteredProperties(internalWindow['SVGElement'].prototype, eventNames, ignoreProperties);
|
||||
}
|
||||
patchFilteredProperties(Element.prototype, eventNames, ignoreProperties);
|
||||
patchFilteredProperties(HTMLElement.prototype, eventNames, ignoreProperties);
|
||||
patchFilteredProperties(HTMLMediaElement.prototype, mediaElementEventNames, ignoreProperties);
|
||||
patchFilteredProperties(
|
||||
HTMLFrameSetElement.prototype, windowEventNames.concat(frameSetEventNames),
|
||||
ignoreProperties);
|
||||
patchFilteredProperties(
|
||||
HTMLBodyElement.prototype, windowEventNames.concat(frameSetEventNames), ignoreProperties);
|
||||
patchFilteredProperties(HTMLFrameElement.prototype, frameEventNames, ignoreProperties);
|
||||
patchFilteredProperties(HTMLIFrameElement.prototype, frameEventNames, ignoreProperties);
|
||||
|
||||
const HTMLMarqueeElement = internalWindow['HTMLMarqueeElement'];
|
||||
if (HTMLMarqueeElement) {
|
||||
patchFilteredProperties(HTMLMarqueeElement.prototype, marqueeEventNames, ignoreProperties);
|
||||
}
|
||||
const Worker = internalWindow['Worker'];
|
||||
if (Worker) {
|
||||
patchFilteredProperties(Worker.prototype, workerEventNames, ignoreProperties);
|
||||
}
|
||||
}
|
||||
const XMLHttpRequest = _global['XMLHttpRequest'];
|
||||
if (XMLHttpRequest) {
|
||||
// XMLHttpRequest is not available in ServiceWorker, so we need to check here
|
||||
patchFilteredProperties(XMLHttpRequest.prototype, XMLHttpRequestEventNames, ignoreProperties);
|
||||
}
|
||||
const XMLHttpRequestEventTarget = _global['XMLHttpRequestEventTarget'];
|
||||
if (XMLHttpRequestEventTarget) {
|
||||
patchFilteredProperties(
|
||||
XMLHttpRequestEventTarget && XMLHttpRequestEventTarget.prototype, XMLHttpRequestEventNames,
|
||||
ignoreProperties);
|
||||
}
|
||||
if (typeof IDBIndex !== 'undefined') {
|
||||
patchFilteredProperties(IDBIndex.prototype, IDBIndexEventNames, ignoreProperties);
|
||||
patchFilteredProperties(IDBRequest.prototype, IDBIndexEventNames, ignoreProperties);
|
||||
patchFilteredProperties(IDBOpenDBRequest.prototype, IDBIndexEventNames, ignoreProperties);
|
||||
patchFilteredProperties(IDBDatabase.prototype, IDBIndexEventNames, ignoreProperties);
|
||||
patchFilteredProperties(IDBTransaction.prototype, IDBIndexEventNames, ignoreProperties);
|
||||
patchFilteredProperties(IDBCursor.prototype, IDBIndexEventNames, ignoreProperties);
|
||||
}
|
||||
if (supportsWebSocket) {
|
||||
patchFilteredProperties(WebSocket.prototype, websocketEventNames, ignoreProperties);
|
||||
patchTargets = patchTargets.concat([
|
||||
'XMLHttpRequest', 'XMLHttpRequestEventTarget', 'IDBIndex', 'IDBRequest', 'IDBOpenDBRequest',
|
||||
'IDBDatabase', 'IDBTransaction', 'IDBCursor', 'WebSocket'
|
||||
]);
|
||||
for (let i = 0; i < patchTargets.length; i++) {
|
||||
const target = _global[patchTargets[i]];
|
||||
target && target.prototype &&
|
||||
patchFilteredProperties(
|
||||
target.prototype, getOnEventNames(target.prototype), ignoreProperties);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,22 +207,20 @@ export function patchProperty(obj: any, prop: string, prototype?: any) {
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
let previousValue = (target as any)[eventNameSymbol];
|
||||
if (previousValue) {
|
||||
|
||||
const previousValue = (target as any)[eventNameSymbol];
|
||||
if (previousValue && typeof previousValue === 'function') {
|
||||
target.removeEventListener(eventName, wrapFn);
|
||||
}
|
||||
|
||||
// issue #978, when onload handler was added before loading zone.js
|
||||
// we should remove it with originalDescSet
|
||||
if (originalDescSet) {
|
||||
originalDescSet.apply(target, NULL_ON_PROP_VALUE);
|
||||
}
|
||||
|
||||
(target as any)[eventNameSymbol] = newValue;
|
||||
if (typeof newValue === 'function') {
|
||||
(target as any)[eventNameSymbol] = newValue;
|
||||
// issue #978, when onload handler was added before loading zone.js
|
||||
// we should remove it with originalDescSet
|
||||
if (originalDescSet) {
|
||||
originalDescSet.apply(target, NULL_ON_PROP_VALUE);
|
||||
}
|
||||
target.addEventListener(eventName, wrapFn, false);
|
||||
} else {
|
||||
(target as any)[eventNameSymbol] = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
@@ -138,17 +138,265 @@ describe('Zone', function() {
|
||||
hookSpy = jasmine.createSpy('hook');
|
||||
eventListenerSpy = jasmine.createSpy('eventListener');
|
||||
});
|
||||
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',
|
||||
'orientationchange',
|
||||
'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',
|
||||
'touchend',
|
||||
'transitioncancel',
|
||||
'transitionend',
|
||||
'waiting',
|
||||
'wheel'
|
||||
];
|
||||
const documentEventNames = [
|
||||
'afterscriptexecute', 'beforescriptexecute', 'DOMContentLoaded', 'freeze',
|
||||
'fullscreenchange', 'mozfullscreenchange', 'webkitfullscreenchange',
|
||||
'msfullscreenchange', 'fullscreenerror', 'mozfullscreenerror', 'webkitfullscreenerror',
|
||||
'msfullscreenerror', 'readystatechange', 'visibilitychange', 'resume'
|
||||
];
|
||||
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',
|
||||
'vrdisplayconnected',
|
||||
'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', 'messageerror'];
|
||||
const marqueeEventNames = ['bounce', 'finish', 'start'];
|
||||
|
||||
function checkIsOnPropertiesPatched(target: any, ignoredProperties?: string[]) {
|
||||
for (let prop in target) {
|
||||
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 workerEventNames = ['error', 'message'];
|
||||
|
||||
const eventNames = globalEventHandlersEventNames.concat(
|
||||
webglEventNames, formEventNames, detailEventNames, documentEventNames,
|
||||
windowEventNames, htmlElementEventNames, ieElementEventNames);
|
||||
|
||||
|
||||
function checkIsOnPropertiesPatched(
|
||||
target: any, shouldPatchedProperties?: string[], ignoredProperties?: string[]) {
|
||||
let checkTargetProps =
|
||||
shouldPatchedProperties && shouldPatchedProperties.map(p => `on${p}`);
|
||||
if (!checkTargetProps) {
|
||||
checkTargetProps = [];
|
||||
for (let prop in target) {
|
||||
checkTargetProps.push(prop);
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < checkTargetProps.length; i++) {
|
||||
const prop = checkTargetProps[i];
|
||||
if (ignoredProperties &&
|
||||
ignoredProperties.filter(ignoreProp => ignoreProp === prop).length > 0) {
|
||||
continue;
|
||||
}
|
||||
if (prop.substr(0, 2) === 'on' && prop.length > 2) {
|
||||
let propExistsOnTarget = false;
|
||||
let checkTarget = target;
|
||||
while (checkTarget && checkTarget !== Object) {
|
||||
const desc = Object.getOwnPropertyDescriptor(checkTarget, prop);
|
||||
if (desc && desc.configurable) {
|
||||
propExistsOnTarget = true;
|
||||
break;
|
||||
}
|
||||
checkTarget = Object.getPrototypeOf(checkTarget);
|
||||
}
|
||||
if (!propExistsOnTarget) {
|
||||
continue;
|
||||
}
|
||||
target[prop] = noop;
|
||||
if (!target[Zone.__symbol__('ON_PROPERTY' + prop.substr(2))]) {
|
||||
console.log('onProp is null:', prop);
|
||||
fail(`${prop} of ${target} is not patched`);
|
||||
} else {
|
||||
target[prop] = null;
|
||||
expect(!target[Zone.__symbol__('ON_PROPERTY' + prop.substr(2))]).toBeTruthy();
|
||||
@@ -157,7 +405,90 @@ describe('Zone', function() {
|
||||
}
|
||||
}
|
||||
|
||||
it('should patch all possbile on properties on element', function() {
|
||||
it('should patch all possible on properties on native prototype', function() {
|
||||
function isPropertyPatched(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 || !desc.configurable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const onPropPatchedSymbol = zoneSymbol('on' + prop + 'patched');
|
||||
if (obj.hasOwnProperty(onPropPatchedSymbol) && obj[onPropPatchedSymbol]) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isPropertiesPatched(obj: any, properties: string[]|null, prototype?: any) {
|
||||
if (!properties) {
|
||||
return [];
|
||||
}
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
if (!isPropertyPatched(obj, 'on' + properties[i], prototype)) {
|
||||
fail(`${properties[i]} is not patched on ${obj}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
isPropertiesPatched(
|
||||
window, eventNames.concat(['messageerror']), Object.getPrototypeOf(window));
|
||||
isPropertiesPatched(Document.prototype, eventNames);
|
||||
|
||||
if (typeof window['SVGElement'] !== 'undefined') {
|
||||
isPropertiesPatched(window['SVGElement'].prototype, eventNames);
|
||||
}
|
||||
isPropertiesPatched(Element.prototype, eventNames);
|
||||
isPropertiesPatched(HTMLElement.prototype, eventNames);
|
||||
isPropertiesPatched(HTMLMediaElement.prototype, mediaElementEventNames);
|
||||
isPropertiesPatched(
|
||||
HTMLFrameSetElement.prototype, windowEventNames.concat(frameSetEventNames));
|
||||
isPropertiesPatched(
|
||||
HTMLBodyElement.prototype, windowEventNames.concat(frameSetEventNames));
|
||||
isPropertiesPatched(HTMLFrameElement.prototype, frameEventNames);
|
||||
isPropertiesPatched(HTMLIFrameElement.prototype, frameEventNames);
|
||||
|
||||
const HTMLMarqueeElement = window['HTMLMarqueeElement'];
|
||||
if (HTMLMarqueeElement) {
|
||||
isPropertiesPatched(HTMLMarqueeElement.prototype, marqueeEventNames);
|
||||
}
|
||||
const Worker = window['Worker'];
|
||||
if (Worker) {
|
||||
isPropertiesPatched(Worker.prototype, workerEventNames);
|
||||
}
|
||||
const XMLHttpRequest = window['XMLHttpRequest'];
|
||||
if (XMLHttpRequest) {
|
||||
// XMLHttpRequest is not available in ServiceWorker, so we need to check here
|
||||
isPropertiesPatched(XMLHttpRequest.prototype, XMLHttpRequestEventNames);
|
||||
}
|
||||
const XMLHttpRequestEventTarget = window['XMLHttpRequestEventTarget'];
|
||||
if (XMLHttpRequestEventTarget) {
|
||||
isPropertiesPatched(
|
||||
XMLHttpRequestEventTarget && XMLHttpRequestEventTarget.prototype,
|
||||
XMLHttpRequestEventNames);
|
||||
}
|
||||
if (typeof IDBIndex !== 'undefined') {
|
||||
isPropertiesPatched(IDBIndex.prototype, IDBIndexEventNames);
|
||||
isPropertiesPatched(IDBRequest.prototype, IDBIndexEventNames);
|
||||
isPropertiesPatched(IDBOpenDBRequest.prototype, IDBIndexEventNames);
|
||||
isPropertiesPatched(IDBDatabase.prototype, IDBIndexEventNames);
|
||||
isPropertiesPatched(IDBTransaction.prototype, IDBIndexEventNames);
|
||||
isPropertiesPatched(IDBCursor.prototype, IDBIndexEventNames);
|
||||
}
|
||||
const WebSocket = window['WebSocket'];
|
||||
if (WebSocket) {
|
||||
isPropertiesPatched(WebSocket.prototype, websocketEventNames);
|
||||
}
|
||||
});
|
||||
|
||||
it('should patch all possible on properties on element', function() {
|
||||
const htmlElementTagNames: string[] = [
|
||||
'a', 'area', 'audio', 'base', 'basefont', 'blockquote', 'br',
|
||||
'button', 'canvas', 'caption', 'col', 'colgroup', 'data', 'datalist',
|
||||
@@ -173,29 +504,60 @@ describe('Zone', function() {
|
||||
'video'
|
||||
];
|
||||
htmlElementTagNames.forEach(tagName => {
|
||||
checkIsOnPropertiesPatched(document.createElement(tagName), ['onorientationchange']);
|
||||
checkIsOnPropertiesPatched(
|
||||
document.createElement(tagName), eventNames, ['onorientationchange']);
|
||||
});
|
||||
});
|
||||
|
||||
it('should patch all possbile on properties on body', function() {
|
||||
checkIsOnPropertiesPatched(document.body, ['onorientationchange']);
|
||||
it('should patch all possible on properties on svg element', function() {
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
checkIsOnPropertiesPatched(svg, eventNames);
|
||||
});
|
||||
|
||||
it('should patch all possbile on properties on Document', function() {
|
||||
checkIsOnPropertiesPatched(document, ['onorientationchange']);
|
||||
it('should patch all possible on properties on media element', function() {
|
||||
const audio = document.createElement('audio');
|
||||
checkIsOnPropertiesPatched(audio, mediaElementEventNames);
|
||||
});
|
||||
|
||||
it('should patch all possbile on properties on Window', function() {
|
||||
checkIsOnPropertiesPatched(window, [
|
||||
'onvrdisplayactivate', 'onvrdisplayblur', 'onvrdisplayconnect',
|
||||
'onvrdisplaydeactivate', 'onvrdisplaydisconnect', 'onvrdisplayfocus',
|
||||
'onvrdisplaypointerrestricted', 'onvrdisplaypointerunrestricted',
|
||||
'onorientationchange', 'onerror'
|
||||
]);
|
||||
it('should patch all possible on properties on frameset element', function() {
|
||||
const frameset = document.createElement('frameset');
|
||||
checkIsOnPropertiesPatched(frameset, windowEventNames.concat(frameSetEventNames));
|
||||
});
|
||||
|
||||
it('should patch all possbile on properties on xhr', function() {
|
||||
checkIsOnPropertiesPatched(new XMLHttpRequest());
|
||||
it('should patch all possible on properties on body', function() {
|
||||
checkIsOnPropertiesPatched(document.body, windowEventNames.concat(frameSetEventNames));
|
||||
});
|
||||
|
||||
it('should patch all possible on properties on Frame', function() {
|
||||
const frame = document.createElement('frame');
|
||||
checkIsOnPropertiesPatched(frame, frameEventNames);
|
||||
});
|
||||
|
||||
it('should patch all possible on properties on marquee element', function() {
|
||||
const marquee = document.createElement('marquee');
|
||||
checkIsOnPropertiesPatched(marquee, marqueeEventNames);
|
||||
});
|
||||
|
||||
it('should patch all possible on properties on Window', function() {
|
||||
checkIsOnPropertiesPatched(window, eventNames.concat(['messageerror']));
|
||||
});
|
||||
|
||||
it('should patch all possible on properties on xhr', function() {
|
||||
checkIsOnPropertiesPatched(new XMLHttpRequest(), XMLHttpRequestEventNames);
|
||||
});
|
||||
|
||||
it('should patch all possible on properties on worker', function() {
|
||||
checkIsOnPropertiesPatched(
|
||||
new Worker('/base/angular/packages/zone.js/test/assets/worker.js'),
|
||||
workerEventNames);
|
||||
});
|
||||
|
||||
it('should patch all possible on properties on websocket', function() {
|
||||
try {
|
||||
checkIsOnPropertiesPatched(new WebSocket('ws://localhost:8001'), websocketEventNames);
|
||||
} catch (e) {
|
||||
console.log('error when creating websocket', e);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not patch ignored on properties', function() {
|
||||
@@ -242,6 +604,16 @@ describe('Zone', function() {
|
||||
document.body.removeChild(div);
|
||||
});
|
||||
|
||||
it('property startsWith on but not event listener should still work as expected', () => {
|
||||
(window as any).one_two_three = {foo: 'bar'};
|
||||
|
||||
expect((window as any).one_two_three).toEqual({foo: 'bar'});
|
||||
(window as any).one_two_three = {bar: 'foo'};
|
||||
expect((window as any).one_two_three).toEqual({bar: 'foo'});
|
||||
(window as any).one_two_three = null;
|
||||
expect((window as any).one_two_three).toBeNull();
|
||||
});
|
||||
|
||||
it('should be able to clear on handler added before load zone.js', function() {
|
||||
const TestTarget: any = (window as any)['TestTarget'];
|
||||
patchFilteredProperties(
|
||||
|
||||
Reference in New Issue
Block a user