@samitouri / QOS-React / commits / 784ebd8fa9

Experimental event API: rework the propagation system for event components (#15462)

Dominic Gannaway committed Apr 23, 2019 at 11:50 UTC 784ebd8fa919bbca41c2a2f5a3eda5be6961f32c
9 files changed +342 -434
packages/react-dom/src/events/DOMEventResponderSystem.js
+48 -64
@@ -18,6 +18,7 @@ import {
18 HostComponent,
19 } from 'shared/ReactWorkTags';
20 import type {
21 + ReactEventResponder,
22 ReactEventResponderEventType,
23 ReactEventComponentInstance,
24 ReactResponderContext,
@@ -61,13 +62,10 @@ type ResponderTimeout = {|
62
63 type ResponderTimer = {|
64 instance: ReactEventComponentInstance,
64 - func: () => boolean,
65 + func: () => void,
66 id: Symbol,
67 |};
68
68 -const ROOT_PHASE = 0;
69 -const BUBBLE_PHASE = 1;
70 -const CAPTURE_PHASE = 2;
69 const activeTimeouts: Map<Symbol, ResponderTimeout> = new Map();
70 const rootEventTypesToEventComponentInstances: Map<
71 DOMTopLevelEventType | string,
@@ -253,7 +251,7 @@ const eventResponderContext: ReactResponderContext = {
251 triggerOwnershipListeners();
252 return false;
253 },
256 - setTimeout(func: () => boolean, delay): Symbol {
254 + setTimeout(func: () => void, delay): Symbol {
255 validateResponderContext();
256 if (currentTimers === null) {
257 currentTimers = new Map();
@@ -349,16 +347,13 @@ const eventResponderContext: ReactResponderContext = {
347
348 function processTimers(timers: Map<Symbol, ResponderTimer>): void {
349 const timersArr = Array.from(timers.values());
352 - let shouldStopPropagation = false;
350 currentEventQueue = createEventQueue();
351 try {
352 for (let i = 0; i < timersArr.length; i++) {
353 const {instance, func, id} = timersArr[i];
354 currentInstance = instance;
355 try {
359 - if (!shouldStopPropagation) {
360 - shouldStopPropagation = func();
361 - }
356 + func();
357 } finally {
358 activeTimeouts.delete(id);
359 }
@@ -390,7 +385,6 @@ function createResponderEvent(
385 nativeEvent: AnyNativeEvent,
386 nativeEventTarget: Element | Document,
387 eventSystemFlags: EventSystemFlags,
393 - phase: 0 | 1 | 2,
388 ): ReactResponderEvent {
389 const responderEvent = {
390 nativeEvent: nativeEvent,
@@ -398,7 +392,6 @@ function createResponderEvent(
392 type: topLevelType,
393 passive: (eventSystemFlags & IS_PASSIVE) !== 0,
394 passiveSupported: (eventSystemFlags & PASSIVE_NOT_SUPPORTED) === 0,
401 - phase,
395 };
396 if (__DEV__) {
397 Object.freeze(responderEvent);
@@ -510,16 +503,7 @@ function getRootEventResponderInstances(
503 return eventResponderInstances;
504 }
505
513 -function triggerEventResponderEventListener(
514 - responderEvent: ReactResponderEvent,
515 - eventComponentInstance: ReactEventComponentInstance,
516 -): boolean {
517 - const {responder, props, state} = eventComponentInstance;
518 - currentInstance = eventComponentInstance;
519 - return responder.onEvent(responderEvent, eventResponderContext, props, state);
520 -}
521 -
522 -function traverseAndTriggerEventResponderInstances(
506 +function traverseAndHandleEventResponderInstances(
507 topLevelType: DOMTopLevelEventType,
508 targetFiber: null | Fiber,
509 nativeEvent: AnyNativeEvent,
@@ -535,46 +519,54 @@ function traverseAndTriggerEventResponderInstances(
519 topLevelType,
520 targetFiber,
521 );
522 + const responderEvent = createResponderEvent(
523 + ((topLevelType: any): string),
524 + nativeEvent,
525 + ((nativeEventTarget: any): Element | Document),
526 + eventSystemFlags,
527 + );
528 + const propagatedEventResponders: Set<ReactEventResponder> = new Set();
529 let length = targetEventResponderInstances.length;
530 let i;
540 - let shouldStopPropagation = false;
541 - let responderEvent;
531
532 + // Captured and bubbled event phases have the notion of local propagation.
533 + // This means that the propgation chain can be stopped part of the the way
534 + // through processing event component instances. The major difference to other
535 + // events systems is that the stopping of propgation is localized to a single
536 + // phase, rather than both phases.
537 if (length > 0) {
538 // Capture target phase
545 - responderEvent = createResponderEvent(
546 - ((topLevelType: any): string),
547 - nativeEvent,
548 - ((nativeEventTarget: any): Element | Document),
549 - eventSystemFlags,
550 - CAPTURE_PHASE,
551 - );
539 for (i = length; i-- > 0; ) {
540 const targetEventResponderInstance = targetEventResponderInstances[i];
554 - shouldStopPropagation = triggerEventResponderEventListener(
555 - responderEvent,
556 - targetEventResponderInstance,
557 - );
558 - if (shouldStopPropagation) {
559 - return;
541 + const {responder, props, state} = targetEventResponderInstance;
542 + if (responder.stopLocalPropagation) {
543 + if (propagatedEventResponders.has(responder)) {
544 + continue;
545 + }
546 + propagatedEventResponders.add(responder);
547 + }
548 + const eventListener = responder.onEventCapture;
549 + if (eventListener !== undefined) {
550 + currentInstance = targetEventResponderInstance;
551 + eventListener(responderEvent, eventResponderContext, props, state);
552 }
553 }
554 + // We clean propagated event responders between phases.
555 + propagatedEventResponders.clear();
556 // Bubble target phase
563 - responderEvent = createResponderEvent(
564 - ((topLevelType: any): string),
565 - nativeEvent,
566 - ((nativeEventTarget: any): Element | Document),
567 - eventSystemFlags,
568 - BUBBLE_PHASE,
569 - );
557 for (i = 0; i < length; i++) {
558 const targetEventResponderInstance = targetEventResponderInstances[i];
572 - shouldStopPropagation = triggerEventResponderEventListener(
573 - responderEvent,
574 - targetEventResponderInstance,
575 - );
576 - if (shouldStopPropagation) {
577 - return;
559 + const {responder, props, state} = targetEventResponderInstance;
560 + if (responder.stopLocalPropagation) {
561 + if (propagatedEventResponders.has(responder)) {
562 + continue;
563 + }
564 + propagatedEventResponders.add(responder);
565 + }
566 + const eventListener = responder.onEvent;
567 + if (eventListener !== undefined) {
568 + currentInstance = targetEventResponderInstance;
569 + eventListener(responderEvent, eventResponderContext, props, state);
570 }
571 }
572 }
@@ -584,21 +576,13 @@ function traverseAndTriggerEventResponderInstances(
576 );
577 length = rootEventResponderInstances.length;
578 if (length > 0) {
587 - responderEvent = createResponderEvent(
588 - ((topLevelType: any): string),
589 - nativeEvent,
590 - ((nativeEventTarget: any): Element | Document),
591 - eventSystemFlags,
592 - ROOT_PHASE,
593 - );
579 for (i = 0; i < length; i++) {
595 - const targetEventResponderInstance = rootEventResponderInstances[i];
596 - shouldStopPropagation = triggerEventResponderEventListener(
597 - responderEvent,
598 - targetEventResponderInstance,
599 - );
600 - if (shouldStopPropagation) {
601 - return;
580 + const rootEventResponderInstance = rootEventResponderInstances[i];
581 + const {responder, props, state} = rootEventResponderInstance;
582 + const eventListener = responder.onRootEvent;
583 + if (eventListener !== undefined) {
584 + currentInstance = rootEventResponderInstance;
585 + eventListener(responderEvent, eventResponderContext, props, state);
586 }
587 }
588 }
@@ -672,7 +656,7 @@ export function dispatchEventForResponderEventSystem(
656 if (enableEventAPI) {
657 currentEventQueue = createEventQueue();
658 try {
675 - traverseAndTriggerEventResponderInstances(
659 + traverseAndHandleEventResponderInstances(
660 topLevelType,
661 targetFiber,
662 nativeEvent,
packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
+154 -166
@@ -18,15 +18,21 @@ function createReactEventComponent(
18 targetEventTypes,
19 createInitialState,
20 onEvent,
21 + onEventCapture,
22 + onRootEvent,
23 onUnmount,
24 onOwnershipChange,
25 + stopLocalPropagation,
26 ) {
27 const testEventResponder = {
28 targetEventTypes,
29 createInitialState,
30 onEvent,
31 + onEventCapture,
32 + onRootEvent,
33 onUnmount,
34 onOwnershipChange,
35 + stopLocalPropagation: stopLocalPropagation || false,
36 };
37
38 return {
@@ -37,18 +43,6 @@ function createReactEventComponent(
43 };
44 }
45
40 -const ROOT_PHASE = 0;
41 -const BUBBLE_PHASE = 1;
42 -const CAPTURE_PHASE = 2;
43 -
44 -function phaseToString(phase) {
45 - return phase === ROOT_PHASE
46 - ? 'root'
47 - : phase === BUBBLE_PHASE
48 - ? 'bubble'
49 - : 'capture';
50 -}
51 -
46 function dispatchEvent(element, type) {
47 const event = document.createEvent('Event');
48 event.initEvent(type, true, true);
@@ -88,7 +82,7 @@ describe('DOMEventResponderSystem', () => {
82 container = null;
83 });
84
91 - it('the event responder onEvent() function should fire on click event', () => {
85 + it('the event responder event listeners should fire on click event', () => {
86 let eventResponderFiredCount = 0;
87 let eventLog = [];
88 const buttonRef = React.createRef();
@@ -102,7 +96,16 @@ describe('DOMEventResponderSystem', () => {
96 name: event.type,
97 passive: event.passive,
98 passiveSupported: event.passiveSupported,
105 - phase: event.phase,
99 + phase: 'bubble',
100 + });
101 + },
102 + (event, context, props) => {
103 + eventResponderFiredCount++;
104 + eventLog.push({
105 + name: event.type,
106 + passive: event.passive,
107 + passiveSupported: event.passiveSupported,
108 + phase: 'capture',
109 });
110 },
111 );
@@ -127,13 +130,13 @@ describe('DOMEventResponderSystem', () => {
130 name: 'click',
131 passive: false,
132 passiveSupported: false,
130 - phase: CAPTURE_PHASE,
133 + phase: 'capture',
134 },
135 {
136 name: 'click',
137 passive: false,
138 passiveSupported: false,
136 - phase: BUBBLE_PHASE,
139 + phase: 'bubble',
140 },
141 ]);
142
@@ -149,7 +152,7 @@ describe('DOMEventResponderSystem', () => {
152 expect(eventResponderFiredCount).toBe(4);
153 });
154
152 - it('the event responder onEvent() function should fire on click event (passive events forced)', () => {
155 + it('the event responder event listeners should fire on click event (passive events forced)', () => {
156 // JSDOM does not support passive events, so this manually overrides the value to be true
157 const checkPassiveEvents = require('react-dom/src/events/checkPassiveEvents');
158 checkPassiveEvents.passiveBrowserEventsSupported = true;
@@ -165,7 +168,15 @@ describe('DOMEventResponderSystem', () => {
168 name: event.type,
169 passive: event.passive,
170 passiveSupported: event.passiveSupported,
168 - phase: event.phase,
171 + phase: 'bubble',
172 + });
173 + },
174 + (event, context, props) => {
175 + eventLog.push({
176 + name: event.type,
177 + passive: event.passive,
178 + passiveSupported: event.passiveSupported,
179 + phase: 'capture',
180 });
181 },
182 );
@@ -187,18 +198,18 @@ describe('DOMEventResponderSystem', () => {
198 name: 'click',
199 passive: true,
200 passiveSupported: true,
190 - phase: CAPTURE_PHASE,
201 + phase: 'capture',
202 },
203 {
204 name: 'click',
205 passive: true,
206 passiveSupported: true,
196 - phase: BUBBLE_PHASE,
207 + phase: 'bubble',
208 },
209 ]);
210 });
211
201 - it('nested event responders and their onEvent() function should fire multiple times', () => {
212 + it('nested event responders and their event listeners should fire multiple times', () => {
213 let eventResponderFiredCount = 0;
214 let eventLog = [];
215 const buttonRef = React.createRef();
@@ -212,7 +223,16 @@ describe('DOMEventResponderSystem', () => {
223 name: event.type,
224 passive: event.passive,
225 passiveSupported: event.passiveSupported,
215 - phase: event.phase,
226 + phase: 'bubble',
227 + });
228 + },
229 + (event, context, props) => {
230 + eventResponderFiredCount++;
231 + eventLog.push({
232 + name: event.type,
233 + passive: event.passive,
234 + passiveSupported: event.passiveSupported,
235 + phase: 'capture',
236 });
237 },
238 );
@@ -238,30 +258,30 @@ describe('DOMEventResponderSystem', () => {
258 name: 'click',
259 passive: false,
260 passiveSupported: false,
241 - phase: CAPTURE_PHASE,
261 + phase: 'capture',
262 },
263 {
264 name: 'click',
265 passive: false,
266 passiveSupported: false,
247 - phase: CAPTURE_PHASE,
267 + phase: 'capture',
268 },
269 {
270 name: 'click',
271 passive: false,
272 passiveSupported: false,
253 - phase: BUBBLE_PHASE,
273 + phase: 'bubble',
274 },
275 {
276 name: 'click',
277 passive: false,
278 passiveSupported: false,
259 - phase: BUBBLE_PHASE,
279 + phase: 'bubble',
280 },
281 ]);
282 });
283
264 - it('nested event responders and their onEvent() should fire in the correct order', () => {
284 + it('nested event responders and their event listeners should fire in the correct order', () => {
285 let eventLog = [];
286 const buttonRef = React.createRef();
287
@@ -269,7 +289,10 @@ describe('DOMEventResponderSystem', () => {
289 ['click'],
290 undefined,
291 (event, context, props) => {
272 - eventLog.push(`A [${phaseToString(event.phase)}]`);
292 + eventLog.push(`A [bubble]`);
293 + },
294 + (event, context, props) => {
295 + eventLog.push(`A [capture]`);
296 },
297 );
298
@@ -277,7 +300,10 @@ describe('DOMEventResponderSystem', () => {
300 ['click'],
301 undefined,
302 (event, context, props) => {
280 - eventLog.push(`B [${phaseToString(event.phase)}]`);
303 + eventLog.push(`B [bubble]`);
304 + },
305 + (event, context, props) => {
306 + eventLog.push(`B [capture]`);
307 },
308 );
309
@@ -303,140 +329,81 @@ describe('DOMEventResponderSystem', () => {
329 ]);
330 });
331
306 - it('nested event responders and their onEvent() should fire in the correct order with stopPropagation', () => {
307 - let eventLog;
308 - let stopPropagationOnPhase;
332 + it('nested event responders should fire in the correct order without stopLocalPropagation', () => {
333 + let eventLog = [];
334 const buttonRef = React.createRef();
335
311 - const ClickEventComponentA = createReactEventComponent(
336 + const ClickEventComponent = createReactEventComponent(
337 ['click'],
338 undefined,
339 (event, context, props) => {
315 - context.addRootEventTypes(event.target.ownerDocument, [
316 - 'click',
317 - 'pointermove',
318 - ]);
319 - eventLog.push(`A [${event.type}, ${phaseToString(event.phase)}]`);
340 + eventLog.push(`${props.name} [bubble]`);
341 },
321 - );
322 -
323 - const ClickEventComponentB = createReactEventComponent(
324 - ['click'],
325 - undefined,
342 (event, context, props) => {
327 - context.addRootEventTypes(event.target.ownerDocument, [
328 - 'click',
329 - 'pointermove',
330 - ]);
331 - eventLog.push(`B [${event.type}, ${phaseToString(event.phase)}]`);
332 - if (event.phase === stopPropagationOnPhase) {
333 - return true;
334 - }
343 + eventLog.push(`${props.name} [capture]`);
344 },
345 + undefined,
346 + undefined,
347 + undefined,
348 + false,
349 );
350
351 const Test = () => (
339 - <ClickEventComponentA>
340 - <ClickEventComponentB>
352 + <ClickEventComponent name="A">
353 + <ClickEventComponent name="B">
354 <button ref={buttonRef}>Click me!</button>
342 - </ClickEventComponentB>
343 - </ClickEventComponentA>
355 + </ClickEventComponent>
356 + </ClickEventComponent>
357 );
358
346 - function runTestWithPhase(phase) {
347 - eventLog = [];
348 - stopPropagationOnPhase = phase;
349 - ReactDOM.render(<Test />, container);
350 - let buttonElement = buttonRef.current;
351 - dispatchClickEvent(buttonElement);
352 - dispatchEvent(buttonElement, 'pointermove');
353 - }
359 + ReactDOM.render(<Test />, container);
360 +
361 + // Clicking the button should trigger the event responder onEvent()
362 + let buttonElement = buttonRef.current;
363 + dispatchClickEvent(buttonElement);
364
355 - runTestWithPhase(BUBBLE_PHASE);
356 - // Root phase should not be skipped for different event type
357 - expect(eventLog).toEqual([
358 - 'A [click, capture]',
359 - 'B [click, capture]',
360 - 'B [click, bubble]',
361 - 'A [pointermove, root]',
362 - 'B [pointermove, root]',
363 - ]);
364 - runTestWithPhase(CAPTURE_PHASE);
365 - // Root phase should not be skipped for different event type
365 expect(eventLog).toEqual([
367 - 'A [click, capture]',
368 - 'B [click, capture]',
369 - 'A [pointermove, root]',
370 - 'B [pointermove, root]',
366 + 'A [capture]',
367 + 'B [capture]',
368 + 'B [bubble]',
369 + 'A [bubble]',
370 ]);
371 });
372
374 - it('nested event responders and their onEvent() should fire in the correct order with stopPropagation #2', () => {
375 - let eventLog;
376 - let stopPropagationOnPhase;
373 + it('nested event responders should fire in the correct order with stopLocalPropagation', () => {
374 + let eventLog = [];
375 const buttonRef = React.createRef();
376
379 - const ClickEventComponentA = createReactEventComponent(
377 + const ClickEventComponent = createReactEventComponent(
378 ['click'],
379 undefined,
380 (event, context, props) => {
383 - context.addRootEventTypes(event.target.ownerDocument, [
384 - 'click',
385 - 'pointermove',
386 - ]);
387 - eventLog.push(`A [${event.type}, ${phaseToString(event.phase)}]`);
388 - if (event.phase === stopPropagationOnPhase) {
389 - return true;
390 - }
381 + eventLog.push(`${props.name} [bubble]`);
382 },
392 - );
393 -
394 - const ClickEventComponentB = createReactEventComponent(
395 - ['click'],
396 - undefined,
383 (event, context, props) => {
398 - context.addRootEventTypes(event.target.ownerDocument, [
399 - 'click',
400 - 'pointermove',
401 - ]);
402 - eventLog.push(`B [${event.type}, ${phaseToString(event.phase)}]`);
384 + eventLog.push(`${props.name} [capture]`);
385 },
386 + undefined,
387 + undefined,
388 + undefined,
389 + true,
390 );
391
392 const Test = () => (
407 - <ClickEventComponentA>
408 - <ClickEventComponentB>
393 + <ClickEventComponent name="A">
394 + <ClickEventComponent name="B">
395 <button ref={buttonRef}>Click me!</button>
410 - </ClickEventComponentB>
411 - </ClickEventComponentA>
396 + </ClickEventComponent>
397 + </ClickEventComponent>
398 );
399
414 - function runTestWithPhase(phase) {
415 - eventLog = [];
416 - stopPropagationOnPhase = phase;
417 - ReactDOM.render(<Test />, container);
418 - let buttonElement = buttonRef.current;
419 - dispatchClickEvent(buttonElement);
420 - dispatchEvent(buttonElement, 'pointermove');
421 - }
400 + ReactDOM.render(<Test />, container);
401
423 - runTestWithPhase(BUBBLE_PHASE);
424 - // Root phase should not be skipped for different event type
425 - expect(eventLog).toEqual([
426 - 'A [click, capture]',
427 - 'B [click, capture]',
428 - 'B [click, bubble]',
429 - 'A [click, bubble]',
430 - 'A [pointermove, root]',
431 - 'B [pointermove, root]',
432 - ]);
433 - runTestWithPhase(CAPTURE_PHASE);
434 - // Root phase should not be skipped for different event type
435 - expect(eventLog).toEqual([
436 - 'A [click, capture]',
437 - 'A [pointermove, root]',
438 - 'B [pointermove, root]',
439 - ]);
402 + // Clicking the button should trigger the event responder onEvent()
403 + let buttonElement = buttonRef.current;
404 + dispatchClickEvent(buttonElement);
405 +
406 + expect(eventLog).toEqual(['A [capture]', 'B [bubble]']);
407 });
408
409 it('custom event dispatching for click -> magicClick works', () => {
@@ -451,7 +418,19 @@ describe('DOMEventResponderSystem', () => {
418 const syntheticEvent = {
419 target: event.target,
420 type: 'magicclick',
454 - phase: phaseToString(event.phase),
421 + phase: 'bubble',
422 + };
423 + context.dispatchEvent(syntheticEvent, props.onMagicClick, {
424 + discrete: true,
425 + });
426 + }
427 + },
428 + (event, context, props) => {
429 + if (props.onMagicClick) {
430 + const syntheticEvent = {
431 + target: event.target,
432 + type: 'magicclick',
433 + phase: 'capture',
434 };
435 context.dispatchEvent(syntheticEvent, props.onMagicClick, {
436 discrete: true,
@@ -490,42 +469,47 @@ describe('DOMEventResponderSystem', () => {
469 let eventLog = [];
470 const buttonRef = React.createRef();
471
472 + function handleEvent(event, context, props, phase) {
473 + const pressEvent = {
474 + target: event.target,
475 + type: 'press',
476 + phase,
477 + };
478 + context.dispatchEvent(pressEvent, props.onPress, {discrete: true});
479 +
480 + context.setTimeout(() => {
481 + if (props.onLongPress) {
482 + const longPressEvent = {
483 + target: event.target,
484 + type: 'longpress',
485 + phase,
486 + };
487 + context.dispatchEvent(longPressEvent, props.onLongPress, {
488 + discrete: true,
489 + });
490 + }
491 +
492 + if (props.onLongPressChange) {
493 + const longPressChangeEvent = {
494 + target: event.target,
495 + type: 'longpresschange',
496 + phase,
497 + };
498 + context.dispatchEvent(longPressChangeEvent, props.onLongPressChange, {
499 + discrete: true,
500 + });
501 + }
502 + }, 500);
503 + }
504 +
505 const LongPressEventComponent = createReactEventComponent(
506 ['click'],
507 undefined,
508 (event, context, props) => {
497 - const pressEvent = {
498 - target: event.target,
499 - type: 'press',
500 - phase: phaseToString(event.phase),
501 - };
502 - context.dispatchEvent(pressEvent, props.onPress, {discrete: true});
503 -
504 - context.setTimeout(() => {
505 - if (props.onLongPress) {
506 - const longPressEvent = {
507 - target: event.target,
508 - type: 'longpress',
509 - phase: phaseToString(event.phase),
510 - };
511 - context.dispatchEvent(longPressEvent, props.onLongPress, {
512 - discrete: true,
513 - });
514 - }
515 -
516 - if (props.onLongPressChange) {
517 - const longPressChangeEvent = {
518 - target: event.target,
519 - type: 'longpresschange',
520 - phase: phaseToString(event.phase),
521 - };
522 - context.dispatchEvent(
523 - longPressChangeEvent,
524 - props.onLongPressChange,
525 - {discrete: true},
526 - );
527 - }
528 - }, 500);
509 + handleEvent(event, context, props, 'bubble');
510 + },
511 + (event, context, props) => {
512 + handleEvent(event, context, props, 'capture');
513 },
514 );
515
@@ -565,6 +549,8 @@ describe('DOMEventResponderSystem', () => {
549 const EventComponent = createReactEventComponent(
550 [],
551 undefined,
552 + undefined,
553 + undefined,
554 (event, context, props, state) => {},
555 () => {
556 onUnmountFired++;
@@ -590,7 +576,9 @@ describe('DOMEventResponderSystem', () => {
576 () => ({
577 incrementAmount: 5,
578 }),
593 - (event, context, props, state) => {},
579 + undefined,
580 + undefined,
581 + undefined,
582 (context, props, state) => {
583 counter += state.incrementAmount;
584 },
@@ -616,11 +604,11 @@ describe('DOMEventResponderSystem', () => {
604 ['click'],
605 undefined,
606 (event, context, props, state) => {
619 - if (event.phase === BUBBLE_PHASE) {
620 - ownershipGained = context.requestOwnership();
621 - }
607 + ownershipGained = context.requestOwnership();
608 },
609 undefined,
610 + undefined,
611 + undefined,
612 () => {
613 onOwnershipChangeFired++;
614 },
packages/react-events/src/Drag.js
+23 -13
@@ -13,9 +13,12 @@ import type {
13 } from 'shared/ReactTypes';
14 import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
15
16 -const CAPTURE_PHASE = 2;
17 -const targetEventTypes = ['pointerdown', 'pointercancel'];
18 -const rootEventTypes = ['pointerup', {name: 'pointermove', passive: false}];
16 +const targetEventTypes = ['pointerdown'];
17 +const rootEventTypes = [
18 + 'pointerup',
19 + 'pointercancel',
20 + {name: 'pointermove', passive: false},
21 +];
22
23 type DragState = {
24 dragTarget: null | Element | Document,
@@ -30,8 +33,8 @@ type DragState = {
33 // In the case we don't have PointerEvents (Safari), we listen to touch events
34 // too
35 if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
33 - targetEventTypes.push('touchstart', 'touchend', 'mousedown', 'touchcancel');
34 - rootEventTypes.push('mouseup', 'mousemove', {
36 + targetEventTypes.push('touchstart', 'mousedown');
37 + rootEventTypes.push('mouseup', 'mousemove', 'touchend', 'touchcancel', {
38 name: 'touchmove',
39 passive: false,
40 });
@@ -88,18 +91,15 @@ const DragResponder = {
91 y: 0,
92 };
93 },
94 + stopLocalPropagation: true,
95 onEvent(
96 event: ReactResponderEvent,
97 context: ReactResponderContext,
98 props: Object,
99 state: DragState,
96 - ): boolean {
97 - const {target, phase, type, nativeEvent} = event;
100 + ): void {
101 + const {target, type, nativeEvent} = event;
102
99 - // Drag doesn't handle capture target events at this point
100 - if (phase === CAPTURE_PHASE) {
101 - return false;
102 - }
103 switch (type) {
104 case 'touchstart':
105 case 'mousedown':
@@ -133,11 +133,22 @@ const DragResponder = {
133 }
134 break;
135 }
136 + }
137 + },
138 + onRootEvent(
139 + event: ReactResponderEvent,
140 + context: ReactResponderContext,
141 + props: Object,
142 + state: DragState,
143 + ): void {
144 + const {type, nativeEvent} = event;
145 +
146 + switch (type) {
147 case 'touchmove':
148 case 'mousemove':
149 case 'pointermove': {
150 if (event.passive) {
140 - return false;
151 + return;
152 }
153 if (state.isPointerDown) {
154 const obj =
@@ -230,7 +241,6 @@ const DragResponder = {
241 break;
242 }
243 }
233 - return false;
244 },
245 };
246
packages/react-events/src/Focus.js
+3 -13
@@ -14,8 +14,6 @@ import type {
14 import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
15 import {getEventCurrentTarget} from './utils.js';
16
17 -const CAPTURE_PHASE = 2;
18 -
17 type FocusProps = {
18 disabled: boolean,
19 onBlur: (e: FocusEvent) => void,
@@ -112,10 +110,8 @@ const FocusResponder = {
110 context: ReactResponderContext,
111 props: Object,
112 state: FocusState,
115 - ): boolean {
116 - const {type, phase, target} = event;
117 - const shouldStopPropagation =
118 - props.stopPropagation === undefined ? true : props.stopPropagation;
113 + ): void {
114 + const {type, target} = event;
115
116 if (props.disabled) {
117 if (state.isFocused) {
@@ -123,12 +119,7 @@ const FocusResponder = {
119 state.isFocused = false;
120 state.focusTarget = null;
121 }
126 - return false;
127 - }
128 -
129 - // Focus doesn't handle capture target events at this point
130 - if (phase === CAPTURE_PHASE) {
131 - return false;
122 + return;
123 }
124
125 switch (type) {
@@ -153,7 +144,6 @@ const FocusResponder = {
144 break;
145 }
146 }
156 - return shouldStopPropagation;
147 },
148 onUnmount(
149 context: ReactResponderContext,
packages/react-events/src/Hover.js
+9 -19
@@ -18,8 +18,6 @@ import {
18 isEventPositionWithinTouchHitTarget,
19 } from './utils';
20
21 -const CAPTURE_PHASE = 2;
22 -
21 type HoverProps = {
22 disabled: boolean,
23 delayHoverEnd: number,
@@ -141,7 +139,6 @@ function dispatchHoverStartEvents(
139 state.hoverStartTimeout = context.setTimeout(() => {
140 state.hoverStartTimeout = null;
141 activate();
144 - return false;
142 }, delayHoverStart);
143 } else {
144 activate();
@@ -201,7 +198,6 @@ function dispatchHoverEndEvents(
198 if (delayHoverEnd > 0) {
199 state.hoverEndTimeout = context.setTimeout(() => {
200 deactivate();
204 - return false;
201 }, delayHoverEnd);
202 } else {
203 deactivate();
@@ -245,12 +241,13 @@ const HoverResponder = {
241 ignoreEmulatedMouseEvents: false,
242 };
243 },
244 + stopLocalPropagation: true,
245 onEvent(
246 event: ReactResponderEvent,
247 context: ReactResponderContext,
248 props: HoverProps,
249 state: HoverState,
253 - ): boolean {
250 + ): void {
251 const {type} = event;
252
253 if (props.disabled) {
@@ -261,14 +258,8 @@ const HoverResponder = {
258 if (state.isTouched) {
259 state.isTouched = false;
260 }
264 - return false;
265 - }
266 -
267 - // Hover doesn't handle capture target events at this point
268 - if (event.phase === CAPTURE_PHASE) {
269 - return false;
261 + return;
262 }
271 -
263 const pointerType = getEventPointerType(event);
264
265 switch (type) {
@@ -280,23 +271,23 @@ const HoverResponder = {
271 // Prevent hover events for touch
272 if (state.isTouched || pointerType === 'touch') {
273 state.isTouched = true;
283 - return false;
274 + return;
275 }
276
277 // Prevent hover events for emulated events
278 if (isEmulatedMouseEvent(event, state)) {
288 - return false;
279 + return;
280 }
281
282 if (isEventPositionWithinTouchHitTarget(event, context)) {
283 state.isOverTouchHitTarget = true;
293 - return false;
284 + return;
285 }
286 state.hoverTarget = getEventCurrentTarget(event, context);
287 state.ignoreEmulatedMouseEvents = true;
288 dispatchHoverStartEvents(event, context, props, state);
289 }
299 - return false;
290 + return;
291 }
292
293 // MOVE
@@ -331,7 +322,7 @@ const HoverResponder = {
322 }
323 }
324 }
334 - return false;
325 + return;
326 }
327
328 // END
@@ -347,10 +338,9 @@ const HoverResponder = {
338 if (state.isTouched) {
339 state.isTouched = false;
340 }
350 - return false;
341 + return;
342 }
343 }
353 - return false;
344 },
345 onUnmount(
346 context: ReactResponderContext,
packages/react-events/src/Press.js
+64 -59
@@ -19,8 +19,6 @@ import {
19 isEventPositionWithinTouchHitTarget,
20 } from './utils';
21
22 -const CAPTURE_PHASE = 2;
23 -
22 type PressProps = {
23 disabled: boolean,
24 delayLongPress: number,
@@ -103,10 +101,11 @@ const rootEventTypes = ['keyup', 'pointerup', 'pointermove', 'scroll'];
101
102 // If PointerEvents is not supported (e.g., Safari), also listen to touch and mouse events.
103 if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
106 - targetEventTypes.push('touchstart', 'touchend', 'touchcancel', 'mousedown');
104 + targetEventTypes.push('touchstart', 'touchcancel', 'mousedown');
105 rootEventTypes.push(
106 {name: 'mouseup', passive: false},
107 'touchmove',
108 + 'touchend',
109 'mousemove',
110 );
111 }
@@ -199,8 +198,6 @@ function dispatchPressStartEvents(
198 props: PressProps,
199 state: PressState,
200 ): void {
202 - const shouldStopPropagation =
203 - props.stopPropagation === undefined ? true : props.stopPropagation;
201 state.isPressed = true;
202
203 if (state.pressEndTimeout !== null) {
@@ -230,7 +227,6 @@ function dispatchPressStartEvents(
227 if (props.onLongPressChange) {
228 dispatchLongPressChangeEvent(context, props, state);
229 }
233 - return shouldStopPropagation;
230 }, delayLongPress);
231 }
232 };
@@ -245,7 +241,6 @@ function dispatchPressStartEvents(
241 state.pressStartTimeout = context.setTimeout(() => {
242 state.pressStartTimeout = null;
243 dispatch();
248 - return shouldStopPropagation;
244 }, delayPressStart);
245 } else {
246 dispatch();
@@ -258,8 +253,6 @@ function dispatchPressEndEvents(
253 props: PressProps,
254 state: PressState,
255 ): void {
261 - const shouldStopPropagation =
262 - props.stopPropagation === undefined ? true : props.stopPropagation;
256 const wasActivePressStart = state.isActivePressStart;
257 let activationWasForced = false;
258
@@ -295,7 +288,6 @@ function dispatchPressEndEvents(
288 state.pressEndTimeout = context.setTimeout(() => {
289 state.pressEndTimeout = null;
290 deactivate(context, props, state);
298 - return shouldStopPropagation;
291 }, delayPressEnd);
292 } else {
293 deactivate(context, props, state);
@@ -381,6 +373,24 @@ function unmountResponder(
373 }
374 }
375
376 +function dispatchCancel(
377 + type: string,
378 + nativeEvent: $PropertyType<ReactResponderEvent, 'nativeEvent'>,
379 + context: ReactResponderContext,
380 + props: PressProps,
381 + state: PressState,
382 +): void {
383 + if (state.isPressed) {
384 + if (type === 'contextmenu' && props.preventDefault !== false) {
385 + (nativeEvent: any).preventDefault();
386 + } else {
387 + state.ignoreEmulatedMouseEvents = false;
388 + dispatchPressEndEvents(context, props, state);
389 + context.removeRootEventTypes(rootEventTypes);
390 + }
391 + }
392 +}
393 +
394 const PressResponder = {
395 targetEventTypes,
396 createInitialState(): PressState {
@@ -400,30 +410,23 @@ const PressResponder = {
410 ignoreEmulatedMouseEvents: false,
411 };
412 },
413 + stopLocalPropagation: true,
414 onEvent(
415 event: ReactResponderEvent,
416 context: ReactResponderContext,
417 props: PressProps,
418 state: PressState,
408 - ): boolean {
409 - const {phase, target, type} = event;
419 + ): void {
420 + const {target, type} = event;
421
422 if (props.disabled) {
423 dispatchPressEndEvents(context, props, state);
424 context.removeRootEventTypes(rootEventTypes);
425 state.ignoreEmulatedMouseEvents = false;
415 - return false;
426 + return;
427 }
417 -
418 - // Press doesn't handle capture target events at this point
419 - if (phase === CAPTURE_PHASE) {
420 - return false;
421 - }
422 -
428 const nativeEvent: any = event.nativeEvent;
429 const pointerType = getEventPointerType(event);
425 - const shouldStopPropagation =
426 - props.stopPropagation === undefined ? true : props.stopPropagation;
430
431 switch (type) {
432 // START
@@ -440,7 +443,7 @@ const PressResponder = {
443 // Ignore unrelated key events
444 if (pointerType === 'keyboard') {
445 if (!isValidKeyPress(nativeEvent.key)) {
443 - return shouldStopPropagation;
446 + return;
447 }
448 }
449
@@ -451,13 +454,13 @@ const PressResponder = {
454 state.ignoreEmulatedMouseEvents ||
455 isEventPositionWithinTouchHitTarget(event, context)
456 ) {
454 - return shouldStopPropagation;
457 + return;
458 }
459 }
460
461 // Ignore any device buttons except left-mouse and touch/pen contact
462 if (nativeEvent.button > 0) {
460 - return shouldStopPropagation;
463 + return;
464 }
465
466 state.pointerType = pointerType;
@@ -465,17 +468,46 @@ const PressResponder = {
468 state.isPressWithinResponderRegion = true;
469 dispatchPressStartEvents(context, props, state);
470 context.addRootEventTypes(target.ownerDocument, rootEventTypes);
468 - return shouldStopPropagation;
471 } else {
472 // Prevent spacebar press from scrolling the window
473 if (isValidKeyPress(nativeEvent.key) && nativeEvent.key === ' ') {
474 nativeEvent.preventDefault();
473 - return shouldStopPropagation;
475 }
476 }
476 - return shouldStopPropagation;
477 + break;
478 + }
479 +
480 + // CANCEL
481 + case 'contextmenu': {
482 + dispatchCancel(type, nativeEvent, context, props, state);
483 + break;
484 }
485
486 + case 'click': {
487 + if (isAnchorTagElement(target)) {
488 + const {ctrlKey, metaKey, shiftKey} = (nativeEvent: MouseEvent);
489 + // Check "open in new window/tab" and "open context menu" key modifiers
490 + const preventDefault = props.preventDefault;
491 + if (preventDefault !== false && !shiftKey && !metaKey && !ctrlKey) {
492 + nativeEvent.preventDefault();
493 + }
494 + }
495 + break;
496 + }
497 + }
498 + },
499 + onRootEvent(
500 + event: ReactResponderEvent,
501 + context: ReactResponderContext,
502 + props: PressProps,
503 + state: PressState,
504 + ): void {
505 + const {target, type} = event;
506 +
507 + const nativeEvent: any = event.nativeEvent;
508 + const pointerType = getEventPointerType(event);
509 +
510 + switch (type) {
511 // MOVE
512 case 'pointermove':
513 case 'mousemove':
@@ -484,7 +516,7 @@ const PressResponder = {
516 // Ignore emulated events (pointermove will dispatch touch and mouse events)
517 // Ignore pointermove events during a keyboard press
518 if (state.pointerType !== pointerType) {
487 - return shouldStopPropagation;
519 + return;
520 }
521
522 if (state.responderRegion == null) {
@@ -504,9 +536,8 @@ const PressResponder = {
536 state.isPressWithinResponderRegion = false;
537 dispatchPressEndEvents(context, props, state);
538 }
507 - return shouldStopPropagation;
539 }
509 - return false;
540 + break;
541 }
542
543 // END
@@ -518,7 +549,7 @@ const PressResponder = {
549 // Ignore unrelated keyboard events
550 if (pointerType === 'keyboard') {
551 if (!isValidKeyPress(nativeEvent.key)) {
521 - return false;
552 + return;
553 }
554 }
555
@@ -539,45 +570,19 @@ const PressResponder = {
570 }
571 }
572 context.removeRootEventTypes(rootEventTypes);
542 - return shouldStopPropagation;
573 } else if (type === 'mouseup' && state.ignoreEmulatedMouseEvents) {
574 state.ignoreEmulatedMouseEvents = false;
575 }
546 - return false;
576 + break;
577 }
578
579 // CANCEL
550 - case 'contextmenu':
580 case 'pointercancel':
581 case 'scroll':
582 case 'touchcancel': {
554 - if (state.isPressed) {
555 - if (type === 'contextmenu' && props.preventDefault !== false) {
556 - nativeEvent.preventDefault();
557 - } else {
558 - state.ignoreEmulatedMouseEvents = false;
559 - dispatchPressEndEvents(context, props, state);
560 - context.removeRootEventTypes(rootEventTypes);
561 - }
562 - return shouldStopPropagation;
563 - }
564 - return false;
565 - }
566 -
567 - case 'click': {
568 - if (isAnchorTagElement(target)) {
569 - const {ctrlKey, metaKey, shiftKey} = (nativeEvent: MouseEvent);
570 - // Check "open in new window/tab" and "open context menu" key modifiers
571 - const preventDefault = props.preventDefault;
572 - if (preventDefault !== false && !shiftKey && !metaKey && !ctrlKey) {
573 - nativeEvent.preventDefault();
574 - }
575 - return shouldStopPropagation;
576 - }
577 - return false;
583 + dispatchCancel(type, nativeEvent, context, props, state);
584 }
585 }
580 - return false;
586 },
587 onUnmount(
588 context: ReactResponderContext,
packages/react-events/src/Swipe.js
+25 -15
@@ -13,15 +13,18 @@ import type {
13 } from 'shared/ReactTypes';
14 import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
15
16 -const CAPTURE_PHASE = 2;
17 -const targetEventTypes = ['pointerdown', 'pointercancel'];
18 -const rootEventTypes = ['pointerup', {name: 'pointermove', passive: false}];
16 +const targetEventTypes = ['pointerdown'];
17 +const rootEventTypes = [
18 + 'pointerup',
19 + 'pointercancel',
20 + {name: 'pointermove', passive: false},
21 +];
22
23 // In the case we don't have PointerEvents (Safari), we listen to touch events
24 // too
25 if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
23 - targetEventTypes.push('touchstart', 'touchend', 'mousedown', 'touchcancel');
24 - rootEventTypes.push('mouseup', 'mousemove', {
26 + targetEventTypes.push('touchstart', 'mousedown');
27 + rootEventTypes.push('mouseup', 'mousemove', 'touchend', 'touchcancel', {
28 name: 'touchmove',
29 passive: false,
30 });
@@ -92,18 +95,15 @@ const SwipeResponder = {
95 y: 0,
96 };
97 },
98 + stopLocalPropagation: true,
99 onEvent(
100 event: ReactResponderEvent,
101 context: ReactResponderContext,
102 props: Object,
103 state: SwipeState,
100 - ): boolean {
101 - const {target, phase, type, nativeEvent} = event;
104 + ): void {
105 + const {target, type, nativeEvent} = event;
106
103 - // Swipe doesn't handle capture target events at this point
104 - if (phase === CAPTURE_PHASE) {
105 - return false;
106 - }
107 switch (type) {
108 case 'touchstart':
109 case 'mousedown':
@@ -136,11 +136,22 @@ const SwipeResponder = {
136 }
137 break;
138 }
139 + }
140 + },
141 + onRootEvent(
142 + event: ReactResponderEvent,
143 + context: ReactResponderContext,
144 + props: Object,
145 + state: SwipeState,
146 + ): void {
147 + const {type, nativeEvent} = event;
148 +
149 + switch (type) {
150 case 'touchmove':
151 case 'mousemove':
152 case 'pointermove': {
153 if (event.passive) {
143 - return false;
154 + return;
155 }
156 if (state.isSwiping) {
157 let obj = null;
@@ -160,7 +171,7 @@ const SwipeResponder = {
171 state.swipeTarget = null;
172 state.touchId = null;
173 context.removeRootEventTypes(rootEventTypes);
163 - return false;
174 + return;
175 }
176 const x = (obj: any).screenX;
177 const y = (obj: any).screenY;
@@ -196,7 +207,7 @@ const SwipeResponder = {
207 case 'pointerup': {
208 if (state.isSwiping) {
209 if (state.x === state.startX && state.y === state.startY) {
199 - return false;
210 + return;
211 }
212 if (props.onShouldClaimOwnership) {
213 context.releaseOwnership();
@@ -240,7 +251,6 @@ const SwipeResponder = {
251 break;
252 }
253 }
243 - return false;
254 },
255 };
256
packages/react-events/src/__tests__/Press-test.internal.js
-81
@@ -1127,15 +1127,10 @@ describe('Event responder: Press', () => {
1127 'pointerdown',
1128 'inner: onPressStart',
1129 'inner: onPressChange',
1130 - 'outer: onPressStart',
1131 - 'outer: onPressChange',
1130 'pointerup',
1131 'inner: onPressEnd',
1132 'inner: onPressChange',
1133 'inner: onPress',
1136 - 'outer: onPressEnd',
1137 - 'outer: onPressChange',
1138 - 'outer: onPress',
1134 ]);
1135 });
1136
@@ -1214,82 +1209,6 @@ describe('Event responder: Press', () => {
1209 expect(fn).toHaveBeenCalledTimes(2);
1210 });
1211 });
1217 -
1218 - describe('correctly bubble to other event responders when stopPropagation is set to false', () => {
1219 - it('for onPress', () => {
1220 - const ref = React.createRef();
1221 - const fn = jest.fn();
1222 - const element = (
1223 - <Press onPress={fn}>
1224 - <Press onPress={fn} stopPropagation={false}>
1225 - <div ref={ref} />
1226 - </Press>
1227 - </Press>
1228 - );
1229 - ReactDOM.render(element, container);
1230 -
1231 - ref.current.dispatchEvent(createPointerEvent('pointerdown'));
1232 - ref.current.dispatchEvent(createPointerEvent('pointerup'));
1233 - expect(fn).toHaveBeenCalledTimes(2);
1234 - });
1235 -
1236 - it('for onLongPress', () => {
1237 - const ref = React.createRef();
1238 - const fn = jest.fn();
1239 - const element = (
1240 - <Press onLongPress={fn}>
1241 - <Press onLongPress={fn} stopPropagation={false}>
1242 - <div ref={ref} />
1243 - </Press>
1244 - </Press>
1245 - );
1246 - ReactDOM.render(element, container);
1247 -
1248 - ref.current.dispatchEvent(createPointerEvent('pointerdown'));
1249 - jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY);
1250 - ref.current.dispatchEvent(createPointerEvent('pointerup'));
1251 - expect(fn).toHaveBeenCalledTimes(2);
1252 - });
1253 -
1254 - it('for onPressStart/onPressEnd', () => {
1255 - const ref = React.createRef();
1256 - const fn = jest.fn();
1257 - const fn2 = jest.fn();
1258 - const element = (
1259 - <Press onPressStart={fn} onPressEnd={fn2}>
1260 - <Press onPressStart={fn} onPressEnd={fn2} stopPropagation={false}>
1261 - <div ref={ref} />
1262 - </Press>
1263 - </Press>
1264 - );
1265 - ReactDOM.render(element, container);
1266 -
1267 - ref.current.dispatchEvent(createPointerEvent('pointerdown'));
1268 - expect(fn).toHaveBeenCalledTimes(2);
1269 - expect(fn2).toHaveBeenCalledTimes(0);
1270 - ref.current.dispatchEvent(createPointerEvent('pointerup'));
1271 - expect(fn).toHaveBeenCalledTimes(2);
1272 - expect(fn2).toHaveBeenCalledTimes(2);
1273 - });
1274 -
1275 - it('for onPressChange', () => {
1276 - const ref = React.createRef();
1277 - const fn = jest.fn();
1278 - const element = (
1279 - <Press onPressChange={fn}>
1280 - <Press onPressChange={fn} stopPropagation={false}>
1281 - <div ref={ref} />
1282 - </Press>
1283 - </Press>
1284 - );
1285 - ReactDOM.render(element, container);
1286 -
1287 - ref.current.dispatchEvent(createPointerEvent('pointerdown'));
1288 - expect(fn).toHaveBeenCalledTimes(2);
1289 - ref.current.dispatchEvent(createPointerEvent('pointerup'));
1290 - expect(fn).toHaveBeenCalledTimes(4);
1291 - });
1292 - });
1212 });
1213
1214 describe('link components', () => {
packages/shared/ReactTypes.js
+16 -4
@@ -88,12 +88,25 @@ export type ReactEventResponderEventType =
88 export type ReactEventResponder = {
89 targetEventTypes: Array<ReactEventResponderEventType>,
90 createInitialState?: (props: null | Object) => Object,
91 - onEvent: (
91 + stopLocalPropagation: boolean,
92 + onEvent?: (
93 event: ReactResponderEvent,
94 context: ReactResponderContext,
95 props: null | Object,
96 state: null | Object,
96 - ) => boolean,
97 + ) => void,
98 + onEventCapture?: (
99 + event: ReactResponderEvent,
100 + context: ReactResponderContext,
101 + props: null | Object,
102 + state: null | Object,
103 + ) => void,
104 + onRootEvent?: (
105 + event: ReactResponderEvent,
106 + context: ReactResponderContext,
107 + props: null | Object,
108 + state: null | Object,
109 + ) => void,
110 onUnmount: (
111 context: ReactResponderContext,
112 props: null | Object,
@@ -135,7 +148,6 @@ export type ReactResponderEvent = {
148 type: string,
149 passive: boolean,
150 passiveSupported: boolean,
138 - phase: 0 | 1 | 2,
151 };
152
153 export type ReactResponderDispatchEventOptions = {
@@ -168,7 +180,7 @@ export type ReactResponderContext = {
180 hasOwnership: () => boolean,
181 requestOwnership: () => boolean,
182 releaseOwnership: () => boolean,
171 - setTimeout: (func: () => boolean, timeout: number) => Symbol,
183 + setTimeout: (func: () => void, timeout: number) => Symbol,
184 clearTimeout: (timerId: Symbol) => void,
185 getEventTargetsFromTarget: (
186 target: Element | Document,