Enable image preview in styles panel

This change adds image preview functionality to the styles panel
including the computed styles view. The preview functionality within
ElementsPanel is removed because it seems unused (equivalent code
in ElementsTreeOutline handles the previews).

Screenshot: https://i.imgur.com/TIU3Rg3.png

Fixed: 1040019
Change-Id: I6e387826de908df74ea6e5d7a89e2293291e692b
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2156347
Commit-Queue: Alex Rudenko <alexrudenko@chromium.org>
Reviewed-by: Mathias Bynens <mathias@chromium.org>
Reviewed-by: Peter Marshall <petermarshall@chromium.org>
This commit is contained in:
Alex Rudenko
2020-04-20 15:43:46 +00:00
committed by Commit Bot
parent 035eedc849
commit 1ebbb30be2
8 changed files with 129 additions and 92 deletions
+1
View File
@@ -1167,6 +1167,7 @@ all_devtools_modules = [
"elements/ElementsTreeElementHighlighter.js",
"elements/ElementsTreeOutline.js",
"elements/EventListenersWidget.js",
"elements/ImagePreviewPopover.js",
"elements/InspectElementModeController.js",
"elements/MarkerDecorator.js",
"elements/MetricsSidebarPane.js",
+10
View File
@@ -34,6 +34,7 @@ import * as SDK from '../sdk/sdk.js';
import * as UI from '../ui/ui.js';
import {ComputedStyle, ComputedStyleModel, Events} from './ComputedStyleModel.js'; // eslint-disable-line no-unused-vars
import {ImagePreviewPopover} from './ImagePreviewPopover.js';
import {PlatformFontsWidget} from './PlatformFontsWidget.js';
import {StylePropertiesSection, StylesSidebarPane, StylesSidebarPropertyRenderer} from './StylesSidebarPane.js';
@@ -78,6 +79,14 @@ export class ComputedStyleWidget extends UI.ThrottledWidget.ThrottledWidget {
this._linkifier = new Components.Linkifier.Linkifier(_maxLinkLength);
this._imagePreviewPopover = new ImagePreviewPopover(this.contentElement, event => {
const link = event.composedPath()[0];
if (link instanceof Element) {
return link;
}
return null;
}, () => this._computedStyleModel.node());
/**
* @param {?RegExp} regex
* @this {ComputedStyleWidget}
@@ -164,6 +173,7 @@ export class ComputedStyleWidget extends UI.ThrottledWidget.ThrottledWidget {
expandedProperties.add(propertyName);
}
const hadFocus = this._propertiesOutline.element.hasFocus();
this._imagePreviewPopover.hide();
this._propertiesOutline.removeChildren();
this._linkifier.reset();
const cssModel = this._computedStyleModel.cssModel();
+1 -40
View File
@@ -37,7 +37,7 @@ import * as UI from '../ui/ui.js';
import {ComputedStyleWidget} from './ComputedStyleWidget.js';
import {ElementsBreadcrumbs, Events} from './ElementsBreadcrumbs.js';
import {ElementsTreeElement, HrefSymbol} from './ElementsTreeElement.js'; // eslint-disable-line no-unused-vars
import {ElementsTreeElement} from './ElementsTreeElement.js'; // eslint-disable-line no-unused-vars
import {ElementsTreeElementHighlighter} from './ElementsTreeElementHighlighter.js';
import {ElementsTreeOutline} from './ElementsTreeOutline.js';
import {MarkerDecorator} from './MarkerDecorator.js'; // eslint-disable-line no-unused-vars
@@ -306,9 +306,6 @@ export class ElementsPanel extends UI.Panel.Panel {
this._contentElement.removeChild(header);
}
}
if (this._popoverHelper) {
this._popoverHelper.hidePopover();
}
super.willHide();
self.UI.context.setFlavor(ElementsPanel, null);
}
@@ -523,36 +520,6 @@ export class ElementsPanel extends UI.Panel.Panel {
UI.ViewManager.ViewManager.instance().showView('elements').then(() => this.selectDOMNode(node, true));
}
/**
* @param {!Event} event
* @return {?UI.PopoverRequest}
*/
_getPopoverRequest(event) {
let link = event.target;
while (link && !link[HrefSymbol]) {
link = link.parentElementOrShadowHost();
}
if (!link) {
return null;
}
return {
box: link.boxInWindow(),
show: async popover => {
const node = this.selectedDOMNode();
if (!node) {
return false;
}
const preview =
await Components.ImagePreview.ImagePreview.build(node.domModel().target(), link[HrefSymbol], true);
if (preview) {
popover.contentElement.appendChild(preview);
}
return !!preview;
}
};
}
_jumpToSearchResult(index) {
if (!this._searchResults) {
return;
@@ -880,12 +847,6 @@ export class ElementsPanel extends UI.Panel.Panel {
this.sidebarPaneView = UI.ViewManager.ViewManager.instance().createTabbedLocation(
() => UI.ViewManager.ViewManager.instance().showView('elements'));
const tabbedPane = this.sidebarPaneView.tabbedPane();
if (this._popoverHelper) {
this._popoverHelper.hidePopover();
}
this._popoverHelper = new UI.PopoverHelper.PopoverHelper(tabbedPane.element, this._getPopoverRequest.bind(this));
this._popoverHelper.setHasPadding(true);
this._popoverHelper.setTimeout(0);
if (this._splitMode !== _splitMode.Vertical) {
this._splitWidget.installResizer(tabbedPane.headerElement());
+2 -3
View File
@@ -39,6 +39,7 @@ import * as UI from '../ui/ui.js';
import {canGetJSPath, cssPath, jsPath, xPath} from './DOMPath.js';
import {MappedCharToEntity, UpdateRecord} from './ElementsTreeOutline.js'; // eslint-disable-line no-unused-vars
import {ImagePreviewPopover} from './ImagePreviewPopover.js';
import {MarkerDecorator} from './MarkerDecorator.js';
/**
@@ -1410,8 +1411,7 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
const link = node.nodeName().toLowerCase() === 'a' ?
UI.XLink.XLink.create(rewrittenHref, value, '', true /* preventClick */) :
Components.Linkifier.Linkifier.linkifyURL(rewrittenHref, {text: value, preventClick: true});
link[HrefSymbol] = rewrittenHref;
return link;
return ImagePreviewPopover.setImageUrl(link, rewrittenHref);
}
const nodeName = node ? node.nodeName().toLowerCase() : '';
@@ -1838,7 +1838,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
}
}
export const HrefSymbol = Symbol('ElementsTreeElement.Href');
export const InitialChildrenLimit = 500;
// A union of HTML4 and HTML5-Draft elements that explicitly
+20 -39
View File
@@ -29,13 +29,13 @@
*/
import * as Common from '../common/common.js';
import * as Components from '../components/components.js';
import * as ProtocolClient from '../protocol_client/protocol_client.js'; // eslint-disable-line no-unused-vars
import * as SDK from '../sdk/sdk.js';
import * as UI from '../ui/ui.js';
import {linkifyDeferredNodeReference} from './DOMLinkifier.js';
import {ElementsTreeElement, HrefSymbol, InitialChildrenLimit} from './ElementsTreeElement.js';
import {ElementsTreeElement, InitialChildrenLimit} from './ElementsTreeElement.js';
import {HrefSymbol, ImagePreviewPopover} from './ImagePreviewPopover.js';
/**
* @unrestricted
@@ -88,9 +88,22 @@ export class ElementsTreeOutline extends UI.TreeOutline.TreeOutline {
this._visible = false;
this._popoverHelper = new UI.PopoverHelper.PopoverHelper(this._element, this._getPopoverRequest.bind(this));
this._popoverHelper.setHasPadding(true);
this._popoverHelper.setTimeout(0, 100);
this._imagePreviewPopover = new ImagePreviewPopover(
this.contentElement,
event => {
let link = event.target;
while (link && !link[HrefSymbol]) {
link = link.parentElementOrShadowHost();
}
return link;
},
link => {
const listItem = link.enclosingNodeOrSelfWithNodeName('li');
if (!listItem) {
return null;
}
return /** @type {!ElementsTreeElement} */ (listItem.treeElement).node();
});
/** @type {!Map<!SDK.DOMModel.DOMNode, !UpdateRecord>} */
this._updateRecords = new Map();
@@ -324,7 +337,7 @@ export class ElementsTreeOutline extends UI.TreeOutline.TreeOutline {
}
this._visible = visible;
if (!this._visible) {
this._popoverHelper.hidePopover();
this._imagePreviewPopover.hide();
if (this._multilineEditing) {
this._multilineEditing.cancel();
}
@@ -574,38 +587,6 @@ export class ElementsTreeOutline extends UI.TreeOutline.TreeOutline {
return element;
}
/**
* @param {!Event} event
* @return {?UI.PopoverRequest}
*/
_getPopoverRequest(event) {
let link = event.target;
while (link && !link[HrefSymbol]) {
link = link.parentElementOrShadowHost();
}
if (!link) {
return null;
}
return {
box: link.boxInWindow(),
show: async popover => {
const listItem = link.enclosingNodeOrSelfWithNodeName('li');
if (!listItem) {
return false;
}
const node = /** @type {!ElementsTreeElement} */ (listItem.treeElement).node();
const precomputedFeatures = await Components.ImagePreview.ImagePreview.loadDimensionsForNode(node);
const preview = await Components.ImagePreview.ImagePreview.build(
node.domModel().target(), link[HrefSymbol], true, {precomputedFeatures});
if (preview) {
popover.contentElement.appendChild(preview);
}
return !!preview;
}
};
}
/**
* @param {!Event} event
*/
@@ -1047,7 +1028,7 @@ export class ElementsTreeOutline extends UI.TreeOutline.TreeOutline {
_reset() {
this.rootDOMNode = null;
this.selectDOMNode(null, false);
this._popoverHelper.hidePopover();
this._imagePreviewPopover.hide();
delete this._clipboardNodeData;
SDK.OverlayModel.OverlayModel.hideDOMNodeHighlight();
this._updateRecords.clear();
+71
View File
@@ -0,0 +1,71 @@
// Copyright 2020 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.
import * as Components from '../components/components.js';
import * as SDK from '../sdk/sdk.js'; // eslint-disable-line no-unused-vars
import * as UI from '../ui/ui.js';
/**
* ImagePreviewPopover sets listeners on the container element to display
* an image preview if needed. The image URL comes from the event (mouseover) target
* in a propery identified by HrefSymbol. To enable preview for any child element
* set the property HrefSymbol.
*/
export class ImagePreviewPopover {
/**
* @param {!Element} container
* @param {function(!Event):?Element} getLinkElement
* @param {function(!Element):?SDK.DOMModel.DOMNode} getDOMNode
*/
constructor(container, getLinkElement, getDOMNode) {
this._getLinkElement = getLinkElement;
this._getDOMNode = getDOMNode;
this._popover = new UI.PopoverHelper.PopoverHelper(container, this._handleRequest.bind(this));
this._popover.setHasPadding(true);
this._popover.setTimeout(0, 100);
}
/**
* @param {!Event} event
* @return {?UI.PopoverHelper.PopoverRequest}
*/
_handleRequest(event) {
const link = this._getLinkElement(event);
if (!link || !link[HrefSymbol]) {
return null;
}
return {
box: link.boxInWindow(),
hide: undefined,
show: async popover => {
const node = this._getDOMNode(/** @type {!Element} */ (link));
if (!node) {
return false;
}
const precomputedFeatures = await Components.ImagePreview.ImagePreview.loadDimensionsForNode(node);
const preview = await Components.ImagePreview.ImagePreview.build(
node.domModel().target(), link[HrefSymbol], true, {precomputedFeatures});
if (preview) {
popover.contentElement.appendChild(preview);
}
return !!preview;
}
};
}
hide() {
this._popover.hidePopover();
}
/**
* @param {!Element} element
* @param {string} url
*/
static setImageUrl(element, url) {
element[HrefSymbol] = url;
return element;
}
}
export const HrefSymbol = Symbol('ImagePreviewPopover.Href');
+22 -9
View File
@@ -39,6 +39,7 @@ import * as UI from '../ui/ui.js';
import {ColorSwatchPopoverIcon, ShadowSwatchPopoverHelper} from './ColorSwatchPopoverIcon.js';
import {linkifyDeferredNodeReference} from './DOMLinkifier.js';
import {ElementsSidebarPane} from './ElementsSidebarPane.js';
import {ImagePreviewPopover} from './ImagePreviewPopover.js';
import {StylePropertyHighlighter} from './StylePropertyHighlighter.js';
import {StylePropertyTreeElement} from './StylePropertyTreeElement.js';
import {Context} from './StylePropertyTreeElement.js'; // eslint-disable-line no-unused-vars
@@ -90,6 +91,14 @@ export class StylesSidebarPane extends ElementsSidebarPane {
self.UI.context.addFlavorChangeListener(SDK.DOMModel.DOMNode, this.forceUpdate, this);
this.contentElement.addEventListener('copy', this._clipboardCopy.bind(this));
this._resizeThrottler = new Common.Throttler.Throttler(100);
this._imagePreviewPopover = new ImagePreviewPopover(this.contentElement, event => {
const link = event.composedPath()[0];
if (link instanceof Element) {
return link;
}
return null;
}, () => this.node());
}
/**
@@ -707,6 +716,7 @@ export class StylesSidebarPane extends ElementsSidebarPane {
*/
willHide() {
this._swatchPopoverHelper.hide();
this._imagePreviewPopover.hide();
super.willHide();
}
@@ -2704,15 +2714,18 @@ export class StylesSidebarPropertyRenderer {
} else if (this._node) {
hrefUrl = this._node.resolveURL(url);
}
container.appendChild(Components.Linkifier.Linkifier.linkifyURL(hrefUrl || url, {
text: url,
preventClick: true,
// crbug.com/1027168
// We rely on CSS text-overflow: ellipsis to hide long URLs in the Style panel,
// so that we don't have to keep two versions (original vs. trimmed) of URL
// at the same time, which complicates both StylesSidebarPane and StylePropertyTreeElement.
bypassURLTrimming: true,
}));
const link = ImagePreviewPopover.setImageUrl(
Components.Linkifier.Linkifier.linkifyURL(hrefUrl || url, {
text: url,
preventClick: true,
// crbug.com/1027168
// We rely on CSS text-overflow: ellipsis to hide long URLs in the Style panel,
// so that we don't have to keep two versions (original vs. trimmed) of URL
// at the same time, which complicates both StylesSidebarPane and StylePropertyTreeElement.
bypassURLTrimming: true,
}),
url);
container.appendChild(link);
container.createTextChild(')');
return container;
}
+2 -1
View File
@@ -323,7 +323,8 @@
"ElementsPanel.js",
"ClassesPaneWidget.js",
"ElementStatePaneWidget.js",
"ElementsTreeElementHighlighter.js"
"ElementsTreeElementHighlighter.js",
"ImagePreviewPopover.js"
],
"resources": [
"breadcrumbs.css",