package org.limewire.ui.swing.browser;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.client.utils.URIUtils;
import org.limewire.ui.swing.browser.UriAction.TargetedUri;
import org.mozilla.browser.MozillaExecutor;
import org.mozilla.browser.MozillaRuntimeException;
import org.mozilla.dom.NodeFactory;
import org.mozilla.interfaces.nsIDOMEvent;
import org.mozilla.interfaces.nsIDOMEventListener;
import org.mozilla.interfaces.nsISupports;
import org.mozilla.xpcom.Mozilla;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
/**
* Notifies registered actions of links being clicked and if the actions
* handle the event, it prevents the default action from happening.
*/
public class LimeDomListener implements nsIDOMEventListener {
private static final Log LOG = LogFactory.getLog(LimeDomListener.class);
private final Map<String, UriAction> targetActions = new ConcurrentHashMap<String, UriAction>();
private final Map<String, UriAction> protocolActions = new ConcurrentHashMap<String, UriAction>();
/**
* Adds a {@link UriAction} for the specified target. They are only invoked
* if there is no matching protocol action. Use empty string to register
* actions for link clicks that don't have a target
*/
public void addTargetedUrlAction(String target, UriAction action) {
targetActions.put(target, action);
}
/**
* Adds a {@link UriAction} for the specified uri protocol (magnet, etc..)
* Protocol handlers take precedence over target handlers, so if a uri matches both,
* only the protocol handler will be run.
*/
void addProtocolHandlerAction(String protocol, UriAction action) {
protocolActions.put(protocol, action);
}
public void handleEvent(nsIDOMEvent event) {
assert MozillaExecutor.isMozillaThread();
try {
UriAction.TargetedUri targetedUri = getTargetedUri(event);
if (targetedUri != null) {
String protocol = targetedUri.getProtocol();
if (protocol != null) {
UriAction action = protocolActions.get(protocol);
if (action != null) {
if (action.uriClicked(targetedUri)) {
event.preventDefault();
return;
}
}
}
String target = targetedUri.getTarget();
UriAction action = targetActions.get(target);
if (action != null) {
if (action.uriClicked(targetedUri)) {
event.preventDefault();
return;
}
}
}
} catch (MozillaRuntimeException e) {
// This should not occur
LOG.error("MozillaRuntimeException", e);
}
}
/**
*
* @return TargetedUrl if the event contains a URL, null if there is no URL.
*/
private UriAction.TargetedUri getTargetedUri(nsIDOMEvent event) {
UriAction.TargetedUri targetedUrl = null;
Node node = NodeFactory.getNodeInstance(event.getTarget());
if("click".equals(event.getType())) {
if (!"html".equalsIgnoreCase(node.getNodeName())) {
targetedUrl = getTargetedUri(node);
if (targetedUrl == null) {
// also check parent node
targetedUrl = getTargetedUri(node.getParentNode());
}
}
} else if("submit".equals(event.getType())) {
//TODO this is a special case to only handle submits for magnet links
//it would make sense in the future to just move all magnet link handling
//to its own listener, to not pollute the code with this special casing
targetedUrl = getTargetedFormAction(node);
if("magnet".equals(targetedUrl.getProtocol())) {
return targetedUrl;
} else {
//only do this for magent links so that forms can be submitted still.
return null;
}
}
return targetedUrl;
}
/**
* Crawls up the DOM for the given node finding the first form. Returning
* a TargetedUri populated with its action, or return null of no form or
* action could be found.
*/
private TargetedUri getTargetedFormAction(Node node) {
if(node != null) {
if("form".equalsIgnoreCase(node.getNodeName())) {
NamedNodeMap map = node.getAttributes();
Node actionNode = map.getNamedItem("action");
Node targetNode = map.getNamedItem("target");
String action = actionNode != null ? actionNode.getNodeValue() : null;
String target = targetNode != null && targetNode.getNodeValue() != null ? targetNode.getNodeValue() : "";
if(action != null) {
return new UriAction.TargetedUri(target, action);
}
} else {
return getTargetedFormAction(node.getParentNode());
}
}
return null ;
}
/**
*
* @return a TargetedUrl if the nodes attributes contain href, null if not.
*/
private UriAction.TargetedUri getTargetedUri(Node node) {
if (node != null) {
NamedNodeMap map = node.getAttributes();
if (map != null) {
Node hrefNode = map.getNamedItem("href");
if (hrefNode != null) {
String target = "";
URI absoluteURI = null;
try {
absoluteURI = new URI(encode(hrefNode.getNodeValue()));
if(!absoluteURI.isAbsolute()) {
//we need to pass an absolute uri to the targeted urls.
if(node.getBaseURI() != null) {
URI baseUri = new URI(encode(node.getBaseURI()));
URI relativeURI = new URI(encode(hrefNode.getNodeValue()));
absoluteURI = URIUtils.resolve(baseUri, relativeURI);
}
}
} catch (URISyntaxException e) {
absoluteURI = null;
}
if (absoluteURI == null || !absoluteURI.isAbsolute()) {
return null;
}
Node targetNode = map.getNamedItem("target");
if (targetNode != null) {
target = targetNode.getNodeValue();
}
return new UriAction.TargetedUri(target, absoluteURI.toASCIIString());
}
}
}
return null;
}
public String encode(String uri) {
if(uri == null) {
return null;
}
return uri.replaceAll(" ", "%20");
}
public nsISupports queryInterface(String uuid) {
return Mozilla.queryInterface(this, uuid);
}
}