OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2007 Apple Inc. All rights reserved. | 2 * Copyright (C) 2007 Apple Inc. All rights reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions | 5 * modification, are permitted provided that the following conditions |
6 * are met: | 6 * are met: |
7 * | 7 * |
8 * 1. Redistributions of source code must retain the above copyright | 8 * 1. Redistributions of source code must retain the above copyright |
9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
10 * 2. Redistributions in binary form must reproduce the above copyright | 10 * 2. Redistributions in binary form must reproduce the above copyright |
11 * notice, this list of conditions and the following disclaimer in the | 11 * notice, this list of conditions and the following disclaimer in the |
12 * documentation and/or other materials provided with the distribution. | 12 * documentation and/or other materials provided with the distribution. |
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of | 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 | 14 * its contributors may be used to endorse or promote products derived |
15 * from this software without specific prior written permission. | 15 * from this software without specific prior written permission. |
16 * | 16 * |
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY | 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 | 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 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 | 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | 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 | 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 | 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 | 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. | 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
27 */ | 27 */ |
28 | |
29 /** | 28 /** |
30 * @constructor | 29 * @unrestricted |
31 * @extends {WebInspector.ElementsSidebarPane} | |
32 */ | 30 */ |
33 WebInspector.MetricsSidebarPane = function() | 31 WebInspector.MetricsSidebarPane = class extends WebInspector.ElementsSidebarPane
{ |
34 { | 32 constructor() { |
35 WebInspector.ElementsSidebarPane.call(this); | 33 super(); |
| 34 } |
| 35 |
| 36 /** |
| 37 * @override |
| 38 * @protected |
| 39 * @return {!Promise.<?>} |
| 40 */ |
| 41 doUpdate() { |
| 42 // "style" attribute might have changed. Update metrics unless they are bein
g edited |
| 43 // (if a CSS property is added, a StyleSheetChanged event is dispatched). |
| 44 if (this._isEditingMetrics) |
| 45 return Promise.resolve(); |
| 46 |
| 47 // FIXME: avoid updates of a collapsed pane. |
| 48 var node = this.node(); |
| 49 var cssModel = this.cssModel(); |
| 50 if (!node || node.nodeType() !== Node.ELEMENT_NODE || !cssModel) { |
| 51 this.element.removeChildren(); |
| 52 return Promise.resolve(); |
| 53 } |
| 54 |
| 55 /** |
| 56 * @param {?Map.<string, string>} style |
| 57 * @this {WebInspector.MetricsSidebarPane} |
| 58 */ |
| 59 function callback(style) { |
| 60 if (!style || this.node() !== node) |
| 61 return; |
| 62 this._updateMetrics(style); |
| 63 } |
| 64 /** |
| 65 * @param {?WebInspector.CSSModel.InlineStyleResult} inlineStyleResult |
| 66 * @this {WebInspector.MetricsSidebarPane} |
| 67 */ |
| 68 function inlineStyleCallback(inlineStyleResult) { |
| 69 if (inlineStyleResult && this.node() === node) |
| 70 this.inlineStyle = inlineStyleResult.inlineStyle; |
| 71 } |
| 72 |
| 73 var promises = [ |
| 74 cssModel.computedStylePromise(node.id).then(callback.bind(this)), |
| 75 cssModel.inlineStylesPromise(node.id).then(inlineStyleCallback.bind(this)) |
| 76 ]; |
| 77 return Promise.all(promises); |
| 78 } |
| 79 |
| 80 /** |
| 81 * @override |
| 82 */ |
| 83 onCSSModelChanged() { |
| 84 this.update(); |
| 85 } |
| 86 |
| 87 /** |
| 88 * @param {!Map.<string, string>} style |
| 89 * @param {string} propertyName |
| 90 * @return {number} |
| 91 */ |
| 92 _getPropertyValueAsPx(style, propertyName) { |
| 93 return Number(style.get(propertyName).replace(/px$/, '') || 0); |
| 94 } |
| 95 |
| 96 /** |
| 97 * @param {!Map.<string, string>} computedStyle |
| 98 * @param {string} componentName |
| 99 */ |
| 100 _getBox(computedStyle, componentName) { |
| 101 var suffix = componentName === 'border' ? '-width' : ''; |
| 102 var left = this._getPropertyValueAsPx(computedStyle, componentName + '-left'
+ suffix); |
| 103 var top = this._getPropertyValueAsPx(computedStyle, componentName + '-top' +
suffix); |
| 104 var right = this._getPropertyValueAsPx(computedStyle, componentName + '-righ
t' + suffix); |
| 105 var bottom = this._getPropertyValueAsPx(computedStyle, componentName + '-bot
tom' + suffix); |
| 106 return {left: left, top: top, right: right, bottom: bottom}; |
| 107 } |
| 108 |
| 109 /** |
| 110 * @param {boolean} showHighlight |
| 111 * @param {string} mode |
| 112 * @param {!Event} event |
| 113 */ |
| 114 _highlightDOMNode(showHighlight, mode, event) { |
| 115 event.consume(); |
| 116 if (showHighlight && this.node()) { |
| 117 if (this._highlightMode === mode) |
| 118 return; |
| 119 this._highlightMode = mode; |
| 120 this.node().highlight(mode); |
| 121 } else { |
| 122 delete this._highlightMode; |
| 123 WebInspector.DOMModel.hideDOMNodeHighlight(); |
| 124 } |
| 125 |
| 126 for (var i = 0; this._boxElements && i < this._boxElements.length; ++i) { |
| 127 var element = this._boxElements[i]; |
| 128 if (!this.node() || mode === 'all' || element._name === mode) |
| 129 element.style.backgroundColor = element._backgroundColor; |
| 130 else |
| 131 element.style.backgroundColor = ''; |
| 132 } |
| 133 } |
| 134 |
| 135 /** |
| 136 * @param {!Map.<string, string>} style |
| 137 */ |
| 138 _updateMetrics(style) { |
| 139 // Updating with computed style. |
| 140 var metricsElement = createElement('div'); |
| 141 metricsElement.className = 'metrics'; |
| 142 var self = this; |
| 143 |
| 144 /** |
| 145 * @param {!Map.<string, string>} style |
| 146 * @param {string} name |
| 147 * @param {string} side |
| 148 * @param {string} suffix |
| 149 * @this {WebInspector.MetricsSidebarPane} |
| 150 */ |
| 151 function createBoxPartElement(style, name, side, suffix) { |
| 152 var propertyName = (name !== 'position' ? name + '-' : '') + side + suffix
; |
| 153 var value = style.get(propertyName); |
| 154 if (value === '' || (name !== 'position' && value === '0px')) |
| 155 value = '\u2012'; |
| 156 else if (name === 'position' && value === 'auto') |
| 157 value = '\u2012'; |
| 158 value = value.replace(/px$/, ''); |
| 159 value = Number.toFixedIfFloating(value); |
| 160 |
| 161 var element = createElement('div'); |
| 162 element.className = side; |
| 163 element.textContent = value; |
| 164 element.addEventListener('dblclick', this.startEditing.bind(this, element,
name, propertyName, style), false); |
| 165 return element; |
| 166 } |
| 167 |
| 168 /** |
| 169 * @param {!Map.<string, string>} style |
| 170 * @return {string} |
| 171 */ |
| 172 function getContentAreaWidthPx(style) { |
| 173 var width = style.get('width').replace(/px$/, ''); |
| 174 if (!isNaN(width) && style.get('box-sizing') === 'border-box') { |
| 175 var borderBox = self._getBox(style, 'border'); |
| 176 var paddingBox = self._getBox(style, 'padding'); |
| 177 |
| 178 width = width - borderBox.left - borderBox.right - paddingBox.left - pad
dingBox.right; |
| 179 } |
| 180 |
| 181 return Number.toFixedIfFloating(width.toString()); |
| 182 } |
| 183 |
| 184 /** |
| 185 * @param {!Map.<string, string>} style |
| 186 * @return {string} |
| 187 */ |
| 188 function getContentAreaHeightPx(style) { |
| 189 var height = style.get('height').replace(/px$/, ''); |
| 190 if (!isNaN(height) && style.get('box-sizing') === 'border-box') { |
| 191 var borderBox = self._getBox(style, 'border'); |
| 192 var paddingBox = self._getBox(style, 'padding'); |
| 193 |
| 194 height = height - borderBox.top - borderBox.bottom - paddingBox.top - pa
ddingBox.bottom; |
| 195 } |
| 196 |
| 197 return Number.toFixedIfFloating(height.toString()); |
| 198 } |
| 199 |
| 200 // Display types for which margin is ignored. |
| 201 var noMarginDisplayType = { |
| 202 'table-cell': true, |
| 203 'table-column': true, |
| 204 'table-column-group': true, |
| 205 'table-footer-group': true, |
| 206 'table-header-group': true, |
| 207 'table-row': true, |
| 208 'table-row-group': true |
| 209 }; |
| 210 |
| 211 // Display types for which padding is ignored. |
| 212 var noPaddingDisplayType = { |
| 213 'table-column': true, |
| 214 'table-column-group': true, |
| 215 'table-footer-group': true, |
| 216 'table-header-group': true, |
| 217 'table-row': true, |
| 218 'table-row-group': true |
| 219 }; |
| 220 |
| 221 // Position types for which top, left, bottom and right are ignored. |
| 222 var noPositionType = {'static': true}; |
| 223 |
| 224 var boxes = ['content', 'padding', 'border', 'margin', 'position']; |
| 225 var boxColors = [ |
| 226 WebInspector.Color.PageHighlight.Content, WebInspector.Color.PageHighlight
.Padding, |
| 227 WebInspector.Color.PageHighlight.Border, WebInspector.Color.PageHighlight.
Margin, |
| 228 WebInspector.Color.fromRGBA([0, 0, 0, 0]) |
| 229 ]; |
| 230 var boxLabels = [ |
| 231 WebInspector.UIString('content'), WebInspector.UIString('padding'), WebIns
pector.UIString('border'), |
| 232 WebInspector.UIString('margin'), WebInspector.UIString('position') |
| 233 ]; |
| 234 var previousBox = null; |
| 235 this._boxElements = []; |
| 236 for (var i = 0; i < boxes.length; ++i) { |
| 237 var name = boxes[i]; |
| 238 |
| 239 if (name === 'margin' && noMarginDisplayType[style.get('display')]) |
| 240 continue; |
| 241 if (name === 'padding' && noPaddingDisplayType[style.get('display')]) |
| 242 continue; |
| 243 if (name === 'position' && noPositionType[style.get('position')]) |
| 244 continue; |
| 245 |
| 246 var boxElement = createElement('div'); |
| 247 boxElement.className = name; |
| 248 boxElement._backgroundColor = boxColors[i].asString(WebInspector.Color.For
mat.RGBA); |
| 249 boxElement._name = name; |
| 250 boxElement.style.backgroundColor = boxElement._backgroundColor; |
| 251 boxElement.addEventListener( |
| 252 'mouseover', this._highlightDOMNode.bind(this, true, name === 'positio
n' ? 'all' : name), false); |
| 253 this._boxElements.push(boxElement); |
| 254 |
| 255 if (name === 'content') { |
| 256 var widthElement = createElement('span'); |
| 257 widthElement.textContent = getContentAreaWidthPx(style); |
| 258 widthElement.addEventListener( |
| 259 'dblclick', this.startEditing.bind(this, widthElement, 'width', 'wid
th', style), false); |
| 260 |
| 261 var heightElement = createElement('span'); |
| 262 heightElement.textContent = getContentAreaHeightPx(style); |
| 263 heightElement.addEventListener( |
| 264 'dblclick', this.startEditing.bind(this, heightElement, 'height', 'h
eight', style), false); |
| 265 |
| 266 boxElement.appendChild(widthElement); |
| 267 boxElement.createTextChild(' \u00D7 '); |
| 268 boxElement.appendChild(heightElement); |
| 269 } else { |
| 270 var suffix = (name === 'border' ? '-width' : ''); |
| 271 |
| 272 var labelElement = createElement('div'); |
| 273 labelElement.className = 'label'; |
| 274 labelElement.textContent = boxLabels[i]; |
| 275 boxElement.appendChild(labelElement); |
| 276 |
| 277 boxElement.appendChild(createBoxPartElement.call(this, style, name, 'top
', suffix)); |
| 278 boxElement.appendChild(createElement('br')); |
| 279 boxElement.appendChild(createBoxPartElement.call(this, style, name, 'lef
t', suffix)); |
| 280 |
| 281 if (previousBox) |
| 282 boxElement.appendChild(previousBox); |
| 283 |
| 284 boxElement.appendChild(createBoxPartElement.call(this, style, name, 'rig
ht', suffix)); |
| 285 boxElement.appendChild(createElement('br')); |
| 286 boxElement.appendChild(createBoxPartElement.call(this, style, name, 'bot
tom', suffix)); |
| 287 } |
| 288 |
| 289 previousBox = boxElement; |
| 290 } |
| 291 |
| 292 metricsElement.appendChild(previousBox); |
| 293 metricsElement.addEventListener('mouseover', this._highlightDOMNode.bind(thi
s, false, 'all'), false); |
| 294 this.element.removeChildren(); |
| 295 this.element.appendChild(metricsElement); |
| 296 } |
| 297 |
| 298 /** |
| 299 * @param {!Element} targetElement |
| 300 * @param {string} box |
| 301 * @param {string} styleProperty |
| 302 * @param {!Map.<string, string>} computedStyle |
| 303 */ |
| 304 startEditing(targetElement, box, styleProperty, computedStyle) { |
| 305 if (WebInspector.isBeingEdited(targetElement)) |
| 306 return; |
| 307 |
| 308 var context = {box: box, styleProperty: styleProperty, computedStyle: comput
edStyle}; |
| 309 var boundKeyDown = this._handleKeyDown.bind(this, context, styleProperty); |
| 310 context.keyDownHandler = boundKeyDown; |
| 311 targetElement.addEventListener('keydown', boundKeyDown, false); |
| 312 |
| 313 this._isEditingMetrics = true; |
| 314 |
| 315 var config = new WebInspector.InplaceEditor.Config( |
| 316 this._editingCommitted.bind(this), this.editingCancelled.bind(this), con
text); |
| 317 WebInspector.InplaceEditor.startEditing(targetElement, config); |
| 318 |
| 319 targetElement.getComponentSelection().setBaseAndExtent(targetElement, 0, tar
getElement, 1); |
| 320 } |
| 321 |
| 322 _handleKeyDown(context, styleProperty, event) { |
| 323 var element = event.currentTarget; |
| 324 |
| 325 /** |
| 326 * @param {string} originalValue |
| 327 * @param {string} replacementString |
| 328 * @this {WebInspector.MetricsSidebarPane} |
| 329 */ |
| 330 function finishHandler(originalValue, replacementString) { |
| 331 this._applyUserInput(element, replacementString, originalValue, context, f
alse); |
| 332 } |
| 333 |
| 334 /** |
| 335 * @param {string} prefix |
| 336 * @param {number} number |
| 337 * @param {string} suffix |
| 338 * @return {string} |
| 339 */ |
| 340 function customNumberHandler(prefix, number, suffix) { |
| 341 if (styleProperty !== 'margin' && number < 0) |
| 342 number = 0; |
| 343 return prefix + number + suffix; |
| 344 } |
| 345 |
| 346 WebInspector.handleElementValueModifications( |
| 347 event, element, finishHandler.bind(this), undefined, customNumberHandler
); |
| 348 } |
| 349 |
| 350 editingEnded(element, context) { |
| 351 delete this.originalPropertyData; |
| 352 delete this.previousPropertyDataCandidate; |
| 353 element.removeEventListener('keydown', context.keyDownHandler, false); |
| 354 delete this._isEditingMetrics; |
| 355 } |
| 356 |
| 357 editingCancelled(element, context) { |
| 358 if ('originalPropertyData' in this && this.inlineStyle) { |
| 359 if (!this.originalPropertyData) { |
| 360 // An added property, remove the last property in the style. |
| 361 var pastLastSourcePropertyIndex = this.inlineStyle.pastLastSourcePropert
yIndex(); |
| 362 if (pastLastSourcePropertyIndex) |
| 363 this.inlineStyle.allProperties[pastLastSourcePropertyIndex - 1].setTex
t('', false); |
| 364 } else |
| 365 this.inlineStyle.allProperties[this.originalPropertyData.index].setText( |
| 366 this.originalPropertyData.propertyText, false); |
| 367 } |
| 368 this.editingEnded(element, context); |
| 369 this.update(); |
| 370 } |
| 371 |
| 372 _applyUserInput(element, userInput, previousContent, context, commitEditor) { |
| 373 if (!this.inlineStyle) { |
| 374 // Element has no renderer. |
| 375 return this.editingCancelled(element, context); // nothing changed, so ca
ncel |
| 376 } |
| 377 |
| 378 if (commitEditor && userInput === previousContent) |
| 379 return this.editingCancelled(element, context); // nothing changed, so ca
ncel |
| 380 |
| 381 if (context.box !== 'position' && (!userInput || userInput === '\u2012')) |
| 382 userInput = '0px'; |
| 383 else if (context.box === 'position' && (!userInput || userInput === '\u2012'
)) |
| 384 userInput = 'auto'; |
| 385 |
| 386 userInput = userInput.toLowerCase(); |
| 387 // Append a "px" unit if the user input was just a number. |
| 388 if (/^\d+$/.test(userInput)) |
| 389 userInput += 'px'; |
| 390 |
| 391 var styleProperty = context.styleProperty; |
| 392 var computedStyle = context.computedStyle; |
| 393 |
| 394 if (computedStyle.get('box-sizing') === 'border-box' && (styleProperty === '
width' || styleProperty === 'height')) { |
| 395 if (!userInput.match(/px$/)) { |
| 396 WebInspector.console.error( |
| 397 'For elements with box-sizing: border-box, only absolute content are
a dimensions can be applied'); |
| 398 return; |
| 399 } |
| 400 |
| 401 var borderBox = this._getBox(computedStyle, 'border'); |
| 402 var paddingBox = this._getBox(computedStyle, 'padding'); |
| 403 var userValuePx = Number(userInput.replace(/px$/, '')); |
| 404 if (isNaN(userValuePx)) |
| 405 return; |
| 406 if (styleProperty === 'width') |
| 407 userValuePx += borderBox.left + borderBox.right + paddingBox.left + padd
ingBox.right; |
| 408 else |
| 409 userValuePx += borderBox.top + borderBox.bottom + paddingBox.top + paddi
ngBox.bottom; |
| 410 |
| 411 userInput = userValuePx + 'px'; |
| 412 } |
| 413 |
| 414 this.previousPropertyDataCandidate = null; |
| 415 |
| 416 var allProperties = this.inlineStyle.allProperties; |
| 417 for (var i = 0; i < allProperties.length; ++i) { |
| 418 var property = allProperties[i]; |
| 419 if (property.name !== context.styleProperty || !property.activeInStyle()) |
| 420 continue; |
| 421 |
| 422 this.previousPropertyDataCandidate = property; |
| 423 property.setValue(userInput, commitEditor, true, callback.bind(this)); |
| 424 return; |
| 425 } |
| 426 |
| 427 this.inlineStyle.appendProperty(context.styleProperty, userInput, callback.b
ind(this)); |
| 428 |
| 429 /** |
| 430 * @param {boolean} success |
| 431 * @this {WebInspector.MetricsSidebarPane} |
| 432 */ |
| 433 function callback(success) { |
| 434 if (!success) |
| 435 return; |
| 436 if (!('originalPropertyData' in this)) |
| 437 this.originalPropertyData = this.previousPropertyDataCandidate; |
| 438 |
| 439 if (typeof this._highlightMode !== 'undefined') |
| 440 this.node().highlight(this._highlightMode); |
| 441 |
| 442 if (commitEditor) |
| 443 this.update(); |
| 444 } |
| 445 } |
| 446 |
| 447 _editingCommitted(element, userInput, previousContent, context) { |
| 448 this.editingEnded(element, context); |
| 449 this._applyUserInput(element, userInput, previousContent, context, true); |
| 450 } |
36 }; | 451 }; |
37 | |
38 WebInspector.MetricsSidebarPane.prototype = { | |
39 /** | |
40 * @override | |
41 * @protected | |
42 * @return {!Promise.<?>} | |
43 */ | |
44 doUpdate: function() | |
45 { | |
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) | |
49 return Promise.resolve(); | |
50 | |
51 // FIXME: avoid updates of a collapsed pane. | |
52 var node = this.node(); | |
53 var cssModel = this.cssModel(); | |
54 if (!node || node.nodeType() !== Node.ELEMENT_NODE || !cssModel) { | |
55 this.element.removeChildren(); | |
56 return Promise.resolve(); | |
57 } | |
58 | |
59 /** | |
60 * @param {?Map.<string, string>} style | |
61 * @this {WebInspector.MetricsSidebarPane} | |
62 */ | |
63 function callback(style) | |
64 { | |
65 if (!style || this.node() !== node) | |
66 return; | |
67 this._updateMetrics(style); | |
68 } | |
69 /** | |
70 * @param {?WebInspector.CSSModel.InlineStyleResult} inlineStyleResult | |
71 * @this {WebInspector.MetricsSidebarPane} | |
72 */ | |
73 function inlineStyleCallback(inlineStyleResult) | |
74 { | |
75 if (inlineStyleResult && this.node() === node) | |
76 this.inlineStyle = inlineStyleResult.inlineStyle; | |
77 } | |
78 | |
79 var promises = [ | |
80 cssModel.computedStylePromise(node.id).then(callback.bind(this)), | |
81 cssModel.inlineStylesPromise(node.id).then(inlineStyleCallback.bind(
this)) | |
82 ]; | |
83 return Promise.all(promises); | |
84 }, | |
85 | |
86 /** | |
87 * @override | |
88 */ | |
89 onCSSModelChanged: function() | |
90 { | |
91 this.update(); | |
92 }, | |
93 | |
94 /** | |
95 * @param {!Map.<string, string>} style | |
96 * @param {string} propertyName | |
97 * @return {number} | |
98 */ | |
99 _getPropertyValueAsPx: function(style, propertyName) | |
100 { | |
101 return Number(style.get(propertyName).replace(/px$/, "") || 0); | |
102 }, | |
103 | |
104 /** | |
105 * @param {!Map.<string, string>} computedStyle | |
106 * @param {string} componentName | |
107 */ | |
108 _getBox: function(computedStyle, componentName) | |
109 { | |
110 var suffix = componentName === "border" ? "-width" : ""; | |
111 var left = this._getPropertyValueAsPx(computedStyle, componentName + "-l
eft" + suffix); | |
112 var top = this._getPropertyValueAsPx(computedStyle, componentName + "-to
p" + suffix); | |
113 var right = this._getPropertyValueAsPx(computedStyle, componentName + "-
right" + suffix); | |
114 var bottom = this._getPropertyValueAsPx(computedStyle, componentName + "
-bottom" + suffix); | |
115 return { left: left, top: top, right: right, bottom: bottom }; | |
116 }, | |
117 | |
118 /** | |
119 * @param {boolean} showHighlight | |
120 * @param {string} mode | |
121 * @param {!Event} event | |
122 */ | |
123 _highlightDOMNode: function(showHighlight, mode, event) | |
124 { | |
125 event.consume(); | |
126 if (showHighlight && this.node()) { | |
127 if (this._highlightMode === mode) | |
128 return; | |
129 this._highlightMode = mode; | |
130 this.node().highlight(mode); | |
131 } else { | |
132 delete this._highlightMode; | |
133 WebInspector.DOMModel.hideDOMNodeHighlight(); | |
134 } | |
135 | |
136 for (var i = 0; this._boxElements && i < this._boxElements.length; ++i)
{ | |
137 var element = this._boxElements[i]; | |
138 if (!this.node() || mode === "all" || element._name === mode) | |
139 element.style.backgroundColor = element._backgroundColor; | |
140 else | |
141 element.style.backgroundColor = ""; | |
142 } | |
143 }, | |
144 | |
145 /** | |
146 * @param {!Map.<string, string>} style | |
147 */ | |
148 _updateMetrics: function(style) | |
149 { | |
150 // Updating with computed style. | |
151 var metricsElement = createElement("div"); | |
152 metricsElement.className = "metrics"; | |
153 var self = this; | |
154 | |
155 /** | |
156 * @param {!Map.<string, string>} style | |
157 * @param {string} name | |
158 * @param {string} side | |
159 * @param {string} suffix | |
160 * @this {WebInspector.MetricsSidebarPane} | |
161 */ | |
162 function createBoxPartElement(style, name, side, suffix) | |
163 { | |
164 var propertyName = (name !== "position" ? name + "-" : "") + side +
suffix; | |
165 var value = style.get(propertyName); | |
166 if (value === "" || (name !== "position" && value === "0px")) | |
167 value = "\u2012"; | |
168 else if (name === "position" && value === "auto") | |
169 value = "\u2012"; | |
170 value = value.replace(/px$/, ""); | |
171 value = Number.toFixedIfFloating(value); | |
172 | |
173 var element = createElement("div"); | |
174 element.className = side; | |
175 element.textContent = value; | |
176 element.addEventListener("dblclick", this.startEditing.bind(this, el
ement, name, propertyName, style), false); | |
177 return element; | |
178 } | |
179 | |
180 /** | |
181 * @param {!Map.<string, string>} style | |
182 * @return {string} | |
183 */ | |
184 function getContentAreaWidthPx(style) | |
185 { | |
186 var width = style.get("width").replace(/px$/, ""); | |
187 if (!isNaN(width) && style.get("box-sizing") === "border-box") { | |
188 var borderBox = self._getBox(style, "border"); | |
189 var paddingBox = self._getBox(style, "padding"); | |
190 | |
191 width = width - borderBox.left - borderBox.right - paddingBox.le
ft - paddingBox.right; | |
192 } | |
193 | |
194 return Number.toFixedIfFloating(width.toString()); | |
195 } | |
196 | |
197 /** | |
198 * @param {!Map.<string, string>} style | |
199 * @return {string} | |
200 */ | |
201 function getContentAreaHeightPx(style) | |
202 { | |
203 var height = style.get("height").replace(/px$/, ""); | |
204 if (!isNaN(height) && style.get("box-sizing") === "border-box") { | |
205 var borderBox = self._getBox(style, "border"); | |
206 var paddingBox = self._getBox(style, "padding"); | |
207 | |
208 height = height - borderBox.top - borderBox.bottom - paddingBox.
top - paddingBox.bottom; | |
209 } | |
210 | |
211 return Number.toFixedIfFloating(height.toString()); | |
212 } | |
213 | |
214 // Display types for which margin is ignored. | |
215 var noMarginDisplayType = { | |
216 "table-cell": true, | |
217 "table-column": true, | |
218 "table-column-group": true, | |
219 "table-footer-group": true, | |
220 "table-header-group": true, | |
221 "table-row": true, | |
222 "table-row-group": true | |
223 }; | |
224 | |
225 // Display types for which padding is ignored. | |
226 var noPaddingDisplayType = { | |
227 "table-column": true, | |
228 "table-column-group": true, | |
229 "table-footer-group": true, | |
230 "table-header-group": true, | |
231 "table-row": true, | |
232 "table-row-group": true | |
233 }; | |
234 | |
235 // Position types for which top, left, bottom and right are ignored. | |
236 var noPositionType = { | |
237 "static": true | |
238 }; | |
239 | |
240 var boxes = ["content", "padding", "border", "margin", "position"]; | |
241 var boxColors = [ | |
242 WebInspector.Color.PageHighlight.Content, | |
243 WebInspector.Color.PageHighlight.Padding, | |
244 WebInspector.Color.PageHighlight.Border, | |
245 WebInspector.Color.PageHighlight.Margin, | |
246 WebInspector.Color.fromRGBA([0, 0, 0, 0]) | |
247 ]; | |
248 var boxLabels = [WebInspector.UIString("content"), WebInspector.UIString
("padding"), WebInspector.UIString("border"), WebInspector.UIString("margin"), W
ebInspector.UIString("position")]; | |
249 var previousBox = null; | |
250 this._boxElements = []; | |
251 for (var i = 0; i < boxes.length; ++i) { | |
252 var name = boxes[i]; | |
253 | |
254 if (name === "margin" && noMarginDisplayType[style.get("display")]) | |
255 continue; | |
256 if (name === "padding" && noPaddingDisplayType[style.get("display")]
) | |
257 continue; | |
258 if (name === "position" && noPositionType[style.get("position")]) | |
259 continue; | |
260 | |
261 var boxElement = createElement("div"); | |
262 boxElement.className = name; | |
263 boxElement._backgroundColor = boxColors[i].asString(WebInspector.Col
or.Format.RGBA); | |
264 boxElement._name = name; | |
265 boxElement.style.backgroundColor = boxElement._backgroundColor; | |
266 boxElement.addEventListener("mouseover", this._highlightDOMNode.bind
(this, true, name === "position" ? "all" : name), false); | |
267 this._boxElements.push(boxElement); | |
268 | |
269 if (name === "content") { | |
270 var widthElement = createElement("span"); | |
271 widthElement.textContent = getContentAreaWidthPx(style); | |
272 widthElement.addEventListener("dblclick", this.startEditing.bind
(this, widthElement, "width", "width", style), false); | |
273 | |
274 var heightElement = createElement("span"); | |
275 heightElement.textContent = getContentAreaHeightPx(style); | |
276 heightElement.addEventListener("dblclick", this.startEditing.bin
d(this, heightElement, "height", "height", style), false); | |
277 | |
278 boxElement.appendChild(widthElement); | |
279 boxElement.createTextChild(" \u00D7 "); | |
280 boxElement.appendChild(heightElement); | |
281 } else { | |
282 var suffix = (name === "border" ? "-width" : ""); | |
283 | |
284 var labelElement = createElement("div"); | |
285 labelElement.className = "label"; | |
286 labelElement.textContent = boxLabels[i]; | |
287 boxElement.appendChild(labelElement); | |
288 | |
289 boxElement.appendChild(createBoxPartElement.call(this, style, na
me, "top", suffix)); | |
290 boxElement.appendChild(createElement("br")); | |
291 boxElement.appendChild(createBoxPartElement.call(this, style, na
me, "left", suffix)); | |
292 | |
293 if (previousBox) | |
294 boxElement.appendChild(previousBox); | |
295 | |
296 boxElement.appendChild(createBoxPartElement.call(this, style, na
me, "right", suffix)); | |
297 boxElement.appendChild(createElement("br")); | |
298 boxElement.appendChild(createBoxPartElement.call(this, style, na
me, "bottom", suffix)); | |
299 } | |
300 | |
301 previousBox = boxElement; | |
302 } | |
303 | |
304 metricsElement.appendChild(previousBox); | |
305 metricsElement.addEventListener("mouseover", this._highlightDOMNode.bind
(this, false, "all"), false); | |
306 this.element.removeChildren(); | |
307 this.element.appendChild(metricsElement); | |
308 }, | |
309 | |
310 /** | |
311 * @param {!Element} targetElement | |
312 * @param {string} box | |
313 * @param {string} styleProperty | |
314 * @param {!Map.<string, string>} computedStyle | |
315 */ | |
316 startEditing: function(targetElement, box, styleProperty, computedStyle) | |
317 { | |
318 if (WebInspector.isBeingEdited(targetElement)) | |
319 return; | |
320 | |
321 var context = { box: box, styleProperty: styleProperty, computedStyle: c
omputedStyle }; | |
322 var boundKeyDown = this._handleKeyDown.bind(this, context, styleProperty
); | |
323 context.keyDownHandler = boundKeyDown; | |
324 targetElement.addEventListener("keydown", boundKeyDown, false); | |
325 | |
326 this._isEditingMetrics = true; | |
327 | |
328 var config = new WebInspector.InplaceEditor.Config(this._editingCommitte
d.bind(this), this.editingCancelled.bind(this), context); | |
329 WebInspector.InplaceEditor.startEditing(targetElement, config); | |
330 | |
331 targetElement.getComponentSelection().setBaseAndExtent(targetElement, 0,
targetElement, 1); | |
332 }, | |
333 | |
334 _handleKeyDown: function(context, styleProperty, event) | |
335 { | |
336 var element = event.currentTarget; | |
337 | |
338 /** | |
339 * @param {string} originalValue | |
340 * @param {string} replacementString | |
341 * @this {WebInspector.MetricsSidebarPane} | |
342 */ | |
343 function finishHandler(originalValue, replacementString) | |
344 { | |
345 this._applyUserInput(element, replacementString, originalValue, cont
ext, false); | |
346 } | |
347 | |
348 /** | |
349 * @param {string} prefix | |
350 * @param {number} number | |
351 * @param {string} suffix | |
352 * @return {string} | |
353 */ | |
354 function customNumberHandler(prefix, number, suffix) | |
355 { | |
356 if (styleProperty !== "margin" && number < 0) | |
357 number = 0; | |
358 return prefix + number + suffix; | |
359 } | |
360 | |
361 WebInspector.handleElementValueModifications(event, element, finishHandl
er.bind(this), undefined, customNumberHandler); | |
362 }, | |
363 | |
364 editingEnded: function(element, context) | |
365 { | |
366 delete this.originalPropertyData; | |
367 delete this.previousPropertyDataCandidate; | |
368 element.removeEventListener("keydown", context.keyDownHandler, false); | |
369 delete this._isEditingMetrics; | |
370 }, | |
371 | |
372 editingCancelled: function(element, context) | |
373 { | |
374 if ("originalPropertyData" in this && this.inlineStyle) { | |
375 if (!this.originalPropertyData) { | |
376 // An added property, remove the last property in the style. | |
377 var pastLastSourcePropertyIndex = this.inlineStyle.pastLastSourc
ePropertyIndex(); | |
378 if (pastLastSourcePropertyIndex) | |
379 this.inlineStyle.allProperties[pastLastSourcePropertyIndex -
1].setText("", false); | |
380 } else | |
381 this.inlineStyle.allProperties[this.originalPropertyData.index].
setText(this.originalPropertyData.propertyText, false); | |
382 } | |
383 this.editingEnded(element, context); | |
384 this.update(); | |
385 }, | |
386 | |
387 _applyUserInput: function(element, userInput, previousContent, context, comm
itEditor) | |
388 { | |
389 if (!this.inlineStyle) { | |
390 // Element has no renderer. | |
391 return this.editingCancelled(element, context); // nothing changed,
so cancel | |
392 } | |
393 | |
394 if (commitEditor && userInput === previousContent) | |
395 return this.editingCancelled(element, context); // nothing changed,
so cancel | |
396 | |
397 if (context.box !== "position" && (!userInput || userInput === "\u2012")
) | |
398 userInput = "0px"; | |
399 else if (context.box === "position" && (!userInput || userInput === "\u2
012")) | |
400 userInput = "auto"; | |
401 | |
402 userInput = userInput.toLowerCase(); | |
403 // Append a "px" unit if the user input was just a number. | |
404 if (/^\d+$/.test(userInput)) | |
405 userInput += "px"; | |
406 | |
407 var styleProperty = context.styleProperty; | |
408 var computedStyle = context.computedStyle; | |
409 | |
410 if (computedStyle.get("box-sizing") === "border-box" && (styleProperty =
== "width" || styleProperty === "height")) { | |
411 if (!userInput.match(/px$/)) { | |
412 WebInspector.console.error("For elements with box-sizing: border
-box, only absolute content area dimensions can be applied"); | |
413 return; | |
414 } | |
415 | |
416 var borderBox = this._getBox(computedStyle, "border"); | |
417 var paddingBox = this._getBox(computedStyle, "padding"); | |
418 var userValuePx = Number(userInput.replace(/px$/, "")); | |
419 if (isNaN(userValuePx)) | |
420 return; | |
421 if (styleProperty === "width") | |
422 userValuePx += borderBox.left + borderBox.right + paddingBox.lef
t + paddingBox.right; | |
423 else | |
424 userValuePx += borderBox.top + borderBox.bottom + paddingBox.top
+ paddingBox.bottom; | |
425 | |
426 userInput = userValuePx + "px"; | |
427 } | |
428 | |
429 this.previousPropertyDataCandidate = null; | |
430 | |
431 var allProperties = this.inlineStyle.allProperties; | |
432 for (var i = 0; i < allProperties.length; ++i) { | |
433 var property = allProperties[i]; | |
434 if (property.name !== context.styleProperty || !property.activeInSty
le()) | |
435 continue; | |
436 | |
437 this.previousPropertyDataCandidate = property; | |
438 property.setValue(userInput, commitEditor, true, callback.bind(this)
); | |
439 return; | |
440 } | |
441 | |
442 this.inlineStyle.appendProperty(context.styleProperty, userInput, callba
ck.bind(this)); | |
443 | |
444 /** | |
445 * @param {boolean} success | |
446 * @this {WebInspector.MetricsSidebarPane} | |
447 */ | |
448 function callback(success) | |
449 { | |
450 if (!success) | |
451 return; | |
452 if (!("originalPropertyData" in this)) | |
453 this.originalPropertyData = this.previousPropertyDataCandidate; | |
454 | |
455 if (typeof this._highlightMode !== "undefined") | |
456 this.node().highlight(this._highlightMode); | |
457 | |
458 if (commitEditor) | |
459 this.update(); | |
460 } | |
461 }, | |
462 | |
463 _editingCommitted: function(element, userInput, previousContent, context) | |
464 { | |
465 this.editingEnded(element, context); | |
466 this._applyUserInput(element, userInput, previousContent, context, true)
; | |
467 }, | |
468 | |
469 __proto__: WebInspector.ElementsSidebarPane.prototype | |
470 }; | |
OLD | NEW |