2 * Copyright (C) 2007 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
31 * @extends {WebInspector.ElementsSidebarPane}
33 WebInspector
.MetricsSidebarPane = function()
35 WebInspector
.ElementsSidebarPane
.call(this, WebInspector
.UIString("Metrics"));
38 WebInspector
.MetricsSidebarPane
.prototype = {
41 * @param {!WebInspector.Throttler.FinishCallback} finishedCallback
44 doUpdate: function(finishedCallback
)
46 // "style" attribute might have changed. Update metrics unless they are being edited
47 // (if a CSS property is added, a StyleSheetChanged event is dispatched).
48 if (this._isEditingMetrics
) {
53 // FIXME: avoid updates of a collapsed pane.
54 var node
= this.node();
55 var cssModel
= this.cssModel();
56 if (!node
|| node
.nodeType() !== Node
.ELEMENT_NODE
|| !cssModel
) {
57 this.element
.removeChildren();
63 * @param {?WebInspector.CSSStyleDeclaration} style
64 * @this {WebInspector.MetricsSidebarPane}
66 function callback(style
)
68 if (!style
|| this.node() !== node
)
70 this._updateMetrics(style
);
73 * @param {?WebInspector.CSSStyleModel.InlineStyleResult} inlineStyleResult
74 * @this {WebInspector.MetricsSidebarPane}
76 function inlineStyleCallback(inlineStyleResult
)
78 if (inlineStyleResult
&& this.node() === node
)
79 this.inlineStyle
= inlineStyleResult
.inlineStyle
;
83 cssModel
.computedStylePromise(node
.id
).then(callback
.bind(this)),
84 cssModel
.inlineStylesPromise(node
.id
).then(inlineStyleCallback
.bind(this))
87 .then(finishedCallback
.bind(null, undefined))
88 .catch(/** @type {function()} */(finishedCallback
));
94 onDOMModelChanged: function()
102 onCSSModelChanged: function()
110 onFrameResizedThrottled: function()
115 _getPropertyValueAsPx: function(style
, propertyName
)
117 return Number(style
.getPropertyValue(propertyName
).replace(/px$/, "") || 0);
120 _getBox: function(computedStyle
, componentName
)
122 var suffix
= componentName
=== "border" ? "-width" : "";
123 var left
= this._getPropertyValueAsPx(computedStyle
, componentName
+ "-left" + suffix
);
124 var top
= this._getPropertyValueAsPx(computedStyle
, componentName
+ "-top" + suffix
);
125 var right
= this._getPropertyValueAsPx(computedStyle
, componentName
+ "-right" + suffix
);
126 var bottom
= this._getPropertyValueAsPx(computedStyle
, componentName
+ "-bottom" + suffix
);
127 return { left
: left
, top
: top
, right
: right
, bottom
: bottom
};
131 * @param {boolean} showHighlight
132 * @param {string} mode
133 * @param {!Event} event
135 _highlightDOMNode: function(showHighlight
, mode
, event
)
138 if (showHighlight
&& this.node()) {
139 if (this._highlightMode
=== mode
)
141 this._highlightMode
= mode
;
142 this.node().highlight(mode
);
144 delete this._highlightMode
;
145 WebInspector
.DOMModel
.hideDOMNodeHighlight();
148 for (var i
= 0; this._boxElements
&& i
< this._boxElements
.length
; ++i
) {
149 var element
= this._boxElements
[i
];
150 if (!this.node() || mode
=== "all" || element
._name
=== mode
)
151 element
.style
.backgroundColor
= element
._backgroundColor
;
153 element
.style
.backgroundColor
= "";
158 * @param {!WebInspector.CSSStyleDeclaration} style
160 _updateMetrics: function(style
)
162 // Updating with computed style.
163 var metricsElement
= createElement("div");
164 metricsElement
.className
= "metrics";
168 * @param {!WebInspector.CSSStyleDeclaration} style
169 * @param {string} name
170 * @param {string} side
171 * @param {string} suffix
172 * @this {WebInspector.MetricsSidebarPane}
174 function createBoxPartElement(style
, name
, side
, suffix
)
176 var propertyName
= (name
!== "position" ? name
+ "-" : "") + side
+ suffix
;
177 var value
= style
.getPropertyValue(propertyName
);
178 if (value
=== "" || (name
!== "position" && value
=== "0px"))
180 else if (name
=== "position" && value
=== "auto")
182 value
= value
.replace(/px$/, "");
183 value
= Number
.toFixedIfFloating(value
);
185 var element
= createElement("div");
186 element
.className
= side
;
187 element
.textContent
= value
;
188 element
.addEventListener("dblclick", this.startEditing
.bind(this, element
, name
, propertyName
, style
), false);
192 function getContentAreaWidthPx(style
)
194 var width
= style
.getPropertyValue("width").replace(/px$/, "");
195 if (!isNaN(width
) && style
.getPropertyValue("box-sizing") === "border-box") {
196 var borderBox
= self
._getBox(style
, "border");
197 var paddingBox
= self
._getBox(style
, "padding");
199 width
= width
- borderBox
.left
- borderBox
.right
- paddingBox
.left
- paddingBox
.right
;
202 return Number
.toFixedIfFloating(width
);
205 function getContentAreaHeightPx(style
)
207 var height
= style
.getPropertyValue("height").replace(/px$/, "");
208 if (!isNaN(height
) && style
.getPropertyValue("box-sizing") === "border-box") {
209 var borderBox
= self
._getBox(style
, "border");
210 var paddingBox
= self
._getBox(style
, "padding");
212 height
= height
- borderBox
.top
- borderBox
.bottom
- paddingBox
.top
- paddingBox
.bottom
;
215 return Number
.toFixedIfFloating(height
);
218 // Display types for which margin is ignored.
219 var noMarginDisplayType
= {
221 "table-column": true,
222 "table-column-group": true,
223 "table-footer-group": true,
224 "table-header-group": true,
226 "table-row-group": true
229 // Display types for which padding is ignored.
230 var noPaddingDisplayType
= {
231 "table-column": true,
232 "table-column-group": true,
233 "table-footer-group": true,
234 "table-header-group": true,
236 "table-row-group": true
239 // Position types for which top, left, bottom and right are ignored.
240 var noPositionType
= {
244 var boxes
= ["content", "padding", "border", "margin", "position"];
246 WebInspector
.Color
.PageHighlight
.Content
,
247 WebInspector
.Color
.PageHighlight
.Padding
,
248 WebInspector
.Color
.PageHighlight
.Border
,
249 WebInspector
.Color
.PageHighlight
.Margin
,
250 WebInspector
.Color
.fromRGBA([0, 0, 0, 0])
252 var boxLabels
= [WebInspector
.UIString("content"), WebInspector
.UIString("padding"), WebInspector
.UIString("border"), WebInspector
.UIString("margin"), WebInspector
.UIString("position")];
253 var previousBox
= null;
254 this._boxElements
= [];
255 for (var i
= 0; i
< boxes
.length
; ++i
) {
258 if (name
=== "margin" && noMarginDisplayType
[style
.getPropertyValue("display")])
260 if (name
=== "padding" && noPaddingDisplayType
[style
.getPropertyValue("display")])
262 if (name
=== "position" && noPositionType
[style
.getPropertyValue("position")])
265 var boxElement
= createElement("div");
266 boxElement
.className
= name
;
267 boxElement
._backgroundColor
= boxColors
[i
].asString(WebInspector
.Color
.Format
.RGBA
);
268 boxElement
._name
= name
;
269 boxElement
.style
.backgroundColor
= boxElement
._backgroundColor
;
270 boxElement
.addEventListener("mouseover", this._highlightDOMNode
.bind(this, true, name
=== "position" ? "all" : name
), false);
271 this._boxElements
.push(boxElement
);
273 if (name
=== "content") {
274 var widthElement
= createElement("span");
275 widthElement
.textContent
= getContentAreaWidthPx(style
);
276 widthElement
.addEventListener("dblclick", this.startEditing
.bind(this, widthElement
, "width", "width", style
), false);
278 var heightElement
= createElement("span");
279 heightElement
.textContent
= getContentAreaHeightPx(style
);
280 heightElement
.addEventListener("dblclick", this.startEditing
.bind(this, heightElement
, "height", "height", style
), false);
282 boxElement
.appendChild(widthElement
);
283 boxElement
.createTextChild(" \u00D7 ");
284 boxElement
.appendChild(heightElement
);
286 var suffix
= (name
=== "border" ? "-width" : "");
288 var labelElement
= createElement("div");
289 labelElement
.className
= "label";
290 labelElement
.textContent
= boxLabels
[i
];
291 boxElement
.appendChild(labelElement
);
293 boxElement
.appendChild(createBoxPartElement
.call(this, style
, name
, "top", suffix
));
294 boxElement
.appendChild(createElement("br"));
295 boxElement
.appendChild(createBoxPartElement
.call(this, style
, name
, "left", suffix
));
298 boxElement
.appendChild(previousBox
);
300 boxElement
.appendChild(createBoxPartElement
.call(this, style
, name
, "right", suffix
));
301 boxElement
.appendChild(createElement("br"));
302 boxElement
.appendChild(createBoxPartElement
.call(this, style
, name
, "bottom", suffix
));
305 previousBox
= boxElement
;
308 metricsElement
.appendChild(previousBox
);
309 metricsElement
.addEventListener("mouseover", this._highlightDOMNode
.bind(this, false, "all"), false);
310 this.element
.removeChildren();
311 this.element
.appendChild(metricsElement
);
314 startEditing: function(targetElement
, box
, styleProperty
, computedStyle
)
316 if (WebInspector
.isBeingEdited(targetElement
))
319 var context
= { box
: box
, styleProperty
: styleProperty
, computedStyle
: computedStyle
};
320 var boundKeyDown
= this._handleKeyDown
.bind(this, context
, styleProperty
);
321 context
.keyDownHandler
= boundKeyDown
;
322 targetElement
.addEventListener("keydown", boundKeyDown
, false);
324 this._isEditingMetrics
= true;
326 var config
= new WebInspector
.InplaceEditor
.Config(this.editingCommitted
.bind(this), this.editingCancelled
.bind(this), context
);
327 WebInspector
.InplaceEditor
.startEditing(targetElement
, config
);
329 targetElement
.getComponentSelection().setBaseAndExtent(targetElement
, 0, targetElement
, 1);
332 _handleKeyDown: function(context
, styleProperty
, event
)
334 var element
= event
.currentTarget
;
337 * @param {string} originalValue
338 * @param {string} replacementString
339 * @this {WebInspector.MetricsSidebarPane}
341 function finishHandler(originalValue
, replacementString
)
343 this._applyUserInput(element
, replacementString
, originalValue
, context
, false);
347 * @param {string} prefix
348 * @param {number} number
349 * @param {string} suffix
352 function customNumberHandler(prefix
, number
, suffix
)
354 if (styleProperty
!== "margin" && number
< 0)
356 return prefix
+ number
+ suffix
;
359 WebInspector
.handleElementValueModifications(event
, element
, finishHandler
.bind(this), undefined, customNumberHandler
);
362 editingEnded: function(element
, context
)
364 delete this.originalPropertyData
;
365 delete this.previousPropertyDataCandidate
;
366 element
.removeEventListener("keydown", context
.keyDownHandler
, false);
367 delete this._isEditingMetrics
;
370 editingCancelled: function(element
, context
)
372 if ("originalPropertyData" in this && this.inlineStyle
) {
373 if (!this.originalPropertyData
) {
374 // An added property, remove the last property in the style.
375 var pastLastSourcePropertyIndex
= this.inlineStyle
.pastLastSourcePropertyIndex();
376 if (pastLastSourcePropertyIndex
)
377 this.inlineStyle
.allProperties
[pastLastSourcePropertyIndex
- 1].setText("", false);
379 this.inlineStyle
.allProperties
[this.originalPropertyData
.index
].setText(this.originalPropertyData
.propertyText
, false);
381 this.editingEnded(element
, context
);
385 _applyUserInput: function(element
, userInput
, previousContent
, context
, commitEditor
)
387 if (!this.inlineStyle
) {
388 // Element has no renderer.
389 return this.editingCancelled(element
, context
); // nothing changed, so cancel
392 if (commitEditor
&& userInput
=== previousContent
)
393 return this.editingCancelled(element
, context
); // nothing changed, so cancel
395 if (context
.box
!== "position" && (!userInput
|| userInput
=== "\u2012"))
397 else if (context
.box
=== "position" && (!userInput
|| userInput
=== "\u2012"))
400 userInput
= userInput
.toLowerCase();
401 // Append a "px" unit if the user input was just a number.
402 if (/^\d+$/.test(userInput
))
405 var styleProperty
= context
.styleProperty
;
406 var computedStyle
= context
.computedStyle
;
408 if (computedStyle
.getPropertyValue("box-sizing") === "border-box" && (styleProperty
=== "width" || styleProperty
=== "height")) {
409 if (!userInput
.match(/px$/)) {
410 WebInspector
.console
.error("For elements with box-sizing: border-box, only absolute content area dimensions can be applied");
414 var borderBox
= this._getBox(computedStyle
, "border");
415 var paddingBox
= this._getBox(computedStyle
, "padding");
416 var userValuePx
= Number(userInput
.replace(/px$/, ""));
417 if (isNaN(userValuePx
))
419 if (styleProperty
=== "width")
420 userValuePx
+= borderBox
.left
+ borderBox
.right
+ paddingBox
.left
+ paddingBox
.right
;
422 userValuePx
+= borderBox
.top
+ borderBox
.bottom
+ paddingBox
.top
+ paddingBox
.bottom
;
424 userInput
= userValuePx
+ "px";
427 this.previousPropertyDataCandidate
= null;
429 var allProperties
= this.inlineStyle
.allProperties
;
430 for (var i
= 0; i
< allProperties
.length
; ++i
) {
431 var property
= allProperties
[i
];
432 if (property
.name
!== context
.styleProperty
|| property
.inactive
)
435 this.previousPropertyDataCandidate
= property
;
436 property
.setValue(userInput
, commitEditor
, true, callback
.bind(this));
440 this.inlineStyle
.appendProperty(context
.styleProperty
, userInput
, callback
.bind(this));
443 * @param {?WebInspector.CSSStyleDeclaration} style
444 * @this {WebInspector.MetricsSidebarPane}
446 function callback(style
)
450 this.inlineStyle
= style
;
451 if (!("originalPropertyData" in this))
452 this.originalPropertyData
= this.previousPropertyDataCandidate
;
454 if (typeof this._highlightMode
!== "undefined")
455 this._node
.highlight(this._highlightMode
);
462 editingCommitted: function(element
, userInput
, previousContent
, context
)
464 this.editingEnded(element
, context
);
465 this._applyUserInput(element
, userInput
, previousContent
, context
, true);
468 __proto__
: WebInspector
.ElementsSidebarPane
.prototype