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. */ package com.taobao.weex.devtools.inspector.protocol.module; import android.graphics.Color; import android.view.View; import android.view.ViewGroup; import com.taobao.weex.devtools.common.Accumulator; import com.taobao.weex.devtools.common.ArrayListAccumulator; import com.taobao.weex.devtools.common.LogUtil; import com.taobao.weex.devtools.common.UncheckedCallable; import com.taobao.weex.devtools.common.Util; import com.taobao.weex.devtools.inspector.elements.Document; import com.taobao.weex.devtools.inspector.elements.DocumentView; import com.taobao.weex.devtools.inspector.elements.ElementInfo; import com.taobao.weex.devtools.inspector.elements.NodeDescriptor; import com.taobao.weex.devtools.inspector.elements.NodeType; import com.taobao.weex.devtools.inspector.elements.StyleAccumulator; import com.taobao.weex.devtools.inspector.helper.ChromePeerManager; import com.taobao.weex.devtools.inspector.helper.PeersRegisteredListener; import com.taobao.weex.devtools.inspector.jsonrpc.JsonRpcException; import com.taobao.weex.devtools.inspector.jsonrpc.JsonRpcPeer; import com.taobao.weex.devtools.inspector.jsonrpc.JsonRpcResult; import com.taobao.weex.devtools.inspector.jsonrpc.protocol.JsonRpcError; import com.taobao.weex.devtools.inspector.protocol.ChromeDevtoolsDomain; import com.taobao.weex.devtools.inspector.protocol.ChromeDevtoolsMethod; import com.taobao.weex.devtools.inspector.screencast.ScreencastDispatcher; import com.taobao.weex.devtools.json.ObjectMapper; import com.taobao.weex.devtools.json.annotation.JsonProperty; import com.taobao.weex.ui.component.WXComponent; import com.taobao.weex.utils.WXViewUtils; import org.json.JSONObject; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.Nullable; public class DOM implements ChromeDevtoolsDomain { private static boolean sNativeMode = true; private final ObjectMapper mObjectMapper; private final Document mDocument; private final Map<String, List<Integer>> mSearchResults; private final AtomicInteger mResultCounter; private final ChromePeerManager mPeerManager; private final DocumentUpdateListener mListener; private ChildNodeRemovedEvent mCachedChildNodeRemovedEvent; private ChildNodeInsertedEvent mCachedChildNodeInsertedEvent; public DOM(Document document) { mObjectMapper = new ObjectMapper(); mDocument = Util.throwIfNull(document); mSearchResults = Collections.synchronizedMap(new HashMap<String, List<Integer>>()); mResultCounter = new AtomicInteger(0); mPeerManager = new ChromePeerManager(); mPeerManager.setListener(new PeerManagerListener()); mListener = new DocumentUpdateListener(); } public static void setNativeMode(boolean isNativeMode) { sNativeMode = isNativeMode; } public static boolean isNativeMode() { return sNativeMode; } @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(); result.root = mDocument.postAndWait(new UncheckedCallable<Node>() { @Override public Node call() { Object element = mDocument.getRootElement(); return createNodeForElement(element, mDocument.getDocumentView(), null); } }); return result; } @ChromeDevtoolsMethod public void highlightNode(JsonRpcPeer peer, JSONObject params) { final 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"); return; } final RGBAColor contentColor = request.highlightConfig.contentColor; if (contentColor == null) { LogUtil.w("DOM.highlightNode was not given a color to highlight with"); return; } mDocument.postAndWait(new Runnable() { @Override public void run() { Object element = mDocument.getElementForNodeId(request.nodeId); if (element != null) { mDocument.highlightElement(element, contentColor.getColor()); } } }); } @ChromeDevtoolsMethod public void hideHighlight(JsonRpcPeer peer, JSONObject params) { mDocument.postAndWait(new Runnable() { @Override public void run() { mDocument.hideHighlight(); } }); } @ChromeDevtoolsMethod public ResolveNodeResponse resolveNode(JsonRpcPeer peer, JSONObject params) throws JsonRpcException { final ResolveNodeRequest request = mObjectMapper.convertValue(params, ResolveNodeRequest.class); final Object element = mDocument.postAndWait(new UncheckedCallable<Object>() { @Override public Object call() { return mDocument.getElementForNodeId(request.nodeId); } }); if (element == null) { throw new JsonRpcException(new JsonRpcError(JsonRpcError.ErrorCode.INVALID_PARAMS, "No known nodeId=" + request.nodeId, null /* data */)); } int mappedObjectId = Runtime.mapObject(peer, element); Runtime.RemoteObject remoteObject = new Runtime.RemoteObject(); remoteObject.type = Runtime.ObjectType.OBJECT; remoteObject.subtype = Runtime.ObjectSubType.NODE; remoteObject.className = element.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 setAttributesAsText(JsonRpcPeer peer, JSONObject params) { final SetAttributesAsTextRequest request = mObjectMapper.convertValue(params, SetAttributesAsTextRequest.class); mDocument.postAndWait(new Runnable() { @Override public void run() { Object element = mDocument.getElementForNodeId(request.nodeId); if (element != null) { mDocument.setAttributesAsText(element, request.text); } } }); } @ChromeDevtoolsMethod public void setInspectModeEnabled(JsonRpcPeer peer, JSONObject params) { final SetInspectModeEnabledRequest request = mObjectMapper.convertValue(params, SetInspectModeEnabledRequest.class); mDocument.postAndWait(new Runnable() { @Override public void run() { mDocument.setInspectModeEnabled(request.enabled); } }); } @ChromeDevtoolsMethod public PerformSearchResponse performSearch(JsonRpcPeer peer, final JSONObject params) { final PerformSearchRequest request = mObjectMapper.convertValue(params, PerformSearchRequest.class); final ArrayListAccumulator<Integer> resultNodeIds = new ArrayListAccumulator<>(); mDocument.postAndWait(new Runnable() { @Override public void run() { mDocument.findMatchingElements(request.query, resultNodeIds); } }); // Each search action has a unique ID so that // it can be queried later. final String searchId = String.valueOf(mResultCounter.getAndIncrement()); mSearchResults.put(searchId, resultNodeIds); final PerformSearchResponse response = new PerformSearchResponse(); response.searchId = searchId; response.resultCount = resultNodeIds.size(); return response; } @ChromeDevtoolsMethod public GetSearchResultsResponse getSearchResults(JsonRpcPeer peer, JSONObject params) { final GetSearchResultsRequest request = mObjectMapper.convertValue(params, GetSearchResultsRequest.class); if (request.searchId == null) { LogUtil.w("searchId may not be null"); return null; } final List<Integer> results = mSearchResults.get(request.searchId); if (results == null) { LogUtil.w("\"" + request.searchId + "\" is not a valid reference to a search result"); return null; } final List<Integer> resultsRange = results.subList(request.fromIndex, request.toIndex); final GetSearchResultsResponse response = new GetSearchResultsResponse(); response.nodeIds = resultsRange; return response; } @ChromeDevtoolsMethod public void discardSearchResults(JsonRpcPeer peer, JSONObject params) { final DiscardSearchResultsRequest request = mObjectMapper.convertValue(params, DiscardSearchResultsRequest.class); if (request.searchId != null) { mSearchResults.remove(request.searchId); } } @ChromeDevtoolsMethod public GetNodeForLocationResponse getNodeForLocation(JsonRpcPeer peer, JSONObject params) { GetNodeForLocationResponse result = new GetNodeForLocationResponse(); final GetNodeForLocationRequest request = mObjectMapper.convertValue(params, GetNodeForLocationRequest.class); if (request.x > 0 && request.y > 0) { result.nodeId = findViewByLocation(request.x, request.y); } return result; } public int findViewByLocation(final int x, final int y) { final ArrayListAccumulator<Integer> resultNodeIds = new ArrayListAccumulator<>(); mDocument.postAndWait(new Runnable() { @Override public void run() { mDocument.findMatchingElements(x, y, resultNodeIds); } }); if (resultNodeIds.size() > 0) { return resultNodeIds.get(resultNodeIds.size() - 1); } return 0; } @ChromeDevtoolsMethod public GetBoxModelResponse getBoxModel(JsonRpcPeer peer, JSONObject params) { GetBoxModelResponse response = new GetBoxModelResponse(); final BoxModel model = new BoxModel(); final GetBoxModelRequest request = mObjectMapper.convertValue(params, GetBoxModelRequest.class); if (request.nodeId == null) { return null; } response.model = model; mDocument.postAndWait(new Runnable() { @Override public void run() { final Object elementForNodeId = mDocument.getElementForNodeId(request.nodeId); if (elementForNodeId == null) { LogUtil.w("Failed to get style of an element that does not exist, nodeid=" + request.nodeId); return; } mDocument.getElementStyles(elementForNodeId, new StyleAccumulator() { @Override public void store(String name, String value, boolean isDefault) { double left = 0; double right = 0; double top = 0; double bottom = 0; double paddingLeft = 0; double paddingRight = 0; double paddingTop = 0; double paddingBottom = 0; double marginLeft = 0; double marginRight = 0; double marginTop = 0; double marginBottom = 0; double borderLeftWidth = 0; double borderRightWidth = 0; double borderTopWidth = 0; double borderBottomWidth = 0; View view = null; if (isNativeMode()) { if (elementForNodeId instanceof View) { view = (View) elementForNodeId; } } else { if (elementForNodeId instanceof WXComponent) { view = ((WXComponent) elementForNodeId).getHostView(); } } if (view != null && view.isShown()) { float scale = ScreencastDispatcher.getsBitmapScale(); model.width = view.getWidth(); model.height = view.getHeight(); if (!DOM.isNativeMode()) { model.width = (int) (model.width * 750 / WXViewUtils.getScreenWidth() + 0.5); model.height = (int) (model.height * 750 / WXViewUtils.getScreenWidth() + 0.5); } int[] location = new int[2]; view.getLocationOnScreen(location); left = location[0] * scale; top = location[1] * scale; right = left + view.getWidth() * scale; bottom = top + view.getHeight() * scale; paddingLeft = view.getPaddingLeft() * scale; paddingTop = view.getPaddingTop() * scale; paddingRight = view.getPaddingRight() * scale; paddingBottom = view.getPaddingBottom() * scale; if (view instanceof ViewGroup) { ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); if (layoutParams != null) { if (layoutParams instanceof ViewGroup.MarginLayoutParams) { ViewGroup.MarginLayoutParams margins = (ViewGroup.MarginLayoutParams) layoutParams; marginLeft = margins.leftMargin * scale; marginTop = margins.topMargin * scale; marginRight = margins.rightMargin * scale; marginBottom = margins.bottomMargin * scale; } } } } ArrayList<Double> padding = new ArrayList<>(8); padding.add(left + borderLeftWidth); padding.add(top + borderTopWidth); padding.add(right - borderRightWidth); padding.add(top + borderTopWidth); padding.add(right - borderRightWidth); padding.add(bottom - borderBottomWidth); padding.add(left + borderLeftWidth); padding.add(bottom - borderBottomWidth); model.padding = padding; ArrayList<Double> content = new ArrayList<>(8); content.add(left + borderLeftWidth + paddingLeft); content.add(top + borderTopWidth + paddingTop); content.add(right - borderRightWidth - paddingRight); content.add(top + borderTopWidth + paddingTop); content.add(right - borderRightWidth - paddingRight); content.add(bottom - borderBottomWidth - paddingBottom); content.add(left + borderLeftWidth + paddingLeft); content.add(bottom - borderBottomWidth - paddingBottom); model.content = content; ArrayList<Double> border = new ArrayList<>(8); border.add(left); border.add(top); border.add(right); border.add(top); border.add(right); border.add(bottom); border.add(left); border.add(bottom); model.border = border; ArrayList<Double> margin = new ArrayList<>(8); margin.add(left - marginLeft); margin.add(top - marginTop); margin.add(right + marginRight); margin.add(top - marginTop); margin.add(right + marginRight); margin.add(bottom + marginBottom); margin.add(left - marginLeft); margin.add(bottom + marginBottom); model.margin = margin; } }); } }); return response; } public static final class GetBoxModelResponse implements JsonRpcResult { @JsonProperty(required = true) public BoxModel model; } private static class GetBoxModelRequest { @JsonProperty public Integer nodeId; } private static class BoxModel { @JsonProperty(required = true) public List<Double> content; @JsonProperty(required = true) public List<Double> padding; @JsonProperty(required = true) public List<Double> border; @JsonProperty(required = true) public List<Double> margin; @JsonProperty(required = true) public Integer width; @JsonProperty(required = true) public Integer height; } private Node createNodeForElement(Object element, DocumentView view, @Nullable Accumulator<Object> processedElements) { if (processedElements != null) { processedElements.store(element); } NodeDescriptor descriptor = mDocument.getNodeDescriptor(element); Node node = new DOM.Node(); node.nodeId = mDocument.getNodeIdForElement(element); node.nodeType = descriptor.getNodeType(element); node.nodeName = descriptor.getNodeName(element); node.localName = descriptor.getLocalName(element); node.nodeValue = descriptor.getNodeValue(element); Document.AttributeListAccumulator accumulator = new Document.AttributeListAccumulator(); descriptor.getAttributes(element, accumulator); // Attributes node.attributes = accumulator; // Children ElementInfo elementInfo = view.getElementInfo(element); List<Node> childrenNodes = (elementInfo.children.size() == 0) ? Collections.<Node>emptyList() : new ArrayList<Node>(elementInfo.children.size()); for (int i = 0, N = elementInfo.children.size(); i < N; ++i) { final Object childElement = elementInfo.children.get(i); Node childNode = createNodeForElement(childElement, view, processedElements); childrenNodes.add(childNode); } node.children = childrenNodes; node.childNodeCount = childrenNodes.size(); return node; } private ChildNodeInsertedEvent acquireChildNodeInsertedEvent() { ChildNodeInsertedEvent childNodeInsertedEvent = mCachedChildNodeInsertedEvent; if (childNodeInsertedEvent == null) { childNodeInsertedEvent = new ChildNodeInsertedEvent(); } mCachedChildNodeInsertedEvent = null; return childNodeInsertedEvent; } private void releaseChildNodeInsertedEvent(ChildNodeInsertedEvent childNodeInsertedEvent) { childNodeInsertedEvent.parentNodeId = -1; childNodeInsertedEvent.previousNodeId = -1; childNodeInsertedEvent.node = null; if (mCachedChildNodeInsertedEvent == null) { mCachedChildNodeInsertedEvent = childNodeInsertedEvent; } } private ChildNodeRemovedEvent acquireChildNodeRemovedEvent() { ChildNodeRemovedEvent childNodeRemovedEvent = mCachedChildNodeRemovedEvent; if (childNodeRemovedEvent == null) { childNodeRemovedEvent = new ChildNodeRemovedEvent(); } mCachedChildNodeRemovedEvent = null; return childNodeRemovedEvent; } private void releaseChildNodeRemovedEvent(ChildNodeRemovedEvent childNodeRemovedEvent) { childNodeRemovedEvent.parentNodeId = -1; childNodeRemovedEvent.nodeId = -1; if (mCachedChildNodeRemovedEvent == null) { mCachedChildNodeRemovedEvent = childNodeRemovedEvent; } } private final class DocumentUpdateListener implements Document.UpdateListener { public void onAttributeModified(Object element, String name, String value) { AttributeModifiedEvent message = new AttributeModifiedEvent(); message.nodeId = mDocument.getNodeIdForElement(element); message.name = name; message.value = value; mPeerManager.sendNotificationToPeers("DOM.onAttributeModified", message); } public void onAttributeRemoved(Object element, String name) { AttributeRemovedEvent message = new AttributeRemovedEvent(); message.nodeId = mDocument.getNodeIdForElement(element); message.name = name; mPeerManager.sendNotificationToPeers("DOM.attributeRemoved", message); } public void onInspectRequested(Object element) { Integer nodeId = mDocument.getNodeIdForElement(element); if (nodeId == null) { LogUtil.d("DocumentProvider.Listener.onInspectRequested() " + "called for a non-mapped node: element=%s", element); } else { InspectNodeRequestedEvent message = new InspectNodeRequestedEvent(); message.nodeId = nodeId; mPeerManager.sendNotificationToPeers("DOM.inspectNodeRequested", message); } } public void onChildNodeRemoved(int parentNodeId, int nodeId) { ChildNodeRemovedEvent removedEvent = acquireChildNodeRemovedEvent(); removedEvent.parentNodeId = parentNodeId; removedEvent.nodeId = nodeId; mPeerManager.sendNotificationToPeers("DOM.childNodeRemoved", removedEvent); releaseChildNodeRemovedEvent(removedEvent); } public void onChildNodeInserted(DocumentView view, Object element, int parentNodeId, int previousNodeId, Accumulator<Object> insertedElements) { ChildNodeInsertedEvent insertedEvent = acquireChildNodeInsertedEvent(); insertedEvent.parentNodeId = parentNodeId; insertedEvent.previousNodeId = previousNodeId; insertedEvent.node = createNodeForElement(element, view, insertedElements); mPeerManager.sendNotificationToPeers("DOM.childNodeInserted", insertedEvent); releaseChildNodeInsertedEvent(insertedEvent); } } private final class PeerManagerListener extends PeersRegisteredListener { @Override protected synchronized void onFirstPeerRegistered() { mDocument.addRef(); mDocument.addUpdateListener(mListener); } @Override protected synchronized void onLastPeerUnregistered() { mSearchResults.clear(); mDocument.removeUpdateListener(mListener); mDocument.release(); } } 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 SetAttributesAsTextRequest { @JsonProperty(required = true) public int nodeId; @JsonProperty(required = true) public String text; } private static class ResolveNodeResponse implements JsonRpcResult { @JsonProperty(required = true) public Runtime.RemoteObject object; } private static class PerformSearchRequest { @JsonProperty(required = true) public String query; @JsonProperty public Boolean includeUserAgentShadowDOM; } private static class PerformSearchResponse implements JsonRpcResult { @JsonProperty(required = true) public String searchId; @JsonProperty(required = true) public int resultCount; } private static class GetSearchResultsRequest { @JsonProperty(required = true) public String searchId; @JsonProperty(required = true) public int fromIndex; @JsonProperty(required = true) public int toIndex; } private static class GetSearchResultsResponse implements JsonRpcResult { @JsonProperty(required = true) public List<Integer> nodeIds; } private static class DiscardSearchResultsRequest { @JsonProperty(required = true) public String searchId; } private static class GetNodeForLocationRequest { @JsonProperty(required = true) public int x; @JsonProperty(required = true) public int y; } private static class GetNodeForLocationResponse implements JsonRpcResult { @JsonProperty(required = true) public Integer nodeId; } }