Diff
Modified: trunk/LayoutTests/ChangeLog (144746 => 144747)
--- trunk/LayoutTests/ChangeLog 2013-03-05 10:53:38 UTC (rev 144746)
+++ trunk/LayoutTests/ChangeLog 2013-03-05 11:31:28 UTC (rev 144747)
@@ -1,3 +1,15 @@
+2013-03-05 Antoine Quint <[email protected]>
+
+ Web Inspector: allow retrieval of composited layers in a given DOM subtree
+ https://bugs.webkit.org/show_bug.cgi?id=111312
+
+ New test for the LayerTree.layersForNode() method.
+
+ Reviewed by Simon Fraser.
+
+ * inspector-protocol/layers/layers-for-node-expected.txt: Added.
+ * inspector-protocol/layers/layers-for-node.html: Added.
+
2013-03-05 Andrei Bucur <[email protected]>
[CSS Regions] Crash when MathML used in CSS Regions
Added: trunk/LayoutTests/inspector-protocol/layers/layers-for-node-expected.txt (0 => 144747)
--- trunk/LayoutTests/inspector-protocol/layers/layers-for-node-expected.txt (rev 0)
+++ trunk/LayoutTests/inspector-protocol/layers/layers-for-node-expected.txt 2013-03-05 11:31:28 UTC (rev 144747)
@@ -0,0 +1,267 @@
+
+=== Enable the LayerTree agent ===
+
+PASS
+
+=== Get the Document ===
+
+PASS
+
+=== Get the initial layer tree ===
+
+PASS
+
+[
+ {
+ "layerId": "string",
+ "nodeId": "number",
+ "bounds": {
+ "x": 0,
+ "y": 0,
+ "width": "number",
+ "height": "number"
+ },
+ "memory": "number",
+ "compositedBounds": {
+ "x": 0,
+ "y": 0,
+ "width": "number",
+ "height": "number"
+ },
+ "paintCount": "number"
+ },
+ {
+ "layerId": "string",
+ "nodeId": "number",
+ "bounds": {
+ "x": 0,
+ "y": 0,
+ "width": 50,
+ "height": 50
+ },
+ "memory": "number",
+ "compositedBounds": {
+ "x": 0,
+ "y": 0,
+ "width": 50,
+ "height": 50
+ },
+ "paintCount": "number"
+ },
+ {
+ "layerId": "string",
+ "nodeId": "number",
+ "bounds": {
+ "x": 0,
+ "y": 0,
+ "width": 50,
+ "height": 50
+ },
+ "memory": "number",
+ "compositedBounds": {
+ "x": 0,
+ "y": 0,
+ "width": 50,
+ "height": 50
+ },
+ "paintCount": "number"
+ },
+ {
+ "layerId": "string",
+ "nodeId": "number",
+ "bounds": {
+ "x": 0,
+ "y": 0,
+ "width": 100,
+ "height": 100
+ },
+ "memory": "number",
+ "compositedBounds": {
+ "x": 0,
+ "y": 0,
+ "width": 100,
+ "height": 100
+ },
+ "paintCount": "number"
+ },
+ {
+ "layerId": "string",
+ "nodeId": "number",
+ "bounds": {
+ "x": 0,
+ "y": 0,
+ "width": 50,
+ "height": 50
+ },
+ "memory": "number",
+ "compositedBounds": {
+ "x": 0,
+ "y": 0,
+ "width": 50,
+ "height": 50
+ },
+ "paintCount": "number"
+ },
+ {
+ "layerId": "string",
+ "nodeId": "number",
+ "bounds": {
+ "x": 0,
+ "y": 0,
+ "width": "number",
+ "height": "number"
+ },
+ "memory": "number",
+ "compositedBounds": {
+ "x": 0,
+ "y": 0,
+ "width": "number",
+ "height": "number"
+ },
+ "paintCount": "number"
+ }
+]
+
+=== Message the page to add a new composited layer ===
+
+PASS
+
+=== Get the modified layer tree ===
+
+PASS
+
+[
+ {
+ "layerId": "string",
+ "nodeId": "number",
+ "bounds": {
+ "x": 0,
+ "y": 0,
+ "width": "number",
+ "height": "number"
+ },
+ "memory": "number",
+ "compositedBounds": {
+ "x": 0,
+ "y": 0,
+ "width": "number",
+ "height": "number"
+ },
+ "paintCount": "number"
+ },
+ {
+ "layerId": "string",
+ "nodeId": "number",
+ "bounds": {
+ "x": 0,
+ "y": 0,
+ "width": 50,
+ "height": 50
+ },
+ "memory": "number",
+ "compositedBounds": {
+ "x": 0,
+ "y": 0,
+ "width": 50,
+ "height": 50
+ },
+ "paintCount": "number"
+ },
+ {
+ "layerId": "string",
+ "nodeId": "number",
+ "bounds": {
+ "x": 0,
+ "y": 0,
+ "width": 50,
+ "height": 50
+ },
+ "memory": "number",
+ "compositedBounds": {
+ "x": 0,
+ "y": 0,
+ "width": 50,
+ "height": 50
+ },
+ "paintCount": "number"
+ },
+ {
+ "layerId": "string",
+ "nodeId": "number",
+ "bounds": {
+ "x": 0,
+ "y": 0,
+ "width": 100,
+ "height": 100
+ },
+ "memory": "number",
+ "compositedBounds": {
+ "x": 0,
+ "y": 0,
+ "width": 100,
+ "height": 100
+ },
+ "paintCount": "number"
+ },
+ {
+ "layerId": "string",
+ "nodeId": "number",
+ "bounds": {
+ "x": 0,
+ "y": 0,
+ "width": 50,
+ "height": 50
+ },
+ "memory": "number",
+ "compositedBounds": {
+ "x": 0,
+ "y": 0,
+ "width": 50,
+ "height": 50
+ },
+ "paintCount": "number"
+ },
+ {
+ "layerId": "string",
+ "nodeId": "number",
+ "bounds": {
+ "x": 0,
+ "y": 0,
+ "width": "number",
+ "height": "number"
+ },
+ "memory": "number",
+ "compositedBounds": {
+ "x": 0,
+ "y": 0,
+ "width": "number",
+ "height": "number"
+ },
+ "paintCount": "number"
+ },
+ {
+ "layerId": "string",
+ "nodeId": "number",
+ "bounds": {
+ "x": 0,
+ "y": 0,
+ "width": 50,
+ "height": 50
+ },
+ "memory": "number",
+ "compositedBounds": {
+ "x": 0,
+ "y": 0,
+ "width": 50,
+ "height": 50
+ },
+ "paintCount": "number"
+ }
+]
+
+=== Get attributes for the newly inserted node ===
+
+PASS
+
+=== Test complete, all expected conditions met ===
+
Added: trunk/LayoutTests/inspector-protocol/layers/layers-for-node.html (0 => 144747)
--- trunk/LayoutTests/inspector-protocol/layers/layers-for-node.html (rev 0)
+++ trunk/LayoutTests/inspector-protocol/layers/layers-for-node.html 2013-03-05 11:31:28 UTC (rev 144747)
@@ -0,0 +1,236 @@
+<html>
+<head>
+<script type="text/_javascript_" src=""
+<script type="text/_javascript_">
+
+function addCompositedLayer()
+{
+ var element = document.createElement("div");
+ element.className = "composited";
+ element.id = "last-element";
+ document.body.appendChild(element);
+};
+
+function test()
+{
+ var documentNode;
+ var initialLayers;
+ var eventsCount = 0;
+
+ InspectorTest.eventHandler["LayerTree.layerTreeDidChange"] = function (messageObject) {
+ eventsCount++;
+ };
+
+ enableLayerTreeAgent();
+
+ function enableLayerTreeAgent(result)
+ {
+ step({
+ name: "Enable the LayerTree agent",
+ command: "LayerTree.enable",
+ parameters: {},
+ callback: getDocument
+ });
+ };
+
+ function getDocument(result)
+ {
+ step({
+ name: "Get the Document",
+ command: "DOM.getDocument",
+ parameters: {},
+ callback: getInitialLayerTree
+ });
+ };
+
+ function getInitialLayerTree(result)
+ {
+ documentNode = result.root;
+ step({
+ name: "Get the initial layer tree",
+ command: "LayerTree.layersForNode",
+ parameters: {"nodeId": documentNode.nodeId},
+ callback: gotInitialLayerTree
+ });
+ };
+
+ function gotInitialLayerTree(result)
+ {
+ initialLayers = result.layers;
+
+ dumpLayers(initialLayers);
+
+ step({
+ name: "Message the page to add a new composited layer",
+ command: "Runtime.evaluate",
+ parameters: {"_expression_": "addCompositedLayer()"},
+ callback: getModifiedLayerTree
+ });
+ };
+
+ function getModifiedLayerTree(result)
+ {
+ step({
+ name: "Get the modified layer tree",
+ command: "LayerTree.layersForNode",
+ parameters: {"nodeId": documentNode.nodeId},
+ callback: gotModifiedLayerTree
+ });
+ };
+
+ var layerCount = 0;
+
+ function gotModifiedLayerTree(result)
+ {
+ dumpLayers(result.layers);
+
+ var mutations = layerMutations(initialLayers, result.layers);
+ var newLayer = mutations.additions[0];
+
+ step({
+ name: "Get attributes for the newly inserted node",
+ command: "DOM.getAttributes",
+ parameters: {"nodeId": newLayer.nodeId},
+ callback: gotNodeAttributes
+ });
+ };
+
+ function gotNodeAttributes(result)
+ {
+ var attributes = attributesDictionaryFromArray(result.attributes);
+ if (attributes.id !== "last-element")
+ InspectorTest.log("FAIL: Did not obtain the expected element for the last inserted layer.");
+
+ finishTest();
+ };
+
+ function finishTest()
+ {
+ if (!eventsCount)
+ InspectorTest.log("FAIL: Did not receive layerTreeDidChange events.");
+ else
+ InspectorTest.log("\n=== Test complete, all expected conditions met ===");
+
+ InspectorTest.completeTest();
+ };
+
+ function layerMutations(oldLayers, newLayers)
+ {
+ function layerIdMap(layer) {
+ return layer.layerId;
+ }
+
+ var oldLayerIds = oldLayers.map(layerIdMap);
+ var newLayerIds = newLayers.map(layerIdMap);
+
+ return {
+ additions: newLayers.filter(function (layer) {
+ return (oldLayerIds.indexOf(layer.layerId) === -1);
+ }),
+ removals: oldLayers.filter(function (layer) {
+ return (newLayerIds.indexOf(layer.layerId) === -1);
+ })
+ };
+ };
+
+ function attributesDictionaryFromArray(attributes)
+ {
+ var dictionary = {}
+ for (var i = 0, count = attributes.length; i < count; i += 2) {
+ dictionary[attributes[i]] = attributes[i + 1];
+ }
+ return dictionary;
+ };
+
+ function dumpLayers(layers)
+ {
+ function replacer(key, value)
+ {
+ if (key === "layerId" || key === "nodeId" || key === "memory" || key === "paintCount")
+ return typeof(value);
+
+ // some values differ based on port, but the ones we most
+ // care about will always be less or equal 100.
+ if ((key === "width" || key === "height") && value > 100)
+ return typeof(value);
+
+ return value;
+ };
+
+ InspectorTest.log("\n" + JSON.stringify(layers, replacer, " "));
+ };
+
+ function step(test)
+ {
+ InspectorTest.log("\n=== " + test.name + " ===\n")
+ InspectorTest.sendCommand(test.command, test.parameters, function(messageObject) {
+ if (messageObject.hasOwnProperty("error")) {
+ InspectorTest.log("FAIL: " + messageObject.error.message + " (" + messageObject.error.code + ")");
+ InspectorTest.completeTest();
+ return;
+ }
+
+ InspectorTest.log("PASS");
+ test.callback(messageObject.result);
+ });
+ };
+
+ function assert(name, actual, expected)
+ {
+ if (expected === actual)
+ InspectorTest.log("PASS: " + name + ".");
+ else
+ InspectorTest.log("FAIL: " + name + ". Expected " + expected + " but got " + actual);
+ };
+
+};
+
+window.addEventListener("DOMContentLoaded", function () {
+ runTest();
+}, false);
+
+</script>
+<style type="text/css">
+
+ div {
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+
+ .regular {
+ width: 100px;
+ height: 100px;
+ background-color: black;
+ }
+
+ .composited {
+ top: 25px;
+ left: 25px;
+ width: 50px;
+ height: 50px;
+ background-color: blue;
+ -webkit-transform: translateZ(0);
+ }
+
+ .offset {
+ left: 200px;
+ -webkit-transform: translateZ(0);
+ }
+
+</style>
+</head>
+<body>
+
+ <div class="regular"></div>
+
+ <div class="composited">
+ <div class="composited"></div>
+ </div>
+
+ <div class="regular offset">
+ <div class="composited"></div>
+ </div>
+
+</body>
+</html>
Modified: trunk/Source/WebCore/ChangeLog (144746 => 144747)
--- trunk/Source/WebCore/ChangeLog 2013-03-05 10:53:38 UTC (rev 144746)
+++ trunk/Source/WebCore/ChangeLog 2013-03-05 11:31:28 UTC (rev 144747)
@@ -1,3 +1,51 @@
+2013-03-05 Antoine Quint <[email protected]>
+
+ Web Inspector: allow retrieval of composited layers in a given DOM subtree
+ https://bugs.webkit.org/show_bug.cgi?id=111312
+
+ Introduce the LayerTreeAgent.layersForNode(node) method allowing the front-end
+ to gather all composited layers associated with nodes in the subtree of which
+ the provided node is the root.
+
+ In order to gather the layers in the subtree, we first traverse the node's
+ renderer's RenderObject hierarchy and whenever we encounter a RenderObject
+ that has a RenderLayer, we then traverse that renderLayer's RenderLayer
+ hierarchy. This allows for a quick path through the relevant objects we're
+ gathering.
+
+ Layers gathered will push the node to which they're associated, allowing a
+ nodeId for this layer to be listed in the Layer object sent to the front-end.
+ It is crucial to be able to provide a nodeId as well as a layerId for a Layer
+ object in order to be able to correctly assess mutations in the layer tree.
+ For instance, it is expected that a node's layer be replaced by a new layer
+ to represent a slightly different rendering of its content, but the front-end
+ should be able to represent this layer as an object for which only certain
+ attributes have changed (like the "paintCount" property).
+
+ Layer objects also indicate whether they're associated to a node hosted in a
+ shadow tree (the optional "isInShadowTree" property) in order for the front-end
+ to be able to only show this layer if the option to show nodes hosted in shadow
+ tree is enabled.
+
+ Finally, since we're only gathering composited layers, we're removing the
+ "isLayerComposited" property and removing the optional flag on the "paintCount",
+ "memory" and "compositedBounds" properties.
+
+ Reviewed by Simon Fraser.
+
+ Test: inspector-protocol/layers/layers-for-node.html
+
+ * inspector/Inspector.json:
+ * inspector/InspectorLayerTreeAgent.cpp:
+ (WebCore::InspectorLayerTreeAgent::layersForNode):
+ (WebCore):
+ (WebCore::InspectorLayerTreeAgent::gatherLayersUsingRenderObjectHierarchy):
+ (WebCore::InspectorLayerTreeAgent::gatherLayersUsingRenderLayerHierarchy):
+ (WebCore::InspectorLayerTreeAgent::buildObjectForLayer):
+ (WebCore::InspectorLayerTreeAgent::idForNode):
+ * inspector/InspectorLayerTreeAgent.h:
+ (InspectorLayerTreeAgent):
+
2013-03-04 Andrey Kosyakov <[email protected]>
Web Inspector: use timestamps consistent with rest of Timeline in WebSocket events.
Modified: trunk/Source/WebCore/inspector/Inspector.json (144746 => 144747)
--- trunk/Source/WebCore/inspector/Inspector.json 2013-03-05 10:53:38 UTC (rev 144746)
+++ trunk/Source/WebCore/inspector/Inspector.json 2013-03-05 11:31:28 UTC (rev 144747)
@@ -3662,11 +3662,12 @@
"description": "Information about a compositing layer.",
"properties": [
{ "name": "layerId", "$ref": "LayerId", "description": "The unique id for this layer." },
+ { "name": "nodeId", "$ref": "DOM.NodeId", "description": "The id for the node associated with this layer." },
{ "name": "bounds", "$ref": "IntRect", "description": "Bounds of the layer." },
- { "name": "isComposited", "type": "boolean", "optional": true, "description": "Indicates whether this layer is composited." },
- { "name": "paintCount", "type": "integer", "optional": true, "description": "Indicates how many time this layer has painted." },
- { "name": "memory", "type": "integer", "optional": true, "description": "Estimated memory used by this layer." },
- { "name": "compositedBounds", "$ref": "IntRect", "optional": true, "description": "The bounds of the composited layer." }
+ { "name": "paintCount", "type": "integer", "description": "Indicates how many time this layer has painted." },
+ { "name": "memory", "type": "integer", "description": "Estimated memory used by this layer." },
+ { "name": "compositedBounds", "$ref": "IntRect", "description": "The bounds of the composited layer." },
+ { "name": "isInShadowTree", "type": "boolean", "optional": true, "description": "Indicates whether this layer is associated with an element hosted in a shadow tree." }
]
}
],
@@ -3678,6 +3679,15 @@
{
"name": "disable",
"description": "Disables compositing tree inspection."
+ },
+ {
+ "name": "layersForNode",
+ "parameters": [
+ { "name": "nodeId", "$ref": "DOM.NodeId", "description": "Root of the subtree for which we want to gather layers." } ],
+ "description": "Returns the layer tree structure of the current page.",
+ "returns": [
+ { "name": "layers", "type": "array", "items": { "$ref": "Layer" }, "description": "Child layers." }
+ ]
}
],
"events": [
Modified: trunk/Source/WebCore/inspector/InspectorLayerTreeAgent.cpp (144746 => 144747)
--- trunk/Source/WebCore/inspector/InspectorLayerTreeAgent.cpp 2013-03-05 10:53:38 UTC (rev 144746)
+++ trunk/Source/WebCore/inspector/InspectorLayerTreeAgent.cpp 2013-03-05 11:31:28 UTC (rev 144747)
@@ -109,25 +109,76 @@
unbind(renderLayer);
}
-PassRefPtr<TypeBuilder::LayerTree::Layer> InspectorLayerTreeAgent::buildObjectForLayer(RenderLayer* renderLayer)
+void InspectorLayerTreeAgent::layersForNode(ErrorString* errorString, int nodeId, RefPtr<TypeBuilder::Array<TypeBuilder::LayerTree::Layer> >& layers)
{
+ layers = TypeBuilder::Array<TypeBuilder::LayerTree::Layer>::create();
+
+ Node* node = m_instrumentingAgents->inspectorDOMAgent()->nodeForId(nodeId);
+ if (!node) {
+ *errorString = "Provided node id doesn't match any known node";
+ return;
+ }
+
+ RenderObject* renderer = node->renderer();
+ if (!renderer) {
+ *errorString = "Node for provided node id doesn't have a renderer";
+ return;
+ }
+
+ gatherLayersUsingRenderObjectHierarchy(errorString, renderer, layers);
+}
+
+void InspectorLayerTreeAgent::gatherLayersUsingRenderObjectHierarchy(ErrorString* errorString, RenderObject* renderer, RefPtr<TypeBuilder::Array<TypeBuilder::LayerTree::Layer> >& layers)
+{
+ if (renderer->hasLayer()) {
+ gatherLayersUsingRenderLayerHierarchy(errorString, toRenderLayerModelObject(renderer)->layer(), layers);
+ return;
+ }
+
+ for (renderer = renderer->firstChild(); renderer; renderer = renderer->nextSibling())
+ gatherLayersUsingRenderObjectHierarchy(errorString, renderer, layers);
+}
+
+void InspectorLayerTreeAgent::gatherLayersUsingRenderLayerHierarchy(ErrorString* errorString, RenderLayer* renderLayer, RefPtr<TypeBuilder::Array<TypeBuilder::LayerTree::Layer> >& layers)
+{
+ if (renderLayer->isComposited())
+ layers->addItem(buildObjectForLayer(errorString, renderLayer));
+
+ for (renderLayer = renderLayer->firstChild(); renderLayer; renderLayer = renderLayer->nextSibling())
+ gatherLayersUsingRenderLayerHierarchy(errorString, renderLayer, layers);
+}
+
+PassRefPtr<TypeBuilder::LayerTree::Layer> InspectorLayerTreeAgent::buildObjectForLayer(ErrorString* errorString, RenderLayer* renderLayer)
+{
+ Node* node = renderLayer->renderer()->node();
+ RenderLayerBacking* backing = renderLayer->backing();
+
// Basic set of properties.
RefPtr<TypeBuilder::LayerTree::Layer> layerObject = TypeBuilder::LayerTree::Layer::create()
.setLayerId(bind(renderLayer))
- .setBounds(buildObjectForIntRect(enclosingIntRect(renderLayer->localBoundingBox())));
+ .setNodeId(idForNode(errorString, node))
+ .setBounds(buildObjectForIntRect(enclosingIntRect(renderLayer->localBoundingBox())))
+ .setMemory(backing->backingStoreMemoryEstimate())
+ .setCompositedBounds(buildObjectForIntRect(backing->compositedBounds()))
+ .setPaintCount(backing->graphicsLayer()->repaintCount());
- // Optional properties for composited layers only.
- if (renderLayer->isComposited()) {
- RenderLayerBacking* backing = renderLayer->backing();
- layerObject->setMemory(backing->backingStoreMemoryEstimate());
- layerObject->setCompositedBounds(buildObjectForIntRect(backing->compositedBounds()));
- layerObject->setPaintCount(backing->graphicsLayer()->repaintCount());
- layerObject->setIsComposited(true);
- }
+ if (node && node->shadowHost())
+ layerObject->setIsInShadowTree(true);
return layerObject;
}
+int InspectorLayerTreeAgent::idForNode(ErrorString* errorString, Node* node)
+{
+ InspectorDOMAgent* domAgent = m_instrumentingAgents->inspectorDOMAgent();
+
+ int nodeId = domAgent->boundNodeId(node);
+ if (!nodeId)
+ nodeId = domAgent->pushNodeToFrontend(errorString, domAgent->boundNodeId(node->document()), node);
+
+ return nodeId;
+}
+
PassRefPtr<TypeBuilder::LayerTree::IntRect> InspectorLayerTreeAgent::buildObjectForIntRect(const IntRect& rect)
{
return TypeBuilder::LayerTree::IntRect::create()
Modified: trunk/Source/WebCore/inspector/InspectorLayerTreeAgent.h (144746 => 144747)
--- trunk/Source/WebCore/inspector/InspectorLayerTreeAgent.h 2013-03-05 10:53:38 UTC (rev 144746)
+++ trunk/Source/WebCore/inspector/InspectorLayerTreeAgent.h 2013-03-05 11:31:28 UTC (rev 144747)
@@ -66,6 +66,7 @@
// Called from the front-end.
virtual void enable(ErrorString*);
virtual void disable(ErrorString*);
+ virtual void layersForNode(ErrorString*, int nodeId, RefPtr<TypeBuilder::Array<TypeBuilder::LayerTree::Layer> >&);
private:
InspectorLayerTreeAgent(InstrumentingAgents*, InspectorCompositeState*);
@@ -74,8 +75,13 @@
String bind(const RenderLayer*);
void unbind(const RenderLayer*);
- PassRefPtr<TypeBuilder::LayerTree::Layer> buildObjectForLayer(RenderLayer*);
+ void gatherLayersUsingRenderObjectHierarchy(ErrorString*, RenderObject*, RefPtr<TypeBuilder::Array<TypeBuilder::LayerTree::Layer> >&);
+ void gatherLayersUsingRenderLayerHierarchy(ErrorString*, RenderLayer*, RefPtr<TypeBuilder::Array<TypeBuilder::LayerTree::Layer> >&);
+
+ PassRefPtr<TypeBuilder::LayerTree::Layer> buildObjectForLayer(ErrorString*, RenderLayer*);
PassRefPtr<TypeBuilder::LayerTree::IntRect> buildObjectForIntRect(const IntRect&);
+
+ int idForNode(ErrorString*, Node*);
InspectorFrontend::LayerTree* m_frontend;