Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Table] TruncatedFormat popover improvements and performance tuning for dev preview #1624

Merged
merged 14 commits into from
Sep 29, 2017

Conversation

themadcreator
Copy link
Contributor

@themadcreator themadcreator commented Sep 26, 2017

Switches <TrunctedFormat> to measure text with a canvas element, which does not trigger a relayout.

Also adding switches to the dev preview to demonstrate how the table performs with:

  • Slow native layout
  • An isolated layout boundary

Uses off-DOM canvas text measuring to avoid forced reflows.
SlowLayout component allows us to highlight potential perf issues in the dev preview.

private contentDiv: HTMLDivElement;
private cachedFontString: string;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we cache the font string on first use

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will be out of date if the style changes, but async font loading will not be an issue since the fontstring will still apply once the font is loaded.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine moving forward with this and letting someone file a ticket if out-of-date styles become an issue during the life of the page.

);
} else {
return (
<span className={Classes.TABLE_TRUNCATED_POPOVER_TARGET} onClick={this.handlePopoverOpen}>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this reproduces <Popover>'s internal structure without calling any layout-triggering listeners

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏, but can we add a comment in Popover—or somewhere—indicating that this assumption-ridden code is here?

Copy link
Contributor

@cmslewis cmslewis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woo woo! Left some stuff.

@@ -40,6 +63,7 @@
flex: 1;
order: 2;
z-index: 2;
width: 100%;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reasoning for this rule? Is it because of:

To be a layout boundary, the element must...Not have an implicit or auto width value

?

@@ -52,6 +76,7 @@
.sidebar {
background-color: #EBF1F5;
flex-basis: 300px;
flex-shrink: 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this rule?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i had some trouble getting the preview layout to work with the slowlayoutstack. i'd like to revisit these styles and solidify how we're going to recommend creating layout boundaries.

it might even be worth adding a <LayoutBoundary> react component that uses style attributes (to avoid any class overrides from breaking them)

@@ -34,6 +34,7 @@ import { RenderMode } from "../src/common/renderMode";
import { IRegion } from "../src/regions";
import { DenseGridMutableStore } from "./denseGridMutableStore";
import { LocalStore } from "./localStore";
import { SlowLayoutStack } from "./slowLayout";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would prefer to rename to ./slowLayoutStack to make the export match the filename

}

// const CELL_FONT_PROPERTIES_STRING = `normal normal normal normal 12px / 20px -apple-system, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", Icons16, sans-serif`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete commented code


private contentDiv: HTMLDivElement;
private cachedFontString: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine moving forward with this and letting someone file a ticket if out-of-date styles become an issue during the life of the page.

// <Popover> will always check the content's position on update
// regardless if it is open or not. This negatively affects perf due to
// layout thrashing. So instead we manage the popover state ourselves
// and mimic its popover target
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eww, but so clever

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

really, this should just be done by popover. the dom measuring should not be done on update, but rather on interaction

);
} else {
return (
<span className={Classes.TABLE_TRUNCATED_POPOVER_TARGET} onClick={this.handlePopoverOpen}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏, but can we add a comment in Popover—or somewhere—indicating that this assumption-ridden code is here?

const containerWidth = parseInt(this.props.parentCellWidth, 10);
const availableWidth = containerWidth - CONTAINER_PADDING;
const isTruncated = contentWidth > availableWidth;
this.setState({ isTruncated } as ITruncatedFormatState);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// summoning @gscshoyru for thorough review of this new code

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// appears in a puff of smoke, surrounded by chanting and candles

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...um. Where'd height go? Why are we only measuring the width of the text itself? This doesn't actually work for any cases where we've got work wrap turned on. Did you test with that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so, uh, the only thing you can measure with canvas is width because that's all that exists on TextMetrics.

the text measurement assumes no wrapping in the text and is only useful at detecting horizontal overflow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

😢

@cmslewis cmslewis changed the title [Table] TrunactedFormat perf [Table] Improve TruncatedFormat performance Sep 27, 2017
Copy link
Contributor

@gscshoyru gscshoyru left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...this removes any and all measuring considerations around height. Which means this is horribly broken for any cases where we have text wrapping turned on.

renderRowHeader={this.renderRowHeader}
selectionModes={this.getEnabledSelectionModes()}
styledRegionGroups={this.getStyledRegionGroups()}
<SlowLayoutStack
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

..not that it really matters, and maybe I'm misunderstanding something -- but would it have been better to split the changes to truncated format and the changes to the preview into separate commits?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, and might still need to do that, but i'm using this slowlayoutstack to test my changes to truncatedformat.

const containerWidth = parseInt(this.props.parentCellWidth, 10);
const availableWidth = containerWidth - CONTAINER_PADDING;
const isTruncated = contentWidth > availableWidth;
this.setState({ isTruncated } as ITruncatedFormatState);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// appears in a puff of smoke, surrounded by chanting and candles

const containerWidth = parseInt(this.props.parentCellWidth, 10);
const availableWidth = containerWidth - CONTAINER_PADDING;
const isTruncated = contentWidth > availableWidth;
this.setState({ isTruncated } as ITruncatedFormatState);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...um. Where'd height go? Why are we only measuring the width of the text itself? This doesn't actually work for any cases where we've got work wrap turned on. Did you test with that?

// Since we measure only the `textContent` of the cell to determine the
// truncation state, we must account for the padding that is applied via CSS to
// the cell.
const CONTAINER_PADDING = 20;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the change?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously we had to consider the current truncation state when doing the calculation for content width because the element containing the text would have a different size if the "..." button was shown.

Now, since we're measuring with canvas, the size of the text content element is irrelevant. Instead we just need to consider the amount of padding between the parent element and the text content element.

@blueprint-bot
Copy link

fix tests for real this time

Preview: documentation | table
Coverage: core | datetime

@cmslewis
Copy link
Contributor

cmslewis commented Sep 27, 2017

@gscshoyru @themadcreator @llorca

RE: not considering parent-cell height anymore:

It looks like the only side effect of that omission is that we'll occasionally show the ··· icon more often than we need to (e.g. if a long block of text fits in a cell with wrapping enabled but wouldn't with wrapping disabled). I'm okay with that slight goofiness given the huge perf benefits of this PR.

Copy link
Contributor

@cmslewis cmslewis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@themadcreator looks okay overall, but not approving yet just to make sure we have a clear story on https://github.com/palantir/blueprint/pull/1624/files#diff-820e3fef53ee313553edff37ddbf4737R19 first.

enableMultiSelection: true,
enableRowReordering: false,
enableRowResizing: false,
enableRowSelection: true,
enableSlowLayout: false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@llorca FWIW the state-key names in this example have been a great testing ground for the ideas in #1625. Notice that we have both enable... and show... happening here. We really just need the former I think.

(No action required; just FYI)

// Since we measure only the `textContent` of the cell to determine the
// truncation state, we must account for the padding that is applied via CSS to
// the cell.
const CONTAINER_PADDING = 20;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@themadcreator themadcreator changed the title [Table] Improve TruncatedFormat performance [Table] Performance tuning for dev preview Sep 28, 2017
@blueprint-bot
Copy link

Reverting canvas measuring. Keeping dev preview updates

Preview: documentation | landing | table
Coverage: core | datetime

@themadcreator
Copy link
Contributor Author

some test in core is failing:

  Toaster
    with autoFocus set to true
      ✖ focuses on newly created toast

@blueprint-bot
Copy link

coverage

Preview: documentation | table
Coverage: core | datetime

function getRandomObject(propCount: number, depth = 0): object {
const childPropCount = propCount;
const obj: any = {};
while (propCount-- >= 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I prefer to avoid the -- and ++ operators, because they're so sneaky and tend to make lines denser.

*
* Returns a `TextMetrics` object.
*/
measureElementTextContent(element: Element, fontProperties?: string | ICssFontProperties) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we revert all these utility changes?

@blueprint-bot
Copy link

revert utils and test changes. use for loop

Preview: documentation | table
Coverage: core | datetime

@cmslewis cmslewis changed the title [Table] Performance tuning for dev preview [Table] TruncatedFormat popover improvements and performance tuning for dev preview Sep 29, 2017
Copy link
Contributor

@cmslewis cmslewis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! I updated the PR title to help with future bookkeeping.

@cmslewis cmslewis dismissed gscshoyru’s stale review September 29, 2017 19:39

Reverted the TruncatedFormat measurement changes

@themadcreator themadcreator merged commit 78cb34e into master Sep 29, 2017
@themadcreator themadcreator deleted the bd/table-perf-12 branch September 29, 2017 19:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants