Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / elements / MetricsSidebarPane.js
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
29 /**
30  * @constructor
31  * @extends {WebInspector.ElementsSidebarPane}
32  */
33 WebInspector.MetricsSidebarPane = function()
34 {
35     WebInspector.ElementsSidebarPane.call(this, WebInspector.UIString("Metrics"));
36 }
37
38 WebInspector.MetricsSidebarPane.prototype = {
39     /**
40      * @param {?WebInspector.DOMNode} node
41      */
42     setNode: function(node)
43     {
44         WebInspector.ElementsSidebarPane.prototype.setNode.call(this, node);
45         this._updateTarget(node.target());
46     },
47
48     /**
49      * @param {!WebInspector.Target} target
50      */
51     _updateTarget: function(target)
52     {
53         if (this._target === target)
54             return;
55
56         if (this._target) {
57             this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this.update, this);
58             this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this.update, this);
59             this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this.update, this);
60             this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this.update, this);
61             this._target.domModel.removeEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributesUpdated, this);
62             this._target.domModel.removeEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributesUpdated, this);
63             this._target.resourceTreeModel.removeEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameResized, this.update, this);
64         }
65         this._target = target;
66         this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this.update, this);
67         this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this.update, this);
68         this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this.update, this);
69         this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this.update, this);
70         this._target.domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributesUpdated, this);
71         this._target.domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributesUpdated, this);
72         this._target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameResized, this.update, this);
73     },
74
75     /**
76      * @param {!WebInspector.Throttler.FinishCallback} finishedCallback
77      * @protected
78      */
79     doUpdate: function(finishedCallback)
80     {
81         // "style" attribute might have changed. Update metrics unless they are being edited
82         // (if a CSS property is added, a StyleSheetChanged event is dispatched).
83         if (this._isEditingMetrics) {
84             finishedCallback();
85             return;
86         }
87
88         // FIXME: avoid updates of a collapsed pane.
89         var node = this.node();
90
91         if (!node || node.nodeType() !== Node.ELEMENT_NODE) {
92             this.bodyElement.removeChildren();
93             finishedCallback();
94             return;
95         }
96
97         /**
98          * @param {?WebInspector.CSSStyleDeclaration} style
99          * @this {WebInspector.MetricsSidebarPane}
100          */
101         function callback(style)
102         {
103             if (!style || this.node() !== node)
104                 return;
105             this._updateMetrics(style);
106         }
107         this._target.cssModel.getComputedStyleAsync(node.id, callback.bind(this));
108
109         /**
110          * @param {?WebInspector.CSSStyleDeclaration} style
111          * @this {WebInspector.MetricsSidebarPane}
112          */
113         function inlineStyleCallback(style)
114         {
115             if (style && this.node() === node)
116                 this.inlineStyle = style;
117             finishedCallback();
118         }
119         this._target.cssModel.getInlineStylesAsync(node.id, inlineStyleCallback.bind(this));
120     },
121
122     _attributesUpdated: function(event)
123     {
124         if (this.node() !== event.data.node)
125             return;
126
127         this.update();
128     },
129
130     _getPropertyValueAsPx: function(style, propertyName)
131     {
132         return Number(style.getPropertyValue(propertyName).replace(/px$/, "") || 0);
133     },
134
135     _getBox: function(computedStyle, componentName)
136     {
137         var suffix = componentName === "border" ? "-width" : "";
138         var left = this._getPropertyValueAsPx(computedStyle, componentName + "-left" + suffix);
139         var top = this._getPropertyValueAsPx(computedStyle, componentName + "-top" + suffix);
140         var right = this._getPropertyValueAsPx(computedStyle, componentName + "-right" + suffix);
141         var bottom = this._getPropertyValueAsPx(computedStyle, componentName + "-bottom" + suffix);
142         return { left: left, top: top, right: right, bottom: bottom };
143     },
144
145     /**
146      * @param {boolean} showHighlight
147      * @param {string} mode
148      * @param {!Event} event
149      */
150     _highlightDOMNode: function(showHighlight, mode, event)
151     {
152         event.consume();
153         if (showHighlight && this.node()) {
154             if (this._highlightMode === mode)
155                 return;
156             this._highlightMode = mode;
157             this.node().highlight(mode);
158         } else {
159             delete this._highlightMode;
160             this._target.domModel.hideDOMNodeHighlight();
161         }
162
163         for (var i = 0; this._boxElements && i < this._boxElements.length; ++i) {
164             var element = this._boxElements[i];
165             if (!this.node() || mode === "all" || element._name === mode)
166                 element.style.backgroundColor = element._backgroundColor;
167             else
168                 element.style.backgroundColor = "";
169         }
170     },
171
172     /**
173      * @param {!WebInspector.CSSStyleDeclaration} style
174      */
175     _updateMetrics: function(style)
176     {
177         // Updating with computed style.
178         var metricsElement = createElement("div");
179         metricsElement.className = "metrics";
180         var self = this;
181
182         /**
183          * @param {!WebInspector.CSSStyleDeclaration} style
184          * @param {string} name
185          * @param {string} side
186          * @param {string} suffix
187          * @this {WebInspector.MetricsSidebarPane}
188          */
189         function createBoxPartElement(style, name, side, suffix)
190         {
191             var propertyName = (name !== "position" ? name + "-" : "") + side + suffix;
192             var value = style.getPropertyValue(propertyName);
193             if (value === "" || (name !== "position" && value === "0px"))
194                 value = "\u2012";
195             else if (name === "position" && value === "auto")
196                 value = "\u2012";
197             value = value.replace(/px$/, "");
198             value = Number.toFixedIfFloating(value);
199
200             var element = createElement("div");
201             element.className = side;
202             element.textContent = value;
203             element.addEventListener("dblclick", this.startEditing.bind(this, element, name, propertyName, style), false);
204             return element;
205         }
206
207         function getContentAreaWidthPx(style)
208         {
209             var width = style.getPropertyValue("width").replace(/px$/, "");
210             if (!isNaN(width) && style.getPropertyValue("box-sizing") === "border-box") {
211                 var borderBox = self._getBox(style, "border");
212                 var paddingBox = self._getBox(style, "padding");
213
214                 width = width - borderBox.left - borderBox.right - paddingBox.left - paddingBox.right;
215             }
216
217             return Number.toFixedIfFloating(width);
218         }
219
220         function getContentAreaHeightPx(style)
221         {
222             var height = style.getPropertyValue("height").replace(/px$/, "");
223             if (!isNaN(height) && style.getPropertyValue("box-sizing") === "border-box") {
224                 var borderBox = self._getBox(style, "border");
225                 var paddingBox = self._getBox(style, "padding");
226
227                 height = height - borderBox.top - borderBox.bottom - paddingBox.top - paddingBox.bottom;
228             }
229
230             return Number.toFixedIfFloating(height);
231         }
232
233         // Display types for which margin is ignored.
234         var noMarginDisplayType = {
235             "table-cell": true,
236             "table-column": true,
237             "table-column-group": true,
238             "table-footer-group": true,
239             "table-header-group": true,
240             "table-row": true,
241             "table-row-group": true
242         };
243
244         // Display types for which padding is ignored.
245         var noPaddingDisplayType = {
246             "table-column": true,
247             "table-column-group": true,
248             "table-footer-group": true,
249             "table-header-group": true,
250             "table-row": true,
251             "table-row-group": true
252         };
253
254         // Position types for which top, left, bottom and right are ignored.
255         var noPositionType = {
256             "static": true
257         };
258
259         var boxes = ["content", "padding", "border", "margin", "position"];
260         var boxColors = [
261             WebInspector.Color.PageHighlight.Content,
262             WebInspector.Color.PageHighlight.Padding,
263             WebInspector.Color.PageHighlight.Border,
264             WebInspector.Color.PageHighlight.Margin,
265             WebInspector.Color.fromRGBA([0, 0, 0, 0])
266         ];
267         var boxLabels = [WebInspector.UIString("content"), WebInspector.UIString("padding"), WebInspector.UIString("border"), WebInspector.UIString("margin"), WebInspector.UIString("position")];
268         var previousBox = null;
269         this._boxElements = [];
270         for (var i = 0; i < boxes.length; ++i) {
271             var name = boxes[i];
272
273             if (name === "margin" && noMarginDisplayType[style.getPropertyValue("display")])
274                 continue;
275             if (name === "padding" && noPaddingDisplayType[style.getPropertyValue("display")])
276                 continue;
277             if (name === "position" && noPositionType[style.getPropertyValue("position")])
278                 continue;
279
280             var boxElement = createElement("div");
281             boxElement.className = name;
282             boxElement._backgroundColor = boxColors[i].toString(WebInspector.Color.Format.RGBA);
283             boxElement._name = name;
284             boxElement.style.backgroundColor = boxElement._backgroundColor;
285             boxElement.addEventListener("mouseover", this._highlightDOMNode.bind(this, true, name === "position" ? "all" : name), false);
286             this._boxElements.push(boxElement);
287
288             if (name === "content") {
289                 var widthElement = createElement("span");
290                 widthElement.textContent = getContentAreaWidthPx(style);
291                 widthElement.addEventListener("dblclick", this.startEditing.bind(this, widthElement, "width", "width", style), false);
292
293                 var heightElement = createElement("span");
294                 heightElement.textContent = getContentAreaHeightPx(style);
295                 heightElement.addEventListener("dblclick", this.startEditing.bind(this, heightElement, "height", "height", style), false);
296
297                 boxElement.appendChild(widthElement);
298                 boxElement.createTextChild(" \u00D7 ");
299                 boxElement.appendChild(heightElement);
300             } else {
301                 var suffix = (name === "border" ? "-width" : "");
302
303                 var labelElement = createElement("div");
304                 labelElement.className = "label";
305                 labelElement.textContent = boxLabels[i];
306                 boxElement.appendChild(labelElement);
307
308                 boxElement.appendChild(createBoxPartElement.call(this, style, name, "top", suffix));
309                 boxElement.appendChild(createElement("br"));
310                 boxElement.appendChild(createBoxPartElement.call(this, style, name, "left", suffix));
311
312                 if (previousBox)
313                     boxElement.appendChild(previousBox);
314
315                 boxElement.appendChild(createBoxPartElement.call(this, style, name, "right", suffix));
316                 boxElement.appendChild(createElement("br"));
317                 boxElement.appendChild(createBoxPartElement.call(this, style, name, "bottom", suffix));
318             }
319
320             previousBox = boxElement;
321         }
322
323         metricsElement.appendChild(previousBox);
324         metricsElement.addEventListener("mouseover", this._highlightDOMNode.bind(this, false, "all"), false);
325         this.bodyElement.removeChildren();
326         this.bodyElement.appendChild(metricsElement);
327     },
328
329     startEditing: function(targetElement, box, styleProperty, computedStyle)
330     {
331         if (WebInspector.isBeingEdited(targetElement))
332             return;
333
334         var context = { box: box, styleProperty: styleProperty, computedStyle: computedStyle };
335         var boundKeyDown = this._handleKeyDown.bind(this, context, styleProperty);
336         context.keyDownHandler = boundKeyDown;
337         targetElement.addEventListener("keydown", boundKeyDown, false);
338
339         this._isEditingMetrics = true;
340
341         var config = new WebInspector.InplaceEditor.Config(this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
342         WebInspector.InplaceEditor.startEditing(targetElement, config);
343
344         window.getSelection().setBaseAndExtent(targetElement, 0, targetElement, 1);
345     },
346
347     _handleKeyDown: function(context, styleProperty, event)
348     {
349         var element = event.currentTarget;
350
351         /**
352          * @param {string} originalValue
353          * @param {string} replacementString
354          * @this {WebInspector.MetricsSidebarPane}
355          */
356         function finishHandler(originalValue, replacementString)
357         {
358             this._applyUserInput(element, replacementString, originalValue, context, false);
359         }
360
361         /**
362          * @param {string} prefix
363          * @param {number} number
364          * @param {string} suffix
365          * @return {string}
366          */
367         function customNumberHandler(prefix, number, suffix)
368         {
369             if (styleProperty !== "margin" && number < 0)
370                 number = 0;
371             return prefix + number + suffix;
372         }
373
374         WebInspector.handleElementValueModifications(event, element, finishHandler.bind(this), undefined, customNumberHandler);
375     },
376
377     editingEnded: function(element, context)
378     {
379         delete this.originalPropertyData;
380         delete this.previousPropertyDataCandidate;
381         element.removeEventListener("keydown", context.keyDownHandler, false);
382         delete this._isEditingMetrics;
383     },
384
385     editingCancelled: function(element, context)
386     {
387         if ("originalPropertyData" in this && this.inlineStyle) {
388             if (!this.originalPropertyData) {
389                 // An added property, remove the last property in the style.
390                 var pastLastSourcePropertyIndex = this.inlineStyle.pastLastSourcePropertyIndex();
391                 if (pastLastSourcePropertyIndex)
392                     this.inlineStyle.allProperties[pastLastSourcePropertyIndex - 1].setText("", false);
393             } else
394                 this.inlineStyle.allProperties[this.originalPropertyData.index].setText(this.originalPropertyData.propertyText, false);
395         }
396         this.editingEnded(element, context);
397         this.update();
398     },
399
400     _applyUserInput: function(element, userInput, previousContent, context, commitEditor)
401     {
402         if (!this.inlineStyle) {
403             // Element has no renderer.
404             return this.editingCancelled(element, context); // nothing changed, so cancel
405         }
406
407         if (commitEditor && userInput === previousContent)
408             return this.editingCancelled(element, context); // nothing changed, so cancel
409
410         if (context.box !== "position" && (!userInput || userInput === "\u2012"))
411             userInput = "0px";
412         else if (context.box === "position" && (!userInput || userInput === "\u2012"))
413             userInput = "auto";
414
415         userInput = userInput.toLowerCase();
416         // Append a "px" unit if the user input was just a number.
417         if (/^\d+$/.test(userInput))
418             userInput += "px";
419
420         var styleProperty = context.styleProperty;
421         var computedStyle = context.computedStyle;
422
423         if (computedStyle.getPropertyValue("box-sizing") === "border-box" && (styleProperty === "width" || styleProperty === "height")) {
424             if (!userInput.match(/px$/)) {
425                 WebInspector.console.error("For elements with box-sizing: border-box, only absolute content area dimensions can be applied");
426                 return;
427             }
428
429             var borderBox = this._getBox(computedStyle, "border");
430             var paddingBox = this._getBox(computedStyle, "padding");
431             var userValuePx = Number(userInput.replace(/px$/, ""));
432             if (isNaN(userValuePx))
433                 return;
434             if (styleProperty === "width")
435                 userValuePx += borderBox.left + borderBox.right + paddingBox.left + paddingBox.right;
436             else
437                 userValuePx += borderBox.top + borderBox.bottom + paddingBox.top + paddingBox.bottom;
438
439             userInput = userValuePx + "px";
440         }
441
442         this.previousPropertyDataCandidate = null;
443
444         var allProperties = this.inlineStyle.allProperties;
445         for (var i = 0; i < allProperties.length; ++i) {
446             var property = allProperties[i];
447             if (property.name !== context.styleProperty || property.inactive)
448                 continue;
449
450             this.previousPropertyDataCandidate = property;
451             property.setValue(userInput, commitEditor, true, callback.bind(this));
452             return;
453         }
454
455         this.inlineStyle.appendProperty(context.styleProperty, userInput, callback.bind(this));
456
457         /**
458          * @param {?WebInspector.CSSStyleDeclaration} style
459          * @this {WebInspector.MetricsSidebarPane}
460          */
461         function callback(style)
462         {
463             if (!style)
464                 return;
465             this.inlineStyle = style;
466             if (!("originalPropertyData" in this))
467                 this.originalPropertyData = this.previousPropertyDataCandidate;
468
469             if (typeof this._highlightMode !== "undefined")
470                 this._node.highlight(this._highlightMode);
471
472             if (commitEditor)
473                 this.update();
474         }
475     },
476
477     editingCommitted: function(element, userInput, previousContent, context)
478     {
479         this.editingEnded(element, context);
480         this._applyUserInput(element, userInput, previousContent, context, true);
481     },
482
483     __proto__: WebInspector.ElementsSidebarPane.prototype
484 }