Skip to content

Commit

Permalink
[DevTools] ElementsBreadcrumbs Perf Improvements & Cleanup
Browse files Browse the repository at this point in the history
Reduces the number of layouts performed.
Moves update to be run in a requestAnimationFrame callback.
Cleans up the code a bit to move pieces of the massive update functions
into their own methods for a clearer breakdown of where time is spent.

BUG=657141
R=lushnikov,paulirish

Review-Url: https://codereview.chromium.org/2551403002
Cr-Commit-Position: refs/heads/master@{#437742}
  • Loading branch information
patrickhulce authored and Commit bot committed Dec 10, 2016
1 parent 485edbd commit 0bb8f9c
Showing 1 changed file with 107 additions and 84 deletions.
191 changes: 107 additions & 84 deletions front_end/elements/ElementsBreadcrumbs.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Elements.ElementsBreadcrumbs = class extends UI.HBox {
*/
setSelectedNode(node) {
this._currentDOMNode = node;
this.update();
this.crumbsElement.window().requestAnimationFrame(() => this.update());
}

_mouseMovedInCrumbs(event) {
Expand All @@ -59,6 +59,60 @@ Elements.ElementsBreadcrumbs = class extends UI.HBox {
SDK.DOMModel.hideDOMNodeHighlight();
}


/**
* @param {!Event} event
* @this {Elements.ElementsBreadcrumbs}
*/
_onClickCrumb(event) {
event.preventDefault();
var crumb = /** @type {!Element} */ (event.currentTarget);
if (!crumb.classList.contains('collapsed')) {
this.dispatchEventToListeners(Elements.ElementsBreadcrumbs.Events.NodeSelected, crumb[this._nodeSymbol]);
return;
}

// Clicking a collapsed crumb will expose the hidden crumbs.
if (crumb === this.crumbsElement.firstChild) {
// If the clicked crumb is the first child, pick the farthest crumb
// that is still hidden. This allows the user to expose every crumb.
var currentCrumb = crumb;
while (currentCrumb) {
var hidden = currentCrumb.classList.contains('hidden');
var collapsed = currentCrumb.classList.contains('collapsed');
if (!hidden && !collapsed)
break;
crumb = currentCrumb;
currentCrumb = currentCrumb.nextSiblingElement;
}
}

this.updateSizes(crumb);
}

/**
* @param {!SDK.DOMNode} domNode
* @return {?string}
*/
_determineElementTitle(domNode) {
switch (domNode.nodeType()) {
case Node.ELEMENT_NODE:
if (domNode.pseudoType())
return '::' + domNode.pseudoType();
return null;
case Node.TEXT_NODE:
return Common.UIString('(text)');
case Node.COMMENT_NODE:
return '<!-->';
case Node.DOCUMENT_TYPE_NODE:
return '<!DOCTYPE>';
case Node.DOCUMENT_FRAGMENT_NODE:
return domNode.shadowRootType() ? '#shadow-root' : domNode.nodeNameInCorrectCase();
default:
return domNode.nodeNameInCorrectCase();
}
}

/**
* @param {boolean=} force
*/
Expand Down Expand Up @@ -91,81 +145,22 @@ Elements.ElementsBreadcrumbs = class extends UI.HBox {

crumbs.removeChildren();

var panel = this;

/**
* @param {!Event} event
* @this {Elements.ElementsBreadcrumbs}
*/
function selectCrumb(event) {
event.preventDefault();
var crumb = /** @type {!Element} */ (event.currentTarget);
if (!crumb.classList.contains('collapsed')) {
this.dispatchEventToListeners(Elements.ElementsBreadcrumbs.Events.NodeSelected, crumb[this._nodeSymbol]);
return;
}

// Clicking a collapsed crumb will expose the hidden crumbs.
if (crumb === panel.crumbsElement.firstChild) {
// If the focused crumb is the first child, pick the farthest crumb
// that is still hidden. This allows the user to expose every crumb.
var currentCrumb = crumb;
while (currentCrumb) {
var hidden = currentCrumb.classList.contains('hidden');
var collapsed = currentCrumb.classList.contains('collapsed');
if (!hidden && !collapsed)
break;
crumb = currentCrumb;
currentCrumb = currentCrumb.nextSiblingElement;
}
}

this.updateSizes(crumb);
}

var boundSelectCrumb = selectCrumb.bind(this);
for (var current = currentDOMNode; current; current = current.parentNode) {
if (current.nodeType() === Node.DOCUMENT_NODE)
continue;

crumb = createElementWithClass('span', 'crumb');
crumb[this._nodeSymbol] = current;
crumb.addEventListener('mousedown', boundSelectCrumb, false);

var crumbTitle = '';
switch (current.nodeType()) {
case Node.ELEMENT_NODE:
if (current.pseudoType())
crumbTitle = '::' + current.pseudoType();
else
Components.DOMPresentationUtils.decorateNodeLabel(current, crumb);
break;

case Node.TEXT_NODE:
crumbTitle = Common.UIString('(text)');
break;

case Node.COMMENT_NODE:
crumbTitle = '<!-->';
break;

case Node.DOCUMENT_TYPE_NODE:
crumbTitle = '<!DOCTYPE>';
break;

case Node.DOCUMENT_FRAGMENT_NODE:
crumbTitle = current.shadowRootType() ? '#shadow-root' : current.nodeNameInCorrectCase();
break;

default:
crumbTitle = current.nodeNameInCorrectCase();
}
crumb.addEventListener('mousedown', this._onClickCrumb.bind(this), false);

if (!crumb.childNodes.length) {
var crumbTitle = this._determineElementTitle(current);
if (crumbTitle) {
var nameElement = createElement('span');
nameElement.textContent = crumbTitle;
crumb.appendChild(nameElement);
crumb.title = crumbTitle;
} else {
Components.DOMPresentationUtils.decorateNodeLabel(current, crumb);
}

if (current === currentDOMNode)
Expand All @@ -178,18 +173,13 @@ Elements.ElementsBreadcrumbs = class extends UI.HBox {

/**
* @param {!Element=} focusedCrumb
* @return {{selectedIndex: number, focusedIndex: number, selectedCrumb: ?Element}}
*/
updateSizes(focusedCrumb) {
if (!this.isShowing())
return;

_resetCrumbStylesAndFindSelections(focusedCrumb) {
var crumbs = this.crumbsElement;
if (!crumbs.firstChild)
return;

var selectedIndex = 0;
var focusedIndex = 0;
var selectedCrumb;
var selectedCrumb = null;

// Reset crumb styles.
for (var i = 0; i < crumbs.childNodes.length; ++i) {
Expand All @@ -207,14 +197,31 @@ Elements.ElementsBreadcrumbs = class extends UI.HBox {
crumb.classList.remove('compact', 'collapsed', 'hidden');
}

// Layout 1: Measure total and normal crumb sizes
var contentElementWidth = this.contentElement.offsetWidth;
return {selectedIndex: selectedIndex, focusedIndex: focusedIndex, selectedCrumb: selectedCrumb};
}

/**
* @return {{normal: !Array.<number>, compact: !Array.<number>, collapsed: number, available: number}}
*/
_measureElementSizes() {
var crumbs = this.crumbsElement;

// Layout 1: Measure total and normal crumb sizes at the same time as a
// dummy element for the collapsed size.
var collapsedElement = createElementWithClass('span', 'crumb collapsed');
crumbs.insertBefore(collapsedElement, crumbs.firstChild);

var available = crumbs.offsetWidth;
var collapsed = collapsedElement.offsetWidth;

var normalSizes = [];
for (var i = 0; i < crumbs.childNodes.length; ++i) {
for (var i = 1; i < crumbs.childNodes.length; ++i) {
var crumb = crumbs.childNodes[i];
normalSizes[i] = crumb.offsetWidth;
normalSizes[i - 1] = crumb.offsetWidth;
}

crumbs.removeChild(collapsedElement);

// Layout 2: Measure collapsed crumb sizes
var compactSizes = [];
for (var i = 0; i < crumbs.childNodes.length; ++i) {
Expand All @@ -226,30 +233,46 @@ Elements.ElementsBreadcrumbs = class extends UI.HBox {
compactSizes[i] = crumb.offsetWidth;
}

// Layout 3: Measure collapsed crumb size
crumbs.firstChild.classList.add('collapsed');
var collapsedSize = crumbs.firstChild.offsetWidth;

// Clean up.
for (var i = 0; i < crumbs.childNodes.length; ++i) {
var crumb = crumbs.childNodes[i];
crumb.classList.remove('compact', 'collapsed');
}

return {normal: normalSizes, compact: compactSizes, collapsed: collapsed, available: available};
}

/**
* @param {!Element=} focusedCrumb
*/
updateSizes(focusedCrumb) {
if (!this.isShowing())
return;

var crumbs = this.crumbsElement;
if (!crumbs.firstChild)
return;

var selections = this._resetCrumbStylesAndFindSelections(focusedCrumb);
var sizes = this._measureElementSizes();
var selectedIndex = selections.selectedIndex;
var focusedIndex = selections.focusedIndex;
var selectedCrumb = selections.selectedCrumb;

function crumbsAreSmallerThanContainer() {
var totalSize = 0;
for (var i = 0; i < crumbs.childNodes.length; ++i) {
var crumb = crumbs.childNodes[i];
if (crumb.classList.contains('hidden'))
continue;
if (crumb.classList.contains('collapsed')) {
totalSize += collapsedSize;
totalSize += sizes.collapsed;
continue;
}
totalSize += crumb.classList.contains('compact') ? compactSizes[i] : normalSizes[i];
totalSize += crumb.classList.contains('compact') ? sizes.compact[i] : sizes.normal[i];
}
const rightPadding = 10;
return totalSize + rightPadding < contentElementWidth;
return totalSize + rightPadding < sizes.available;
}

if (crumbsAreSmallerThanContainer())
Expand Down

0 comments on commit 0bb8f9c

Please sign in to comment.