blob: e6a08e0c8ac428509ef229299aeb80911c412c10 [file] [log] [blame]
Blink Reformat4c46d092018-04-07 15:32:371/*
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 * @unrestricted
31 */
32Elements.MetricsSidebarPane = class extends Elements.ElementsSidebarPane {
33 constructor() {
34 super();
35 this.registerRequiredCSS('elements/metricsSidebarPane.css');
36
37 /** @type {?SDK.CSSStyleDeclaration} */
38 this._inlineStyle = null;
39 }
40
41 /**
42 * @override
43 * @protected
44 * @return {!Promise.<?>}
45 */
46 doUpdate() {
47 // "style" attribute might have changed. Update metrics unless they are being edited
48 // (if a CSS property is added, a StyleSheetChanged event is dispatched).
Tim van der Lippe1d6e57a2019-09-30 11:55:3449 if (this._isEditingMetrics) {
Blink Reformat4c46d092018-04-07 15:32:3750 return Promise.resolve();
Tim van der Lippe1d6e57a2019-09-30 11:55:3451 }
Blink Reformat4c46d092018-04-07 15:32:3752
53 // FIXME: avoid updates of a collapsed pane.
54 const node = this.node();
55 const cssModel = this.cssModel();
56 if (!node || node.nodeType() !== Node.ELEMENT_NODE || !cssModel) {
57 this.contentElement.removeChildren();
58 return Promise.resolve();
59 }
60
61 /**
62 * @param {?Map.<string, string>} style
63 * @this {Elements.MetricsSidebarPane}
64 */
65 function callback(style) {
Tim van der Lippe1d6e57a2019-09-30 11:55:3466 if (!style || this.node() !== node) {
Blink Reformat4c46d092018-04-07 15:32:3767 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:3468 }
Blink Reformat4c46d092018-04-07 15:32:3769 this._updateMetrics(style);
70 }
71 /**
72 * @param {?SDK.CSSModel.InlineStyleResult} inlineStyleResult
73 * @this {Elements.MetricsSidebarPane}
74 */
75 function inlineStyleCallback(inlineStyleResult) {
Tim van der Lippe1d6e57a2019-09-30 11:55:3476 if (inlineStyleResult && this.node() === node) {
Blink Reformat4c46d092018-04-07 15:32:3777 this._inlineStyle = inlineStyleResult.inlineStyle;
Tim van der Lippe1d6e57a2019-09-30 11:55:3478 }
Blink Reformat4c46d092018-04-07 15:32:3779 }
80
81 const promises = [
82 cssModel.computedStylePromise(node.id).then(callback.bind(this)),
83 cssModel.inlineStylesPromise(node.id).then(inlineStyleCallback.bind(this))
84 ];
85 return Promise.all(promises);
86 }
87
88 /**
89 * @override
90 */
91 onCSSModelChanged() {
92 this.update();
93 }
94
95 /**
96 * @param {!Map.<string, string>} style
97 * @param {string} propertyName
98 * @return {number}
99 */
100 _getPropertyValueAsPx(style, propertyName) {
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(computedStyle, componentName) {
109 const suffix = componentName === 'border' ? '-width' : '';
110 const left = this._getPropertyValueAsPx(computedStyle, componentName + '-left' + suffix);
111 const top = this._getPropertyValueAsPx(computedStyle, componentName + '-top' + suffix);
112 const right = this._getPropertyValueAsPx(computedStyle, componentName + '-right' + suffix);
113 const bottom = this._getPropertyValueAsPx(computedStyle, componentName + '-bottom' + suffix);
114 return {left: left, top: top, right: right, bottom: bottom};
115 }
116
117 /**
118 * @param {boolean} showHighlight
119 * @param {string} mode
120 * @param {!Event} event
121 */
122 _highlightDOMNode(showHighlight, mode, event) {
123 event.consume();
124 if (showHighlight && this.node()) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34125 if (this._highlightMode === mode) {
Blink Reformat4c46d092018-04-07 15:32:37126 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34127 }
Blink Reformat4c46d092018-04-07 15:32:37128 this._highlightMode = mode;
129 this.node().highlight(mode);
130 } else {
131 delete this._highlightMode;
132 SDK.OverlayModel.hideDOMNodeHighlight();
133 }
134
135 for (let i = 0; this._boxElements && i < this._boxElements.length; ++i) {
136 const element = this._boxElements[i];
Tim van der Lippe1d6e57a2019-09-30 11:55:34137 if (!this.node() || mode === 'all' || element._name === mode) {
Blink Reformat4c46d092018-04-07 15:32:37138 element.style.backgroundColor = element._backgroundColor;
Tim van der Lippe1d6e57a2019-09-30 11:55:34139 } else {
Blink Reformat4c46d092018-04-07 15:32:37140 element.style.backgroundColor = '';
Tim van der Lippe1d6e57a2019-09-30 11:55:34141 }
Blink Reformat4c46d092018-04-07 15:32:37142 }
143 }
144
145 /**
146 * @param {!Map.<string, string>} style
147 */
148 _updateMetrics(style) {
149 // Updating with computed style.
150 const metricsElement = createElement('div');
151 metricsElement.className = 'metrics';
152 const self = this;
153
154 /**
155 * @param {!Map.<string, string>} style
156 * @param {string} name
157 * @param {string} side
158 * @param {string} suffix
159 * @this {Elements.MetricsSidebarPane}
160 */
161 function createBoxPartElement(style, name, side, suffix) {
162 const propertyName = (name !== 'position' ? name + '-' : '') + side + suffix;
163 let value = style.get(propertyName);
Tim van der Lippe1d6e57a2019-09-30 11:55:34164 if (value === '' || (name !== 'position' && value === '0px')) {
Blink Reformat4c46d092018-04-07 15:32:37165 value = '\u2012';
Tim van der Lippe1d6e57a2019-09-30 11:55:34166 } else if (name === 'position' && value === 'auto') {
Blink Reformat4c46d092018-04-07 15:32:37167 value = '\u2012';
Tim van der Lippe1d6e57a2019-09-30 11:55:34168 }
Blink Reformat4c46d092018-04-07 15:32:37169 value = value.replace(/px$/, '');
170 value = Number.toFixedIfFloating(value);
171
172 const element = createElement('div');
173 element.className = side;
174 element.textContent = value;
175 element.addEventListener('dblclick', this.startEditing.bind(this, element, name, propertyName, style), false);
176 return element;
177 }
178
179 /**
180 * @param {!Map.<string, string>} style
181 * @return {string}
182 */
183 function getContentAreaWidthPx(style) {
184 let width = style.get('width').replace(/px$/, '');
185 if (!isNaN(width) && style.get('box-sizing') === 'border-box') {
186 const borderBox = self._getBox(style, 'border');
187 const paddingBox = self._getBox(style, 'padding');
188
189 width = width - borderBox.left - borderBox.right - paddingBox.left - paddingBox.right;
190 }
191
192 return Number.toFixedIfFloating(width.toString());
193 }
194
195 /**
196 * @param {!Map.<string, string>} style
197 * @return {string}
198 */
199 function getContentAreaHeightPx(style) {
200 let height = style.get('height').replace(/px$/, '');
201 if (!isNaN(height) && style.get('box-sizing') === 'border-box') {
202 const borderBox = self._getBox(style, 'border');
203 const paddingBox = self._getBox(style, 'padding');
204
205 height = height - borderBox.top - borderBox.bottom - paddingBox.top - paddingBox.bottom;
206 }
207
208 return Number.toFixedIfFloating(height.toString());
209 }
210
211 // Display types for which margin is ignored.
212 const noMarginDisplayType = {
213 'table-cell': true,
214 'table-column': true,
215 'table-column-group': true,
216 'table-footer-group': true,
217 'table-header-group': true,
218 'table-row': true,
219 'table-row-group': true
220 };
221
222 // Display types for which padding is ignored.
223 const noPaddingDisplayType = {
224 'table-column': true,
225 'table-column-group': true,
226 'table-footer-group': true,
227 'table-header-group': true,
228 'table-row': true,
229 'table-row-group': true
230 };
231
232 // Position types for which top, left, bottom and right are ignored.
233 const noPositionType = {'static': true};
234
235 const boxes = ['content', 'padding', 'border', 'margin', 'position'];
236 const boxColors = [
237 Common.Color.PageHighlight.Content, Common.Color.PageHighlight.Padding, Common.Color.PageHighlight.Border,
238 Common.Color.PageHighlight.Margin, Common.Color.fromRGBA([0, 0, 0, 0])
239 ];
240 const boxLabels = [
241 Common.UIString('content'), Common.UIString('padding'), Common.UIString('border'), Common.UIString('margin'),
242 Common.UIString('position')
243 ];
244 let previousBox = null;
245 this._boxElements = [];
246 for (let i = 0; i < boxes.length; ++i) {
247 const name = boxes[i];
248
Tim van der Lippe1d6e57a2019-09-30 11:55:34249 if (name === 'margin' && noMarginDisplayType[style.get('display')]) {
Blink Reformat4c46d092018-04-07 15:32:37250 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34251 }
252 if (name === 'padding' && noPaddingDisplayType[style.get('display')]) {
Blink Reformat4c46d092018-04-07 15:32:37253 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34254 }
255 if (name === 'position' && noPositionType[style.get('position')]) {
Blink Reformat4c46d092018-04-07 15:32:37256 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34257 }
Blink Reformat4c46d092018-04-07 15:32:37258
259 const boxElement = createElement('div');
260 boxElement.className = name;
261 boxElement._backgroundColor = boxColors[i].asString(Common.Color.Format.RGBA);
262 boxElement._name = name;
263 boxElement.style.backgroundColor = boxElement._backgroundColor;
264 boxElement.addEventListener(
265 'mouseover', this._highlightDOMNode.bind(this, true, name === 'position' ? 'all' : name), false);
266 this._boxElements.push(boxElement);
267
268 if (name === 'content') {
269 const widthElement = createElement('span');
270 widthElement.textContent = getContentAreaWidthPx(style);
271 widthElement.addEventListener(
272 'dblclick', this.startEditing.bind(this, widthElement, 'width', 'width', style), false);
273
274 const heightElement = createElement('span');
275 heightElement.textContent = getContentAreaHeightPx(style);
276 heightElement.addEventListener(
277 'dblclick', this.startEditing.bind(this, heightElement, 'height', 'height', style), false);
278
279 boxElement.appendChild(widthElement);
280 boxElement.createTextChild(' \u00D7 ');
281 boxElement.appendChild(heightElement);
282 } else {
283 const suffix = (name === 'border' ? '-width' : '');
284
285 const labelElement = createElement('div');
286 labelElement.className = 'label';
287 labelElement.textContent = boxLabels[i];
288 boxElement.appendChild(labelElement);
289
290 boxElement.appendChild(createBoxPartElement.call(this, style, name, 'top', suffix));
291 boxElement.appendChild(createElement('br'));
292 boxElement.appendChild(createBoxPartElement.call(this, style, name, 'left', suffix));
293
Tim van der Lippe1d6e57a2019-09-30 11:55:34294 if (previousBox) {
Blink Reformat4c46d092018-04-07 15:32:37295 boxElement.appendChild(previousBox);
Tim van der Lippe1d6e57a2019-09-30 11:55:34296 }
Blink Reformat4c46d092018-04-07 15:32:37297
298 boxElement.appendChild(createBoxPartElement.call(this, style, name, 'right', suffix));
299 boxElement.appendChild(createElement('br'));
300 boxElement.appendChild(createBoxPartElement.call(this, style, name, 'bottom', suffix));
301 }
302
303 previousBox = boxElement;
304 }
305
306 metricsElement.appendChild(previousBox);
307 metricsElement.addEventListener('mouseover', this._highlightDOMNode.bind(this, false, 'all'), false);
308 this.contentElement.removeChildren();
309 this.contentElement.appendChild(metricsElement);
James Lissiakd2f1a2f2019-03-26 17:36:51310
311 // Record the elements tool load time after the sidepane has loaded.
312 Host.userMetrics.panelLoaded('elements', 'DevTools.Launch.Elements');
Blink Reformat4c46d092018-04-07 15:32:37313 }
314
315 /**
316 * @param {!Element} targetElement
317 * @param {string} box
318 * @param {string} styleProperty
319 * @param {!Map.<string, string>} computedStyle
320 */
321 startEditing(targetElement, box, styleProperty, computedStyle) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34322 if (UI.isBeingEdited(targetElement)) {
Blink Reformat4c46d092018-04-07 15:32:37323 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34324 }
Blink Reformat4c46d092018-04-07 15:32:37325
326 const context = {box: box, styleProperty: styleProperty, computedStyle: computedStyle};
327 const boundKeyDown = this._handleKeyDown.bind(this, context, styleProperty);
328 context.keyDownHandler = boundKeyDown;
329 targetElement.addEventListener('keydown', boundKeyDown, false);
330
331 this._isEditingMetrics = true;
332
333 const config =
334 new UI.InplaceEditor.Config(this._editingCommitted.bind(this), this.editingCancelled.bind(this), context);
335 UI.InplaceEditor.startEditing(targetElement, config);
336
337 targetElement.getComponentSelection().selectAllChildren(targetElement);
338 }
339
340 _handleKeyDown(context, styleProperty, event) {
341 const element = event.currentTarget;
342
343 /**
344 * @param {string} originalValue
345 * @param {string} replacementString
346 * @this {Elements.MetricsSidebarPane}
347 */
348 function finishHandler(originalValue, replacementString) {
349 this._applyUserInput(element, replacementString, originalValue, context, false);
350 }
351
352 /**
353 * @param {string} prefix
354 * @param {number} number
355 * @param {string} suffix
356 * @return {string}
357 */
358 function customNumberHandler(prefix, number, suffix) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34359 if (styleProperty !== 'margin' && number < 0) {
Blink Reformat4c46d092018-04-07 15:32:37360 number = 0;
Tim van der Lippe1d6e57a2019-09-30 11:55:34361 }
Blink Reformat4c46d092018-04-07 15:32:37362 return prefix + number + suffix;
363 }
364
365 UI.handleElementValueModifications(event, element, finishHandler.bind(this), undefined, customNumberHandler);
366 }
367
368 editingEnded(element, context) {
369 delete this.originalPropertyData;
370 delete this.previousPropertyDataCandidate;
371 element.removeEventListener('keydown', context.keyDownHandler, false);
372 delete this._isEditingMetrics;
373 }
374
375 editingCancelled(element, context) {
376 if ('originalPropertyData' in this && this._inlineStyle) {
377 if (!this.originalPropertyData) {
378 // An added property, remove the last property in the style.
379 const pastLastSourcePropertyIndex = this._inlineStyle.pastLastSourcePropertyIndex();
Tim van der Lippe1d6e57a2019-09-30 11:55:34380 if (pastLastSourcePropertyIndex) {
Blink Reformat4c46d092018-04-07 15:32:37381 this._inlineStyle.allProperties()[pastLastSourcePropertyIndex - 1].setText('', false);
Tim van der Lippe1d6e57a2019-09-30 11:55:34382 }
Blink Reformat4c46d092018-04-07 15:32:37383 } else {
384 this._inlineStyle.allProperties()[this.originalPropertyData.index].setText(
385 this.originalPropertyData.propertyText, false);
386 }
387 }
388 this.editingEnded(element, context);
389 this.update();
390 }
391
392 _applyUserInput(element, userInput, previousContent, context, commitEditor) {
393 if (!this._inlineStyle) {
394 // Element has no renderer.
395 return this.editingCancelled(element, context); // nothing changed, so cancel
396 }
397
Tim van der Lippe1d6e57a2019-09-30 11:55:34398 if (commitEditor && userInput === previousContent) {
399 return this.editingCancelled(element, context);
400 } // nothing changed, so cancel
Blink Reformat4c46d092018-04-07 15:32:37401
Tim van der Lippe1d6e57a2019-09-30 11:55:34402 if (context.box !== 'position' && (!userInput || userInput === '\u2012')) {
Blink Reformat4c46d092018-04-07 15:32:37403 userInput = '0px';
Tim van der Lippe1d6e57a2019-09-30 11:55:34404 } else if (context.box === 'position' && (!userInput || userInput === '\u2012')) {
Blink Reformat4c46d092018-04-07 15:32:37405 userInput = 'auto';
Tim van der Lippe1d6e57a2019-09-30 11:55:34406 }
Blink Reformat4c46d092018-04-07 15:32:37407
408 userInput = userInput.toLowerCase();
409 // Append a "px" unit if the user input was just a number.
Tim van der Lippe1d6e57a2019-09-30 11:55:34410 if (/^\d+$/.test(userInput)) {
Blink Reformat4c46d092018-04-07 15:32:37411 userInput += 'px';
Tim van der Lippe1d6e57a2019-09-30 11:55:34412 }
Blink Reformat4c46d092018-04-07 15:32:37413
414 const styleProperty = context.styleProperty;
415 const computedStyle = context.computedStyle;
416
417 if (computedStyle.get('box-sizing') === 'border-box' && (styleProperty === 'width' || styleProperty === 'height')) {
418 if (!userInput.match(/px$/)) {
419 Common.console.error(
420 'For elements with box-sizing: border-box, only absolute content area dimensions can be applied');
421 return;
422 }
423
424 const borderBox = this._getBox(computedStyle, 'border');
425 const paddingBox = this._getBox(computedStyle, 'padding');
426 let userValuePx = Number(userInput.replace(/px$/, ''));
Tim van der Lippe1d6e57a2019-09-30 11:55:34427 if (isNaN(userValuePx)) {
Blink Reformat4c46d092018-04-07 15:32:37428 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34429 }
430 if (styleProperty === 'width') {
Blink Reformat4c46d092018-04-07 15:32:37431 userValuePx += borderBox.left + borderBox.right + paddingBox.left + paddingBox.right;
Tim van der Lippe1d6e57a2019-09-30 11:55:34432 } else {
Blink Reformat4c46d092018-04-07 15:32:37433 userValuePx += borderBox.top + borderBox.bottom + paddingBox.top + paddingBox.bottom;
Tim van der Lippe1d6e57a2019-09-30 11:55:34434 }
Blink Reformat4c46d092018-04-07 15:32:37435
436 userInput = userValuePx + 'px';
437 }
438
439 this.previousPropertyDataCandidate = null;
440
441 const allProperties = this._inlineStyle.allProperties();
442 for (let i = 0; i < allProperties.length; ++i) {
443 const property = allProperties[i];
Tim van der Lippe1d6e57a2019-09-30 11:55:34444 if (property.name !== context.styleProperty || !property.activeInStyle()) {
Blink Reformat4c46d092018-04-07 15:32:37445 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34446 }
Blink Reformat4c46d092018-04-07 15:32:37447
448 this.previousPropertyDataCandidate = property;
449 property.setValue(userInput, commitEditor, true, callback.bind(this));
450 return;
451 }
452
453 this._inlineStyle.appendProperty(context.styleProperty, userInput, callback.bind(this));
454
455 /**
456 * @param {boolean} success
457 * @this {Elements.MetricsSidebarPane}
458 */
459 function callback(success) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34460 if (!success) {
Blink Reformat4c46d092018-04-07 15:32:37461 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34462 }
463 if (!('originalPropertyData' in this)) {
Blink Reformat4c46d092018-04-07 15:32:37464 this.originalPropertyData = this.previousPropertyDataCandidate;
Tim van der Lippe1d6e57a2019-09-30 11:55:34465 }
Blink Reformat4c46d092018-04-07 15:32:37466
Tim van der Lippe1d6e57a2019-09-30 11:55:34467 if (typeof this._highlightMode !== 'undefined') {
Blink Reformat4c46d092018-04-07 15:32:37468 this.node().highlight(this._highlightMode);
Tim van der Lippe1d6e57a2019-09-30 11:55:34469 }
Blink Reformat4c46d092018-04-07 15:32:37470
Tim van der Lippe1d6e57a2019-09-30 11:55:34471 if (commitEditor) {
Blink Reformat4c46d092018-04-07 15:32:37472 this.update();
Tim van der Lippe1d6e57a2019-09-30 11:55:34473 }
Blink Reformat4c46d092018-04-07 15:32:37474 }
475 }
476
477 _editingCommitted(element, userInput, previousContent, context) {
478 this.editingEnded(element, context);
479 this._applyUserInput(element, userInput, previousContent, context, true);
480 }
481};