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