Java tutorial
/* * Copyright (c) 2014-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ // // Copyright 2004-present Facebook. All Rights Reserved. package com.facebook.stetho.inspector.protocol.module; import android.graphics.Color; import com.facebook.stetho.common.LogUtil; import com.facebook.stetho.common.Util; import com.facebook.stetho.inspector.elements.AttributeAccumulator; import com.facebook.stetho.inspector.elements.DOMProvider; import com.facebook.stetho.inspector.elements.NodeDescriptor; import com.facebook.stetho.inspector.elements.NodeType; import com.facebook.stetho.inspector.helper.ChromePeerManager; import com.facebook.stetho.inspector.helper.ObjectIdMapper; import com.facebook.stetho.inspector.helper.PeersRegisteredListener; import com.facebook.stetho.inspector.jsonrpc.JsonRpcException; import com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer; import com.facebook.stetho.inspector.jsonrpc.JsonRpcResult; import com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain; import com.facebook.stetho.inspector.protocol.ChromeDevtoolsMethod; import com.facebook.stetho.json.ObjectMapper; import com.facebook.stetho.json.annotation.JsonProperty; import com.facebook.stetho.json.annotation.JsonValue; import org.json.JSONObject; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.annotation.Nullable; public class DOM implements ChromeDevtoolsDomain { private final ChromePeerManager mPeerManager; private final DOMProvider.Factory mDOMProviderFactory; private final ObjectMapper mObjectMapper; private final DOMObjectIdMapper mObjectIdMapper; @Nullable private volatile DOMProvider mDOMProvider; public DOM(DOMProvider.Factory providerFactory) { mDOMProviderFactory = Util.throwIfNull(providerFactory); mPeerManager = new ChromePeerManager(); mPeerManager.setListener(new PeerManagerListener()); mObjectMapper = new ObjectMapper(); mObjectIdMapper = new DOMObjectIdMapper(); } @ChromeDevtoolsMethod public void enable(JsonRpcPeer peer, JSONObject params) { mPeerManager.addPeer(peer); } @ChromeDevtoolsMethod public void disable(JsonRpcPeer peer, JSONObject params) { mPeerManager.removePeer(peer); } @ChromeDevtoolsMethod public JsonRpcResult getDocument(JsonRpcPeer peer, JSONObject params) { final GetDocumentResponse result = new GetDocumentResponse(); mDOMProvider.postAndWait(new Runnable() { @Override public void run() { Object rootElement = mDOMProvider.getRootElement(); if (rootElement != null) { result.root = createNodeForElement(rootElement); } } }); return result; } @ChromeDevtoolsMethod public void highlightNode(JsonRpcPeer peer, JSONObject params) { HighlightNodeRequest request = mObjectMapper.convertValue(params, HighlightNodeRequest.class); if (request.nodeId == null) { LogUtil.w("DOM.highlightNode was not given a nodeId; JS objectId is not supported"); } else { final RGBAColor contentColor = request.highlightConfig.contentColor; if (contentColor == null) { LogUtil.w("DOM.highlightNode was not given a color to highlight with"); } else { final Object element = mObjectIdMapper.getObjectForId(request.nodeId); mDOMProvider.postAndWait(new Runnable() { @Override public void run() { mDOMProvider.highlightElement(element, contentColor.getColor()); } }); } } } @ChromeDevtoolsMethod public void hideHighlight(JsonRpcPeer peer, JSONObject params) { mDOMProvider.hideHighlight(); } @ChromeDevtoolsMethod public ResolveNodeResponse resolveNode(JsonRpcPeer peer, JSONObject params) throws JsonRpcException { ResolveNodeRequest request = mObjectMapper.convertValue(params, ResolveNodeRequest.class); Object object = mObjectIdMapper.getObjectForId(request.nodeId); int mappedObjectId = Runtime.mapObject(peer, object); Runtime.RemoteObject remoteObject = new Runtime.RemoteObject(); remoteObject.type = Runtime.ObjectType.OBJECT; remoteObject.subtype = Runtime.ObjectSubType.NODE; remoteObject.className = remoteObject.getClass().getName(); remoteObject.value = null; // not a primitive remoteObject.description = null; // not sure what this does... remoteObject.objectId = String.valueOf(mappedObjectId); ResolveNodeResponse response = new ResolveNodeResponse(); response.object = remoteObject; return response; } @ChromeDevtoolsMethod public void setInspectModeEnabled(JsonRpcPeer peer, JSONObject params) { SetInspectModeEnabledRequest request = mObjectMapper.convertValue(params, SetInspectModeEnabledRequest.class); mDOMProvider.setInspectModeEnabled(request.enabled); } private Node createNodeForElement(Object element) { NodeDescriptor descriptor = mDOMProvider.getNodeDescriptor(element); Node node = new Node(); node.nodeId = mObjectIdMapper.putObject(element); node.nodeType = descriptor.getNodeType(element); node.nodeName = descriptor.getNodeName(element); node.localName = descriptor.getLocalName(element); node.nodeValue = descriptor.getNodeValue(element); node.children = getChildNodesForElement(element); node.childNodeCount = node.children.size(); node.attributes = new ArrayList<String>(); descriptor.copyAttributes(element, new AttributeListAccumulator(node.attributes)); return node; } private List<Node> getChildNodesForElement(Object element) { NodeDescriptor descriptor = mDOMProvider.getNodeDescriptor(element); int childNodeCount = descriptor.getChildCount(element); List<Node> childNodes; if (childNodeCount == 0) { childNodes = Collections.emptyList(); } else { childNodes = new ArrayList<Node>(childNodeCount); for (int i = 0; i < childNodeCount; ++i) { Object childElement = descriptor.getChildAt(element, i); Node childNode = createNodeForElement(childElement); childNodes.add(childNode); } } return childNodes; } private void removeElementTree(Object element) { if (!mObjectIdMapper.containsObject(element)) { LogUtil.w("DOM.removeElementTree() called for a non-mapped node: element=%s", element); return; } NodeDescriptor descriptor = mDOMProvider.getNodeDescriptor(element); int childCount = descriptor.getChildCount(element); for (int i = 0; i < childCount; ++i) { Object childElement = descriptor.getChildAt(element, i); removeElementTree(childElement); } mObjectIdMapper.removeObject(element); } private final class PeerManagerListener extends PeersRegisteredListener { @Override protected synchronized void onFirstPeerRegistered() { mDOMProvider = mDOMProviderFactory.create(); mDOMProvider.setListener(new ProviderListener()); } @Override protected synchronized void onLastPeerUnregistered() { mDOMProvider.postAndWait(new Runnable() { @Override public void run() { mObjectIdMapper.clear(); } }); mDOMProvider.dispose(); mDOMProvider = null; } } private static final class AttributeListAccumulator implements AttributeAccumulator { private final List<String> mList; public AttributeListAccumulator(List<String> list) { mList = Util.throwIfNull(list); } @Override public void add(String name, String value) { mList.add(name); mList.add(value); } } private final class DOMObjectIdMapper extends ObjectIdMapper { @Override protected void onMapped(Object object, int id) { NodeDescriptor descriptor = mDOMProvider.getNodeDescriptor(object); descriptor.hook(object); } @Override protected void onUnmapped(Object object, int id) { NodeDescriptor descriptor = mDOMProvider.getNodeDescriptor(object); descriptor.unhook(object); } } private final class ProviderListener implements DOMProvider.Listener { @Override public void onAttributeModified(Object element, String name, String value) { AttributeModifiedEvent message = new AttributeModifiedEvent(); message.nodeId = mObjectIdMapper.getIdForObject(element); message.name = name; message.value = value; mPeerManager.sendNotificationToPeers("DOM.attributeModified", message); } @Override public void onAttributeRemoved(Object element, String name) { AttributeRemovedEvent message = new AttributeRemovedEvent(); message.nodeId = mObjectIdMapper.getIdForObject(element); message.name = name; mPeerManager.sendNotificationToPeers("DOM.attributeRemoved", message); } @Override public void onChildInserted(Object parentElement, Object previousElement, Object childElement) { ChildNodeInsertedEvent message = new ChildNodeInsertedEvent(); message.parentNodeId = mObjectIdMapper.getIdForObject(parentElement); // using -1 was just a guess, and it seemed to work. this is for the case where we // go from 0 to 1 children, or if we just happen to be inserting at index 0 message.previousNodeId = (previousElement == null) ? -1 : mObjectIdMapper.getIdForObject(previousElement); message.node = createNodeForElement(childElement); mPeerManager.sendNotificationToPeers("DOM.childNodeInserted", message); } @Override public void onChildRemoved(Object parentElement, Object childElement) { Integer parentNodeId = mObjectIdMapper.getIdForObject(parentElement); Integer childNodeId = mObjectIdMapper.getIdForObject(childElement); if (parentNodeId == null || childNodeId == null) { LogUtil.d( "DOMProvider.Listener.onChildRemoved() called for a non-mapped node: " + "parentElement=(nodeId=%s, %s), childElement=(nodeId=%s, %s)", parentNodeId, parentElement, childNodeId, childElement); } else { ChildNodeRemovedEvent message = new ChildNodeRemovedEvent(); message.parentNodeId = parentNodeId; message.nodeId = childNodeId; mPeerManager.sendNotificationToPeers("DOM.childNodeRemoved", message); } removeElementTree(childElement); } @Override public void onInspectRequested(Object element) { Integer nodeId = mObjectIdMapper.getIdForObject(element); if (nodeId == null) { LogUtil.d("DOMProvider.Listener.onInspectRequested() called for a non-mapped node: element=%s", element); } else { InspectNodeRequestedEvent message = new InspectNodeRequestedEvent(); message.nodeId = nodeId; mPeerManager.sendNotificationToPeers("DOM.inspectNodeRequested", message); } } } private static class GetDocumentResponse implements JsonRpcResult { @JsonProperty(required = true) public Node root; } private static class Node implements JsonRpcResult { @JsonProperty(required = true) public int nodeId; @JsonProperty(required = true) public NodeType nodeType; @JsonProperty(required = true) public String nodeName; @JsonProperty(required = true) public String localName; @JsonProperty(required = true) public String nodeValue; @JsonProperty public Integer childNodeCount; @JsonProperty public List<Node> children; @JsonProperty public List<String> attributes; } private static class AttributeModifiedEvent { @JsonProperty(required = true) public int nodeId; @JsonProperty(required = true) public String name; @JsonProperty(required = true) public String value; } private static class AttributeRemovedEvent { @JsonProperty(required = true) public int nodeId; @JsonProperty(required = true) public String name; } private static class ChildNodeInsertedEvent { @JsonProperty(required = true) public int parentNodeId; @JsonProperty(required = true) public int previousNodeId; @JsonProperty(required = true) public Node node; } private static class ChildNodeRemovedEvent { @JsonProperty(required = true) public int parentNodeId; @JsonProperty(required = true) public int nodeId; } private static class HighlightNodeRequest { @JsonProperty(required = true) public HighlightConfig highlightConfig; @JsonProperty public Integer nodeId; @JsonProperty public String objectId; } private static class HighlightConfig { @JsonProperty public RGBAColor contentColor; } private static class InspectNodeRequestedEvent { @JsonProperty public int nodeId; } private static class SetInspectModeEnabledRequest { @JsonProperty(required = true) public boolean enabled; @JsonProperty public Boolean inspectShadowDOM; @JsonProperty public HighlightConfig highlightConfig; } private static class RGBAColor { @JsonProperty(required = true) public int r; @JsonProperty(required = true) public int g; @JsonProperty(required = true) public int b; @JsonProperty public Double a; public int getColor() { byte alpha; if (this.a == null) { alpha = (byte) 255; } else { long aLong = Math.round(this.a * 255.0); alpha = (aLong < 0) ? (byte) 0 : (aLong >= 255) ? (byte) 255 : (byte) aLong; } return Color.argb(alpha, this.r, this.g, this.b); } } private static class ResolveNodeRequest { @JsonProperty(required = true) public int nodeId; @JsonProperty public String objectGroup; } private static class ResolveNodeResponse implements JsonRpcResult { @JsonProperty(required = true) public Runtime.RemoteObject object; } }