Commit 1aa38677 authored by achicu@adobe.com's avatar achicu@adobe.com

Web Inspector: DOM.performSearch should accept a list of context nodes

https://bugs.webkit.org/show_bug.cgi?id=124390

Reviewed by Timothy Hatcher.

Source/WebCore:

Extracted the code in InspectorDOMAgent::performSearch into its own helper class
called InspectorNodeFinder. Also added a new array parameter called "nodeIds"
that can be used to limit the search results to just partial subtrees.

Tests: inspector-protocol/dom/dom-search-crash.html
       inspector-protocol/dom/dom-search-with-context.html
       inspector-protocol/dom/dom-search.html

* CMakeLists.txt:
* GNUmakefile.list.am:
* WebCore.vcxproj/WebCore.vcxproj:
* WebCore.vcxproj/WebCore.vcxproj.filters:
* WebCore.xcodeproj/project.pbxproj:
* inspector/protocol/DOM.json:
* inspector/InspectorAllInOne.cpp:
* inspector/InspectorDOMAgent.cpp:
(WebCore::InspectorDOMAgent::performSearch):
* inspector/InspectorDOMAgent.h:
* inspector/InspectorNodeFinder.cpp: Added.
(WebCore::stripCharacters):
(WebCore::InspectorNodeFinder::InspectorNodeFinder):
(WebCore::InspectorNodeFinder::performSearch):
(WebCore::InspectorNodeFinder::searchUsingDOMTreeTraversal):
(WebCore::InspectorNodeFinder::matchesAttribute):
(WebCore::InspectorNodeFinder::matchesElement):
(WebCore::InspectorNodeFinder::searchUsingXPath):
(WebCore::InspectorNodeFinder::searchUsingCSSSelectors):
* inspector/InspectorNodeFinder.h: Added.
(WebCore::InspectorNodeFinder::results):

LayoutTests:

Added new inspector-protocol tests to check for the DOM.performSearch implementation.
Also, ported an existing test from the old "inspector" format.

* http/tests/inspector-protocol/resources/InspectorDOMListener.js: Added boilerplate code that
can be used to track node ids and class names.
(createDOMListener.createNodeAttributesMap):
(createDOMListener.collectNode):
(createDOMListener.onSetChildNodes):
(createDOMListener.onChildNodeRemoved):
(createDOMListener.onChildNodeInserted):
(createDOMListener.return.getNodeById):
(createDOMListener.return.getNodeIdentifier):
* http/tests/inspector-protocol/resources/InspectorTest.js:
(InspectorFrontendAPI.dispatchMessageAsync): Added a way to catch all the messages received in the inspector.
It is useful for debugging the test file.
(InspectorTest.addEventListener): Helper method to register multiple handlers for the same event.
* inspector-protocol/dom/dom-search-crash-expected.txt: Added.
* inspector-protocol/dom/dom-search-crash.html: Added.
* inspector-protocol/dom/dom-search-expected.txt: Added.
* inspector-protocol/dom/dom-search-with-context-expected.txt: Added.
* inspector-protocol/dom/dom-search-with-context.html: Added.
* inspector-protocol/dom/dom-search.html: Added.
* inspector-protocol/dom/resources/dom-search-crash-iframe.html: Cloned from inspector/dom/resources/dom-search-crash-iframe.html
* inspector-protocol/dom/resources/dom-search-iframe.html: Added.
* inspector-protocol/dom/resources/dom-search-queries.js: Added.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@159357 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 7e740e9f
2013-11-15 Alexandru Chiculita <achicu@adobe.com>
Web Inspector: DOM.performSearch should accept a list of context nodes
https://bugs.webkit.org/show_bug.cgi?id=124390
Reviewed by Timothy Hatcher.
Added new inspector-protocol tests to check for the DOM.performSearch implementation.
Also, ported an existing test from the old "inspector" format.
* http/tests/inspector-protocol/resources/InspectorDOMListener.js: Added boilerplate code that
can be used to track node ids and class names.
(createDOMListener.createNodeAttributesMap):
(createDOMListener.collectNode):
(createDOMListener.onSetChildNodes):
(createDOMListener.onChildNodeRemoved):
(createDOMListener.onChildNodeInserted):
(createDOMListener.return.getNodeById):
(createDOMListener.return.getNodeIdentifier):
* http/tests/inspector-protocol/resources/InspectorTest.js:
(InspectorFrontendAPI.dispatchMessageAsync): Added a way to catch all the messages received in the inspector.
It is useful for debugging the test file.
(InspectorTest.addEventListener): Helper method to register multiple handlers for the same event.
* inspector-protocol/dom/dom-search-crash-expected.txt: Added.
* inspector-protocol/dom/dom-search-crash.html: Added.
* inspector-protocol/dom/dom-search-expected.txt: Added.
* inspector-protocol/dom/dom-search-with-context-expected.txt: Added.
* inspector-protocol/dom/dom-search-with-context.html: Added.
* inspector-protocol/dom/dom-search.html: Added.
* inspector-protocol/dom/resources/dom-search-crash-iframe.html: Cloned from inspector/dom/resources/dom-search-crash-iframe.html
* inspector-protocol/dom/resources/dom-search-iframe.html: Added.
* inspector-protocol/dom/resources/dom-search-queries.js: Added.
2013-11-15 Tim Horton <timothy_horton@apple.com>
Make lint-test-expectations pass for platform/win
......
/*
* Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer.
* 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
function createDOMListener()
{
var nodesById = {};
InspectorTest.addEventListener("DOM.setChildNodes", onSetChildNodes);
InspectorTest.addEventListener("DOM.childNodeRemoved", onChildNodeRemoved);
InspectorTest.addEventListener("DOM.childNodeInserted", onChildNodeInserted);
function createNodeAttributesMap(attributes)
{
var attributesMap = {};
for (var i = 0; i < attributes.length; i += 2)
attributesMap[attributes[i]] = attributes[i + 1];
return attributesMap;
}
function collectNode(node)
{
if (node.nodeType === 1)
node.attributes = createNodeAttributesMap(node.attributes);
if (node.children)
node.children.forEach(collectNode);
nodesById[node.nodeId] = node;
}
function nodeToString(node)
{
switch (node.nodeType) {
case 1:
var name = node.localName;
if (node.attributes.id)
name += "#" + node.attributes.id;
if (node.attributes["class"])
name += node.attributes["class"].split(" ").map(function(className) { return "." + className; }).join("");
return name;
case 3:
return "<text node " + JSON.stringify(node.nodeValue) + ">";
default:
return "<nodeType " + node.nodeType + ">";
}
}
function onSetChildNodes(response)
{
response.params.nodes.forEach(collectNode);
}
function onChildNodeRemoved(response)
{
delete nodesById[response.params.nodeId];
}
function onChildNodeInserted(response)
{
collectNode(response.params.node);
}
return {
getNodeById: function(id)
{
return nodesById[id];
},
getNodeIdentifier: function(nodeId)
{
if (!nodeId)
return "<invalid node id>";
var node = nodesById[nodeId];
return node ? nodeToString(node) : "<unknown node>";
},
collectNode: collectNode
};
}
......@@ -62,9 +62,37 @@ InspectorFrontendAPI.dispatchMessageAsync = function(messageObject)
var eventHandler = InspectorTest.eventHandler[eventName];
if (eventHandler)
eventHandler(messageObject);
else if (InspectorTest.defaultEventHandler)
InspectorTest.defaultEventHandler(messageObject);
}
}
/**
* Registers an event handler for messages coming from the InspectorBackend.
* If multiple callbacks are registered for the same event, it will chain the execution.
* @param {string} event name
* @param {function} handler to be executed
* @param {boolean} execute the handler before all other handlers
*/
InspectorTest.addEventListener = function(eventName, callback, capture)
{
if (!InspectorTest.eventHandler[eventName]) {
InspectorTest.eventHandler[eventName] = callback;
return;
}
var firstHandler = InspectorTest.eventHandler[eventName];
var secondHandler = callback;
if (capture) {
// Swap firstHandler with the new callback, so that we execute the callback first.
[firstHandler, secondHandler] = [secondHandler, firstHandler];
}
InspectorTest.eventHandler[eventName] = function(messageObject)
{
firstHandler(messageObject);
secondHandler(messageObject);
};
}
/**
* Logs message to document.
* @param {string} message
......@@ -163,7 +191,7 @@ InspectorTest.importInspectorScripts = function()
InspectorTest.importScript("../../../../../Source/WebInspectorUI/UserInterface/" + inspectorScripts[i] + ".js");
// The initialization should be in sync with WebInspector.loaded in Main.js.
// FIXME: As soon as we can support all the observers and managers we should remove UI related tasks
// FIXME: As soon as we can support all the observers and managers we should remove UI related tasks
// from WebInspector.loaded, so that it can be used from the LayoutTests.
InspectorBackend.registerPageDispatcher(new WebInspector.PageObserver);
......
Tests that elements panel search is not crashing on documentElement-less cases.
PASS: Test passes if it doesn't crash.
<html>
<head>
<script type="text/javascript" src="../../http/tests/inspector-protocol/resources/protocol-test.js"></script>
<script>
function test()
{
InspectorTest.sendCommand("DOM.getDocument", {}, onGotDocument);
function onGotDocument(message) {
InspectorTest.checkForError(message);
InspectorTest.sendCommand("DOM.performSearch", {query: "FooBar"}, onSearchCompleted);
}
function onSearchCompleted(message)
{
InspectorTest.checkForError(message);
InspectorTest.log("PASS: Test passes if it doesn't crash.");
InspectorTest.completeTest();
}
}
</script>
</head>
<body>
<p>Tests that elements panel search is not crashing on documentElement-less cases.</p>
<iframe src="resources/dom-search-crash-iframe.html" onload="runTest()"></iframe>
</body>
</html>
Testing DOM.performSearch with no parent node ids.
=== Query: "body" ===
Count: 2
body.main-frame
body.inside-iframe
=== Query: "<body" ===
Count: 2
body.main-frame
body.inside-iframe
=== Query: "body>" ===
Count: 2
body.main-frame
body.inside-iframe
=== Query: "<body>" ===
Count: 2
body.main-frame
body.inside-iframe
=== Query: "onload" ===
Count: 1
body.main-frame
=== Query: "runTest()" ===
Count: 1
body.main-frame
=== Query: "\"runTest()" ===
Count: 1
body.main-frame
=== Query: "\"runTest()\"" ===
Count: 1
body.main-frame
=== Query: "runTest()\"" ===
Count: 1
body.main-frame
=== Query: ".body-inside-iframe" ===
Count: 0
=== Query: "*" ===
Count: 12
html
head
script
script
body.main-frame
p
iframe
html.inside-iframe
head.inside-iframe
body.inside-iframe
div.base1.inside-iframe
p.inside-iframe
=== Query: "/html/body" ===
Count: 2
body.main-frame
body.inside-iframe
=== Query: "/html/body/@onload" ===
Count: 1
body.main-frame
Testing DOM.performSearch with parent node ids.
=== Query: "p" in [] ===
Count: 0
=== Query: "p" in [.base1] ===
Count: 1
p.base1.main-frame
=== Query: "p" in [.base2] ===
Count: 1
p.base2.main-frame
=== Query: "p" in [.base1, .base2] ===
Count: 2
p.base1.main-frame
p.base2.main-frame
=== Query: "p" in [iframe] ===
Count: 1
p.inside-iframe
=== Query: "//p" in [.base1] ===
Count: 1
p.base1.main-frame
=== Query: "//div" in [.base1] ===
Count: 1
div.base1
<html>
<head>
<script type="text/javascript" src="../../http/tests/inspector-protocol/resources/protocol-test.js"></script>
<script>
function test()
{
// Create a DOM listener to convert nodeIds to tag names.
InspectorTest.importScript("InspectorDOMListener.js");
var dom = createDOMListener();
// Caching the output to avoid searching through the log.
var output = [];
var documentId;
var domSearchQueries = [
{
query: "p",
nodes: []
},
{
query: "p",
nodes: [".base1"]
},
{
query: "p",
nodes: [".base2"]
},
{
query: "p",
nodes: [".base1", ".base2"]
},
{
query: "p",
nodes: ["iframe"]
},
// XPath should return just the children of the selected nodes.
{
query: "//p",
nodes: [".base1"]
},
{
query: "//div",
nodes: [".base1"]
}
];
InspectorTest.sendCommand("DOM.getDocument", {}, onGotDocument);
function onGotDocument(message) {
InspectorTest.checkForError(message);
dom.collectNode(message.result.root);
documentId = message.result.root.nodeId;
performSearches(domSearchQueries, testFinished);
}
function performSearches(list, callback)
{
function next() {
if (list.length)
search(list.shift(), next);
else
callback();
}
next();
}
function search(queryData, callback)
{
resolveSelectors(queryData.nodes, function(nodeIds) {
output.push("=== Query: " + JSON.stringify(queryData.query) + " in [" + queryData.nodes.join(", ") + "] ===");
InspectorTest.sendCommand("DOM.performSearch", {query: queryData.query, nodeIds: nodeIds}, function(message) {
InspectorTest.checkForError(message);
printSearchResults(message.result, callback);
});
});
}
function resolveSelectors(nodes, callback)
{
var results = new Array(nodes.length);
var remaining = nodes.length;
if (!remaining)
return callback(results);
nodes.forEach(function(selector, index) {
InspectorTest.sendCommand("DOM.querySelector", {nodeId: documentId, selector: selector}, function(message) {
InspectorTest.checkForError(message);
results[index] = message.result.nodeId;
if (--remaining <= 0)
callback(results);
});
});
}
function printSearchResults(results, callback)
{
output.push("Count: " + results.resultCount);
if (!results.resultCount)
return callback();
var options = {"searchId": results.searchId, "fromIndex": 0, "toIndex": results.resultCount};
InspectorTest.sendCommand("DOM.getSearchResults", options, function(message) {
for (var nodeId of message.result.nodeIds)
output.push(dom.getNodeIdentifier(nodeId));
callback();
});
}
function testFinished()
{
InspectorTest.log(output.join("\n"));
InspectorTest.completeTest();
}
}
</script>
</head>
<body onload="runTest()" class="main-frame">
<p>Testing DOM.performSearch with parent node ids.</p>
<div class="base1">
<p class="base1 main-frame"></p>
</div>
<div class="base2">
<p class="base2 main-frame"></p>
</div>
<iframe src="resources/dom-search-iframe.html"></iframe>
</body>
</html>
<html>
<head>
<script type="text/javascript" src="../../http/tests/inspector-protocol/resources/protocol-test.js"></script>
<script>
function test()
{
// Loading the queries from external file to avoid having them show up in the results.
InspectorTest.importScript("../../../../inspector-protocol/dom/resources/dom-search-queries.js");
// Create a DOM listener to convert nodeIds to tag names.
InspectorTest.importScript("InspectorDOMListener.js");
var dom = createDOMListener();
// Caching the output to avoid searching through the log.
var output = [];
InspectorTest.sendCommand("DOM.getDocument", {}, onGotDocument);
function onGotDocument(message) {
InspectorTest.checkForError(message);
dom.collectNode(message.result.root);
performSearches(domSearchQueries, testFinished);
}
function performSearches(list, callback)
{
function next() {
if (list.length)
search(list.shift(), next);
else
callback();
}
next();
}
function search(query, callback)
{
output.push("=== Query: " + JSON.stringify(query) + " ===");
InspectorTest.sendCommand("DOM.performSearch", {query: query}, function(message) {
InspectorTest.checkForError(message);
printSearchResults(message.result, callback);
});
}
function printSearchResults(results, callback)
{
output.push("Count: " + results.resultCount);
if (!results.resultCount)
return callback();
var options = {"searchId": results.searchId, "fromIndex": 0, "toIndex": results.resultCount};
InspectorTest.sendCommand("DOM.getSearchResults", options, function onResultsReceived(message) {
for (var nodeId of message.result.nodeIds)
output.push(dom.getNodeIdentifier(nodeId));
callback();
});
}
function testFinished()
{
InspectorTest.log(output.join("\n"));
InspectorTest.completeTest();
}
}
</script>
</head>
<body onload="runTest()" class="main-frame">
<p>Testing DOM.performSearch with no parent node ids.</p>
<iframe src="resources/dom-search-iframe.html"></iframe>
</body>
</html>
<script>
document.documentElement.parentNode.removeChild(document.documentElement);
</script>
<html class="inside-iframe">
<head class="inside-iframe">
</head>
<body class="inside-iframe">
<div class="base1 inside-iframe">
<p class="inside-iframe"></p>
</div>
</body>
</html>
\ No newline at end of file
// Having the queries in an external file, so that DOM search will not find the script when searching for values.
var domSearchQueries = [
"body",
"<body",
"body>",
"<body>",
// Attribute names
"onload",
// Attribute values
"runTest()",
"\"runTest()",
"\"runTest()\"",
"runTest()\"",
// CSS selectors
".body-inside-iframe",
"*",
// XPath query
"/html/body",
"/html/body/@onload",
];
\ No newline at end of file
......@@ -1594,6 +1594,7 @@ set(WebCore_SOURCES
inspector/InspectorInstrumentation.cpp
inspector/InspectorLayerTreeAgent.cpp
inspector/InspectorMemoryAgent.cpp
inspector/InspectorNodeFinder.cpp
inspector/InspectorOverlay.cpp
inspector/InspectorPageAgent.cpp
inspector/InspectorProfilerAgent.cpp
......
2013-11-15 Alexandru Chiculita <achicu@adobe.com>
Web Inspector: DOM.performSearch should accept a list of context nodes
https://bugs.webkit.org/show_bug.cgi?id=124390
Reviewed by Timothy Hatcher.
Extracted the code in InspectorDOMAgent::performSearch into its own helper class
called InspectorNodeFinder. Also added a new array parameter called "nodeIds"
that can be used to limit the search results to just partial subtrees.
Tests: inspector-protocol/dom/dom-search-crash.html
inspector-protocol/dom/dom-search-with-context.html
inspector-protocol/dom/dom-search.html
* CMakeLists.txt:
* GNUmakefile.list.am:
* WebCore.vcxproj/WebCore.vcxproj:
* WebCore.vcxproj/WebCore.vcxproj.filters:
* WebCore.xcodeproj/project.pbxproj:
* inspector/protocol/DOM.json:
* inspector/InspectorAllInOne.cpp:
* inspector/InspectorDOMAgent.cpp:
(WebCore::InspectorDOMAgent::performSearch):
* inspector/InspectorDOMAgent.h:
* inspector/InspectorNodeFinder.cpp: Added.
(WebCore::stripCharacters):
(WebCore::InspectorNodeFinder::InspectorNodeFinder):
(WebCore::InspectorNodeFinder::performSearch):
(WebCore::InspectorNodeFinder::searchUsingDOMTreeTraversal):
(WebCore::InspectorNodeFinder::matchesAttribute):
(WebCore::InspectorNodeFinder::matchesElement):
(WebCore::InspectorNodeFinder::searchUsingXPath):
(WebCore::InspectorNodeFinder::searchUsingCSSSelectors):
* inspector/InspectorNodeFinder.h: Added.
(WebCore::InspectorNodeFinder::results):
2013-11-15 Brady Eidson <beidson@apple.com>
Remove IDBBackingStoreInterface.h includes that are no longer needed
......
......@@ -3822,6 +3822,8 @@ webcore_sources += \
Source/WebCore/inspector/InspectorLayerTreeAgent.h \
Source/WebCore/inspector/InspectorMemoryAgent.cpp \
Source/WebCore/inspector/InspectorMemoryAgent.h \
Source/WebCore/inspector/InspectorNodeFinder.cpp \
Source/WebCore/inspector/InspectorNodeFinder.h \
Source/WebCore/inspector/InspectorOverlay.cpp \
Source/WebCore/inspector/InspectorOverlay.h \
Source/WebCore/inspector/InspectorPageAgent.cpp \
......
......@@ -17802,6 +17802,14 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|Win32'">true</ExcludedFromBuild>