1/*
2 * Copyright (C) 2007 Apple Inc.  All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29var injectedScriptConstructor = (function (InjectedScriptHost, inspectedWindow, injectedScriptId) {
30
31var InjectedScript = {};
32
33InjectedScript.lastBoundObjectId = 1;
34InjectedScript.idToWrappedObject = {};
35InjectedScript.objectGroups = {};
36InjectedScript.wrapObject = function(object, objectGroupName)
37{
38    var objectId;
39    if (typeof object === "object" || typeof object === "function" ||
40        (typeof object === "undefined" && object instanceof inspectedWindow.HTMLAllCollection)) { // FIXME(33716)
41        var id = InjectedScript.lastBoundObjectId++;
42        objectId = "object#" + id;
43        InjectedScript.idToWrappedObject[objectId] = object;
44
45        var group = InjectedScript.objectGroups[objectGroupName];
46        if (!group) {
47            group = [];
48            InjectedScript.objectGroups[objectGroupName] = group;
49        }
50        group.push(objectId);
51    }
52    return InjectedScript.createProxyObject(object, objectId);
53};
54
55InjectedScript.unwrapObject = function(objectId) {
56    return InjectedScript.idToWrappedObject[objectId];
57};
58
59InjectedScript.releaseWrapperObjectGroup = function(objectGroupName) {
60    var group = InjectedScript.objectGroups[objectGroupName];
61    if (!group)
62        return;
63    for (var i = 0; i < group.length; i++)
64        delete InjectedScript.idToWrappedObject[group[i]];
65    delete InjectedScript.objectGroups[objectGroupName];
66};
67
68// Called from within InspectorController on the 'inspected page' side.
69InjectedScript.reset = function()
70{
71    InjectedScript._styles = {};
72    InjectedScript._styleRules = {};
73    InjectedScript._lastStyleId = 0;
74    InjectedScript._lastStyleRuleId = 0;
75    InjectedScript._searchResults = [];
76    InjectedScript._includedInSearchResultsPropertyName = "__includedInInspectorSearchResults";
77}
78
79InjectedScript.reset();
80
81InjectedScript.dispatch = function(methodName, args, callId)
82{
83    var argsArray = eval("(" + args + ")");
84    if (callId)
85        argsArray.splice(0, 0, callId);  // Methods that run asynchronously have a call back id parameter.
86    var result = InjectedScript[methodName].apply(InjectedScript, argsArray);
87    if (typeof result === "undefined") {
88        InjectedScript._window().console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName);
89        result = null;
90    }
91    return result;
92}
93
94InjectedScript.getStyles = function(nodeId, authorOnly)
95{
96    var node = InjectedScript._nodeForId(nodeId);
97    if (!node)
98        return false;
99    var defaultView = node.ownerDocument.defaultView;
100    var matchedRules = defaultView.getMatchedCSSRules(node, "", authorOnly);
101    var matchedCSSRules = [];
102    for (var i = 0; matchedRules && i < matchedRules.length; ++i)
103        matchedCSSRules.push(InjectedScript._serializeRule(matchedRules[i]));
104
105    var styleAttributes = {};
106    var attributes = node.attributes;
107    for (var i = 0; attributes && i < attributes.length; ++i) {
108        if (attributes[i].style)
109            styleAttributes[attributes[i].name] = InjectedScript._serializeStyle(attributes[i].style, true);
110    }
111    var result = {};
112    result.inlineStyle = InjectedScript._serializeStyle(node.style, true);
113    result.computedStyle = InjectedScript._serializeStyle(defaultView.getComputedStyle(node));
114    result.matchedCSSRules = matchedCSSRules;
115    result.styleAttributes = styleAttributes;
116    return result;
117}
118
119InjectedScript.getComputedStyle = function(nodeId)
120{
121    var node = InjectedScript._nodeForId(nodeId);
122    if (!node)
123        return false;
124    return InjectedScript._serializeStyle(node.ownerDocument.defaultView.getComputedStyle(node));
125}
126
127InjectedScript.getInlineStyle = function(nodeId)
128{
129    var node = InjectedScript._nodeForId(nodeId);
130    if (!node)
131        return false;
132    return InjectedScript._serializeStyle(node.style, true);
133}
134
135InjectedScript.applyStyleText = function(styleId, styleText, propertyName)
136{
137    var style = InjectedScript._styles[styleId];
138    if (!style)
139        return false;
140
141    var styleTextLength = styleText.length;
142
143    // Create a new element to parse the user input CSS.
144    var parseElement = document.createElement("span");
145    parseElement.setAttribute("style", styleText);
146
147    var tempStyle = parseElement.style;
148    if (tempStyle.length || !styleTextLength) {
149        // The input was parsable or the user deleted everything, so remove the
150        // original property from the real style declaration. If this represents
151        // a shorthand remove all the longhand properties.
152        if (style.getPropertyShorthand(propertyName)) {
153            var longhandProperties = InjectedScript._getLonghandProperties(style, propertyName);
154            for (var i = 0; i < longhandProperties.length; ++i)
155                style.removeProperty(longhandProperties[i]);
156        } else
157            style.removeProperty(propertyName);
158    }
159
160    // Notify caller that the property was successfully deleted.
161    if (!styleTextLength)
162        return [null, [propertyName]];
163
164    if (!tempStyle.length)
165        return false;
166
167    // Iterate of the properties on the test element's style declaration and
168    // add them to the real style declaration. We take care to move shorthands.
169    var foundShorthands = {};
170    var changedProperties = [];
171    var uniqueProperties = InjectedScript._getUniqueStyleProperties(tempStyle);
172    for (var i = 0; i < uniqueProperties.length; ++i) {
173        var name = uniqueProperties[i];
174        var shorthand = tempStyle.getPropertyShorthand(name);
175
176        if (shorthand && shorthand in foundShorthands)
177            continue;
178
179        if (shorthand) {
180            var value = InjectedScript._getShorthandValue(tempStyle, shorthand);
181            var priority = InjectedScript._getShorthandPriority(tempStyle, shorthand);
182            foundShorthands[shorthand] = true;
183        } else {
184            var value = tempStyle.getPropertyValue(name);
185            var priority = tempStyle.getPropertyPriority(name);
186        }
187
188        // Set the property on the real style declaration.
189        style.setProperty((shorthand || name), value, priority);
190        changedProperties.push(shorthand || name);
191    }
192    return [InjectedScript._serializeStyle(style, true), changedProperties];
193}
194
195InjectedScript.setStyleText = function(style, cssText)
196{
197    style.cssText = cssText;
198    return true;
199}
200
201InjectedScript.toggleStyleEnabled = function(styleId, propertyName, disabled)
202{
203    var style = InjectedScript._styles[styleId];
204    if (!style)
205        return false;
206
207    if (disabled) {
208        if (!style.__disabledPropertyValues || !style.__disabledPropertyPriorities) {
209            style.__disabledProperties = {};
210            style.__disabledPropertyValues = {};
211            style.__disabledPropertyPriorities = {};
212        }
213
214        style.__disabledPropertyValues[propertyName] = style.getPropertyValue(propertyName);
215        style.__disabledPropertyPriorities[propertyName] = style.getPropertyPriority(propertyName);
216
217        if (style.getPropertyShorthand(propertyName)) {
218            var longhandProperties = InjectedScript._getLonghandProperties(style, propertyName);
219            for (var i = 0; i < longhandProperties.length; ++i) {
220                style.__disabledProperties[longhandProperties[i]] = true;
221                style.removeProperty(longhandProperties[i]);
222            }
223        } else {
224            style.__disabledProperties[propertyName] = true;
225            style.removeProperty(propertyName);
226        }
227    } else if (style.__disabledProperties && style.__disabledProperties[propertyName]) {
228        var value = style.__disabledPropertyValues[propertyName];
229        var priority = style.__disabledPropertyPriorities[propertyName];
230
231        style.setProperty(propertyName, value, priority);
232        delete style.__disabledProperties[propertyName];
233        delete style.__disabledPropertyValues[propertyName];
234        delete style.__disabledPropertyPriorities[propertyName];
235    }
236    return InjectedScript._serializeStyle(style, true);
237}
238
239InjectedScript.applyStyleRuleText = function(ruleId, newContent, selectedNodeId)
240{
241    var rule = InjectedScript._styleRules[ruleId];
242    if (!rule)
243        return false;
244
245    var selectedNode = InjectedScript._nodeForId(selectedNodeId);
246
247    try {
248        var stylesheet = rule.parentStyleSheet;
249        stylesheet.addRule(newContent);
250        var newRule = stylesheet.cssRules[stylesheet.cssRules.length - 1];
251        newRule.style.cssText = rule.style.cssText;
252
253        var parentRules = stylesheet.cssRules;
254        for (var i = 0; i < parentRules.length; ++i) {
255            if (parentRules[i] === rule) {
256                rule.parentStyleSheet.removeRule(i);
257                break;
258            }
259        }
260
261        return [InjectedScript._serializeRule(newRule), InjectedScript._doesSelectorAffectNode(newContent, selectedNode)];
262    } catch(e) {
263        // Report invalid syntax.
264        return false;
265    }
266}
267
268InjectedScript.addStyleSelector = function(newContent, selectedNodeId)
269{
270    var selectedNode = InjectedScript._nodeForId(selectedNodeId);
271    if (!selectedNode)
272        return false;
273    var ownerDocument = selectedNode.ownerDocument;
274
275    var stylesheet = ownerDocument.__stylesheet;
276    if (!stylesheet) {
277        var head = ownerDocument.head;
278        var styleElement = ownerDocument.createElement("style");
279        styleElement.type = "text/css";
280        head.appendChild(styleElement);
281        stylesheet = ownerDocument.styleSheets[ownerDocument.styleSheets.length - 1];
282        ownerDocument.__stylesheet = stylesheet;
283    }
284
285    try {
286        stylesheet.addRule(newContent);
287    } catch (e) {
288        // Invalid Syntax for a Selector
289        return false;
290    }
291
292    var rule = stylesheet.cssRules[stylesheet.cssRules.length - 1];
293    rule.__isViaInspector = true;
294
295    return [ InjectedScript._serializeRule(rule), InjectedScript._doesSelectorAffectNode(newContent, selectedNode) ];
296}
297
298InjectedScript._doesSelectorAffectNode = function(selectorText, node)
299{
300    if (!node)
301        return false;
302    var nodes = node.ownerDocument.querySelectorAll(selectorText);
303    for (var i = 0; i < nodes.length; ++i) {
304        if (nodes[i] === node) {
305            return true;
306        }
307    }
308    return false;
309}
310
311InjectedScript.setStyleProperty = function(styleId, name, value)
312{
313    var style = InjectedScript._styles[styleId];
314    if (!style)
315        return false;
316
317    style.setProperty(name, value, "");
318    return true;
319}
320
321InjectedScript._serializeRule = function(rule)
322{
323    var parentStyleSheet = rule.parentStyleSheet;
324
325    var ruleValue = {};
326    ruleValue.selectorText = rule.selectorText;
327    if (parentStyleSheet) {
328        ruleValue.parentStyleSheet = {};
329        ruleValue.parentStyleSheet.href = parentStyleSheet.href;
330    }
331    ruleValue.isUserAgent = parentStyleSheet && !parentStyleSheet.ownerNode && !parentStyleSheet.href;
332    ruleValue.isUser = parentStyleSheet && parentStyleSheet.ownerNode && parentStyleSheet.ownerNode.nodeName == "#document";
333    ruleValue.isViaInspector = !!rule.__isViaInspector;
334
335    // Bind editable scripts only.
336    var doBind = !ruleValue.isUserAgent && !ruleValue.isUser;
337    ruleValue.style = InjectedScript._serializeStyle(rule.style, doBind);
338
339    if (doBind) {
340        if (!rule.id) {
341            rule.id = InjectedScript._lastStyleRuleId++;
342            InjectedScript._styleRules[rule.id] = rule;
343        }
344        ruleValue.id = rule.id;
345        ruleValue.injectedScriptId = injectedScriptId;
346    }
347    return ruleValue;
348}
349
350InjectedScript._serializeStyle = function(style, doBind)
351{
352    var result = {};
353    result.width = style.width;
354    result.height = style.height;
355    result.__disabledProperties = style.__disabledProperties;
356    result.__disabledPropertyValues = style.__disabledPropertyValues;
357    result.__disabledPropertyPriorities = style.__disabledPropertyPriorities;
358    result.properties = [];
359    result.shorthandValues = {};
360    var foundShorthands = {};
361    for (var i = 0; i < style.length; ++i) {
362        var property = {};
363        var name = style[i];
364        property.name = name;
365        property.priority = style.getPropertyPriority(name);
366        property.implicit = style.isPropertyImplicit(name);
367        var shorthand =  style.getPropertyShorthand(name);
368        property.shorthand = shorthand;
369        if (shorthand && !(shorthand in foundShorthands)) {
370            foundShorthands[shorthand] = true;
371            result.shorthandValues[shorthand] = InjectedScript._getShorthandValue(style, shorthand);
372        }
373        property.value = style.getPropertyValue(name);
374        result.properties.push(property);
375    }
376    result.uniqueStyleProperties = InjectedScript._getUniqueStyleProperties(style);
377
378    if (doBind) {
379        if (!style.id) {
380            style.id = InjectedScript._lastStyleId++;
381            InjectedScript._styles[style.id] = style;
382        }
383        result.id = style.id;
384        result.injectedScriptId = injectedScriptId;
385    }
386    return result;
387}
388
389InjectedScript._getUniqueStyleProperties = function(style)
390{
391    var properties = [];
392    var foundProperties = {};
393
394    for (var i = 0; i < style.length; ++i) {
395        var property = style[i];
396        if (property in foundProperties)
397            continue;
398        foundProperties[property] = true;
399        properties.push(property);
400    }
401
402    return properties;
403}
404
405
406InjectedScript._getLonghandProperties = function(style, shorthandProperty)
407{
408    var properties = [];
409    var foundProperties = {};
410
411    for (var i = 0; i < style.length; ++i) {
412        var individualProperty = style[i];
413        if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty)
414            continue;
415        foundProperties[individualProperty] = true;
416        properties.push(individualProperty);
417    }
418
419    return properties;
420}
421
422InjectedScript._getShorthandValue = function(style, shorthandProperty)
423{
424    var value = style.getPropertyValue(shorthandProperty);
425    if (!value) {
426        // Some shorthands (like border) return a null value, so compute a shorthand value.
427        // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15823 is fixed.
428
429        var foundProperties = {};
430        for (var i = 0; i < style.length; ++i) {
431            var individualProperty = style[i];
432            if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty)
433                continue;
434
435            var individualValue = style.getPropertyValue(individualProperty);
436            if (style.isPropertyImplicit(individualProperty) || individualValue === "initial")
437                continue;
438
439            foundProperties[individualProperty] = true;
440
441            if (!value)
442                value = "";
443            else if (value.length)
444                value += " ";
445            value += individualValue;
446        }
447    }
448    return value;
449}
450
451InjectedScript._getShorthandPriority = function(style, shorthandProperty)
452{
453    var priority = style.getPropertyPriority(shorthandProperty);
454    if (!priority) {
455        for (var i = 0; i < style.length; ++i) {
456            var individualProperty = style[i];
457            if (style.getPropertyShorthand(individualProperty) !== shorthandProperty)
458                continue;
459            priority = style.getPropertyPriority(individualProperty);
460            break;
461        }
462    }
463    return priority;
464}
465
466InjectedScript.getPrototypes = function(nodeId)
467{
468    var node = InjectedScript._nodeForId(nodeId);
469    if (!node)
470        return false;
471
472    var result = [];
473    for (var prototype = node; prototype; prototype = prototype.__proto__) {
474        var title = InjectedScript._describe(prototype, true);
475        if (title.match(/Prototype$/)) {
476            title = title.replace(/Prototype$/, "");
477        }
478        result.push(title);
479    }
480    return result;
481}
482
483InjectedScript.getProperties = function(objectProxy, ignoreHasOwnProperty, abbreviate)
484{
485    var object = InjectedScript._resolveObject(objectProxy);
486    if (!InjectedScript._isDefined(object))
487        return false;
488
489    var properties = [];
490
491    // Go over properties, prepare results.
492    for (var propertyName in object) {
493        if (!ignoreHasOwnProperty && "hasOwnProperty" in object && !object.hasOwnProperty(propertyName))
494            continue;
495
496        var property = {};
497        property.name = propertyName;
498        property.parentObjectProxy = objectProxy;
499        var isGetter = object["__lookupGetter__"] && object.__lookupGetter__(propertyName);
500        if (!property.isGetter) {
501            var childObject = object[propertyName];
502            var childObjectProxy = new InjectedScript.createProxyObject(childObject, objectProxy.objectId, abbreviate);
503            childObjectProxy.path = objectProxy.path ? objectProxy.path.slice() : [];
504            childObjectProxy.path.push(propertyName);
505            childObjectProxy.protoDepth = objectProxy.protoDepth || 0;
506            property.value = childObjectProxy;
507        } else {
508            // FIXME: this should show something like "getter" (bug 16734).
509            property.value = { description: "\u2014" }; // em dash
510            property.isGetter = true;
511        }
512        properties.push(property);
513    }
514    return properties;
515}
516
517InjectedScript.setPropertyValue = function(objectProxy, propertyName, expression)
518{
519    var object = InjectedScript._resolveObject(objectProxy);
520    if (!InjectedScript._isDefined(object))
521        return false;
522
523    var expressionLength = expression.length;
524    if (!expressionLength) {
525        delete object[propertyName];
526        return !(propertyName in object);
527    }
528
529    try {
530        // Surround the expression in parenthesis so the result of the eval is the result
531        // of the whole expression not the last potential sub-expression.
532
533        // There is a regression introduced here: eval is now happening against global object,
534        // not call frame while on a breakpoint.
535        // TODO: bring evaluation against call frame back.
536        var result = InjectedScript._window().eval("(" + expression + ")");
537        // Store the result in the property.
538        object[propertyName] = result;
539        return true;
540    } catch(e) {
541        try {
542            var result = InjectedScript._window().eval("\"" + InjectedScript._escapeCharacters(expression, "\"") + "\"");
543            object[propertyName] = result;
544            return true;
545        } catch(e) {
546            return false;
547        }
548    }
549}
550
551InjectedScript.getNodePropertyValue = function(nodeId, propertyName)
552{
553    var node = InjectedScript._nodeForId(nodeId);
554    if (!node)
555        return false;
556    var result = node[propertyName];
557    return result !== undefined ? result : false;
558}
559
560InjectedScript.setOuterHTML = function(nodeId, value, expanded)
561{
562    var node = InjectedScript._nodeForId(nodeId);
563    if (!node)
564        return false;
565
566    var parent = node.parentNode;
567    var prevSibling = node.previousSibling;
568    node.outerHTML = value;
569    var newNode = prevSibling ? prevSibling.nextSibling : parent.firstChild;
570
571    return InjectedScriptHost.pushNodePathToFrontend(newNode, expanded, false);
572}
573
574InjectedScript._getPropertyNames = function(object, resultSet)
575{
576    if (Object.getOwnPropertyNames) {
577        for (var o = object; o; o = o.__proto__) {
578            try {
579                var names = Object.getOwnPropertyNames(o);
580                for (var i = 0; i < names.length; ++i)
581                    resultSet[names[i]] = true;
582            } catch (e) {
583            }
584        }
585    } else {
586        // Chromium doesn't support getOwnPropertyNames yet.
587        for (var name in object)
588            resultSet[name] = true;
589    }
590}
591
592InjectedScript.getCompletions = function(expression, includeInspectorCommandLineAPI, callFrameId)
593{
594    var props = {};
595    try {
596        var expressionResult;
597        // Evaluate on call frame if call frame id is available.
598        if (typeof callFrameId === "number") {
599            var callFrame = InjectedScript._callFrameForId(callFrameId);
600            if (!callFrame)
601                return props;
602            if (expression)
603                expressionResult = InjectedScript._evaluateOn(callFrame.evaluate, callFrame, expression);
604            else {
605                // Evaluate into properties in scope of the selected call frame.
606                var scopeChain = callFrame.scopeChain;
607                for (var i = 0; i < scopeChain.length; ++i)
608                    InjectedScript._getPropertyNames(scopeChain[i], props);
609            }
610        } else {
611            if (!expression)
612                expression = "this";
613            expressionResult = InjectedScript._evaluateOn(InjectedScript._window().eval, InjectedScript._window(), expression);
614        }
615        if (typeof expressionResult == "object")
616            InjectedScript._getPropertyNames(expressionResult, props);
617        if (includeInspectorCommandLineAPI)
618            for (var prop in InjectedScript._window().console._inspectorCommandLineAPI)
619                if (prop.charAt(0) !== '_')
620                    props[prop] = true;
621    } catch(e) {
622    }
623    return props;
624}
625
626InjectedScript.evaluate = function(expression, objectGroup)
627{
628    return InjectedScript._evaluateAndWrap(InjectedScript._window().eval, InjectedScript._window(), expression, objectGroup);
629}
630
631InjectedScript._evaluateAndWrap = function(evalFunction, object, expression, objectGroup)
632{
633    var result = {};
634    try {
635        result.value = InjectedScript.wrapObject(InjectedScript._evaluateOn(evalFunction, object, expression), objectGroup);
636
637        // Handle error that might have happened while describing result.
638        if (result.value.errorText) {
639            result.value = result.value.errorText;
640            result.isException = true;
641        }
642    } catch (e) {
643        result.value = e.toString();
644        result.isException = true;
645    }
646    return result;
647}
648
649InjectedScript._evaluateOn = function(evalFunction, object, expression)
650{
651    InjectedScript._ensureCommandLineAPIInstalled(evalFunction, object);
652    // Surround the expression in with statements to inject our command line API so that
653    // the window object properties still take more precedent than our API functions.
654    expression = "with (window.console._inspectorCommandLineAPI) { with (window) {\n" + expression + "\n} }";
655    var value = evalFunction.call(object, expression);
656
657    // When evaluating on call frame error is not thrown, but returned as a value.
658    if (InjectedScript._type(value) === "error")
659        throw value.toString();
660
661    return value;
662}
663
664InjectedScript.addInspectedNode = function(nodeId)
665{
666    var node = InjectedScript._nodeForId(nodeId);
667    if (!node)
668        return false;
669
670    InjectedScript._ensureCommandLineAPIInstalled(InjectedScript._window().eval, InjectedScript._window());
671    var inspectedNodes = InjectedScript._window().console._inspectorCommandLineAPI._inspectedNodes;
672    inspectedNodes.unshift(node);
673    if (inspectedNodes.length >= 5)
674        inspectedNodes.pop();
675    return true;
676}
677
678InjectedScript.performSearch = function(whitespaceTrimmedQuery)
679{
680    var tagNameQuery = whitespaceTrimmedQuery;
681    var attributeNameQuery = whitespaceTrimmedQuery;
682    var startTagFound = (tagNameQuery.indexOf("<") === 0);
683    var endTagFound = (tagNameQuery.lastIndexOf(">") === (tagNameQuery.length - 1));
684
685    if (startTagFound || endTagFound) {
686        var tagNameQueryLength = tagNameQuery.length;
687        tagNameQuery = tagNameQuery.substring((startTagFound ? 1 : 0), (endTagFound ? (tagNameQueryLength - 1) : tagNameQueryLength));
688    }
689
690    // Check the tagNameQuery is it is a possibly valid tag name.
691    if (!/^[a-zA-Z0-9\-_:]+$/.test(tagNameQuery))
692        tagNameQuery = null;
693
694    // Check the attributeNameQuery is it is a possibly valid tag name.
695    if (!/^[a-zA-Z0-9\-_:]+$/.test(attributeNameQuery))
696        attributeNameQuery = null;
697
698    const escapedQuery = InjectedScript._escapeCharacters(whitespaceTrimmedQuery, "'");
699    const escapedTagNameQuery = (tagNameQuery ?  InjectedScript._escapeCharacters(tagNameQuery, "'") : null);
700    const escapedWhitespaceTrimmedQuery = InjectedScript._escapeCharacters(whitespaceTrimmedQuery, "'");
701    const searchResultsProperty = InjectedScript._includedInSearchResultsPropertyName;
702
703    function addNodesToResults(nodes, length, getItem)
704    {
705        if (!length)
706            return;
707
708        var nodeIds = [];
709        for (var i = 0; i < length; ++i) {
710            var node = getItem.call(nodes, i);
711            // Skip this node if it already has the property.
712            if (searchResultsProperty in node)
713                continue;
714
715            if (!InjectedScript._searchResults.length) {
716                InjectedScript._currentSearchResultIndex = 0;
717            }
718
719            node[searchResultsProperty] = true;
720            InjectedScript._searchResults.push(node);
721            var nodeId = InjectedScriptHost.pushNodePathToFrontend(node, false, false);
722            nodeIds.push(nodeId);
723        }
724        InjectedScriptHost.addNodesToSearchResult(nodeIds.join(","));
725    }
726
727    function matchExactItems(doc)
728    {
729        matchExactId.call(this, doc);
730        matchExactClassNames.call(this, doc);
731        matchExactTagNames.call(this, doc);
732        matchExactAttributeNames.call(this, doc);
733    }
734
735    function matchExactId(doc)
736    {
737        const result = doc.__proto__.getElementById.call(doc, whitespaceTrimmedQuery);
738        addNodesToResults.call(this, result, (result ? 1 : 0), function() { return this });
739    }
740
741    function matchExactClassNames(doc)
742    {
743        const result = doc.__proto__.getElementsByClassName.call(doc, whitespaceTrimmedQuery);
744        addNodesToResults.call(this, result, result.length, result.item);
745    }
746
747    function matchExactTagNames(doc)
748    {
749        if (!tagNameQuery)
750            return;
751        const result = doc.__proto__.getElementsByTagName.call(doc, tagNameQuery);
752        addNodesToResults.call(this, result, result.length, result.item);
753    }
754
755    function matchExactAttributeNames(doc)
756    {
757        if (!attributeNameQuery)
758            return;
759        const result = doc.__proto__.querySelectorAll.call(doc, "[" + attributeNameQuery + "]");
760        addNodesToResults.call(this, result, result.length, result.item);
761    }
762
763    function matchPartialTagNames(doc)
764    {
765        if (!tagNameQuery)
766            return;
767        const result = doc.__proto__.evaluate.call(doc, "//*[contains(name(), '" + escapedTagNameQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
768        addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
769    }
770
771    function matchStartOfTagNames(doc)
772    {
773        if (!tagNameQuery)
774            return;
775        const result = doc.__proto__.evaluate.call(doc, "//*[starts-with(name(), '" + escapedTagNameQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
776        addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
777    }
778
779    function matchPartialTagNamesAndAttributeValues(doc)
780    {
781        if (!tagNameQuery) {
782            matchPartialAttributeValues.call(this, doc);
783            return;
784        }
785
786        const result = doc.__proto__.evaluate.call(doc, "//*[contains(name(), '" + escapedTagNameQuery + "') or contains(@*, '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
787        addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
788    }
789
790    function matchPartialAttributeValues(doc)
791    {
792        const result = doc.__proto__.evaluate.call(doc, "//*[contains(@*, '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
793        addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
794    }
795
796    function matchStyleSelector(doc)
797    {
798        const result = doc.__proto__.querySelectorAll.call(doc, whitespaceTrimmedQuery);
799        addNodesToResults.call(this, result, result.length, result.item);
800    }
801
802    function matchPlainText(doc)
803    {
804        const result = doc.__proto__.evaluate.call(doc, "//text()[contains(., '" + escapedQuery + "')] | //comment()[contains(., '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
805        addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
806    }
807
808    function matchXPathQuery(doc)
809    {
810        const result = doc.__proto__.evaluate.call(doc, whitespaceTrimmedQuery, doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
811        addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
812    }
813
814    function finishedSearching()
815    {
816        // Remove the searchResultsProperty now that the search is finished.
817        for (var i = 0; i < InjectedScript._searchResults.length; ++i)
818            delete InjectedScript._searchResults[i][searchResultsProperty];
819    }
820
821    const mainFrameDocument = InjectedScript._window().document;
822    const searchDocuments = [mainFrameDocument];
823    var searchFunctions;
824    if (tagNameQuery && startTagFound && endTagFound)
825        searchFunctions = [matchExactTagNames, matchPlainText];
826    else if (tagNameQuery && startTagFound)
827        searchFunctions = [matchStartOfTagNames, matchPlainText];
828    else if (tagNameQuery && endTagFound) {
829        // FIXME: we should have a matchEndOfTagNames search function if endTagFound is true but not startTagFound.
830        // This requires ends-with() support in XPath, WebKit only supports starts-with() and contains().
831        searchFunctions = [matchPartialTagNames, matchPlainText];
832    } else if (whitespaceTrimmedQuery === "//*" || whitespaceTrimmedQuery === "*") {
833        // These queries will match every node. Matching everything isn't useful and can be slow for large pages,
834        // so limit the search functions list to plain text and attribute matching.
835        searchFunctions = [matchPartialAttributeValues, matchPlainText];
836    } else
837        searchFunctions = [matchExactItems, matchStyleSelector, matchPartialTagNamesAndAttributeValues, matchPlainText, matchXPathQuery];
838
839    // Find all frames, iframes and object elements to search their documents.
840    const querySelectorAllFunction = InjectedScript._window().Document.prototype.querySelectorAll;
841    const subdocumentResult = querySelectorAllFunction.call(mainFrameDocument, "iframe, frame, object");
842
843    for (var i = 0; i < subdocumentResult.length; ++i) {
844        var element = subdocumentResult.item(i);
845        if (element.contentDocument)
846            searchDocuments.push(element.contentDocument);
847    }
848
849    const panel = InjectedScript;
850    var documentIndex = 0;
851    var searchFunctionIndex = 0;
852    var chunkIntervalIdentifier = null;
853
854    // Split up the work into chunks so we don't block the UI thread while processing.
855
856    function processChunk()
857    {
858        var searchDocument = searchDocuments[documentIndex];
859        var searchFunction = searchFunctions[searchFunctionIndex];
860
861        if (++searchFunctionIndex > searchFunctions.length) {
862            searchFunction = searchFunctions[0];
863            searchFunctionIndex = 0;
864
865            if (++documentIndex > searchDocuments.length) {
866                if (panel._currentSearchChunkIntervalIdentifier === chunkIntervalIdentifier)
867                    delete panel._currentSearchChunkIntervalIdentifier;
868                clearInterval(chunkIntervalIdentifier);
869                finishedSearching.call(panel);
870                return;
871            }
872
873            searchDocument = searchDocuments[documentIndex];
874        }
875
876        if (!searchDocument || !searchFunction)
877            return;
878
879        try {
880            searchFunction.call(panel, searchDocument);
881        } catch(err) {
882            // ignore any exceptions. the query might be malformed, but we allow that.
883        }
884    }
885
886    processChunk();
887
888    chunkIntervalIdentifier = setInterval(processChunk, 25);
889    InjectedScript._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier;
890    return true;
891}
892
893InjectedScript.searchCanceled = function()
894{
895    if (InjectedScript._searchResults) {
896        const searchResultsProperty = InjectedScript._includedInSearchResultsPropertyName;
897        for (var i = 0; i < this._searchResults.length; ++i) {
898            var node = this._searchResults[i];
899
900            // Remove the searchResultsProperty since there might be an unfinished search.
901            delete node[searchResultsProperty];
902        }
903    }
904
905    if (InjectedScript._currentSearchChunkIntervalIdentifier) {
906        clearInterval(InjectedScript._currentSearchChunkIntervalIdentifier);
907        delete InjectedScript._currentSearchChunkIntervalIdentifier;
908    }
909    InjectedScript._searchResults = [];
910    return true;
911}
912
913InjectedScript.openInInspectedWindow = function(url)
914{
915    // Don't call window.open on wrapper - popup blocker mutes it.
916    // URIs should have no double quotes.
917    InjectedScript._window().eval("window.open(\"" + url + "\")");
918    return true;
919}
920
921InjectedScript.callFrames = function()
922{
923    var callFrame = InjectedScriptHost.currentCallFrame();
924    if (!callFrame)
925        return false;
926
927    var result = [];
928    var depth = 0;
929    do {
930        result.push(new InjectedScript.CallFrameProxy(depth++, callFrame));
931        callFrame = callFrame.caller;
932    } while (callFrame);
933    return result;
934}
935
936InjectedScript.evaluateInCallFrame = function(callFrameId, code, objectGroup)
937{
938    var callFrame = InjectedScript._callFrameForId(callFrameId);
939    if (!callFrame)
940        return false;
941    return InjectedScript._evaluateAndWrap(callFrame.evaluate, callFrame, code, objectGroup);
942}
943
944InjectedScript._callFrameForId = function(id)
945{
946    var callFrame = InjectedScriptHost.currentCallFrame();
947    while (--id >= 0 && callFrame)
948        callFrame = callFrame.caller;
949    return callFrame;
950}
951
952InjectedScript.clearConsoleMessages = function()
953{
954    InjectedScriptHost.clearConsoleMessages();
955    return true;
956}
957
958InjectedScript._inspectObject = function(o)
959{
960    if (arguments.length === 0)
961        return;
962
963    inspectedWindow.console.log(o);
964    if (InjectedScript._type(o) === "node") {
965        InjectedScriptHost.pushNodePathToFrontend(o, false, true);
966    } else {
967        switch (InjectedScript._describe(o)) {
968            case "Database":
969                InjectedScriptHost.selectDatabase(o);
970                break;
971            case "Storage":
972                InjectedScriptHost.selectDOMStorage(o);
973                break;
974        }
975    }
976}
977
978InjectedScript._copy = function(o)
979{
980    if (InjectedScript._type(o) === "node") {
981        var nodeId = InjectedScriptHost.pushNodePathToFrontend(o, false, false);
982        InjectedScriptHost.copyNode(nodeId);
983    } else {
984        InjectedScriptHost.copyText(o);
985    }
986}
987
988InjectedScript._ensureCommandLineAPIInstalled = function(evalFunction, evalObject)
989{
990    if (evalFunction.call(evalObject, "window.console._inspectorCommandLineAPI"))
991        return;
992    var inspectorCommandLineAPI = evalFunction.call(evalObject, "window.console._inspectorCommandLineAPI = { \n\
993        $: function() { return document.getElementById.apply(document, arguments) }, \n\
994        $$: function() { return document.querySelectorAll.apply(document, arguments) }, \n\
995        $x: function(xpath, context) \n\
996        { \n\
997            var nodes = []; \n\
998            try { \n\
999                var doc = context || document; \n\
1000                var results = doc.evaluate(xpath, doc, null, XPathResult.ANY_TYPE, null); \n\
1001                var node; \n\
1002                while (node = results.iterateNext()) nodes.push(node); \n\
1003            } catch (e) {} \n\
1004            return nodes; \n\
1005        }, \n\
1006        dir: function() { return console.dir.apply(console, arguments) }, \n\
1007        dirxml: function() { return console.dirxml.apply(console, arguments) }, \n\
1008        keys: function(o) { var a = []; for (var k in o) a.push(k); return a; }, \n\
1009        values: function(o) { var a = []; for (var k in o) a.push(o[k]); return a; }, \n\
1010        profile: function() { return console.profile.apply(console, arguments) }, \n\
1011        profileEnd: function() { return console.profileEnd.apply(console, arguments) }, \n\
1012        _logEvent: function _inspectorCommandLineAPI_logEvent(e) { console.log(e.type, e); }, \n\
1013        _allEventTypes: [\"mouse\", \"key\", \"load\", \"unload\", \"abort\", \"error\", \n\
1014            \"select\", \"change\", \"submit\", \"reset\", \"focus\", \"blur\", \n\
1015            \"resize\", \"scroll\"], \n\
1016        _normalizeEventTypes: function(t) \n\
1017        { \n\
1018            if (typeof t === \"undefined\") \n\
1019                t = console._inspectorCommandLineAPI._allEventTypes; \n\
1020            else if (typeof t === \"string\") \n\
1021                t = [t]; \n\
1022            var i, te = []; \n\
1023            for (i = 0; i < t.length; i++) { \n\
1024                if (t[i] === \"mouse\") \n\
1025                    te.splice(0, 0, \"mousedown\", \"mouseup\", \"click\", \"dblclick\", \n\
1026                        \"mousemove\", \"mouseover\", \"mouseout\"); \n\
1027                else if (t[i] === \"key\") \n\
1028                    te.splice(0, 0, \"keydown\", \"keyup\", \"keypress\"); \n\
1029                else \n\
1030                    te.push(t[i]); \n\
1031            } \n\
1032            return te; \n\
1033        }, \n\
1034        monitorEvents: function(o, t) \n\
1035        { \n\
1036            if (!o || !o.addEventListener || !o.removeEventListener) \n\
1037                return; \n\
1038            t = console._inspectorCommandLineAPI._normalizeEventTypes(t); \n\
1039            for (i = 0; i < t.length; i++) { \n\
1040                o.removeEventListener(t[i], console._inspectorCommandLineAPI._logEvent, false); \n\
1041                o.addEventListener(t[i], console._inspectorCommandLineAPI._logEvent, false); \n\
1042            } \n\
1043        }, \n\
1044        unmonitorEvents: function(o, t) \n\
1045        { \n\
1046            if (!o || !o.removeEventListener) \n\
1047                return; \n\
1048            t = console._inspectorCommandLineAPI._normalizeEventTypes(t); \n\
1049            for (i = 0; i < t.length; i++) { \n\
1050                o.removeEventListener(t[i], console._inspectorCommandLineAPI._logEvent, false); \n\
1051            } \n\
1052        }, \n\
1053        _inspectedNodes: [], \n\
1054        get $0() { return console._inspectorCommandLineAPI._inspectedNodes[0] }, \n\
1055        get $1() { return console._inspectorCommandLineAPI._inspectedNodes[1] }, \n\
1056        get $2() { return console._inspectorCommandLineAPI._inspectedNodes[2] }, \n\
1057        get $3() { return console._inspectorCommandLineAPI._inspectedNodes[3] }, \n\
1058        get $4() { return console._inspectorCommandLineAPI._inspectedNodes[4] }, \n\
1059    };");
1060
1061    inspectorCommandLineAPI.clear = InjectedScript.clearConsoleMessages;
1062    inspectorCommandLineAPI.inspect = InjectedScript._inspectObject;
1063    inspectorCommandLineAPI.copy = InjectedScript._copy;
1064}
1065
1066InjectedScript._resolveObject = function(objectProxy)
1067{
1068    var object = InjectedScript._objectForId(objectProxy.objectId);
1069    var path = objectProxy.path;
1070    var protoDepth = objectProxy.protoDepth;
1071
1072    // Follow the property path.
1073    for (var i = 0; InjectedScript._isDefined(object) && path && i < path.length; ++i)
1074        object = object[path[i]];
1075
1076    // Get to the necessary proto layer.
1077    for (var i = 0; InjectedScript._isDefined(object) && protoDepth && i < protoDepth; ++i)
1078        object = object.__proto__;
1079
1080    return object;
1081}
1082
1083InjectedScript._window = function()
1084{
1085    // TODO: replace with 'return window;' once this script is injected into
1086    // the page's context.
1087    return inspectedWindow;
1088}
1089
1090InjectedScript._nodeForId = function(nodeId)
1091{
1092    if (!nodeId)
1093        return null;
1094    return InjectedScriptHost.nodeForId(nodeId);
1095}
1096
1097InjectedScript._objectForId = function(objectId)
1098{
1099    // There are three types of object ids used:
1100    // - numbers point to DOM Node via the InspectorDOMAgent mapping
1101    // - strings point to console objects cached in InspectorController for lazy evaluation upon them
1102    // - objects contain complex ids and are currently used for scoped objects
1103    if (typeof objectId === "number") {
1104        return InjectedScript._nodeForId(objectId);
1105    } else if (typeof objectId === "string") {
1106        return InjectedScript.unwrapObject(objectId);
1107    } else if (typeof objectId === "object") {
1108        var callFrame = InjectedScript._callFrameForId(objectId.callFrame);
1109        if (objectId.thisObject)
1110            return callFrame.thisObject;
1111        else
1112            return callFrame.scopeChain[objectId.chainIndex];
1113    }
1114    return objectId;
1115}
1116
1117InjectedScript.pushNodeToFrontend = function(objectProxy)
1118{
1119    var object = InjectedScript._resolveObject(objectProxy);
1120    if (!object || InjectedScript._type(object) !== "node")
1121        return false;
1122    return InjectedScriptHost.pushNodePathToFrontend(object, false, false);
1123}
1124
1125InjectedScript.nodeByPath = function(path)
1126{
1127    // We make this call through the injected script only to get a nice
1128    // callback for it.
1129    return InjectedScriptHost.pushNodeByPathToFrontend(path.join(","));
1130}
1131
1132// Called from within InspectorController on the 'inspected page' side.
1133InjectedScript.createProxyObject = function(object, objectId, abbreviate)
1134{
1135    var result = {};
1136    result.injectedScriptId = injectedScriptId;
1137    result.objectId = objectId;
1138    result.type = InjectedScript._type(object);
1139
1140    var type = typeof object;
1141    if ((type === "object" && object !== null) || type === "function") {
1142        for (var subPropertyName in object) {
1143            result.hasChildren = true;
1144            break;
1145        }
1146    }
1147    try {
1148        result.description = InjectedScript._describe(object, abbreviate);
1149    } catch (e) {
1150        result.errorText = e.toString();
1151    }
1152    return result;
1153}
1154
1155InjectedScript.evaluateOnSelf = function(funcBody)
1156{
1157    return window.eval("(" + funcBody + ")();");
1158}
1159
1160InjectedScript.CallFrameProxy = function(id, callFrame)
1161{
1162    this.id = id;
1163    this.type = callFrame.type;
1164    this.functionName = (this.type === "function" ? callFrame.functionName : "");
1165    this.sourceID = callFrame.sourceID;
1166    this.line = callFrame.line;
1167    this.scopeChain = this._wrapScopeChain(callFrame);
1168}
1169
1170InjectedScript.CallFrameProxy.prototype = {
1171    _wrapScopeChain: function(callFrame)
1172    {
1173        var foundLocalScope = false;
1174        var scopeChain = callFrame.scopeChain;
1175        var scopeChainProxy = [];
1176        for (var i = 0; i < scopeChain.length; ++i) {
1177            var scopeObject = scopeChain[i];
1178            var scopeObjectProxy = InjectedScript.createProxyObject(scopeObject, { callFrame: this.id, chainIndex: i }, true);
1179
1180            if (InjectedScriptHost.isActivation(scopeObject)) {
1181                if (!foundLocalScope)
1182                    scopeObjectProxy.thisObject = InjectedScript.createProxyObject(callFrame.thisObject, { callFrame: this.id, thisObject: true }, true);
1183                else
1184                    scopeObjectProxy.isClosure = true;
1185                foundLocalScope = true;
1186                scopeObjectProxy.isLocal = true;
1187            } else if (foundLocalScope && scopeObject instanceof InjectedScript._window().Element)
1188                scopeObjectProxy.isElement = true;
1189            else if (foundLocalScope && scopeObject instanceof InjectedScript._window().Document)
1190                scopeObjectProxy.isDocument = true;
1191            else if (!foundLocalScope)
1192                scopeObjectProxy.isWithBlock = true;
1193            scopeChainProxy.push(scopeObjectProxy);
1194        }
1195        return scopeChainProxy;
1196    }
1197}
1198
1199InjectedScript.executeSql = function(callId, databaseId, query)
1200{
1201    function successCallback(tx, result)
1202    {
1203        var rows = result.rows;
1204        var result = [];
1205        var length = rows.length;
1206        for (var i = 0; i < length; ++i) {
1207            var data = {};
1208            result.push(data);
1209            var row = rows.item(i);
1210            for (var columnIdentifier in row) {
1211                // FIXME: (Bug 19439) We should specially format SQL NULL here
1212                // (which is represented by JavaScript null here, and turned
1213                // into the string "null" by the String() function).
1214                var text = row[columnIdentifier];
1215                data[columnIdentifier] = String(text);
1216            }
1217        }
1218        InjectedScriptHost.reportDidDispatchOnInjectedScript(callId, result, false);
1219    }
1220
1221    function errorCallback(tx, error)
1222    {
1223        InjectedScriptHost.reportDidDispatchOnInjectedScript(callId, error, false);
1224    }
1225
1226    function queryTransaction(tx)
1227    {
1228        tx.executeSql(query, null, successCallback, errorCallback);
1229    }
1230
1231    var database = InjectedScriptHost.databaseForId(databaseId);
1232    if (!database)
1233        errorCallback(null, { code : 2 });  // Return as unexpected version.
1234    database.transaction(queryTransaction, errorCallback);
1235    return true;
1236}
1237
1238InjectedScript._isDefined = function(object)
1239{
1240    return object || object instanceof inspectedWindow.HTMLAllCollection;
1241}
1242
1243InjectedScript._type = function(obj)
1244{
1245    if (obj === null)
1246        return "null";
1247
1248    // FIXME(33716): typeof document.all is always 'undefined'.
1249    if (obj instanceof inspectedWindow.HTMLAllCollection)
1250        return "array";
1251
1252    var type = typeof obj;
1253    if (type !== "object" && type !== "function")
1254        return type;
1255
1256    var win = InjectedScript._window();
1257
1258    if (obj instanceof win.Node)
1259        return (obj.nodeType === undefined ? type : "node");
1260    if (obj instanceof win.String)
1261        return "string";
1262    if (obj instanceof win.Array)
1263        return "array";
1264    if (obj instanceof win.Boolean)
1265        return "boolean";
1266    if (obj instanceof win.Number)
1267        return "number";
1268    if (obj instanceof win.Date)
1269        return "date";
1270    if (obj instanceof win.RegExp)
1271        return "regexp";
1272    if (obj instanceof win.NodeList)
1273        return "array";
1274    if (obj instanceof win.HTMLCollection || obj instanceof win.HTMLAllCollection)
1275        return "array";
1276    if (obj instanceof win.Error)
1277        return "error";
1278    return type;
1279}
1280
1281InjectedScript._describe = function(obj, abbreviated)
1282{
1283    var type1 = InjectedScript._type(obj);
1284    var type2 = InjectedScript._className(obj);
1285
1286    switch (type1) {
1287    case "object":
1288    case "node":
1289    case "array":
1290        return type2;
1291    case "string":
1292        if (!abbreviated)
1293            return obj;
1294        if (obj.length > 100)
1295            return "\"" + obj.substring(0, 100) + "\u2026\"";
1296        return "\"" + obj + "\"";
1297    case "function":
1298        var objectText = String(obj);
1299        if (!/^function /.test(objectText))
1300            objectText = (type2 == "object") ? type1 : type2;
1301        else if (abbreviated)
1302            objectText = /.*/.exec(obj)[0].replace(/ +$/g, "");
1303        return objectText;
1304    default:
1305        return String(obj);
1306    }
1307}
1308
1309InjectedScript._className = function(obj)
1310{
1311    return Object.prototype.toString.call(obj).replace(/^\[object (.*)\]$/i, "$1")
1312}
1313
1314InjectedScript._escapeCharacters = function(str, chars)
1315{
1316    var foundChar = false;
1317    for (var i = 0; i < chars.length; ++i) {
1318        if (str.indexOf(chars.charAt(i)) !== -1) {
1319            foundChar = true;
1320            break;
1321        }
1322    }
1323
1324    if (!foundChar)
1325        return str;
1326
1327    var result = "";
1328    for (var i = 0; i < str.length; ++i) {
1329        if (chars.indexOf(str.charAt(i)) !== -1)
1330            result += "\\";
1331        result += str.charAt(i);
1332    }
1333
1334    return result;
1335}
1336
1337return InjectedScript;
1338});
1339