package org.lobobrowser.html.js;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.lobobrowser.html.domimpl.NodeImpl;
import org.lobobrowser.html.js.Window.JSRunnableTask;
import org.mozilla.javascript.Function;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.events.EventException;
import org.w3c.dom.events.EventListener;
public final class EventTargetManager {
private final Map<NodeImpl, Map<String, List<EventListener>>> nodeOnEventListeners = new IdentityHashMap<>();
private final Window window;
public EventTargetManager(final Window window) {
this.window = window;
}
public void addEventListener(final NodeImpl node, final String type, final EventListener listener, final boolean useCapture) {
final List<EventListener> handlerList = getListenerList(type, node, true);
handlerList.add(listener);
}
private List<EventListener> getListenerList(final String type, final NodeImpl node, final boolean createIfNotExist) {
final Map<String, List<EventListener>> onEventListeners = getEventListeners(node, createIfNotExist);
if (onEventListeners != null) {
if (onEventListeners.containsKey(type)) {
return onEventListeners.get(type);
} else if (createIfNotExist) {
final List<EventListener> handlerList = new ArrayList<>();
onEventListeners.put(type, handlerList);
return handlerList;
} else {
return null;
}
} else {
return null;
}
}
private Map<String, List<EventListener>> getEventListeners(final NodeImpl node, final boolean createIfNotExist) {
if (nodeOnEventListeners.containsKey(node)) {
return nodeOnEventListeners.get(node);
} else {
if (createIfNotExist) {
final Map<String, List<EventListener>> onEventListeners = new HashMap<>();
nodeOnEventListeners.put(node, onEventListeners);
return onEventListeners;
} else {
return null;
}
}
}
public void removeEventListener(final NodeImpl node, final String type, final EventListener listener, final boolean useCapture) {
final Map<String, List<EventListener>> onEventListeners = getEventListeners(node, false);
if (onEventListeners != null) {
if (onEventListeners.containsKey(type)) {
onEventListeners.get(type).remove(listener);
}
}
}
private List<Function> getFunctionList(final String type, final NodeImpl node, final boolean createIfNotExist) {
final Map<String, List<Function>> onEventListeners = getEventFunctions(node, createIfNotExist);
if (onEventListeners != null) {
if (onEventListeners.containsKey(type)) {
return onEventListeners.get(type);
} else if (createIfNotExist) {
final List<Function> handlerList = new ArrayList<>();
onEventListeners.put(type, handlerList);
return handlerList;
} else {
return null;
}
} else {
return null;
}
}
private Map<String, List<Function>> getEventFunctions(final NodeImpl node, final boolean createIfNotExist) {
if (nodeOnEventFunctions.containsKey(node)) {
return nodeOnEventFunctions.get(node);
} else {
if (createIfNotExist) {
final Map<String, List<Function>> onEventListeners = new HashMap<>();
nodeOnEventFunctions.put(node, onEventListeners);
return onEventListeners;
} else {
return null;
}
}
}
public boolean dispatchEvent(final NodeImpl node, final Event evt) throws EventException {
// dispatchEventToHandlers(node, evt, onEventListeners.get(evt.getType()));
// dispatchEventToJSHandlers(node, evt, onEventHandlers.get(evt.getType()));
// TODO: Event Bubbling
// TODO: get Window into the propagation path
final List<NodeImpl> propagationPath = getPropagationPath(node);
// TODO: Capture phase, and distinction between target phase and bubbling phase
evt.setPhase(org.w3c.dom.events.Event.AT_TARGET);
// TODO: The JS Task should be added with the correct base URL
window.addJSTask(new JSRunnableTask(0, "Event dispatch for " + evt, () -> {
for (int i = 0; (i < propagationPath.size()) && !evt.isPropagationStopped(); i++) {
final NodeImpl currNode = propagationPath.get(i);
// System.out.println("Dipatching " + i + " to: " + currNode);
// TODO: Make request manager checks here.
dispatchEventToHandlers(currNode, evt);
dispatchEventToJSHandlers(currNode, evt);
evt.setPhase(org.w3c.dom.events.Event.BUBBLING_PHASE);
}
}
));
// dispatchEventToHandlers(node, evt);
// dispatchEventToJSHandlers(node, evt);
return false;
}
private static List<NodeImpl> getPropagationPath(NodeImpl node) {
final List<NodeImpl> nodes = new LinkedList<>();
while (node != null) {
if ((node instanceof Element) || (node instanceof Document)) { // TODO || node instanceof Window) {
nodes.add(node);
}
node = (NodeImpl) node.getParentNode();
}
// TODO
// nodes.add(window);
return nodes;
}
// private void dispatchEventToHandlers(final NodeImpl node, final Event event, final List<EventListener> handlers) {
private void dispatchEventToHandlers(final NodeImpl node, final Event event) {
final List<EventListener> handlers = getListenerList(event.getType(), node, false);
if (handlers != null) {
// We clone the collection and check if original collection still contains
// the handler before dispatching
// This is to avoid ConcurrentModificationException during dispatch
final ArrayList<EventListener> handlersCopy = new ArrayList<>(handlers);
for (final EventListener h : handlersCopy) {
// TODO: Not sure if we should stop calling handlers after propagation is stopped
// if (event.isPropagationStopped()) {
// return;
// }
if (handlers.contains(h)) {
// window.addJSTask(new JSRunnableTask(0, "Event dispatch for: " + event, new Runnable(){
// public void run() {
h.handleEvent(event);
// }
// }));
// h.handleEvent(event);
// Executor.executeFunction(node, h, event);
}
}
}
}
// protected void dispatchEventToJSHandlers(final NodeImpl node, final Event event, final List<Function> handlers) {
protected void dispatchEventToJSHandlers(final NodeImpl node, final Event event) {
final List<Function> handlers = getFunctionList(event.getType(), node, false);
if (handlers != null) {
// We clone the collection and check if original collection still contains
// the handler before dispatching
// This is to avoid ConcurrentModificationException during dispatch
final ArrayList<Function> handlersCopy = new ArrayList<>(handlers);
for (final Function h : handlersCopy) {
// TODO: Not sure if we should stop calling handlers after propagation is stopped
// if (event.isPropagationStopped()) {
// return;
// }
if (handlers.contains(h)) {
// window.addJSTask(new JSRunnableTask(0, "Event dispatch for " + event, new Runnable(){
// public void run() {
Executor.executeFunction(node, h, event, window.getContextFactory());
// }
// }));
// Executor.executeFunction(node, h, event);
}
}
}
}
// private final Map<String, List<Function>> onEventHandlers = new HashMap<>();
private final Map<NodeImpl, Map<String, List<Function>>> nodeOnEventFunctions = new IdentityHashMap<>();
public void addEventListener(final NodeImpl node, final String type, final Function listener) {
addEventListener(node, type, listener, false);
}
public void addEventListener(final NodeImpl node, final String type, final Function listener, final boolean useCapture) {
// TODO
// System.out.println("node by name: " + node.getNodeName() + " adding Event listener of type: " + type);
/*
List<Function> handlerList = null;
if (onEventHandlers.containsKey(type)) {
handlerList = onEventHandlers.get(type);
} else {
handlerList = new ArrayList<>();
onEventHandlers.put(type, handlerList);
}*/
// final Map<String, List<Function>> handlerList = getEventFunctions(node, true);
final List<Function> handlerList = getFunctionList(type, node, true);
handlerList.add(listener);
}
public void removeEventListener(final NodeImpl node, final String type, final Function listener, final boolean useCapture) {
final Map<String, List<Function>> onEventListeners = getEventFunctions(node, false);
if (onEventListeners != null) {
if (onEventListeners.containsKey(type)) {
onEventListeners.get(type).remove(listener);
}
}
}
public void reset() {
nodeOnEventFunctions.clear();
nodeOnEventListeners.clear();
}
}