/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.apache.batik.anim.dom;
import java.util.HashMap;
import java.util.HashSet;
import org.apache.batik.dom.AbstractNode;
import org.apache.batik.dom.events.AbstractEvent;
import org.apache.batik.dom.events.EventListenerList;
import org.apache.batik.dom.events.EventSupport;
import org.apache.batik.dom.events.NodeEventTarget;
import org.apache.batik.dom.xbl.NodeXBL;
import org.apache.batik.dom.xbl.ShadowTreeEvent;
import org.apache.batik.constants.XMLConstants;
import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventException;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.MutationEvent;
An EventSupport class that handles XBL-specific event processing.
Author: Cameron McCormack Version: $Id: XBLEventSupport.java 1851346 2019-01-15 13:41:00Z ssteiner $
/**
* An EventSupport class that handles XBL-specific event processing.
*
* @author <a href="mailto:cam%40mcc%2eid%2eau">Cameron McCormack</a>
* @version $Id: XBLEventSupport.java 1851346 2019-01-15 13:41:00Z ssteiner $
*/
public class XBLEventSupport extends EventSupport {
The unstoppable capturing listeners table.
/**
* The unstoppable capturing listeners table.
*/
protected HashMap<String, EventListenerList> capturingImplementationListeners;
The unstoppable bubbling listeners table.
/**
* The unstoppable bubbling listeners table.
*/
protected HashMap<String, EventListenerList> bubblingImplementationListeners;
Map of event types to their aliases.
/**
* Map of event types to their aliases.
*/
protected static HashMap<String, String> eventTypeAliases = new HashMap<String, String>();
static {
eventTypeAliases.put("SVGLoad", "load");
eventTypeAliases.put("SVGUnoad", "unload");
eventTypeAliases.put("SVGAbort", "abort");
eventTypeAliases.put("SVGError", "error");
eventTypeAliases.put("SVGResize", "resize");
eventTypeAliases.put("SVGScroll", "scroll");
eventTypeAliases.put("SVGZoom", "zoom");
}
Creates a new XBLEventSupport object.
/**
* Creates a new XBLEventSupport object.
*/
public XBLEventSupport(AbstractNode n) {
super(n);
}
Registers an event listener for the given namespaced event type
in the specified group.
/**
* Registers an event listener for the given namespaced event type
* in the specified group.
*/
public void addEventListenerNS(String namespaceURI,
String type,
EventListener listener,
boolean useCapture,
Object group) {
super.addEventListenerNS
(namespaceURI, type, listener, useCapture, group);
if (namespaceURI == null
|| namespaceURI.equals(XMLConstants.XML_EVENTS_NAMESPACE_URI)) {
String alias = eventTypeAliases.get(type);
if (alias != null) {
super.addEventListenerNS
(namespaceURI, alias, listener, useCapture, group);
}
}
}
Deregisters an event listener.
/**
* Deregisters an event listener.
*/
public void removeEventListenerNS(String namespaceURI,
String type,
EventListener listener,
boolean useCapture) {
super.removeEventListenerNS(namespaceURI, type, listener, useCapture);
if (namespaceURI == null
|| namespaceURI.equals(XMLConstants.XML_EVENTS_NAMESPACE_URI)) {
String alias = eventTypeAliases.get(type);
if (alias != null) {
super.removeEventListenerNS
(namespaceURI, alias, listener, useCapture);
}
}
}
Registers an event listener that will not be stopped by the usual
XBL stopping.
/**
* Registers an event listener that will not be stopped by the usual
* XBL stopping.
*/
public void addImplementationEventListenerNS(String namespaceURI,
String type,
EventListener listener,
boolean useCapture) {
HashMap<String, EventListenerList> listeners;
if (useCapture) {
if (capturingImplementationListeners == null) {
capturingImplementationListeners = new HashMap<String, EventListenerList>();
}
listeners = capturingImplementationListeners;
} else {
if (bubblingImplementationListeners == null) {
bubblingImplementationListeners = new HashMap<String, EventListenerList>();
}
listeners = bubblingImplementationListeners;
}
EventListenerList list = listeners.get(type);
if (list == null) {
list = new EventListenerList();
listeners.put(type, list);
}
list.addListener(namespaceURI, null, listener);
}
Unregisters an implementation event listener.
/**
* Unregisters an implementation event listener.
*/
public void removeImplementationEventListenerNS(String namespaceURI,
String type,
EventListener listener,
boolean useCapture) {
HashMap<String, EventListenerList> listeners = useCapture
? capturingImplementationListeners : bubblingImplementationListeners;
if (listeners == null) {
return;
}
EventListenerList list = listeners.get(type);
if (list == null) {
return;
}
list.removeListener(namespaceURI, listener);
if (list.size() == 0) {
listeners.remove(type);
}
}
Moves all of the event listeners from this EventSupport object to the given EventSupport object. Used by AbstractDocument.renameNode(Node, String, String)
. /**
* Moves all of the event listeners from this EventSupport object
* to the given EventSupport object.
* Used by {@link
* org.apache.batik.dom.AbstractDocument#renameNode(Node,String,String)}.
*/
public void moveEventListeners(EventSupport other) {
super.moveEventListeners(other);
XBLEventSupport es = (XBLEventSupport) other;
es.capturingImplementationListeners = capturingImplementationListeners;
es.bubblingImplementationListeners = bubblingImplementationListeners;
capturingImplementationListeners = null;
bubblingImplementationListeners = null;
}
This method allows the dispatch of events into the
implementations event model. Events dispatched in this manner
will have the same capturing and bubbling behavior as events
dispatched directly by the implementation. The target of the
event is the EventTarget
on which
dispatchEvent
is called.
Params: - target – the target node
- evt – Specifies the event type, behavior, and contextual
information to be used in processing the event.
Throws: - EventException –
UNSPECIFIED_EVENT_TYPE_ERR: Raised if the
Event
's type was not specified by initializing
the event before dispatchEvent
was
called. Specification of the Event
's type as
null
or an empty string will also trigger this
exception.
Returns: The return value of dispatchEvent
indicates whether any of the listeners which handled the event
called preventDefault
. If
preventDefault
was called the value is false, else
the value is true.
/**
* This method allows the dispatch of events into the
* implementations event model. Events dispatched in this manner
* will have the same capturing and bubbling behavior as events
* dispatched directly by the implementation. The target of the
* event is the <code> EventTarget</code> on which
* <code>dispatchEvent</code> is called.
*
* @param target the target node
* @param evt Specifies the event type, behavior, and contextual
* information to be used in processing the event.
*
* @return The return value of <code>dispatchEvent</code>
* indicates whether any of the listeners which handled the event
* called <code>preventDefault</code>. If
* <code>preventDefault</code> was called the value is false, else
* the value is true.
*
* @exception EventException
* UNSPECIFIED_EVENT_TYPE_ERR: Raised if the
* <code>Event</code>'s type was not specified by initializing
* the event before <code>dispatchEvent</code> was
* called. Specification of the <code>Event</code>'s type as
* <code>null</code> or an empty string will also trigger this
* exception.
*/
public boolean dispatchEvent(NodeEventTarget target, Event evt)
throws EventException {
// System.err.println("\t[] dispatching " + e.getType() + " on " + ((Node) target).getNodeName());
if (evt == null) {
return false;
}
if (!(evt instanceof AbstractEvent)) {
throw createEventException
(DOMException.NOT_SUPPORTED_ERR,
"unsupported.event",
new Object[] {});
}
AbstractEvent e = (AbstractEvent) evt;
String type = e.getType();
if (type == null || type.length() == 0) {
throw createEventException
(EventException.UNSPECIFIED_EVENT_TYPE_ERR,
"unspecified.event",
new Object[] {});
}
// fix event status
setTarget(e, target);
stopPropagation(e, false);
stopImmediatePropagation(e, false);
preventDefault(e, false);
// dump the tree hierarchy from top to the target
NodeEventTarget[] ancestors = getAncestors(target);
int bubbleLimit = e.getBubbleLimit();
int minAncestor = 0;
if (isSingleScopeEvent(e)) {
// DOM Mutation events are dispatched only within the
// one shadow scope
AbstractNode targetNode = (AbstractNode) target;
Node boundElement = targetNode.getXblBoundElement();
if (boundElement != null) {
minAncestor = ancestors.length;
while (minAncestor > 0) {
AbstractNode ancestorNode =
(AbstractNode) ancestors[minAncestor - 1];
if (ancestorNode.getXblBoundElement() != boundElement) {
break;
}
minAncestor--;
}
}
} else if (bubbleLimit != 0) {
// Other events may have a bubble limit (such as UI events)
minAncestor = ancestors.length - bubbleLimit + 1;
if (minAncestor < 0) {
minAncestor = 0;
}
}
// System.err.println("\t== ancestors:");
// for (int i = 0; i < ancestors.length; i++) {
// if (i < minAncestor) {
// System.err.print("\t ");
// } else {
// System.err.print("\t * ");
// }
// System.err.println(((Node) ancestors[i]).getNodeName());
// }
AbstractEvent[] es = getRetargettedEvents(target, ancestors, e);
boolean preventDefault = false;
// CAPTURING_PHASE : fire event listeners from top to EventTarget
HashSet stoppedGroups = new HashSet();
HashSet toBeStoppedGroups = new HashSet();
for (int i = 0; i < minAncestor; i++) {
NodeEventTarget node = ancestors[i];
// System.err.println("\t-- CAPTURING " + e.getType() + " " + ((Node) node).getNodeName());
setCurrentTarget(es[i], node);
setEventPhase(es[i], Event.CAPTURING_PHASE);
fireImplementationEventListeners(node, es[i], true);
}
for (int i = minAncestor; i < ancestors.length; i++) {
NodeEventTarget node = ancestors[i];
// System.err.println("\t-- * CAPTURING " + e.getType() + " " + ((Node) node).getNodeName());
setCurrentTarget(es[i], node);
setEventPhase(es[i], Event.CAPTURING_PHASE);
fireImplementationEventListeners(node, es[i], true);
fireEventListeners(node, es[i], true, stoppedGroups,
toBeStoppedGroups);
fireHandlerGroupEventListeners(node, es[i], true, stoppedGroups,
toBeStoppedGroups);
preventDefault = preventDefault || es[i].getDefaultPrevented();
stoppedGroups.addAll(toBeStoppedGroups);
toBeStoppedGroups.clear();
}
// AT_TARGET : fire local event listeners
// System.err.println("\t-- * AT_TARGET " + e.getType() + " " + ((Node) target).getNodeName());
setEventPhase(e, Event.AT_TARGET);
setCurrentTarget(e, target);
fireImplementationEventListeners(target, e, false);
fireEventListeners(target, e, false, stoppedGroups,
toBeStoppedGroups);
fireHandlerGroupEventListeners(node, e, false, stoppedGroups,
toBeStoppedGroups);
stoppedGroups.addAll(toBeStoppedGroups);
toBeStoppedGroups.clear();
preventDefault = preventDefault || e.getDefaultPrevented();
// BUBBLING_PHASE : fire event listeners from target to top
if (e.getBubbles()) {
for (int i = ancestors.length - 1; i >= minAncestor; i--) {
NodeEventTarget node = ancestors[i];
// System.err.println("\t-- * BUBBLING " + e.getType() + " " + ((Node) node).getNodeName());
setCurrentTarget(es[i], node);
setEventPhase(es[i], Event.BUBBLING_PHASE);
fireImplementationEventListeners(node, es[i], false);
fireEventListeners(node, es[i], false, stoppedGroups,
toBeStoppedGroups);
fireHandlerGroupEventListeners
(node, es[i], false, stoppedGroups, toBeStoppedGroups);
preventDefault =
preventDefault || es[i].getDefaultPrevented();
stoppedGroups.addAll(toBeStoppedGroups);
toBeStoppedGroups.clear();
}
for (int i = minAncestor - 1; i >= 0; i--) {
NodeEventTarget node = ancestors[i];
// System.err.println("\t-- BUBBLING " + e.getType() + " " + ((Node) node).getNodeName());
setCurrentTarget(es[i], node);
setEventPhase(es[i], Event.BUBBLING_PHASE);
fireImplementationEventListeners(node, es[i], false);
preventDefault =
preventDefault || es[i].getDefaultPrevented();
}
}
if (!preventDefault) {
runDefaultActions(e);
}
return preventDefault;
}
Fires the event handlers registered on an XBL 'handlerGroup' element.
/**
* Fires the event handlers registered on an XBL 'handlerGroup' element.
*/
protected void fireHandlerGroupEventListeners(NodeEventTarget node,
AbstractEvent e,
boolean useCapture,
HashSet stoppedGroups,
HashSet toBeStoppedGroups) {
// get the XBL definitions in effect for the event target
NodeList defs = ((NodeXBL) node).getXblDefinitions();
for (int j = 0; j < defs.getLength(); j++) {
// find the 'handlerGroup' element
Node n = defs.item(j).getFirstChild();
while (n != null &&
!(n instanceof XBLOMHandlerGroupElement)) {
n = n.getNextSibling();
}
if (n == null) {
continue;
}
node = (NodeEventTarget) n;
String type = e.getType();
EventSupport support = node.getEventSupport();
// check if the event support has been instantiated
if (support == null) {
continue;
}
EventListenerList list = support.getEventListeners(type, useCapture);
// check if the event listeners list is not empty
if (list == null) {
return;
}
// dump event listeners, we get the registered listeners NOW
EventListenerList.Entry[] listeners = list.getEventListeners();
fireEventListeners(node, e, listeners, stoppedGroups,
toBeStoppedGroups);
}
}
Returns whether the given event should be stopped once it crosses
a shadow scope boundary.
/**
* Returns whether the given event should be stopped once it crosses
* a shadow scope boundary.
*/
protected boolean isSingleScopeEvent(Event evt) {
return evt instanceof MutationEvent
|| evt instanceof ShadowTreeEvent;
}
Returns an array of Event objects to be used for each event target
in the event flow. The Event objects are retargetted if an sXBL
shadow scope is crossed and the event is not a DOM mutation event.
/**
* Returns an array of Event objects to be used for each event target
* in the event flow. The Event objects are retargetted if an sXBL
* shadow scope is crossed and the event is not a DOM mutation event.
*/
protected AbstractEvent[] getRetargettedEvents(NodeEventTarget target,
NodeEventTarget[] ancestors,
AbstractEvent e) {
boolean singleScope = isSingleScopeEvent(e);
AbstractNode targetNode = (AbstractNode) target;
AbstractEvent[] es = new AbstractEvent[ancestors.length];
if (ancestors.length > 0) {
int index = ancestors.length - 1;
Node boundElement = targetNode.getXblBoundElement();
AbstractNode ancestorNode = (AbstractNode) ancestors[index];
if (!singleScope &&
ancestorNode.getXblBoundElement() != boundElement) {
es[index] = retargetEvent(e, ancestors[index]);
} else {
es[index] = e;
}
while (--index >= 0) {
ancestorNode = (AbstractNode) ancestors[index + 1];
boundElement = ancestorNode.getXblBoundElement();
AbstractNode nextAncestorNode = (AbstractNode) ancestors[index];
Node nextBoundElement = nextAncestorNode.getXblBoundElement();
if (!singleScope && nextBoundElement != boundElement) {
es[index] = retargetEvent(es[index + 1], ancestors[index]);
} else {
es[index] = es[index + 1];
}
}
}
return es;
}
Clones and retargets the given event.
/**
* Clones and retargets the given event.
*/
protected AbstractEvent retargetEvent(AbstractEvent e,
NodeEventTarget target) {
AbstractEvent clonedEvent = e.cloneEvent();
setTarget(clonedEvent, target);
return clonedEvent;
}
Returns the implementation listneers.
/**
* Returns the implementation listneers.
*/
public EventListenerList getImplementationEventListeners
(String type, boolean useCapture) {
HashMap<String, EventListenerList> listeners = useCapture
? capturingImplementationListeners : bubblingImplementationListeners;
return listeners != null ? listeners.get(type) : null;
}
Fires the registered implementation listeners on the given event
target.
/**
* Fires the registered implementation listeners on the given event
* target.
*/
protected void fireImplementationEventListeners(NodeEventTarget node,
AbstractEvent e,
boolean useCapture) {
String type = e.getType();
XBLEventSupport support = (XBLEventSupport) node.getEventSupport();
// check if the event support has been instantiated
if (support == null) {
return;
}
EventListenerList list =
support.getImplementationEventListeners(type, useCapture);
// check if the event listeners list is not empty
if (list == null) {
return;
}
// dump event listeners, we get the registered listeners NOW
EventListenerList.Entry[] listeners = list.getEventListeners();
fireEventListeners(node, e, listeners, null, null);
}
}