summaryrefslogtreecommitdiffstats
path: root/WebCore/inspector/front-end/InjectedScript.js
diff options
context:
space:
mode:
Diffstat (limited to 'WebCore/inspector/front-end/InjectedScript.js')
-rw-r--r--WebCore/inspector/front-end/InjectedScript.js464
1 files changed, 422 insertions, 42 deletions
diff --git a/WebCore/inspector/front-end/InjectedScript.js b/WebCore/inspector/front-end/InjectedScript.js
index 4611b48..4144826 100644
--- a/WebCore/inspector/front-end/InjectedScript.js
+++ b/WebCore/inspector/front-end/InjectedScript.js
@@ -27,12 +27,20 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-var InjectedScript = {
- _styles: {},
- _styleRules: {},
- _lastStyleId: 0,
- _lastStyleRuleId: 0
-};
+var InjectedScript = {};
+
+// Called from within InspectorController on the 'inspected page' side.
+InjectedScript.reset = function()
+{
+ InjectedScript._styles = {};
+ InjectedScript._styleRules = {};
+ InjectedScript._lastStyleId = 0;
+ InjectedScript._lastStyleRuleId = 0;
+ InjectedScript._searchResults = [];
+ InjectedScript._includedInSearchResultsPropertyName = "__includedInInspectorSearchResults";
+}
+
+InjectedScript.reset();
InjectedScript.getStyles = function(nodeId, authorOnly)
{
@@ -174,12 +182,14 @@ InjectedScript.toggleStyleEnabled = function(styleId, propertyName, disabled)
return InjectedScript._serializeStyle(style, true);
}
-InjectedScript.applyStyleRuleText = function(ruleId, newContent, selectedNode)
+InjectedScript.applyStyleRuleText = function(ruleId, newContent, selectedNodeId)
{
var rule = InjectedScript._styleRules[ruleId];
if (!rule)
return false;
+ var selectedNode = InjectedScript._nodeForId(selectedNodeId);
+
try {
var stylesheet = rule.parentStyleSheet;
stylesheet.addRule(newContent);
@@ -194,20 +204,14 @@ InjectedScript.applyStyleRuleText = function(ruleId, newContent, selectedNode)
}
}
- var nodes = selectedNode.ownerDocument.querySelectorAll(newContent);
- for (var i = 0; i < nodes.length; ++i) {
- if (nodes[i] === selectedNode) {
- return [InjectedScript._serializeRule(newRule), true];
- }
- }
- return [InjectedScript._serializeRule(newRule), false];
+ return [InjectedScript._serializeRule(newRule), InjectedScript._doesSelectorAffectNode(newContent, selectedNode)];
} catch(e) {
// Report invalid syntax.
return false;
}
}
-InjectedScript.addStyleSelector = function(newContent)
+InjectedScript.addStyleSelector = function(newContent, selectedNodeId)
{
var stylesheet = InjectedScript.stylesheet;
if (!stylesheet) {
@@ -227,10 +231,28 @@ InjectedScript.addStyleSelector = function(newContent)
return false;
}
- return InjectedScript._serializeRule(stylesheet.cssRules[stylesheet.cssRules.length - 1]);
+ var selectedNode = InjectedScript._nodeForId(selectedNodeId);
+ var rule = stylesheet.cssRules[stylesheet.cssRules.length - 1];
+ rule.__isViaInspector = true;
+
+ return [ InjectedScript._serializeRule(rule), InjectedScript._doesSelectorAffectNode(newContent, selectedNode) ];
+}
+
+InjectedScript._doesSelectorAffectNode = function(selectorText, node)
+{
+ if (!node)
+ return false;
+ var nodes = node.ownerDocument.querySelectorAll(selectorText);
+ for (var i = 0; i < nodes.length; ++i) {
+ if (nodes[i] === node) {
+ return true;
+ }
+ }
+ return false;
}
-InjectedScript.setStyleProperty = function(styleId, name, value) {
+InjectedScript.setStyleProperty = function(styleId, name, value)
+{
var style = InjectedScript._styles[styleId];
if (!style)
return false;
@@ -251,17 +273,18 @@ InjectedScript._serializeRule = function(rule)
}
ruleValue.isUserAgent = parentStyleSheet && !parentStyleSheet.ownerNode && !parentStyleSheet.href;
ruleValue.isUser = parentStyleSheet && parentStyleSheet.ownerNode && parentStyleSheet.ownerNode.nodeName == "#document";
+ ruleValue.isViaInspector = !!rule.__isViaInspector;
// Bind editable scripts only.
var doBind = !ruleValue.isUserAgent && !ruleValue.isUser;
ruleValue.style = InjectedScript._serializeStyle(rule.style, doBind);
if (doBind) {
- if (!rule._id) {
- rule._id = InjectedScript._lastStyleRuleId++;
- InjectedScript._styleRules[rule._id] = rule;
+ if (!rule.id) {
+ rule.id = InjectedScript._lastStyleRuleId++;
+ InjectedScript._styleRules[rule.id] = rule;
}
- ruleValue.id = rule._id;
+ ruleValue.id = rule.id;
}
return ruleValue;
}
@@ -295,11 +318,11 @@ InjectedScript._serializeStyle = function(style, doBind)
result.uniqueStyleProperties = InjectedScript._getUniqueStyleProperties(style);
if (doBind) {
- if (!style._id) {
- style._id = InjectedScript._lastStyleId++;
- InjectedScript._styles[style._id] = style;
+ if (!style.id) {
+ style.id = InjectedScript._lastStyleId++;
+ InjectedScript._styles[style.id] = style;
}
- result.id = style._id;
+ result.id = style.id;
}
return result;
}
@@ -405,6 +428,7 @@ InjectedScript.getProperties = function(objectProxy, ignoreHasOwnProperty)
return false;
var properties = [];
+
// Go over properties, prepare results.
for (var propertyName in object) {
if (!ignoreHasOwnProperty && "hasOwnProperty" in object && !object.hasOwnProperty(propertyName))
@@ -415,29 +439,31 @@ InjectedScript.getProperties = function(objectProxy, ignoreHasOwnProperty)
continue;
var property = {};
property.name = propertyName;
+ property.parentObjectProxy = objectProxy;
var isGetter = object["__lookupGetter__"] && object.__lookupGetter__(propertyName);
if (!property.isGetter) {
var childObject = object[propertyName];
- property.type = typeof childObject;
- property.textContent = Object.describe(childObject, true);
- property.parentObjectProxy = objectProxy;
- var parentPath = objectProxy.path.slice();
- property.childObjectProxy = {
- objectId : objectProxy.objectId,
- path : parentPath.splice(parentPath.length, 0, propertyName),
- protoDepth : objectProxy.protoDepth
- };
- if (childObject && (property.type === "object" || property.type === "function")) {
+ var childObjectProxy = {};
+ childObjectProxy.objectId = objectProxy.objectId;
+ childObjectProxy.path = objectProxy.path ? objectProxy.path.slice() : [];
+ childObjectProxy.path.push(propertyName);
+
+ childObjectProxy.protoDepth = objectProxy.protoDepth || 0;
+ childObjectProxy.description = Object.describe(childObject, true);
+ property.value = childObjectProxy;
+
+ var type = typeof childObject;
+ if (type === "object" || type === "function") {
for (var subPropertyName in childObject) {
if (propertyName === "__treeElementIdentifier")
continue;
- property.hasChildren = true;
+ childObjectProxy.hasChildren = true;
break;
}
}
} else {
// FIXME: this should show something like "getter" (bug 16734).
- property.textContent = "\u2014"; // em dash
+ property.value = { description: "\u2014" }; // em dash
property.isGetter = true;
}
properties.push(property);
@@ -479,6 +505,338 @@ InjectedScript.setPropertyValue = function(objectProxy, propertyName, expression
}
}
+InjectedScript.evaluate = function(expression)
+{
+ InjectedScript._ensureCommandLineAPIInstalled();
+ // Surround the expression in with statements to inject our command line API so that
+ // the window object properties still take more precedent than our API functions.
+ expression = "with (window._inspectorCommandLineAPI) { with (window) { " + expression + " } }";
+
+ var result = {};
+ try {
+ var value = InjectedScript._window().eval(expression);
+ var wrapper = InspectorController.wrapObject(value);
+ if (typeof wrapper === "object" && wrapper.exception)
+ result.exception = wrapper.exception;
+ else
+ result.value = wrapper;
+ } catch (e) {
+ result.exception = e.toString();
+ }
+ return result;
+}
+
+InjectedScript.addInspectedNode = function(nodeId)
+{
+ var node = InjectedScript._nodeForId(nodeId);
+ if (!node)
+ return false;
+
+ InjectedScript._ensureCommandLineAPIInstalled();
+ var inspectedNodes = InjectedScript._window()._inspectorCommandLineAPI._inspectedNodes;
+ inspectedNodes.unshift(node);
+ if (inspectedNodes.length >= 5)
+ inspectedNodes.pop();
+ return true;
+}
+
+InjectedScript.performSearch = function(whitespaceTrimmedQuery, searchResultsProperty)
+{
+ var tagNameQuery = whitespaceTrimmedQuery;
+ var attributeNameQuery = whitespaceTrimmedQuery;
+ var startTagFound = (tagNameQuery.indexOf("<") === 0);
+ var endTagFound = (tagNameQuery.lastIndexOf(">") === (tagNameQuery.length - 1));
+
+ if (startTagFound || endTagFound) {
+ var tagNameQueryLength = tagNameQuery.length;
+ tagNameQuery = tagNameQuery.substring((startTagFound ? 1 : 0), (endTagFound ? (tagNameQueryLength - 1) : tagNameQueryLength));
+ }
+
+ // Check the tagNameQuery is it is a possibly valid tag name.
+ if (!/^[a-zA-Z0-9\-_:]+$/.test(tagNameQuery))
+ tagNameQuery = null;
+
+ // Check the attributeNameQuery is it is a possibly valid tag name.
+ if (!/^[a-zA-Z0-9\-_:]+$/.test(attributeNameQuery))
+ attributeNameQuery = null;
+
+ const escapedQuery = whitespaceTrimmedQuery.escapeCharacters("'");
+ const escapedTagNameQuery = (tagNameQuery ? tagNameQuery.escapeCharacters("'") : null);
+ const escapedWhitespaceTrimmedQuery = whitespaceTrimmedQuery.escapeCharacters("'");
+ const searchResultsProperty = InjectedScript._includedInSearchResultsPropertyName;
+
+ function addNodesToResults(nodes, length, getItem)
+ {
+ if (!length)
+ return;
+
+ var nodeIds = [];
+ for (var i = 0; i < length; ++i) {
+ var node = getItem.call(nodes, i);
+ // Skip this node if it already has the property.
+ if (searchResultsProperty in node)
+ continue;
+
+ if (!InjectedScript._searchResults.length) {
+ InjectedScript._currentSearchResultIndex = 0;
+ }
+
+ node[searchResultsProperty] = true;
+ InjectedScript._searchResults.push(node);
+ var nodeId = InspectorController.pushNodePathToFrontend(node, false);
+ nodeIds.push(nodeId);
+ }
+ InspectorController.addNodesToSearchResult(nodeIds.join(","));
+ }
+
+ function matchExactItems(doc)
+ {
+ matchExactId.call(this, doc);
+ matchExactClassNames.call(this, doc);
+ matchExactTagNames.call(this, doc);
+ matchExactAttributeNames.call(this, doc);
+ }
+
+ function matchExactId(doc)
+ {
+ const result = doc.__proto__.getElementById.call(doc, whitespaceTrimmedQuery);
+ addNodesToResults.call(this, result, (result ? 1 : 0), function() { return this });
+ }
+
+ function matchExactClassNames(doc)
+ {
+ const result = doc.__proto__.getElementsByClassName.call(doc, whitespaceTrimmedQuery);
+ addNodesToResults.call(this, result, result.length, result.item);
+ }
+
+ function matchExactTagNames(doc)
+ {
+ if (!tagNameQuery)
+ return;
+ const result = doc.__proto__.getElementsByTagName.call(doc, tagNameQuery);
+ addNodesToResults.call(this, result, result.length, result.item);
+ }
+
+ function matchExactAttributeNames(doc)
+ {
+ if (!attributeNameQuery)
+ return;
+ const result = doc.__proto__.querySelectorAll.call(doc, "[" + attributeNameQuery + "]");
+ addNodesToResults.call(this, result, result.length, result.item);
+ }
+
+ function matchPartialTagNames(doc)
+ {
+ if (!tagNameQuery)
+ return;
+ const result = doc.__proto__.evaluate.call(doc, "//*[contains(name(), '" + escapedTagNameQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
+ addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
+ }
+
+ function matchStartOfTagNames(doc)
+ {
+ if (!tagNameQuery)
+ return;
+ const result = doc.__proto__.evaluate.call(doc, "//*[starts-with(name(), '" + escapedTagNameQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
+ addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
+ }
+
+ function matchPartialTagNamesAndAttributeValues(doc)
+ {
+ if (!tagNameQuery) {
+ matchPartialAttributeValues.call(this, doc);
+ return;
+ }
+
+ const result = doc.__proto__.evaluate.call(doc, "//*[contains(name(), '" + escapedTagNameQuery + "') or contains(@*, '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
+ addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
+ }
+
+ function matchPartialAttributeValues(doc)
+ {
+ const result = doc.__proto__.evaluate.call(doc, "//*[contains(@*, '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
+ addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
+ }
+
+ function matchStyleSelector(doc)
+ {
+ const result = doc.__proto__.querySelectorAll.call(doc, whitespaceTrimmedQuery);
+ addNodesToResults.call(this, result, result.length, result.item);
+ }
+
+ function matchPlainText(doc)
+ {
+ const result = doc.__proto__.evaluate.call(doc, "//text()[contains(., '" + escapedQuery + "')] | //comment()[contains(., '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
+ addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
+ }
+
+ function matchXPathQuery(doc)
+ {
+ const result = doc.__proto__.evaluate.call(doc, whitespaceTrimmedQuery, doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
+ addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
+ }
+
+ function finishedSearching()
+ {
+ // Remove the searchResultsProperty now that the search is finished.
+ for (var i = 0; i < InjectedScript._searchResults.length; ++i)
+ delete InjectedScript._searchResults[i][searchResultsProperty];
+ }
+
+ const mainFrameDocument = InjectedScript._window().document;
+ const searchDocuments = [mainFrameDocument];
+ var searchFunctions;
+ if (tagNameQuery && startTagFound && endTagFound)
+ searchFunctions = [matchExactTagNames, matchPlainText];
+ else if (tagNameQuery && startTagFound)
+ searchFunctions = [matchStartOfTagNames, matchPlainText];
+ else if (tagNameQuery && endTagFound) {
+ // FIXME: we should have a matchEndOfTagNames search function if endTagFound is true but not startTagFound.
+ // This requires ends-with() support in XPath, WebKit only supports starts-with() and contains().
+ searchFunctions = [matchPartialTagNames, matchPlainText];
+ } else if (whitespaceTrimmedQuery === "//*" || whitespaceTrimmedQuery === "*") {
+ // These queries will match every node. Matching everything isn't useful and can be slow for large pages,
+ // so limit the search functions list to plain text and attribute matching.
+ searchFunctions = [matchPartialAttributeValues, matchPlainText];
+ } else
+ searchFunctions = [matchExactItems, matchStyleSelector, matchPartialTagNamesAndAttributeValues, matchPlainText, matchXPathQuery];
+
+ // Find all frames, iframes and object elements to search their documents.
+ const querySelectorAllFunction = InjectedScript._window().Document.prototype.querySelectorAll;
+ const subdocumentResult = querySelectorAllFunction.call(mainFrameDocument, "iframe, frame, object");
+
+ for (var i = 0; i < subdocumentResult.length; ++i) {
+ var element = subdocumentResult.item(i);
+ if (element.contentDocument)
+ searchDocuments.push(element.contentDocument);
+ }
+
+ const panel = InjectedScript;
+ var documentIndex = 0;
+ var searchFunctionIndex = 0;
+ var chunkIntervalIdentifier = null;
+
+ // Split up the work into chunks so we don't block the UI thread while processing.
+
+ function processChunk()
+ {
+ var searchDocument = searchDocuments[documentIndex];
+ var searchFunction = searchFunctions[searchFunctionIndex];
+
+ if (++searchFunctionIndex > searchFunctions.length) {
+ searchFunction = searchFunctions[0];
+ searchFunctionIndex = 0;
+
+ if (++documentIndex > searchDocuments.length) {
+ if (panel._currentSearchChunkIntervalIdentifier === chunkIntervalIdentifier)
+ delete panel._currentSearchChunkIntervalIdentifier;
+ clearInterval(chunkIntervalIdentifier);
+ finishedSearching.call(panel);
+ return;
+ }
+
+ searchDocument = searchDocuments[documentIndex];
+ }
+
+ if (!searchDocument || !searchFunction)
+ return;
+
+ try {
+ searchFunction.call(panel, searchDocument);
+ } catch(err) {
+ // ignore any exceptions. the query might be malformed, but we allow that.
+ }
+ }
+
+ processChunk();
+
+ chunkIntervalIdentifier = setInterval(processChunk, 25);
+ InjectedScript._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier;
+ return true;
+}
+
+InjectedScript.searchCanceled = function()
+{
+ if (InjectedScript._searchResults) {
+ const searchResultsProperty = InjectedScript._includedInSearchResultsPropertyName;
+ for (var i = 0; i < this._searchResults.length; ++i) {
+ var node = this._searchResults[i];
+
+ // Remove the searchResultsProperty since there might be an unfinished search.
+ delete node[searchResultsProperty];
+ }
+ }
+
+ if (InjectedScript._currentSearchChunkIntervalIdentifier) {
+ clearInterval(InjectedScript._currentSearchChunkIntervalIdentifier);
+ delete InjectedScript._currentSearchChunkIntervalIdentifier;
+ }
+ InjectedScript._searchResults = [];
+ return true;
+}
+
+InjectedScript.getCookies = function()
+{
+ return InjectedScript._window().document.cookie;
+}
+
+InjectedScript._ensureCommandLineAPIInstalled = function(inspectedWindow)
+{
+ var inspectedWindow = InjectedScript._window();
+ if (inspectedWindow._inspectorCommandLineAPI)
+ return;
+
+ inspectedWindow.eval("window._inspectorCommandLineAPI = { \
+ $: function() { return document.getElementById.apply(document, arguments) }, \
+ $$: function() { return document.querySelectorAll.apply(document, arguments) }, \
+ $x: function(xpath, context) { \
+ var nodes = []; \
+ try { \
+ var doc = context || document; \
+ var results = doc.evaluate(xpath, doc, null, XPathResult.ANY_TYPE, null); \
+ var node; \
+ while (node = results.iterateNext()) nodes.push(node); \
+ } catch (e) {} \
+ return nodes; \
+ }, \
+ dir: function() { return console.dir.apply(console, arguments) }, \
+ dirxml: function() { return console.dirxml.apply(console, arguments) }, \
+ keys: function(o) { var a = []; for (var k in o) a.push(k); return a; }, \
+ values: function(o) { var a = []; for (var k in o) a.push(o[k]); return a; }, \
+ profile: function() { return console.profile.apply(console, arguments) }, \
+ profileEnd: function() { return console.profileEnd.apply(console, arguments) }, \
+ _inspectedNodes: [], \
+ get $0() { return _inspectorCommandLineAPI._inspectedNodes[0] }, \
+ get $1() { return _inspectorCommandLineAPI._inspectedNodes[1] }, \
+ get $2() { return _inspectorCommandLineAPI._inspectedNodes[2] }, \
+ get $3() { return _inspectorCommandLineAPI._inspectedNodes[3] }, \
+ get $4() { return _inspectorCommandLineAPI._inspectedNodes[4] } \
+ };");
+
+ inspectedWindow._inspectorCommandLineAPI.clear = InspectorController.wrapCallback(InspectorController.clearMessages.bind(InspectorController, true));
+ inspectedWindow._inspectorCommandLineAPI.inspect = InspectorController.wrapCallback(inspectObject.bind(this));
+
+ function inspectObject(o)
+ {
+ if (arguments.length === 0)
+ return;
+
+ inspectedWindow.console.log(o);
+ if (Object.type(o, inspectedWindow) === "node") {
+ InspectorController.pushNodePathToFrontend(o, true);
+ } else {
+ switch (Object.describe(o)) {
+ case "Database":
+ InspectorController.selectDatabase(o);
+ break;
+ case "Storage":
+ InspectorController.selectDOMStorage(o);
+ break;
+ }
+ }
+ }
+}
+
InjectedScript._resolveObject = function(objectProxy)
{
var object = InjectedScript._objectForId(objectProxy.objectId);
@@ -486,11 +844,11 @@ InjectedScript._resolveObject = function(objectProxy)
var protoDepth = objectProxy.protoDepth;
// Follow the property path.
- for (var i = 0; object && i < path.length; ++i)
+ for (var i = 0; object && path && i < path.length; ++i)
object = object[path[i]];
// Get to the necessary proto layer.
- for (var i = 0; object && i < protoDepth; ++i)
+ for (var i = 0; object && protoDepth && i < protoDepth; ++i)
object = object.__proto__;
return object;
@@ -505,12 +863,34 @@ InjectedScript._window = function()
InjectedScript._nodeForId = function(nodeId)
{
- // TODO: replace with node lookup in the InspectorDOMAgent once DOMAgent nodes are used.
- return nodeId;
+ if (!nodeId)
+ return null;
+ return InspectorController.nodeForId(nodeId);
}
InjectedScript._objectForId = function(objectId)
{
- // TODO: replace with node lookups for node ids and evaluation result lookups for the rest of ids.
+ if (typeof objectId === "number")
+ return InjectedScript._nodeForId(objectId);
+ else if (typeof objectId === "string")
+ return InspectorController.unwrapObject(objectId);
+
+ // TODO: move scope chain objects to proxy-based schema.
return objectId;
}
+
+// Called from within InspectorController on the 'inspected page' side.
+InjectedScript.createProxyObject = function(object, objectId)
+{
+ var result = {};
+ result.objectId = objectId;
+ result.type = Object.type(object, InjectedScript._window());
+ if (result.type === "node")
+ result.nodeId = InspectorController.pushNodePathToFrontend(object, false);
+ try {
+ result.description = Object.describe(object, true, InjectedScript._window());
+ } catch (e) {
+ result.exception = e.toString();
+ }
+ return result;
+}