mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
8fe4c0c76e
Previously, event handlers would not work when an app was mounted in an iframe, this is caused by a guard in the event handler that checks that the target element is still in the document, but it doesn't check against the correct document in the case of an iframe. This commit changes the check to check against the target's ownerDocument.
101 lines
3.0 KiB
TypeScript
101 lines
3.0 KiB
TypeScript
import { config } from "./config";
|
|
|
|
type EventHandlerSetter = (this: HTMLElement, data: any) => void;
|
|
|
|
interface EventHandlerCreator {
|
|
setup: EventHandlerSetter;
|
|
update: EventHandlerSetter;
|
|
remove: (this: HTMLElement) => void;
|
|
}
|
|
|
|
export function createEventHandler(rawEvent: string): EventHandlerCreator {
|
|
const eventName = rawEvent.split(".")[0];
|
|
const capture = rawEvent.includes(".capture");
|
|
if (rawEvent.includes(".synthetic")) {
|
|
return createSyntheticHandler(eventName, capture);
|
|
} else {
|
|
return createElementHandler(eventName, capture);
|
|
}
|
|
}
|
|
|
|
// Native listener
|
|
let nextNativeEventId = 1;
|
|
function createElementHandler(evName: string, capture: boolean = false): EventHandlerCreator {
|
|
let eventKey = `__event__${evName}_${nextNativeEventId++}`;
|
|
if (capture) {
|
|
eventKey = `${eventKey}_capture`;
|
|
}
|
|
|
|
function listener(ev: Event) {
|
|
const currentTarget = ev.currentTarget as HTMLElement;
|
|
if (!currentTarget || !currentTarget.ownerDocument.contains(currentTarget)) return;
|
|
const data = (currentTarget as any)[eventKey];
|
|
if (!data) return;
|
|
config.mainEventHandler(data, ev, currentTarget);
|
|
}
|
|
|
|
function setup(this: HTMLElement, data: any) {
|
|
(this as any)[eventKey] = data;
|
|
this.addEventListener(evName, listener, { capture });
|
|
}
|
|
|
|
function remove(this: HTMLElement) {
|
|
delete (this as any)[eventKey];
|
|
this.removeEventListener(evName, listener, { capture });
|
|
}
|
|
function update(this: HTMLElement, data: any) {
|
|
(this as any)[eventKey] = data;
|
|
}
|
|
|
|
return { setup, update, remove };
|
|
}
|
|
|
|
// Synthetic handler: a form of event delegation that allows placing only one
|
|
// listener per event type.
|
|
let nextSyntheticEventId = 1;
|
|
function createSyntheticHandler(evName: string, capture: boolean = false): EventHandlerCreator {
|
|
let eventKey = `__event__synthetic_${evName}`;
|
|
if (capture) {
|
|
eventKey = `${eventKey}_capture`;
|
|
}
|
|
setupSyntheticEvent(evName, eventKey, capture);
|
|
const currentId = nextSyntheticEventId++;
|
|
function setup(this: HTMLElement, data: any) {
|
|
const _data = (this as any)[eventKey] || {};
|
|
_data[currentId] = data;
|
|
(this as any)[eventKey] = _data;
|
|
}
|
|
|
|
function remove(this: HTMLElement) {
|
|
delete (this as any)[eventKey];
|
|
}
|
|
|
|
return { setup, update: setup, remove };
|
|
}
|
|
|
|
function nativeToSyntheticEvent(eventKey: string, event: Event) {
|
|
let dom = event.target;
|
|
while (dom !== null) {
|
|
const _data = (dom as any)[eventKey];
|
|
if (_data) {
|
|
for (const data of Object.values(_data)) {
|
|
const stopped = config.mainEventHandler(data, event, dom);
|
|
if (stopped) return;
|
|
}
|
|
}
|
|
dom = (dom as any).parentNode;
|
|
}
|
|
}
|
|
|
|
const CONFIGURED_SYNTHETIC_EVENTS: { [event: string]: boolean } = {};
|
|
|
|
function setupSyntheticEvent(evName: string, eventKey: string, capture: boolean = false) {
|
|
if (CONFIGURED_SYNTHETIC_EVENTS[eventKey]) {
|
|
return;
|
|
}
|
|
document.addEventListener(evName, (event) => nativeToSyntheticEvent(eventKey, event), {
|
|
capture,
|
|
});
|
|
CONFIGURED_SYNTHETIC_EVENTS[eventKey] = true;
|
|
}
|