Skip to content

Commit d7d359e

Browse files
JiaLiPassionmhevery
authored andcommitted
feat: support passive event options by defining global variables in zone.js config file (#34503)
PR Close #34503
1 parent 8cb1f65 commit d7d359e

5 files changed

Lines changed: 97 additions & 9 deletions

File tree

aio/content/guide/user-input.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,24 @@ Following is all the code discussed in this page.
297297

298298
</code-tabs>
299299

300+
### Passive events
300301

302+
Angular also support passive event listeners. For example, we can use the following steps to make scroll event passive.
303+
304+
1. create a file `src/zone-flags.ts`.
305+
2. add the following line into this file.
306+
307+
```
308+
(window as any)['__zone_symbol__PASSIVE_EVENTS'] = ['scroll'];
309+
```
310+
3. in `src/polyfills.ts`, before import zone.js, import the new created `zone-flags`.
311+
312+
```
313+
import './zone-flags';
314+
import 'zone.js/dist/zone'; // Included with Angular CLI.
315+
```
316+
317+
after those steps, if user add event listeners for the `passive` event, the listeners will be `passive`.
301318

302319

303320
## Summary

integration/_payload-limits.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"uncompressed": {
55
"runtime-es2015": 1485,
66
"main-es2015": 141569,
7-
"polyfills-es2015": 36657
7+
"polyfills-es2015": 37300
88
}
99
}
1010
},
@@ -13,7 +13,7 @@
1313
"uncompressed": {
1414
"runtime-es2015": 1485,
1515
"main-es2015": 16514,
16-
"polyfills-es2015": 36657
16+
"polyfills-es2015": 37118
1717
}
1818
}
1919
},
@@ -22,7 +22,7 @@
2222
"uncompressed": {
2323
"runtime-es2015": 1485,
2424
"main-es2015": 147647,
25-
"polyfills-es2015": 36657
25+
"polyfills-es2015": 37300
2626
}
2727
}
2828
},
@@ -31,7 +31,7 @@
3131
"uncompressed": {
3232
"runtime-es2015": 1485,
3333
"main-es2015": 136777,
34-
"polyfills-es2015": 37334
34+
"polyfills-es2015": 37980
3535
}
3636
}
3737
},
@@ -40,7 +40,7 @@
4040
"uncompressed": {
4141
"runtime-es2015": 2289,
4242
"main-es2015": 247198,
43-
"polyfills-es2015": 36657,
43+
"polyfills-es2015": 36958,
4444
"5-es2015": 751
4545
}
4646
}
@@ -50,7 +50,7 @@
5050
"uncompressed": {
5151
"runtime-es2015": 2289,
5252
"main-es2015": 226144,
53-
"polyfills-es2015": 36657,
53+
"polyfills-es2015": 37300,
5454
"5-es2015": 779
5555
}
5656
}

packages/zone.js/lib/common/events.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,30 @@ export function patchEventTarget(
257257
}
258258
}
259259

260+
/**
261+
*
262+
* this util function will build an option object with passive option
263+
* to handle all possible input from the user
264+
*/
265+
function buildPassiveOptions(options: any, passive: boolean) {
266+
if (!passiveSupported || !passive) {
267+
return options;
268+
}
269+
if (!options) {
270+
return {passive: true};
271+
}
272+
if (typeof options === 'boolean') {
273+
return {capture: options, passive: true};
274+
}
275+
if (typeof options === 'object') {
276+
if (typeof options.passive === 'boolean' && !options.passive) {
277+
return options;
278+
}
279+
return {...options, passive: true};
280+
}
281+
return options;
282+
}
283+
260284
const customScheduleGlobal = function(task: Task) {
261285
// if there is already a task for the eventName + capture,
262286
// just return, because we use the shared globalZoneAwareCallback here.
@@ -338,6 +362,7 @@ export function patchEventTarget(
338362
(patchOptions && patchOptions.diff) ? patchOptions.diff : compareTaskCallbackVsDelegate;
339363

340364
const blackListedEvents: string[] = (Zone as any)[zoneSymbol('BLACK_LISTED_EVENTS')];
365+
const passiveEvents: string[] = _global[zoneSymbol('PASSIVE_EVENTS')];
341366

342367
const makeAddListener = function(
343368
nativeListener: any, addSource: string, customScheduleFn: any, customCancelFn: any,
@@ -373,12 +398,19 @@ export function patchEventTarget(
373398
}
374399

375400
const options = arguments[2];
401+
const passive =
402+
passiveSupported && !!passiveEvents && passiveEvents.indexOf(eventName) !== -1;
376403

377404
if (blackListedEvents) {
378405
// check black list
379406
for (let i = 0; i < blackListedEvents.length; i++) {
380407
if (eventName === blackListedEvents[i]) {
381-
return nativeListener.apply(this, arguments);
408+
if (passive) {
409+
const passiveOptions = buildPassiveOptions(options, passive);
410+
return nativeListener.call(target, eventName, delegate, passiveOptions);
411+
} else {
412+
return nativeListener.apply(this, arguments);
413+
}
382414
}
383415
}
384416
}
@@ -431,7 +463,7 @@ export function patchEventTarget(
431463
}
432464
// do not create a new object as task.data to pass those things
433465
// just use the global shared one
434-
taskData.options = options;
466+
taskData.options = buildPassiveOptions(options, passive);
435467
if (once) {
436468
// if addEventListener with once options, we don't pass it to
437469
// native addEventListener, instead we keep the once setting

packages/zone.js/test/browser/browser.spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ describe('Zone', function() {
222222
});
223223

224224
zone.run(() => { document.dispatchEvent(scrollEvent); });
225+
(document as any).removeAllListeners('scroll');
225226
});
226227

227228
it('should be able to clear on handler added before load zone.js', function() {
@@ -799,6 +800,7 @@ describe('Zone', function() {
799800

800801
button.dispatchEvent(clickEvent);
801802
expect(logs).toEqual([]);
803+
(document as any).removeAllListeners('click');
802804
});
803805
}));
804806

@@ -1035,6 +1037,42 @@ describe('Zone', function() {
10351037
button.removeEventListener('click', listener);
10361038
}));
10371039

1040+
describe('passiveEvents', () => {
1041+
let logs: string[] = [];
1042+
const listener = (e: Event) => {
1043+
logs.push(e.defaultPrevented ? 'defaultPrevented' : 'default will run');
1044+
e.preventDefault();
1045+
logs.push(e.defaultPrevented ? 'defaultPrevented' : 'default will run');
1046+
};
1047+
const testPassive = function(eventName: string, expectedPassiveLog: string, options: any) {
1048+
(button as any).addEventListener(eventName, listener, options);
1049+
const evt = document.createEvent('Event');
1050+
evt.initEvent(eventName, true, true);
1051+
button.dispatchEvent(evt);
1052+
expect(logs).toEqual(['default will run', expectedPassiveLog]);
1053+
(button as any).removeAllListeners(eventName);
1054+
};
1055+
beforeEach(() => { logs = []; });
1056+
it('should be passive with global variable defined',
1057+
() => { testPassive('touchstart', 'default will run', {passive: true}); });
1058+
it('should not be passive without global variable defined',
1059+
() => { testPassive('touchend', 'defaultPrevented', undefined); });
1060+
it('should be passive with global variable defined even without passive options',
1061+
() => { testPassive('touchstart', 'default will run', undefined); });
1062+
it('should be passive with global variable defined even without passive options and with capture',
1063+
() => { testPassive('touchstart', 'default will run', {capture: true}); });
1064+
it('should be passive with global variable defined with capture option',
1065+
() => { testPassive('touchstart', 'default will run', true); });
1066+
it('should not be passive with global variable defined with passive false option',
1067+
() => { testPassive('touchstart', 'defaultPrevented', {passive: false}); });
1068+
it('should be passive with global variable defined and also blacklisted', () => {
1069+
(document as any).removeAllListeners('scroll');
1070+
testPassive('scroll', 'default will run', undefined);
1071+
});
1072+
it('should not be passive without global variable defined and also blacklisted',
1073+
() => { testPassive('wheel', 'defaultPrevented', undefined); });
1074+
});
1075+
10381076
it('should support Event.stopImmediatePropagation',
10391077
ifEnvSupports(supportEventListenerOptions, function() {
10401078
const hookSpy = jasmine.createSpy('hook');

packages/zone.js/test/test_fake_polyfill.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,6 @@
7878
global['__Zone_ignore_on_properties'] =
7979
[{target: TestTarget.prototype, ignoreProperties: ['prop1']}];
8080
global[zoneSymbolPrefix + 'FakeAsyncTestMacroTask'] = [{source: 'TestClass.myTimeout'}];
81-
global[zoneSymbolPrefix + 'UNPATCHED_EVENTS'] = ['scroll'];
81+
global[zoneSymbolPrefix + 'UNPATCHED_EVENTS'] = ['scroll', 'wheel'];
82+
global[zoneSymbolPrefix + 'PASSIVE_EVENTS'] = ['touchstart', 'scroll'];
8283
})(typeof window === 'object' && window || typeof self === 'object' && self || global);

0 commit comments

Comments
 (0)