Commit 3672c402 authored by Dmitry Gozman's avatar Dmitry Gozman Committed by Commit Bot

[DevTools] Introduce XWidget and use it in one class

PerfUI.FilmStripView.Dialog is a first user of x-widget.

Bug: none
Change-Id: I8c98d82239d6a64472a9ec1cb825600992ee143e
Reviewed-on: https://chromium-review.googlesource.com/848292Reviewed-by: default avatarAndrey Lushnikov <lushnikov@chromium.org>
Commit-Queue: Dmitry Gozman <dgozman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#526846}
parent 6804847f
......@@ -415,7 +415,6 @@ all_devtools_files = [
"front_end/object_ui/RemoteObjectPreviewFormatter.js",
"front_end/perf_ui/chartViewport.css",
"front_end/perf_ui/ChartViewport.js",
"front_end/perf_ui/filmStripDialog.css",
"front_end/perf_ui/filmStripView.css",
"front_end/perf_ui/FilmStripView.js",
"front_end/perf_ui/flameChart.css",
......@@ -807,6 +806,7 @@ all_devtools_files = [
"front_end/ui/Widget.js",
"front_end/ui/XElement.js",
"front_end/ui/XLink.js",
"front_end/ui/XWidget.js",
"front_end/ui/ZoomManager.js",
"front_end/worker_service/ServiceDispatcher.js",
"front_end/workspace/FileManager.js",
......
......@@ -638,6 +638,15 @@ Element.prototype.animate = function(keyframes, timing) {};
*/
Element.prototype.addEventListener = function(type, listener, options) {};
/**
* @override
* @param {string} type
* @param {(!EventListener|!function (!Event): (boolean|undefined)|null)} listener
* @param {(boolean|!{capture: (boolean|undefined), once: (boolean|undefined), passive: (boolean|undefined)})=} options
* @this {EventTarget}
*/
Element.prototype.removeEventListener = function(type, listener, options) {};
var acorn = {
/**
* @param {string} text
......@@ -802,3 +811,9 @@ Console.prototype.context = function(context) {};
* @return {string}
*/
var ls = function(strings, vararg) {};
/**
* @constructor
* @param {function(!Array<*>)} callback
*/
var ResizeObserver = function(callback) {};
......@@ -201,47 +201,47 @@ PerfUI.FilmStripView.Modes = {
FrameBased: 'FrameBased'
};
/**
* @unrestricted
*/
PerfUI.FilmStripView.Dialog = class extends UI.VBox {
PerfUI.FilmStripView.Dialog = class {
/**
* @param {!SDK.FilmStripModel.Frame} filmStripFrame
* @param {number=} zeroTime
*/
constructor(filmStripFrame, zeroTime) {
super(true);
this.registerRequiredCSS('perf_ui/filmStripDialog.css');
this.contentElement.classList.add('filmstrip-dialog');
this.contentElement.tabIndex = 0;
this._frames = filmStripFrame.model().frames();
this._index = filmStripFrame.index;
this._zeroTime = zeroTime || filmStripFrame.model().zeroTime();
var imageScrollElement = this.contentElement.createChild('div', 'filmstrip-dialog-image-scroll');
this._imageElement = imageScrollElement.createChild('img');
var footerElement = this.contentElement.createChild('div', 'filmstrip-dialog-footer');
footerElement.createChild('div', 'flex-auto');
var prevButton = UI.createTextButton('\u25C0', this._onPrevFrame.bind(this));
prevButton.title = Common.UIString('Previous frame');
footerElement.appendChild(prevButton);
this._timeLabel = footerElement.createChild('div', 'filmstrip-dialog-label');
var nextButton = UI.createTextButton('\u25B6', this._onNextFrame.bind(this));
nextButton.title = Common.UIString('Next frame');
footerElement.appendChild(nextButton);
footerElement.createChild('div', 'flex-auto');
this.contentElement.addEventListener('keydown', this._keyDown.bind(this), false);
this.setDefaultFocusedElement(this.contentElement);
this._fragment = UI.Fragment.build`
<x-widget flex=none margin=12px>
<x-hbox overflow=auto border='1px solid #ddd' max-height=80vh max-width=80vw>
<img $=image></img>
</x-hbox>
<x-hbox x-center justify-content=center margin-top=10px>
${prevButton}
<x-hbox $=time margin=8px></x-hbox>
${nextButton}
</x-hbox>
</x-widget>
`;
this._widget = /** @type {!UI.XWidget} */ (this._fragment.element());
this._widget.tabIndex = 0;
this._widget.addEventListener('keydown', this._keyDown.bind(this), false);
this._frames = filmStripFrame.model().frames();
this._index = filmStripFrame.index;
this._zeroTime = zeroTime || filmStripFrame.model().zeroTime();
/** @type {?UI.Dialog} */
this._dialog = null;
this._render();
}
_resize() {
if (!this._dialog) {
this._dialog = new UI.Dialog();
this.show(this._dialog.contentElement);
this._dialog.contentElement.appendChild(this._widget);
this._dialog.setDefaultFocusedElement(this._widget);
this._dialog.show();
}
this._dialog.setSizeBehavior(UI.GlassPane.SizeBehavior.MeasureContent);
......@@ -303,9 +303,9 @@ PerfUI.FilmStripView.Dialog = class extends UI.VBox {
*/
_render() {
var frame = this._frames[this._index];
this._timeLabel.textContent = Number.millisToString(frame.timestamp - this._zeroTime);
this._fragment.$('time').textContent = Number.millisToString(frame.timestamp - this._zeroTime);
return frame.imageDataPromise()
.then(PerfUI.FilmStripView._setImageData.bind(null, this._imageElement))
.then(PerfUI.FilmStripView._setImageData.bind(null, this._fragment.$('image')))
.then(this._resize.bind(this));
}
};
/*
* Copyright (c) 2015 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
:host {
flex: none !important;
}
.filmstrip-dialog {
margin: 12px;
}
.filmstrip-dialog-image-scroll {
overflow: auto;
border: 1px solid #ddd;
max-height: 80vh;
max-width: 80vw;
}
.filmstrip-dialog-footer {
display: flex;
align-items: center;
margin-top: 10px;
}
.filmstrip-dialog-label {
margin: 8px 8px;
}
......@@ -46,7 +46,6 @@
"resources": [
"chartViewport.css",
"filmStripView.css",
"filmStripDialog.css",
"flameChart.css",
"overviewGrid.css",
"pieChart.css",
......
......@@ -44,6 +44,13 @@ UI.GlassPane = class {
this._widget.registerRequiredCSS(cssFile);
}
/**
* @param {?Element} element
*/
setDefaultFocusedElement(element) {
this._widget.setDefaultFocusedElement(element);
}
/**
* @param {boolean} dimmed
*/
......
......@@ -744,6 +744,7 @@ UI._focusChanged = function(event) {
var document = event.target && event.target.ownerDocument;
var element = document ? document.deepActiveElement() : null;
UI.Widget.focusWidgetForNode(element);
UI.XWidget.focusWidgetForNode(element);
if (!UI._keyboardFocus)
return;
element.setAttribute('data-keyboard-focus', 'true');
......
......@@ -492,9 +492,17 @@ UI.Widget = class extends Common.Object {
for (var child of this._children) {
if (child._visible) {
child.focus();
break;
return;
}
}
var child = this.contentElement.traverseNextNode(this.contentElement);
while (child) {
if (child instanceof UI.XWidget) {
child.focus();
return;
}
child = child.traverseNextNode(this.contentElement);
}
}
}
......
......@@ -8,10 +8,11 @@
UI.XElement = class extends HTMLElement {
static get observedAttributes() {
return [
'flex', 'padding', 'padding-top', 'padding-bottom', 'padding-left', 'padding-right',
'margin', 'margin-top', 'margin-bottom', 'margin-left', 'margin-right', 'overflow',
'overflow-x', 'overflow-y', 'font-size', 'color', 'background', 'background-color',
'border', 'border-top', 'border-bottom', 'border-left', 'border-right'
'flex', 'padding', 'padding-top', 'padding-bottom', 'padding-left',
'padding-right', 'margin', 'margin-top', 'margin-bottom', 'margin-left',
'margin-right', 'overflow', 'overflow-x', 'overflow-y', 'font-size',
'color', 'background', 'background-color', 'border', 'border-top',
'border-bottom', 'border-left', 'border-right', 'max-width', 'max-height'
];
}
......@@ -62,7 +63,7 @@ UI._XBox = class extends UI.XElement {
static get observedAttributes() {
// TODO(dgozman): should be super.observedAttributes, but does not compile.
return UI.XElement.observedAttributes.concat(['x-start', 'x-center', 'x-stretch', 'x-baseline']);
return UI.XElement.observedAttributes.concat(['x-start', 'x-center', 'x-stretch', 'x-baseline', 'justify-content']);
}
/**
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @extends {UI.XElement}
*/
UI.XWidget = class extends UI.XElement {
constructor() {
super();
this.style.setProperty('display', 'flex');
this.style.setProperty('flex-direction', 'column');
this.style.setProperty('align-items', 'stretch');
this.style.setProperty('justify-content', 'flex-start');
this.style.setProperty('contain', 'layout style');
this._visible = false;
/** @type {?DocumentFragment} */
this._shadowRoot;
/** @type {?Element} */
this._defaultFocusedElement = null;
/** @type {!Array<!Element>} */
this._elementsToRestoreScrollPositionsFor = [];
/** @type {?function()} */
this._onShownCallback;
/** @type {?function()} */
this._onHiddenCallback;
/** @type {?function()} */
this._onResizedCallback;
if (!UI.XWidget._observer) {
UI.XWidget._observer = new ResizeObserver(entries => {
for (var entry of entries) {
if (entry.target._visible && entry.target._onResizedCallback)
entry.target._onResizedCallback.call(null);
}
});
}
UI.XWidget._observer.observe(this);
this.setElementsToRestoreScrollPositionsFor([this]);
}
/**
* @param {?Node} node
*/
static focusWidgetForNode(node) {
node = node && node.parentNodeOrShadowHost();
var widget = null;
while (node) {
if (node instanceof UI.XWidget) {
if (widget)
node._defaultFocusedElement = widget;
widget = node;
}
node = node.parentNodeOrShadowHost();
}
}
/**
* @return {boolean}
*/
isShowing() {
return this._visible;
}
/**
* @param {string} cssFile
*/
registerRequiredCSS(cssFile) {
UI.appendStyle(this._shadowRoot || this, cssFile);
}
/**
* @param {?function()} callback
*/
setOnShown(callback) {
this._onShownCallback = callback;
}
/**
* @param {?function()} callback
*/
setOnHidden(callback) {
this._onHiddenCallback = callback;
}
/**
* @param {?function()} callback
*/
setOnResized(callback) {
this._onResizedCallback = callback;
}
/**
* @param {!Array<!Element>} elements
*/
setElementsToRestoreScrollPositionsFor(elements) {
for (var element of this._elementsToRestoreScrollPositionsFor)
element.removeEventListener('scroll', UI.XWidget._storeScrollPosition, {passive: true, capture: false});
this._elementsToRestoreScrollPositionsFor = elements;
for (var element of this._elementsToRestoreScrollPositionsFor)
element.addEventListener('scroll', UI.XWidget._storeScrollPosition, {passive: true, capture: false});
}
restoreScrollPositions() {
for (var element of this._elementsToRestoreScrollPositionsFor) {
if (element._scrollTop)
element.scrollTop = element._scrollTop;
if (element._scrollLeft)
element.scrollLeft = element._scrollLeft;
}
}
/**
* @param {!Event} event
*/
static _storeScrollPosition(event) {
var element = event.currentTarget;
element._scrollTop = element.scrollTop;
element._scrollLeft = element.scrollLeft;
}
/**
* @param {?Element} element
*/
setDefaultFocusedElement(element) {
if (element && !this.isSelfOrAncestor(element))
throw new Error('Default focus must be descendant');
this._defaultFocusedElement = element;
}
/**
* @override
*/
focus() {
if (!this._visible)
return;
var element;
if (this._defaultFocusedElement && this.isSelfOrAncestor(this._defaultFocusedElement)) {
element = this._defaultFocusedElement;
} else if (this.tabIndex !== -1) {
element = this;
} else {
var child = this.traverseNextNode(this);
while (child) {
if ((child instanceof UI.XWidget) && child._visible) {
element = child;
break;
}
child = child.traverseNextNode(this);
}
}
if (!element || element.hasFocus())
return;
if (element === this)
HTMLElement.prototype.focus.call(this);
else
element.focus();
}
/**
* @override
*/
connectedCallback() {
this._visible = true;
this.restoreScrollPositions();
if (this._onShownCallback)
this._onShownCallback.call(null);
}
/**
* @override
*/
disconnectedCallback() {
this._visible = false;
if (this._onHiddenCallback)
this._onHiddenCallback.call(null);
}
};
self.customElements.define('x-widget', UI.XWidget);
......@@ -55,7 +55,8 @@
"ZoomManager.js",
"ShortcutsScreen.js",
"Geometry.js",
"XLink.js"
"XLink.js",
"XWidget.js"
],
"resources": [
"checkboxTextLabel.css",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment