mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-10 13:44:39 +08:00
Move and rename files. NOAUTOREVERT=true NOPRESUBMIT=true NOTREECHECKS=true Bug: 768828 TBR=darin@chromium.org NOTRY=true Change-Id: I66d3b155808bc5bdbf237b80208e1e552bcf7f28 Reviewed-on: https://chromium-review.googlesource.com/1001153 Reviewed-by: Blink Reformat <blink-reformat@chromium.org> Commit-Queue: Blink Reformat <blink-reformat@chromium.org> Cr-Original-Commit-Position: refs/heads/master@{#549061} Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src Cr-Mirrored-Commit: 0aee4434a4dba42a42abaea9bfbc0cd196a63bc1
456 lines
16 KiB
JavaScript
456 lines
16 KiB
JavaScript
/*
|
|
* Copyright (C) 2007 Apple Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
|
|
* its contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/**
|
|
* @unrestricted
|
|
*/
|
|
Elements.MetricsSidebarPane = class extends Elements.ElementsSidebarPane {
|
|
constructor() {
|
|
super();
|
|
this.registerRequiredCSS('elements/metricsSidebarPane.css');
|
|
|
|
/** @type {?SDK.CSSStyleDeclaration} */
|
|
this._inlineStyle = null;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @protected
|
|
* @return {!Promise.<?>}
|
|
*/
|
|
doUpdate() {
|
|
// "style" attribute might have changed. Update metrics unless they are being edited
|
|
// (if a CSS property is added, a StyleSheetChanged event is dispatched).
|
|
if (this._isEditingMetrics)
|
|
return Promise.resolve();
|
|
|
|
// FIXME: avoid updates of a collapsed pane.
|
|
const node = this.node();
|
|
const cssModel = this.cssModel();
|
|
if (!node || node.nodeType() !== Node.ELEMENT_NODE || !cssModel) {
|
|
this.contentElement.removeChildren();
|
|
return Promise.resolve();
|
|
}
|
|
|
|
/**
|
|
* @param {?Map.<string, string>} style
|
|
* @this {Elements.MetricsSidebarPane}
|
|
*/
|
|
function callback(style) {
|
|
if (!style || this.node() !== node)
|
|
return;
|
|
this._updateMetrics(style);
|
|
}
|
|
/**
|
|
* @param {?SDK.CSSModel.InlineStyleResult} inlineStyleResult
|
|
* @this {Elements.MetricsSidebarPane}
|
|
*/
|
|
function inlineStyleCallback(inlineStyleResult) {
|
|
if (inlineStyleResult && this.node() === node)
|
|
this._inlineStyle = inlineStyleResult.inlineStyle;
|
|
}
|
|
|
|
const promises = [
|
|
cssModel.computedStylePromise(node.id).then(callback.bind(this)),
|
|
cssModel.inlineStylesPromise(node.id).then(inlineStyleCallback.bind(this))
|
|
];
|
|
return Promise.all(promises);
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
onCSSModelChanged() {
|
|
this.update();
|
|
}
|
|
|
|
/**
|
|
* @param {!Map.<string, string>} style
|
|
* @param {string} propertyName
|
|
* @return {number}
|
|
*/
|
|
_getPropertyValueAsPx(style, propertyName) {
|
|
return Number(style.get(propertyName).replace(/px$/, '') || 0);
|
|
}
|
|
|
|
/**
|
|
* @param {!Map.<string, string>} computedStyle
|
|
* @param {string} componentName
|
|
*/
|
|
_getBox(computedStyle, componentName) {
|
|
const suffix = componentName === 'border' ? '-width' : '';
|
|
const left = this._getPropertyValueAsPx(computedStyle, componentName + '-left' + suffix);
|
|
const top = this._getPropertyValueAsPx(computedStyle, componentName + '-top' + suffix);
|
|
const right = this._getPropertyValueAsPx(computedStyle, componentName + '-right' + suffix);
|
|
const bottom = this._getPropertyValueAsPx(computedStyle, componentName + '-bottom' + suffix);
|
|
return {left: left, top: top, right: right, bottom: bottom};
|
|
}
|
|
|
|
/**
|
|
* @param {boolean} showHighlight
|
|
* @param {string} mode
|
|
* @param {!Event} event
|
|
*/
|
|
_highlightDOMNode(showHighlight, mode, event) {
|
|
event.consume();
|
|
if (showHighlight && this.node()) {
|
|
if (this._highlightMode === mode)
|
|
return;
|
|
this._highlightMode = mode;
|
|
this.node().highlight(mode);
|
|
} else {
|
|
delete this._highlightMode;
|
|
SDK.OverlayModel.hideDOMNodeHighlight();
|
|
}
|
|
|
|
for (let i = 0; this._boxElements && i < this._boxElements.length; ++i) {
|
|
const element = this._boxElements[i];
|
|
if (!this.node() || mode === 'all' || element._name === mode)
|
|
element.style.backgroundColor = element._backgroundColor;
|
|
else
|
|
element.style.backgroundColor = '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!Map.<string, string>} style
|
|
*/
|
|
_updateMetrics(style) {
|
|
// Updating with computed style.
|
|
const metricsElement = createElement('div');
|
|
metricsElement.className = 'metrics';
|
|
const self = this;
|
|
|
|
/**
|
|
* @param {!Map.<string, string>} style
|
|
* @param {string} name
|
|
* @param {string} side
|
|
* @param {string} suffix
|
|
* @this {Elements.MetricsSidebarPane}
|
|
*/
|
|
function createBoxPartElement(style, name, side, suffix) {
|
|
const propertyName = (name !== 'position' ? name + '-' : '') + side + suffix;
|
|
let value = style.get(propertyName);
|
|
if (value === '' || (name !== 'position' && value === '0px'))
|
|
value = '\u2012';
|
|
else if (name === 'position' && value === 'auto')
|
|
value = '\u2012';
|
|
value = value.replace(/px$/, '');
|
|
value = Number.toFixedIfFloating(value);
|
|
|
|
const element = createElement('div');
|
|
element.className = side;
|
|
element.textContent = value;
|
|
element.addEventListener('dblclick', this.startEditing.bind(this, element, name, propertyName, style), false);
|
|
return element;
|
|
}
|
|
|
|
/**
|
|
* @param {!Map.<string, string>} style
|
|
* @return {string}
|
|
*/
|
|
function getContentAreaWidthPx(style) {
|
|
let width = style.get('width').replace(/px$/, '');
|
|
if (!isNaN(width) && style.get('box-sizing') === 'border-box') {
|
|
const borderBox = self._getBox(style, 'border');
|
|
const paddingBox = self._getBox(style, 'padding');
|
|
|
|
width = width - borderBox.left - borderBox.right - paddingBox.left - paddingBox.right;
|
|
}
|
|
|
|
return Number.toFixedIfFloating(width.toString());
|
|
}
|
|
|
|
/**
|
|
* @param {!Map.<string, string>} style
|
|
* @return {string}
|
|
*/
|
|
function getContentAreaHeightPx(style) {
|
|
let height = style.get('height').replace(/px$/, '');
|
|
if (!isNaN(height) && style.get('box-sizing') === 'border-box') {
|
|
const borderBox = self._getBox(style, 'border');
|
|
const paddingBox = self._getBox(style, 'padding');
|
|
|
|
height = height - borderBox.top - borderBox.bottom - paddingBox.top - paddingBox.bottom;
|
|
}
|
|
|
|
return Number.toFixedIfFloating(height.toString());
|
|
}
|
|
|
|
// Display types for which margin is ignored.
|
|
const noMarginDisplayType = {
|
|
'table-cell': true,
|
|
'table-column': true,
|
|
'table-column-group': true,
|
|
'table-footer-group': true,
|
|
'table-header-group': true,
|
|
'table-row': true,
|
|
'table-row-group': true
|
|
};
|
|
|
|
// Display types for which padding is ignored.
|
|
const noPaddingDisplayType = {
|
|
'table-column': true,
|
|
'table-column-group': true,
|
|
'table-footer-group': true,
|
|
'table-header-group': true,
|
|
'table-row': true,
|
|
'table-row-group': true
|
|
};
|
|
|
|
// Position types for which top, left, bottom and right are ignored.
|
|
const noPositionType = {'static': true};
|
|
|
|
const boxes = ['content', 'padding', 'border', 'margin', 'position'];
|
|
const boxColors = [
|
|
Common.Color.PageHighlight.Content, Common.Color.PageHighlight.Padding, Common.Color.PageHighlight.Border,
|
|
Common.Color.PageHighlight.Margin, Common.Color.fromRGBA([0, 0, 0, 0])
|
|
];
|
|
const boxLabels = [
|
|
Common.UIString('content'), Common.UIString('padding'), Common.UIString('border'), Common.UIString('margin'),
|
|
Common.UIString('position')
|
|
];
|
|
let previousBox = null;
|
|
this._boxElements = [];
|
|
for (let i = 0; i < boxes.length; ++i) {
|
|
const name = boxes[i];
|
|
|
|
if (name === 'margin' && noMarginDisplayType[style.get('display')])
|
|
continue;
|
|
if (name === 'padding' && noPaddingDisplayType[style.get('display')])
|
|
continue;
|
|
if (name === 'position' && noPositionType[style.get('position')])
|
|
continue;
|
|
|
|
const boxElement = createElement('div');
|
|
boxElement.className = name;
|
|
boxElement._backgroundColor = boxColors[i].asString(Common.Color.Format.RGBA);
|
|
boxElement._name = name;
|
|
boxElement.style.backgroundColor = boxElement._backgroundColor;
|
|
boxElement.addEventListener(
|
|
'mouseover', this._highlightDOMNode.bind(this, true, name === 'position' ? 'all' : name), false);
|
|
this._boxElements.push(boxElement);
|
|
|
|
if (name === 'content') {
|
|
const widthElement = createElement('span');
|
|
widthElement.textContent = getContentAreaWidthPx(style);
|
|
widthElement.addEventListener(
|
|
'dblclick', this.startEditing.bind(this, widthElement, 'width', 'width', style), false);
|
|
|
|
const heightElement = createElement('span');
|
|
heightElement.textContent = getContentAreaHeightPx(style);
|
|
heightElement.addEventListener(
|
|
'dblclick', this.startEditing.bind(this, heightElement, 'height', 'height', style), false);
|
|
|
|
boxElement.appendChild(widthElement);
|
|
boxElement.createTextChild(' \u00D7 ');
|
|
boxElement.appendChild(heightElement);
|
|
} else {
|
|
const suffix = (name === 'border' ? '-width' : '');
|
|
|
|
const labelElement = createElement('div');
|
|
labelElement.className = 'label';
|
|
labelElement.textContent = boxLabels[i];
|
|
boxElement.appendChild(labelElement);
|
|
|
|
boxElement.appendChild(createBoxPartElement.call(this, style, name, 'top', suffix));
|
|
boxElement.appendChild(createElement('br'));
|
|
boxElement.appendChild(createBoxPartElement.call(this, style, name, 'left', suffix));
|
|
|
|
if (previousBox)
|
|
boxElement.appendChild(previousBox);
|
|
|
|
boxElement.appendChild(createBoxPartElement.call(this, style, name, 'right', suffix));
|
|
boxElement.appendChild(createElement('br'));
|
|
boxElement.appendChild(createBoxPartElement.call(this, style, name, 'bottom', suffix));
|
|
}
|
|
|
|
previousBox = boxElement;
|
|
}
|
|
|
|
metricsElement.appendChild(previousBox);
|
|
metricsElement.addEventListener('mouseover', this._highlightDOMNode.bind(this, false, 'all'), false);
|
|
this.contentElement.removeChildren();
|
|
this.contentElement.appendChild(metricsElement);
|
|
}
|
|
|
|
/**
|
|
* @param {!Element} targetElement
|
|
* @param {string} box
|
|
* @param {string} styleProperty
|
|
* @param {!Map.<string, string>} computedStyle
|
|
*/
|
|
startEditing(targetElement, box, styleProperty, computedStyle) {
|
|
if (UI.isBeingEdited(targetElement))
|
|
return;
|
|
|
|
const context = {box: box, styleProperty: styleProperty, computedStyle: computedStyle};
|
|
const boundKeyDown = this._handleKeyDown.bind(this, context, styleProperty);
|
|
context.keyDownHandler = boundKeyDown;
|
|
targetElement.addEventListener('keydown', boundKeyDown, false);
|
|
|
|
this._isEditingMetrics = true;
|
|
|
|
const config =
|
|
new UI.InplaceEditor.Config(this._editingCommitted.bind(this), this.editingCancelled.bind(this), context);
|
|
UI.InplaceEditor.startEditing(targetElement, config);
|
|
|
|
targetElement.getComponentSelection().selectAllChildren(targetElement);
|
|
}
|
|
|
|
_handleKeyDown(context, styleProperty, event) {
|
|
const element = event.currentTarget;
|
|
|
|
/**
|
|
* @param {string} originalValue
|
|
* @param {string} replacementString
|
|
* @this {Elements.MetricsSidebarPane}
|
|
*/
|
|
function finishHandler(originalValue, replacementString) {
|
|
this._applyUserInput(element, replacementString, originalValue, context, false);
|
|
}
|
|
|
|
/**
|
|
* @param {string} prefix
|
|
* @param {number} number
|
|
* @param {string} suffix
|
|
* @return {string}
|
|
*/
|
|
function customNumberHandler(prefix, number, suffix) {
|
|
if (styleProperty !== 'margin' && number < 0)
|
|
number = 0;
|
|
return prefix + number + suffix;
|
|
}
|
|
|
|
UI.handleElementValueModifications(event, element, finishHandler.bind(this), undefined, customNumberHandler);
|
|
}
|
|
|
|
editingEnded(element, context) {
|
|
delete this.originalPropertyData;
|
|
delete this.previousPropertyDataCandidate;
|
|
element.removeEventListener('keydown', context.keyDownHandler, false);
|
|
delete this._isEditingMetrics;
|
|
}
|
|
|
|
editingCancelled(element, context) {
|
|
if ('originalPropertyData' in this && this._inlineStyle) {
|
|
if (!this.originalPropertyData) {
|
|
// An added property, remove the last property in the style.
|
|
const pastLastSourcePropertyIndex = this._inlineStyle.pastLastSourcePropertyIndex();
|
|
if (pastLastSourcePropertyIndex)
|
|
this._inlineStyle.allProperties()[pastLastSourcePropertyIndex - 1].setText('', false);
|
|
} else {
|
|
this._inlineStyle.allProperties()[this.originalPropertyData.index].setText(
|
|
this.originalPropertyData.propertyText, false);
|
|
}
|
|
}
|
|
this.editingEnded(element, context);
|
|
this.update();
|
|
}
|
|
|
|
_applyUserInput(element, userInput, previousContent, context, commitEditor) {
|
|
if (!this._inlineStyle) {
|
|
// Element has no renderer.
|
|
return this.editingCancelled(element, context); // nothing changed, so cancel
|
|
}
|
|
|
|
if (commitEditor && userInput === previousContent)
|
|
return this.editingCancelled(element, context); // nothing changed, so cancel
|
|
|
|
if (context.box !== 'position' && (!userInput || userInput === '\u2012'))
|
|
userInput = '0px';
|
|
else if (context.box === 'position' && (!userInput || userInput === '\u2012'))
|
|
userInput = 'auto';
|
|
|
|
userInput = userInput.toLowerCase();
|
|
// Append a "px" unit if the user input was just a number.
|
|
if (/^\d+$/.test(userInput))
|
|
userInput += 'px';
|
|
|
|
const styleProperty = context.styleProperty;
|
|
const computedStyle = context.computedStyle;
|
|
|
|
if (computedStyle.get('box-sizing') === 'border-box' && (styleProperty === 'width' || styleProperty === 'height')) {
|
|
if (!userInput.match(/px$/)) {
|
|
Common.console.error(
|
|
'For elements with box-sizing: border-box, only absolute content area dimensions can be applied');
|
|
return;
|
|
}
|
|
|
|
const borderBox = this._getBox(computedStyle, 'border');
|
|
const paddingBox = this._getBox(computedStyle, 'padding');
|
|
let userValuePx = Number(userInput.replace(/px$/, ''));
|
|
if (isNaN(userValuePx))
|
|
return;
|
|
if (styleProperty === 'width')
|
|
userValuePx += borderBox.left + borderBox.right + paddingBox.left + paddingBox.right;
|
|
else
|
|
userValuePx += borderBox.top + borderBox.bottom + paddingBox.top + paddingBox.bottom;
|
|
|
|
userInput = userValuePx + 'px';
|
|
}
|
|
|
|
this.previousPropertyDataCandidate = null;
|
|
|
|
const allProperties = this._inlineStyle.allProperties();
|
|
for (let i = 0; i < allProperties.length; ++i) {
|
|
const property = allProperties[i];
|
|
if (property.name !== context.styleProperty || !property.activeInStyle())
|
|
continue;
|
|
|
|
this.previousPropertyDataCandidate = property;
|
|
property.setValue(userInput, commitEditor, true, callback.bind(this));
|
|
return;
|
|
}
|
|
|
|
this._inlineStyle.appendProperty(context.styleProperty, userInput, callback.bind(this));
|
|
|
|
/**
|
|
* @param {boolean} success
|
|
* @this {Elements.MetricsSidebarPane}
|
|
*/
|
|
function callback(success) {
|
|
if (!success)
|
|
return;
|
|
if (!('originalPropertyData' in this))
|
|
this.originalPropertyData = this.previousPropertyDataCandidate;
|
|
|
|
if (typeof this._highlightMode !== 'undefined')
|
|
this.node().highlight(this._highlightMode);
|
|
|
|
if (commitEditor)
|
|
this.update();
|
|
}
|
|
}
|
|
|
|
_editingCommitted(element, userInput, previousContent, context) {
|
|
this.editingEnded(element, context);
|
|
this._applyUserInput(element, userInput, previousContent, context, true);
|
|
}
|
|
};
|