Skip to content
236 changes: 236 additions & 0 deletions packages/react-native/src/private/webapis/dom/events/Event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/

/**
* This module implements the `Event` interface from the DOM.
* See https://dom.spec.whatwg.org/#interface-event.
*/

// flowlint unsafe-getters-setters:off

import type EventTarget from './EventTarget';

import {
COMPOSED_PATH_KEY,
CURRENT_TARGET_KEY,
EVENT_PHASE_KEY,
IN_PASSIVE_LISTENER_FLAG_KEY,
IS_TRUSTED_KEY,
STOP_IMMEDIATE_PROPAGATION_FLAG_KEY,
STOP_PROPAGATION_FLAG_KEY,
TARGET_KEY,
getComposedPath,
getCurrentTarget,
getEventPhase,
getInPassiveListenerFlag,
getIsTrusted,
getTarget,
setStopImmediatePropagationFlag,
setStopPropagationFlag,
} from './internals/EventInternals';

type EventInit = {
bubbles?: boolean,
cancelable?: boolean,
composed?: boolean,
};

export default class Event {
static +NONE: 0;
static +CAPTURING_PHASE: 1;
static +AT_TARGET: 2;
static +BUBBLING_PHASE: 3;

+NONE: 0;
+CAPTURING_PHASE: 1;
+AT_TARGET: 2;
+BUBBLING_PHASE: 3;

_bubbles: boolean;
_cancelable: boolean;
_composed: boolean;
_type: string;

_defaultPrevented: boolean = false;
_timeStamp: number = performance.now();

// $FlowExpectedError[unsupported-syntax]
[COMPOSED_PATH_KEY]: boolean = [];

// $FlowExpectedError[unsupported-syntax]
[CURRENT_TARGET_KEY]: EventTarget | null = null;

// $FlowExpectedError[unsupported-syntax]
[EVENT_PHASE_KEY]: boolean = Event.NONE;

// $FlowExpectedError[unsupported-syntax]
[IN_PASSIVE_LISTENER_FLAG_KEY]: boolean = false;

// $FlowExpectedError[unsupported-syntax]
[IS_TRUSTED_KEY]: boolean = false;

// $FlowExpectedError[unsupported-syntax]
[STOP_IMMEDIATE_PROPAGATION_FLAG_KEY]: boolean = false;

// $FlowExpectedError[unsupported-syntax]
[STOP_PROPAGATION_FLAG_KEY]: boolean = false;

// $FlowExpectedError[unsupported-syntax]
[TARGET_KEY]: EventTarget | null = null;

constructor(type: string, options?: ?EventInit) {
if (arguments.length < 1) {
throw new TypeError(
"Failed to construct 'Event': 1 argument required, but only 0 present.",
);
}

const typeOfOptions = typeof options;

if (
options != null &&
typeOfOptions !== 'object' &&
typeOfOptions !== 'function'
) {
throw new TypeError(
"Failed to construct 'Event': The provided value is not of type 'EventInit'.",
);
}

this._type = String(type);
this._bubbles = Boolean(options?.bubbles);
this._cancelable = Boolean(options?.cancelable);
this._composed = Boolean(options?.composed);
}

get bubbles(): boolean {
return this._bubbles;
}

get cancelable(): boolean {
return this._cancelable;
}

get composed(): boolean {
return this._composed;
}

get currentTarget(): EventTarget | null {
return getCurrentTarget(this);
}

get defaultPrevented(): boolean {
return this._defaultPrevented;
}

get eventPhase(): EventPhase {
return getEventPhase(this);
}

get isTrusted(): boolean {
return getIsTrusted(this);
}

get target(): EventTarget | null {
return getTarget(this);
}

get timeStamp(): number {
return this._timeStamp;
}

get type(): string {
return this._type;
}

composedPath(): $ReadOnlyArray<EventTarget> {
return getComposedPath(this).slice();
}

preventDefault(): void {
if (!this._cancelable) {
return;
}

if (getInPassiveListenerFlag(this)) {
console.error(
new Error(
'Unable to preventDefault inside passive event listener invocation.',
),
);
return;
}

this._defaultPrevented = true;
}

stopImmediatePropagation(): void {
setStopPropagationFlag(this, true);
setStopImmediatePropagationFlag(this, true);
}

stopPropagation(): void {
setStopPropagationFlag(this, true);
}
}

// $FlowExpectedError[cannot-write]
Object.defineProperty(Event, 'NONE', {
enumerable: true,
value: 0,
});

// $FlowExpectedError[cannot-write]
Object.defineProperty(Event.prototype, 'NONE', {
enumerable: true,
value: 0,
});

// $FlowExpectedError[cannot-write]
Object.defineProperty(Event, 'CAPTURING_PHASE', {
enumerable: true,
value: 1,
});

// $FlowExpectedError[cannot-write]
Object.defineProperty(Event.prototype, 'CAPTURING_PHASE', {
enumerable: true,
value: 1,
});

// $FlowExpectedError[cannot-write]
Object.defineProperty(Event, 'AT_TARGET', {
enumerable: true,
value: 2,
});

// $FlowExpectedError[cannot-write]
Object.defineProperty(Event.prototype, 'AT_TARGET', {
enumerable: true,
value: 2,
});

// $FlowExpectedError[cannot-write]
Object.defineProperty(Event, 'BUBBLING_PHASE', {
enumerable: true,
value: 3,
});

// $FlowExpectedError[cannot-write]
Object.defineProperty(Event.prototype, 'BUBBLING_PHASE', {
enumerable: true,
value: 3,
});

export type EventPhase =
| (typeof Event)['NONE']
| (typeof Event)['CAPTURING_PHASE']
| (typeof Event)['AT_TARGET']
| (typeof Event)['BUBBLING_PHASE'];
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/

/**
* This module provides helpers for classes to implement event handler IDL
* attributes, as defined in https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-idl-attributes.
*
* Expected usage:
* ```
* import {getEventHandlerAttribute, setEventHandlerAttribute} from '../path/to/EventHandlerAttributes';
*
* class EventTargetSubclass extends EventTarget {
* get oncustomevent(): EventListener | null {
* return getEventHandlerAttribute(this, 'customEvent');
* }
*
* set oncustomevent(listener: EventListener | null) {
* setEventHandlerAttribute(this, 'customEvent', listener);
* }
* }
*
* const eventTargetInstance = new EventTargetSubclass();
*
* eventTargetInstance.oncustomevent = (event: Event) => {
* console.log('custom event received');
* };
* eventTargetInstance.dispatchEvent(new Event('customEvent'));
* // Logs 'custom event received' to the console.
*
* eventTargetInstance.oncustomevent = null;
* eventTargetInstance.dispatchEvent(new Event('customEvent'));
* // Does not log anything to the console.
* ```
*/

import type EventTarget from './EventTarget';
import type {EventCallback} from './EventTarget';

type EventHandler = $ReadOnly<{
handleEvent: EventCallback,
}>;
type EventHandlerAttributeMap = Map<string, EventHandler | null>;

const EVENT_HANDLER_CONTENT_ATTRIBUTE_MAP_KEY = Symbol(
'eventHandlerAttributeMap',
);

function getEventHandlerAttributeMap(
target: EventTarget,
): ?EventHandlerAttributeMap {
// $FlowExpectedError[prop-missing]
return target[EVENT_HANDLER_CONTENT_ATTRIBUTE_MAP_KEY];
}

function setEventHandlerAttributeMap(
target: EventTarget,
map: ?EventHandlerAttributeMap,
) {
// $FlowExpectedError[prop-missing]
target[EVENT_HANDLER_CONTENT_ATTRIBUTE_MAP_KEY] = map;
}

/**
* Returns the event listener registered as an event handler IDL attribute for
* the given target and type.
*
* Should be used to get the current value for `target.on{type}`.
*/
export function getEventHandlerAttribute(
target: EventTarget,
type: string,
): EventCallback | null {
const listener = getEventHandlerAttributeMap(target)?.get(type);
return listener != null ? listener.handleEvent : null;
}

/**
* Sets the event listener registered as an event handler IDL attribute for
* the given target and type.
*
* Should be used to set a value for `target.on{type}`.
*/
export function setEventHandlerAttribute(
target: EventTarget,
type: string,
callback: ?EventCallback,
): void {
let map = getEventHandlerAttributeMap(target);
if (map != null) {
const currentListener = map.get(type);
if (currentListener) {
target.removeEventListener(type, currentListener);
map.delete(type);
}
}

if (
callback != null &&
(typeof callback === 'function' || typeof callback === 'object')
) {
// Register the listener as a different object in the target so it
// occupies its own slot and cannot be removed via `removeEventListener`.
const listener = {
handleEvent: callback,
};

try {
target.addEventListener(type, listener);
// If adding the listener fails, we don't store the value
if (map == null) {
map = new Map();
setEventHandlerAttributeMap(target, map);
}
map.set(type, listener);
} catch (e) {
// Assigning incorrect listener does not throw in setters.
}
}

if (map != null && map.size === 0) {
setEventHandlerAttributeMap(target, null);
}
}
Loading
Loading