/* * Copyright (C) 2007 Apple Inc. 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. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 APPLE OR ITS CONTRIBUTORS 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. */ var injectedScriptConstructor = (function (InjectedScriptHost, inspectedWindow, injectedScriptId) { var InjectedScript = {}; InjectedScript.lastBoundObjectId = 1; InjectedScript.idToWrappedObject = {}; InjectedScript.objectGroups = {}; InjectedScript.wrapObject = function(object, objectGroupName) { var objectId; if (typeof object === "object" || typeof object === "function" || (typeof object === "undefined" && object instanceof inspectedWindow.HTMLAllCollection)) { // FIXME(33716) var id = InjectedScript.lastBoundObjectId++; objectId = "object#" + id; InjectedScript.idToWrappedObject[objectId] = object; var group = InjectedScript.objectGroups[objectGroupName]; if (!group) { group = []; InjectedScript.objectGroups[objectGroupName] = group; } group.push(objectId); } return InjectedScript.createProxyObject(object, objectId); }; InjectedScript.unwrapObject = function(objectId) { return InjectedScript.idToWrappedObject[objectId]; }; InjectedScript.releaseWrapperObjectGroup = function(objectGroupName) { var group = InjectedScript.objectGroups[objectGroupName]; if (!group) return; for (var i = 0; i < group.length; i++) delete InjectedScript.idToWrappedObject[group[i]]; delete InjectedScript.objectGroups[objectGroupName]; }; // 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.dispatch = function(methodName, args, callId) { var argsArray = eval("(" + args + ")"); if (callId) argsArray.splice(0, 0, callId); // Methods that run asynchronously have a call back id parameter. var result = InjectedScript[methodName].apply(InjectedScript, argsArray); if (typeof result === "undefined") { InjectedScript._window().console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName); result = null; } return result; } InjectedScript.getStyles = function(nodeId, authorOnly) { var node = InjectedScript._nodeForId(nodeId); if (!node) return false; var defaultView = node.ownerDocument.defaultView; var matchedRules = defaultView.getMatchedCSSRules(node, "", authorOnly); var matchedCSSRules = []; for (var i = 0; matchedRules && i < matchedRules.length; ++i) matchedCSSRules.push(InjectedScript._serializeRule(matchedRules[i])); var styleAttributes = {}; var attributes = node.attributes; for (var i = 0; attributes && i < attributes.length; ++i) { if (attributes[i].style) styleAttributes[attributes[i].name] = InjectedScript._serializeStyle(attributes[i].style, true); } var result = {}; result.inlineStyle = InjectedScript._serializeStyle(node.style, true); result.computedStyle = InjectedScript._serializeStyle(defaultView.getComputedStyle(node)); result.matchedCSSRules = matchedCSSRules; result.styleAttributes = styleAttributes; return result; } InjectedScript.getComputedStyle = function(nodeId) { var node = InjectedScript._nodeForId(nodeId); if (!node) return false; return InjectedScript._serializeStyle(node.ownerDocument.defaultView.getComputedStyle(node)); } InjectedScript.getInlineStyle = function(nodeId) { var node = InjectedScript._nodeForId(nodeId); if (!node) return false; return InjectedScript._serializeStyle(node.style, true); } InjectedScript.applyStyleText = function(styleId, styleText, propertyName) { var style = InjectedScript._styles[styleId]; if (!style) return false; var styleTextLength = styleText.length; // Create a new element to parse the user input CSS. var parseElement = document.createElement("span"); parseElement.setAttribute("style", styleText); var tempStyle = parseElement.style; if (tempStyle.length || !styleTextLength) { // The input was parsable or the user deleted everything, so remove the // original property from the real style declaration. If this represents // a shorthand remove all the longhand properties. if (style.getPropertyShorthand(propertyName)) { var longhandProperties = InjectedScript._getLonghandProperties(style, propertyName); for (var i = 0; i < longhandProperties.length; ++i) style.removeProperty(longhandProperties[i]); } else style.removeProperty(propertyName); } // Notify caller that the property was successfully deleted. if (!styleTextLength) return [null, [propertyName]]; if (!tempStyle.length) return false; // Iterate of the properties on the test element's style declaration and // add them to the real style declaration. We take care to move shorthands. var foundShorthands = {}; var changedProperties = []; var uniqueProperties = InjectedScript._getUniqueStyleProperties(tempStyle); for (var i = 0; i < uniqueProperties.length; ++i) { var name = uniqueProperties[i]; var shorthand = tempStyle.getPropertyShorthand(name); if (shorthand && shorthand in foundShorthands) continue; if (shorthand) { var value = InjectedScript._getShorthandValue(tempStyle, shorthand); var priority = InjectedScript._getShorthandPriority(tempStyle, shorthand); foundShorthands[shorthand] = true; } else { var value = tempStyle.getPropertyValue(name); var priority = tempStyle.getPropertyPriority(name); } // Set the property on the real style declaration. style.setProperty((shorthand || name), value, priority); changedProperties.push(shorthand || name); } return [InjectedScript._serializeStyle(style, true), changedProperties]; } InjectedScript.setStyleText = function(style, cssText) { style.cssText = cssText; return true; } InjectedScript.toggleStyleEnabled = function(styleId, propertyName, disabled) { var style = InjectedScript._styles[styleId]; if (!style) return false; if (disabled) { if (!style.__disabledPropertyValues || !style.__disabledPropertyPriorities) { style.__disabledProperties = {}; style.__disabledPropertyValues = {}; style.__disabledPropertyPriorities = {}; } style.__disabledPropertyValues[propertyName] = style.getPropertyValue(propertyName); style.__disabledPropertyPriorities[propertyName] = style.getPropertyPriority(propertyName); if (style.getPropertyShorthand(propertyName)) { var longhandProperties = InjectedScript._getLonghandProperties(style, propertyName); for (var i = 0; i < longhandProperties.length; ++i) { style.__disabledProperties[longhandProperties[i]] = true; style.removeProperty(longhandProperties[i]); } } else { style.__disabledProperties[propertyName] = true; style.removeProperty(propertyName); } } else if (style.__disabledProperties && style.__disabledProperties[propertyName]) { var value = style.__disabledPropertyValues[propertyName]; var priority = style.__disabledPropertyPriorities[propertyName]; style.setProperty(propertyName, value, priority); delete style.__disabledProperties[propertyName]; delete style.__disabledPropertyValues[propertyName]; delete style.__disabledPropertyPriorities[propertyName]; } return InjectedScript._serializeStyle(style, true); } 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); var newRule = stylesheet.cssRules[stylesheet.cssRules.length - 1]; newRule.style.cssText = rule.style.cssText; var parentRules = stylesheet.cssRules; for (var i = 0; i < parentRules.length; ++i) { if (parentRules[i] === rule) { rule.parentStyleSheet.removeRule(i); break; } } return [InjectedScript._serializeRule(newRule), InjectedScript._doesSelectorAffectNode(newContent, selectedNode)]; } catch(e) { // Report invalid syntax. return false; } } InjectedScript.addStyleSelector = function(newContent, selectedNodeId) { var selectedNode = InjectedScript._nodeForId(selectedNodeId); if (!selectedNode) return false; var ownerDocument = selectedNode.ownerDocument; var stylesheet = ownerDocument.__stylesheet; if (!stylesheet) { var head = ownerDocument.head; var styleElement = ownerDocument.createElement("style"); styleElement.type = "text/css"; head.appendChild(styleElement); stylesheet = ownerDocument.styleSheets[ownerDocument.styleSheets.length - 1]; ownerDocument.__stylesheet = stylesheet; } try { stylesheet.addRule(newContent); } catch (e) { // Invalid Syntax for a Selector return false; } 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) { var style = InjectedScript._styles[styleId]; if (!style) return false; style.setProperty(name, value, ""); return true; } InjectedScript._serializeRule = function(rule) { var parentStyleSheet = rule.parentStyleSheet; var ruleValue = {}; ruleValue.selectorText = rule.selectorText; if (parentStyleSheet) { ruleValue.parentStyleSheet = {}; ruleValue.parentStyleSheet.href = parentStyleSheet.href; } 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; } ruleValue.id = rule.id; ruleValue.injectedScriptId = injectedScriptId; } return ruleValue; } InjectedScript._serializeStyle = function(style, doBind) { var result = {}; result.width = style.width; result.height = style.height; result.__disabledProperties = style.__disabledProperties; result.__disabledPropertyValues = style.__disabledPropertyValues; result.__disabledPropertyPriorities = style.__disabledPropertyPriorities; result.properties = []; result.shorthandValues = {}; var foundShorthands = {}; for (var i = 0; i < style.length; ++i) { var property = {}; var name = style[i]; property.name = name; property.priority = style.getPropertyPriority(name); property.implicit = style.isPropertyImplicit(name); var shorthand = style.getPropertyShorthand(name); property.shorthand = shorthand; if (shorthand && !(shorthand in foundShorthands)) { foundShorthands[shorthand] = true; result.shorthandValues[shorthand] = InjectedScript._getShorthandValue(style, shorthand); } property.value = style.getPropertyValue(name); result.properties.push(property); } result.uniqueStyleProperties = InjectedScript._getUniqueStyleProperties(style); if (doBind) { if (!style.id) { style.id = InjectedScript._lastStyleId++; InjectedScript._styles[style.id] = style; } result.id = style.id; result.injectedScriptId = injectedScriptId; } return result; } InjectedScript._getUniqueStyleProperties = function(style) { var properties = []; var foundProperties = {}; for (var i = 0; i < style.length; ++i) { var property = style[i]; if (property in foundProperties) continue; foundProperties[property] = true; properties.push(property); } return properties; } InjectedScript._getLonghandProperties = function(style, shorthandProperty) { var properties = []; var foundProperties = {}; for (var i = 0; i < style.length; ++i) { var individualProperty = style[i]; if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty) continue; foundProperties[individualProperty] = true; properties.push(individualProperty); } return properties; } InjectedScript._getShorthandValue = function(style, shorthandProperty) { var value = style.getPropertyValue(shorthandProperty); if (!value) { // Some shorthands (like border) return a null value, so compute a shorthand value. // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15823 is fixed. var foundProperties = {}; for (var i = 0; i < style.length; ++i) { var individualProperty = style[i]; if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty) continue; var individualValue = style.getPropertyValue(individualProperty); if (style.isPropertyImplicit(individualProperty) || individualValue === "initial") continue; foundProperties[individualProperty] = true; if (!value) value = ""; else if (value.length) value += " "; value += individualValue; } } return value; } InjectedScript._getShorthandPriority = function(style, shorthandProperty) { var priority = style.getPropertyPriority(shorthandProperty); if (!priority) { for (var i = 0; i < style.length; ++i) { var individualProperty = style[i]; if (style.getPropertyShorthand(individualProperty) !== shorthandProperty) continue; priority = style.getPropertyPriority(individualProperty); break; } } return priority; } InjectedScript.getPrototypes = function(nodeId) { var node = InjectedScript._nodeForId(nodeId); if (!node) return false; var result = []; for (var prototype = node; prototype; prototype = prototype.__proto__) { var title = InjectedScript._describe(prototype, true); if (title.match(/Prototype$/)) { title = title.replace(/Prototype$/, ""); } result.push(title); } return result; } InjectedScript.getProperties = function(objectProxy, ignoreHasOwnProperty, abbreviate) { var object = InjectedScript._resolveObject(objectProxy); if (!InjectedScript._isDefined(object)) return false; var properties = []; // Go over properties, prepare results. for (var propertyName in object) { if (!ignoreHasOwnProperty && "hasOwnProperty" in object && !object.hasOwnProperty(propertyName)) continue; var property = {}; property.name = propertyName; property.parentObjectProxy = objectProxy; var isGetter = object["__lookupGetter__"] && object.__lookupGetter__(propertyName); if (!property.isGetter) { var childObject = object[propertyName]; var childObjectProxy = new InjectedScript.createProxyObject(childObject, objectProxy.objectId, abbreviate); childObjectProxy.path = objectProxy.path ? objectProxy.path.slice() : []; childObjectProxy.path.push(propertyName); childObjectProxy.protoDepth = objectProxy.protoDepth || 0; property.value = childObjectProxy; } else { // FIXME: this should show something like "getter" (bug 16734). property.value = { description: "\u2014" }; // em dash property.isGetter = true; } properties.push(property); } return properties; } InjectedScript.setPropertyValue = function(objectProxy, propertyName, expression) { var object = InjectedScript._resolveObject(objectProxy); if (!InjectedScript._isDefined(object)) return false; var expressionLength = expression.length; if (!expressionLength) { delete object[propertyName]; return !(propertyName in object); } try { // Surround the expression in parenthesis so the result of the eval is the result // of the whole expression not the last potential sub-expression. // There is a regression introduced here: eval is now happening against global object, // not call frame while on a breakpoint. // TODO: bring evaluation against call frame back. var result = InjectedScript._window().eval("(" + expression + ")"); // Store the result in the property. object[propertyName] = result; return true; } catch(e) { try { var result = InjectedScript._window().eval("\"" + InjectedScript._escapeCharacters(expression, "\"") + "\""); object[propertyName] = result; return true; } catch(e) { return false; } } } InjectedScript.getNodePropertyValue = function(nodeId, propertyName) { var node = InjectedScript._nodeForId(nodeId); if (!node) return false; var result = node[propertyName]; return result !== undefined ? result : false; } InjectedScript.setOuterHTML = function(nodeId, value, expanded) { var node = InjectedScript._nodeForId(nodeId); if (!node) return false; var parent = node.parentNode; var prevSibling = node.previousSibling; node.outerHTML = value; var newNode = prevSibling ? prevSibling.nextSibling : parent.firstChild; return InjectedScriptHost.pushNodePathToFrontend(newNode, expanded, false); } InjectedScript._getPropertyNames = function(object, resultSet) { if (Object.getOwnPropertyNames) { for (var o = object; o; o = o.__proto__) { try { var names = Object.getOwnPropertyNames(o); for (var i = 0; i < names.length; ++i) resultSet[names[i]] = true; } catch (e) { } } } else { // Chromium doesn't support getOwnPropertyNames yet. for (var name in object) resultSet[name] = true; } } InjectedScript.getCompletions = function(expression, includeInspectorCommandLineAPI, callFrameId) { var props = {}; try { var expressionResult; // Evaluate on call frame if call frame id is available. if (typeof callFrameId === "number") { var callFrame = InjectedScript._callFrameForId(callFrameId); if (!callFrame) return props; if (expression) expressionResult = InjectedScript._evaluateOn(callFrame.evaluate, callFrame, expression); else { // Evaluate into properties in scope of the selected call frame. var scopeChain = callFrame.scopeChain; for (var i = 0; i < scopeChain.length; ++i) InjectedScript._getPropertyNames(scopeChain[i], props); } } else { if (!expression) expression = "this"; expressionResult = InjectedScript._evaluateOn(InjectedScript._window().eval, InjectedScript._window(), expression); } if (typeof expressionResult == "object") InjectedScript._getPropertyNames(expressionResult, props); if (includeInspectorCommandLineAPI) for (var prop in InjectedScript._window().console._inspectorCommandLineAPI) if (prop.charAt(0) !== '_') props[prop] = true; } catch(e) { } return props; } InjectedScript.evaluate = function(expression, objectGroup) { return InjectedScript._evaluateAndWrap(InjectedScript._window().eval, InjectedScript._window(), expression, objectGroup); } InjectedScript._evaluateAndWrap = function(evalFunction, object, expression, objectGroup) { var result = {}; try { result.value = InjectedScript.wrapObject(InjectedScript._evaluateOn(evalFunction, object, expression), objectGroup); // Handle error that might have happened while describing result. if (result.value.errorText) { result.value = result.value.errorText; result.isException = true; } } catch (e) { result.value = e.toString(); result.isException = true; } return result; } InjectedScript._evaluateOn = function(evalFunction, object, expression) { InjectedScript._ensureCommandLineAPIInstalled(evalFunction, object); // 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.console._inspectorCommandLineAPI) { with (window) {\n" + expression + "\n} }"; var value = evalFunction.call(object, expression); // When evaluating on call frame error is not thrown, but returned as a value. if (InjectedScript._type(value) === "error") throw value.toString(); return value; } InjectedScript.addInspectedNode = function(nodeId) { var node = InjectedScript._nodeForId(nodeId); if (!node) return false; InjectedScript._ensureCommandLineAPIInstalled(InjectedScript._window().eval, InjectedScript._window()); var inspectedNodes = InjectedScript._window().console._inspectorCommandLineAPI._inspectedNodes; inspectedNodes.unshift(node); if (inspectedNodes.length >= 5) inspectedNodes.pop(); return true; } InjectedScript.performSearch = function(whitespaceTrimmedQuery) { 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 = InjectedScript._escapeCharacters(whitespaceTrimmedQuery, "'"); const escapedTagNameQuery = (tagNameQuery ? InjectedScript._escapeCharacters(tagNameQuery, "'") : null); const escapedWhitespaceTrimmedQuery = InjectedScript._escapeCharacters(whitespaceTrimmedQuery, "'"); 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 = InjectedScriptHost.pushNodePathToFrontend(node, false, false); nodeIds.push(nodeId); } InjectedScriptHost.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.openInInspectedWindow = function(url) { // Don't call window.open on wrapper - popup blocker mutes it. // URIs should have no double quotes. InjectedScript._window().eval("window.open(\"" + url + "\")"); return true; } InjectedScript.callFrames = function() { var callFrame = InjectedScriptHost.currentCallFrame(); if (!callFrame) return false; var result = []; var depth = 0; do { result.push(new InjectedScript.CallFrameProxy(depth++, callFrame)); callFrame = callFrame.caller; } while (callFrame); return result; } InjectedScript.evaluateInCallFrame = function(callFrameId, code, objectGroup) { var callFrame = InjectedScript._callFrameForId(callFrameId); if (!callFrame) return false; return InjectedScript._evaluateAndWrap(callFrame.evaluate, callFrame, code, objectGroup); } InjectedScript._callFrameForId = function(id) { var callFrame = InjectedScriptHost.currentCallFrame(); while (--id >= 0 && callFrame) callFrame = callFrame.caller; return callFrame; } InjectedScript.clearConsoleMessages = function() { InjectedScriptHost.clearConsoleMessages(); return true; } InjectedScript._inspectObject = function(o) { if (arguments.length === 0) return; inspectedWindow.console.log(o); if (InjectedScript._type(o) === "node") { InjectedScriptHost.pushNodePathToFrontend(o, false, true); } else { switch (InjectedScript._describe(o)) { case "Database": InjectedScriptHost.selectDatabase(o); break; case "Storage": InjectedScriptHost.selectDOMStorage(o); break; } } } InjectedScript._copy = function(o) { if (InjectedScript._type(o) === "node") { var nodeId = InjectedScriptHost.pushNodePathToFrontend(o, false, false); InjectedScriptHost.copyNode(nodeId); } else { InjectedScriptHost.copyText(o); } } InjectedScript._ensureCommandLineAPIInstalled = function(evalFunction, evalObject) { if (evalFunction.call(evalObject, "window.console._inspectorCommandLineAPI")) return; var inspectorCommandLineAPI = evalFunction.call(evalObject, "window.console._inspectorCommandLineAPI = { \n\ $: function() { return document.getElementById.apply(document, arguments) }, \n\ $$: function() { return document.querySelectorAll.apply(document, arguments) }, \n\ $x: function(xpath, context) \n\ { \n\ var nodes = []; \n\ try { \n\ var doc = context || document; \n\ var results = doc.evaluate(xpath, doc, null, XPathResult.ANY_TYPE, null); \n\ var node; \n\ while (node = results.iterateNext()) nodes.push(node); \n\ } catch (e) {} \n\ return nodes; \n\ }, \n\ dir: function() { return console.dir.apply(console, arguments) }, \n\ dirxml: function() { return console.dirxml.apply(console, arguments) }, \n\ keys: function(o) { var a = []; for (var k in o) a.push(k); return a; }, \n\ values: function(o) { var a = []; for (var k in o) a.push(o[k]); return a; }, \n\ profile: function() { return console.profile.apply(console, arguments) }, \n\ profileEnd: function() { return console.profileEnd.apply(console, arguments) }, \n\ _logEvent: function _inspectorCommandLineAPI_logEvent(e) { console.log(e.type, e); }, \n\ _allEventTypes: [\"mouse\", \"key\", \"load\", \"unload\", \"abort\", \"error\", \n\ \"select\", \"change\", \"submit\", \"reset\", \"focus\", \"blur\", \n\ \"resize\", \"scroll\"], \n\ _normalizeEventTypes: function(t) \n\ { \n\ if (typeof t === \"undefined\") \n\ t = console._inspectorCommandLineAPI._allEventTypes; \n\ else if (typeof t === \"string\") \n\ t = [t]; \n\ var i, te = []; \n\ for (i = 0; i < t.length; i++) { \n\ if (t[i] === \"mouse\") \n\ te.splice(0, 0, \"mousedown\", \"mouseup\", \"click\", \"dblclick\", \n\ \"mousemove\", \"mouseover\", \"mouseout\"); \n\ else if (t[i] === \"key\") \n\ te.splice(0, 0, \"keydown\", \"keyup\", \"keypress\"); \n\ else \n\ te.push(t[i]); \n\ } \n\ return te; \n\ }, \n\ monitorEvents: function(o, t) \n\ { \n\ if (!o || !o.addEventListener || !o.removeEventListener) \n\ return; \n\ t = console._inspectorCommandLineAPI._normalizeEventTypes(t); \n\ for (i = 0; i < t.length; i++) { \n\ o.removeEventListener(t[i], console._inspectorCommandLineAPI._logEvent, false); \n\ o.addEventListener(t[i], console._inspectorCommandLineAPI._logEvent, false); \n\ } \n\ }, \n\ unmonitorEvents: function(o, t) \n\ { \n\ if (!o || !o.removeEventListener) \n\ return; \n\ t = console._inspectorCommandLineAPI._normalizeEventTypes(t); \n\ for (i = 0; i < t.length; i++) { \n\ o.removeEventListener(t[i], console._inspectorCommandLineAPI._logEvent, false); \n\ } \n\ }, \n\ _inspectedNodes: [], \n\ get $0() { return console._inspectorCommandLineAPI._inspectedNodes[0] }, \n\ get $1() { return console._inspectorCommandLineAPI._inspectedNodes[1] }, \n\ get $2() { return console._inspectorCommandLineAPI._inspectedNodes[2] }, \n\ get $3() { return console._inspectorCommandLineAPI._inspectedNodes[3] }, \n\ get $4() { return console._inspectorCommandLineAPI._inspectedNodes[4] }, \n\ };"); inspectorCommandLineAPI.clear = InjectedScript.clearConsoleMessages; inspectorCommandLineAPI.inspect = InjectedScript._inspectObject; inspectorCommandLineAPI.copy = InjectedScript._copy; } InjectedScript._resolveObject = function(objectProxy) { var object = InjectedScript._objectForId(objectProxy.objectId); var path = objectProxy.path; var protoDepth = objectProxy.protoDepth; // Follow the property path. for (var i = 0; InjectedScript._isDefined(object) && path && i < path.length; ++i) object = object[path[i]]; // Get to the necessary proto layer. for (var i = 0; InjectedScript._isDefined(object) && protoDepth && i < protoDepth; ++i) object = object.__proto__; return object; } InjectedScript._window = function() { // TODO: replace with 'return window;' once this script is injected into // the page's context. return inspectedWindow; } InjectedScript._nodeForId = function(nodeId) { if (!nodeId) return null; return InjectedScriptHost.nodeForId(nodeId); } InjectedScript._objectForId = function(objectId) { // There are three types of object ids used: // - numbers point to DOM Node via the InspectorDOMAgent mapping // - strings point to console objects cached in InspectorController for lazy evaluation upon them // - objects contain complex ids and are currently used for scoped objects if (typeof objectId === "number") { return InjectedScript._nodeForId(objectId); } else if (typeof objectId === "string") { return InjectedScript.unwrapObject(objectId); } else if (typeof objectId === "object") { var callFrame = InjectedScript._callFrameForId(objectId.callFrame); if (objectId.thisObject) return callFrame.thisObject; else return callFrame.scopeChain[objectId.chainIndex]; } return objectId; } InjectedScript.pushNodeToFrontend = function(objectProxy) { var object = InjectedScript._resolveObject(objectProxy); if (!object || InjectedScript._type(object) !== "node") return false; return InjectedScriptHost.pushNodePathToFrontend(object, false, false); } InjectedScript.nodeByPath = function(path) { // We make this call through the injected script only to get a nice // callback for it. return InjectedScriptHost.pushNodeByPathToFrontend(path.join(",")); } // Called from within InspectorController on the 'inspected page' side. InjectedScript.createProxyObject = function(object, objectId, abbreviate) { var result = {}; result.injectedScriptId = injectedScriptId; result.objectId = objectId; result.type = InjectedScript._type(object); var type = typeof object; if ((type === "object" && object !== null) || type === "function") { for (var subPropertyName in object) { result.hasChildren = true; break; } } try { result.description = InjectedScript._describe(object, abbreviate); } catch (e) { result.errorText = e.toString(); } return result; } InjectedScript.evaluateOnSelf = function(funcBody) { return window.eval("(" + funcBody + ")();"); } InjectedScript.CallFrameProxy = function(id, callFrame) { this.id = id; this.type = callFrame.type; this.functionName = (this.type === "function" ? callFrame.functionName : ""); this.sourceID = callFrame.sourceID; this.line = callFrame.line; this.scopeChain = this._wrapScopeChain(callFrame); } InjectedScript.CallFrameProxy.prototype = { _wrapScopeChain: function(callFrame) { var foundLocalScope = false; var scopeChain = callFrame.scopeChain; var scopeChainProxy = []; for (var i = 0; i < scopeChain.length; ++i) { var scopeObject = scopeChain[i]; var scopeObjectProxy = InjectedScript.createProxyObject(scopeObject, { callFrame: this.id, chainIndex: i }, true); if (InjectedScriptHost.isActivation(scopeObject)) { if (!foundLocalScope) scopeObjectProxy.thisObject = InjectedScript.createProxyObject(callFrame.thisObject, { callFrame: this.id, thisObject: true }, true); else scopeObjectProxy.isClosure = true; foundLocalScope = true; scopeObjectProxy.isLocal = true; } else if (foundLocalScope && scopeObject instanceof InjectedScript._window().Element) scopeObjectProxy.isElement = true; else if (foundLocalScope && scopeObject instanceof InjectedScript._window().Document) scopeObjectProxy.isDocument = true; else if (!foundLocalScope) scopeObjectProxy.isWithBlock = true; scopeChainProxy.push(scopeObjectProxy); } return scopeChainProxy; } } InjectedScript.executeSql = function(callId, databaseId, query) { function successCallback(tx, result) { var rows = result.rows; var result = []; var length = rows.length; for (var i = 0; i < length; ++i) { var data = {}; result.push(data); var row = rows.item(i); for (var columnIdentifier in row) { // FIXME: (Bug 19439) We should specially format SQL NULL here // (which is represented by JavaScript null here, and turned // into the string "null" by the String() function). var text = row[columnIdentifier]; data[columnIdentifier] = String(text); } } InjectedScriptHost.reportDidDispatchOnInjectedScript(callId, result, false); } function errorCallback(tx, error) { InjectedScriptHost.reportDidDispatchOnInjectedScript(callId, error, false); } function queryTransaction(tx) { tx.executeSql(query, null, successCallback, errorCallback); } var database = InjectedScriptHost.databaseForId(databaseId); if (!database) errorCallback(null, { code : 2 }); // Return as unexpected version. database.transaction(queryTransaction, errorCallback); return true; } InjectedScript._isDefined = function(object) { return object || object instanceof inspectedWindow.HTMLAllCollection; } InjectedScript._type = function(obj) { if (obj === null) return "null"; // FIXME(33716): typeof document.all is always 'undefined'. if (obj instanceof inspectedWindow.HTMLAllCollection) return "array"; var type = typeof obj; if (type !== "object" && type !== "function") return type; var win = InjectedScript._window(); if (obj instanceof win.Node) return (obj.nodeType === undefined ? type : "node"); if (obj instanceof win.String) return "string"; if (obj instanceof win.Array) return "array"; if (obj instanceof win.Boolean) return "boolean"; if (obj instanceof win.Number) return "number"; if (obj instanceof win.Date) return "date"; if (obj instanceof win.RegExp) return "regexp"; if (obj instanceof win.NodeList) return "array"; if (obj instanceof win.HTMLCollection || obj instanceof win.HTMLAllCollection) return "array"; if (obj instanceof win.Error) return "error"; return type; } InjectedScript._describe = function(obj, abbreviated) { var type1 = InjectedScript._type(obj); var type2 = InjectedScript._className(obj); switch (type1) { case "object": case "node": case "array": return type2; case "string": if (!abbreviated) return obj; if (obj.length > 100) return "\"" + obj.substring(0, 100) + "\u2026\""; return "\"" + obj + "\""; case "function": var objectText = String(obj); if (!/^function /.test(objectText)) objectText = (type2 == "object") ? type1 : type2; else if (abbreviated) objectText = /.*/.exec(obj)[0].replace(/ +$/g, ""); return objectText; default: return String(obj); } } InjectedScript._className = function(obj) { return Object.prototype.toString.call(obj).replace(/^\[object (.*)\]$/i, "$1") } InjectedScript._escapeCharacters = function(str, chars) { var foundChar = false; for (var i = 0; i < chars.length; ++i) { if (str.indexOf(chars.charAt(i)) !== -1) { foundChar = true; break; } } if (!foundChar) return str; var result = ""; for (var i = 0; i < str.length; ++i) { if (chars.indexOf(str.charAt(i)) !== -1) result += "\\"; result += str.charAt(i); } return result; } return InjectedScript; });