• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2007 Apple Inc.  All rights reserved.
3 * Copyright (C) 2009 Joseph Pecoraro
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1.  Redistributions of source code must retain the above copyright
10 *     notice, this list of conditions and the following disclaimer.
11 * 2.  Redistributions in binary form must reproduce the above copyright
12 *     notice, this list of conditions and the following disclaimer in the
13 *     documentation and/or other materials provided with the distribution.
14 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 *     its contributors may be used to endorse or promote products derived
16 *     from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30var InjectedScript = {
31    _styles: {},
32    _styleRules: {},
33    _lastStyleId: 0,
34    _lastStyleRuleId: 0
35};
36
37InjectedScript.getStyles = function(nodeId, authorOnly)
38{
39    var node = InjectedScript._nodeForId(nodeId);
40    if (!node)
41        return false;
42    var matchedRules = InjectedScript._window().getMatchedCSSRules(node, "", authorOnly);
43    var matchedCSSRules = [];
44    for (var i = 0; matchedRules && i < matchedRules.length; ++i)
45        matchedCSSRules.push(InjectedScript._serializeRule(matchedRules[i]));
46
47    var styleAttributes = {};
48    var attributes = node.attributes;
49    for (var i = 0; attributes && i < attributes.length; ++i) {
50        if (attributes[i].style)
51            styleAttributes[attributes[i].name] = InjectedScript._serializeStyle(attributes[i].style, true);
52    }
53    var result = {};
54    result.inlineStyle = InjectedScript._serializeStyle(node.style, true);
55    result.computedStyle = InjectedScript._serializeStyle(InjectedScript._window().getComputedStyle(node));
56    result.matchedCSSRules = matchedCSSRules;
57    result.styleAttributes = styleAttributes;
58    return result;
59}
60
61InjectedScript.getComputedStyle = function(nodeId)
62{
63    var node = InjectedScript._nodeForId(nodeId);
64    if (!node)
65        return false;
66    return InjectedScript._serializeStyle(InjectedScript._window().getComputedStyle(node));
67}
68
69InjectedScript.getInlineStyle = function(nodeId)
70{
71    var node = InjectedScript._nodeForId(nodeId);
72    if (!node)
73        return false;
74    return InjectedScript._serializeStyle(node.style, true);
75}
76
77InjectedScript.applyStyleText = function(styleId, styleText, propertyName)
78{
79    var style = InjectedScript._styles[styleId];
80    if (!style)
81        return false;
82
83    var styleTextLength = styleText.length;
84
85    // Create a new element to parse the user input CSS.
86    var parseElement = document.createElement("span");
87    parseElement.setAttribute("style", styleText);
88
89    var tempStyle = parseElement.style;
90    if (tempStyle.length || !styleTextLength) {
91        // The input was parsable or the user deleted everything, so remove the
92        // original property from the real style declaration. If this represents
93        // a shorthand remove all the longhand properties.
94        if (style.getPropertyShorthand(propertyName)) {
95            var longhandProperties = InjectedScript._getLonghandProperties(style, propertyName);
96            for (var i = 0; i < longhandProperties.length; ++i)
97                style.removeProperty(longhandProperties[i]);
98        } else
99            style.removeProperty(propertyName);
100    }
101
102    if (!tempStyle.length)
103        return false;
104
105    // Iterate of the properties on the test element's style declaration and
106    // add them to the real style declaration. We take care to move shorthands.
107    var foundShorthands = {};
108    var changedProperties = [];
109    var uniqueProperties = InjectedScript._getUniqueStyleProperties(tempStyle);
110    for (var i = 0; i < uniqueProperties.length; ++i) {
111        var name = uniqueProperties[i];
112        var shorthand = tempStyle.getPropertyShorthand(name);
113
114        if (shorthand && shorthand in foundShorthands)
115            continue;
116
117        if (shorthand) {
118            var value = InjectedScript._getShorthandValue(tempStyle, shorthand);
119            var priority = InjectedScript._getShorthandPriority(tempStyle, shorthand);
120            foundShorthands[shorthand] = true;
121        } else {
122            var value = tempStyle.getPropertyValue(name);
123            var priority = tempStyle.getPropertyPriority(name);
124        }
125
126        // Set the property on the real style declaration.
127        style.setProperty((shorthand || name), value, priority);
128        changedProperties.push(shorthand || name);
129    }
130    return [InjectedScript._serializeStyle(style, true), changedProperties];
131}
132
133InjectedScript.setStyleText = function(style, cssText)
134{
135    style.cssText = cssText;
136}
137
138InjectedScript.toggleStyleEnabled = function(styleId, propertyName, disabled)
139{
140    var style = InjectedScript._styles[styleId];
141    if (!style)
142        return false;
143
144    if (disabled) {
145        if (!style.__disabledPropertyValues || !style.__disabledPropertyPriorities) {
146            var inspectedWindow = InjectedScript._window();
147            style.__disabledProperties = new inspectedWindow.Object;
148            style.__disabledPropertyValues = new inspectedWindow.Object;
149            style.__disabledPropertyPriorities = new inspectedWindow.Object;
150        }
151
152        style.__disabledPropertyValues[propertyName] = style.getPropertyValue(propertyName);
153        style.__disabledPropertyPriorities[propertyName] = style.getPropertyPriority(propertyName);
154
155        if (style.getPropertyShorthand(propertyName)) {
156            var longhandProperties = InjectedScript._getLonghandProperties(style, propertyName);
157            for (var i = 0; i < longhandProperties.length; ++i) {
158                style.__disabledProperties[longhandProperties[i]] = true;
159                style.removeProperty(longhandProperties[i]);
160            }
161        } else {
162            style.__disabledProperties[propertyName] = true;
163            style.removeProperty(propertyName);
164        }
165    } else if (style.__disabledProperties && style.__disabledProperties[propertyName]) {
166        var value = style.__disabledPropertyValues[propertyName];
167        var priority = style.__disabledPropertyPriorities[propertyName];
168
169        style.setProperty(propertyName, value, priority);
170        delete style.__disabledProperties[propertyName];
171        delete style.__disabledPropertyValues[propertyName];
172        delete style.__disabledPropertyPriorities[propertyName];
173    }
174    return InjectedScript._serializeStyle(style, true);
175}
176
177InjectedScript.applyStyleRuleText = function(ruleId, newContent, selectedNode)
178{
179    var rule = InjectedScript._styleRules[ruleId];
180    if (!rule)
181        return false;
182
183    try {
184        var stylesheet = rule.parentStyleSheet;
185        stylesheet.addRule(newContent);
186        var newRule = stylesheet.cssRules[stylesheet.cssRules.length - 1];
187        newRule.style.cssText = rule.style.cssText;
188
189        var parentRules = stylesheet.cssRules;
190        for (var i = 0; i < parentRules.length; ++i) {
191            if (parentRules[i] === rule) {
192                rule.parentStyleSheet.removeRule(i);
193                break;
194            }
195        }
196
197        var nodes = selectedNode.ownerDocument.querySelectorAll(newContent);
198        for (var i = 0; i < nodes.length; ++i) {
199            if (nodes[i] === selectedNode) {
200                return [InjectedScript._serializeRule(newRule), true];
201            }
202        }
203        return [InjectedScript._serializeRule(newRule), false];
204    } catch(e) {
205        // Report invalid syntax.
206        return false;
207    }
208}
209
210InjectedScript.addStyleSelector = function(newContent)
211{
212    var stylesheet = InjectedScript.stylesheet;
213    if (!stylesheet) {
214        var inspectedDocument = InjectedScript._window().document;
215        var head = inspectedDocument.getElementsByTagName("head")[0];
216        var styleElement = inspectedDocument.createElement("style");
217        styleElement.type = "text/css";
218        head.appendChild(styleElement);
219        stylesheet = inspectedDocument.styleSheets[inspectedDocument.styleSheets.length - 1];
220        InjectedScript.stylesheet = stylesheet;
221    }
222
223    try {
224        stylesheet.addRule(newContent);
225    } catch (e) {
226        // Invalid Syntax for a Selector
227        return false;
228    }
229
230    return InjectedScript._serializeRule(stylesheet.cssRules[stylesheet.cssRules.length - 1]);
231}
232
233InjectedScript.setStyleProperty = function(styleId, name, value) {
234    var style = InjectedScript._styles[styleId];
235    if (!style)
236        return false;
237
238    style.setProperty(name, value, "");
239    return true;
240}
241
242InjectedScript._serializeRule = function(rule)
243{
244    var parentStyleSheet = rule.parentStyleSheet;
245
246    var ruleValue = {};
247    ruleValue.selectorText = rule.selectorText;
248    if (parentStyleSheet) {
249        ruleValue.parentStyleSheet = {};
250        ruleValue.parentStyleSheet.href = parentStyleSheet.href;
251    }
252    ruleValue.isUserAgent = parentStyleSheet && !parentStyleSheet.ownerNode && !parentStyleSheet.href;
253    ruleValue.isUser = parentStyleSheet && parentStyleSheet.ownerNode && parentStyleSheet.ownerNode.nodeName == "#document";
254
255    // Bind editable scripts only.
256    var doBind = !ruleValue.isUserAgent && !ruleValue.isUser;
257    ruleValue.style = InjectedScript._serializeStyle(rule.style, doBind);
258
259    if (doBind) {
260        if (!rule._id) {
261            rule._id = InjectedScript._lastStyleRuleId++;
262            InjectedScript._styleRules[rule._id] = rule;
263        }
264        ruleValue.id = rule._id;
265    }
266    return ruleValue;
267}
268
269InjectedScript._serializeStyle = function(style, doBind)
270{
271    var result = {};
272    result.width = style.width;
273    result.height = style.height;
274    result.__disabledProperties = style.__disabledProperties;
275    result.__disabledPropertyValues = style.__disabledPropertyValues;
276    result.__disabledPropertyPriorities = style.__disabledPropertyPriorities;
277    result.properties = [];
278    result.shorthandValues = {};
279    var foundShorthands = {};
280    for (var i = 0; i < style.length; ++i) {
281        var property = {};
282        var name = style[i];
283        property.name = name;
284        property.priority = style.getPropertyPriority(name);
285        property.implicit = style.isPropertyImplicit(name);
286        var shorthand =  style.getPropertyShorthand(name);
287        property.shorthand = shorthand;
288        if (shorthand && !(shorthand in foundShorthands)) {
289            foundShorthands[shorthand] = true;
290            result.shorthandValues[shorthand] = InjectedScript._getShorthandValue(style, shorthand);
291        }
292        property.value = style.getPropertyValue(name);
293        result.properties.push(property);
294    }
295    result.uniqueStyleProperties = InjectedScript._getUniqueStyleProperties(style);
296
297    if (doBind) {
298        if (!style._id) {
299            style._id = InjectedScript._lastStyleId++;
300            InjectedScript._styles[style._id] = style;
301        }
302        result.id = style._id;
303    }
304    return result;
305}
306
307InjectedScript._getUniqueStyleProperties = function(style)
308{
309    var properties = [];
310    var foundProperties = {};
311
312    for (var i = 0; i < style.length; ++i) {
313        var property = style[i];
314        if (property in foundProperties)
315            continue;
316        foundProperties[property] = true;
317        properties.push(property);
318    }
319
320    return properties;
321}
322
323
324InjectedScript._getLonghandProperties = function(style, shorthandProperty)
325{
326    var properties = [];
327    var foundProperties = {};
328
329    for (var i = 0; i < style.length; ++i) {
330        var individualProperty = style[i];
331        if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty)
332            continue;
333        foundProperties[individualProperty] = true;
334        properties.push(individualProperty);
335    }
336
337    return properties;
338}
339
340InjectedScript._getShorthandValue = function(style, shorthandProperty)
341{
342    var value = style.getPropertyValue(shorthandProperty);
343    if (!value) {
344        // Some shorthands (like border) return a null value, so compute a shorthand value.
345        // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15823 is fixed.
346
347        var foundProperties = {};
348        for (var i = 0; i < style.length; ++i) {
349            var individualProperty = style[i];
350            if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty)
351                continue;
352
353            var individualValue = style.getPropertyValue(individualProperty);
354            if (style.isPropertyImplicit(individualProperty) || individualValue === "initial")
355                continue;
356
357            foundProperties[individualProperty] = true;
358
359            if (!value)
360                value = "";
361            else if (value.length)
362                value += " ";
363            value += individualValue;
364        }
365    }
366    return value;
367}
368
369InjectedScript._getShorthandPriority = function(style, shorthandProperty)
370{
371    var priority = style.getPropertyPriority(shorthandProperty);
372    if (!priority) {
373        for (var i = 0; i < style.length; ++i) {
374            var individualProperty = style[i];
375            if (style.getPropertyShorthand(individualProperty) !== shorthandProperty)
376                continue;
377            priority = style.getPropertyPriority(individualProperty);
378            break;
379        }
380    }
381    return priority;
382}
383
384InjectedScript.getPrototypes = function(nodeId)
385{
386    var node = InjectedScript._nodeForId(nodeId);
387    if (!node)
388        return false;
389
390    var result = [];
391    for (var prototype = node; prototype; prototype = prototype.__proto__) {
392        var title = Object.describe(prototype);
393        if (title.match(/Prototype$/)) {
394            title = title.replace(/Prototype$/, "");
395        }
396        result.push(title);
397    }
398    return result;
399}
400
401InjectedScript.getProperties = function(objectProxy, ignoreHasOwnProperty)
402{
403    var object = InjectedScript._resolveObject(objectProxy);
404    if (!object)
405        return false;
406
407    var properties = [];
408    // Go over properties, prepare results.
409    for (var propertyName in object) {
410        if (!ignoreHasOwnProperty && "hasOwnProperty" in object && !object.hasOwnProperty(propertyName))
411            continue;
412
413        //TODO: remove this once object becomes really remote.
414        if (propertyName === "__treeElementIdentifier")
415            continue;
416        var property = {};
417        property.name = propertyName;
418        var isGetter = object["__lookupGetter__"] && object.__lookupGetter__(propertyName);
419        if (!property.isGetter) {
420            var childObject = object[propertyName];
421            property.type = typeof childObject;
422            property.textContent = Object.describe(childObject, true);
423            property.parentObjectProxy = objectProxy;
424            var parentPath = objectProxy.path.slice();
425            property.childObjectProxy = {
426                objectId : objectProxy.objectId,
427                path : parentPath.splice(parentPath.length, 0, propertyName),
428                protoDepth : objectProxy.protoDepth
429            };
430            if (childObject && (property.type === "object" || property.type === "function")) {
431                for (var subPropertyName in childObject) {
432                    if (propertyName === "__treeElementIdentifier")
433                        continue;
434                    property.hasChildren = true;
435                    break;
436                }
437            }
438        } else {
439            // FIXME: this should show something like "getter" (bug 16734).
440            property.textContent = "\u2014"; // em dash
441            property.isGetter = true;
442        }
443        properties.push(property);
444    }
445    return properties;
446}
447
448InjectedScript.setPropertyValue = function(objectProxy, propertyName, expression)
449{
450    var object = InjectedScript._resolveObject(objectProxy);
451    if (!object)
452        return false;
453
454    var expressionLength = expression.length;
455    if (!expressionLength) {
456        delete object[propertyName];
457        return !(propertyName in object);
458    }
459
460    try {
461        // Surround the expression in parenthesis so the result of the eval is the result
462        // of the whole expression not the last potential sub-expression.
463
464        // There is a regression introduced here: eval is now happening against global object,
465        // not call frame while on a breakpoint.
466        // TODO: bring evaluation against call frame back.
467        var result = InjectedScript._window().eval("(" + expression + ")");
468        // Store the result in the property.
469        object[propertyName] = result;
470        return true;
471    } catch(e) {
472        try {
473            var result = InjectedScript._window().eval("\"" + expression.escapeCharacters("\"") + "\"");
474            object[propertyName] = result;
475            return true;
476        } catch(e) {
477            return false;
478        }
479    }
480}
481
482InjectedScript._resolveObject = function(objectProxy)
483{
484    var object = InjectedScript._objectForId(objectProxy.objectId);
485    var path = objectProxy.path;
486    var protoDepth = objectProxy.protoDepth;
487
488    // Follow the property path.
489    for (var i = 0; object && i < path.length; ++i)
490        object = object[path[i]];
491
492    // Get to the necessary proto layer.
493    for (var i = 0; object && i < protoDepth; ++i)
494        object = object.__proto__;
495
496    return object;
497}
498
499InjectedScript._window = function()
500{
501    // TODO: replace with 'return window;' once this script is injected into
502    // the page's context.
503    return InspectorController.inspectedWindow();
504}
505
506InjectedScript._nodeForId = function(nodeId)
507{
508    // TODO: replace with node lookup in the InspectorDOMAgent once DOMAgent nodes are used.
509    return nodeId;
510}
511
512InjectedScript._objectForId = function(objectId)
513{
514    // TODO: replace with node lookups for node ids and evaluation result lookups for the rest of ids.
515    return objectId;
516}
517