Add option for query-based token lookup.
When subsequent layers modify dom, they might invalidate pointers
constructed during annotate stage. This change makes it so, the list of
tokens is recalculated every time. The usage migration will take place
in subsequent changes.
The method is considerably slower 0.5ms vs ~10ms, but in overall
update-redraw cycle the change is less noticeable (~25ms -> 35ms)
Google-Bug-Id: b/248342362
Release-Notes: skip
Change-Id: If552c1ef74e2c2edd5dc7d04d80fca1d3b273855
diff --git a/polygerrit-ui/app/api/embed.ts b/polygerrit-ui/app/api/embed.ts
index de2e1bf..af481fd 100644
--- a/polygerrit-ui/app/api/embed.ts
+++ b/polygerrit-ui/app/api/embed.ts
@@ -24,7 +24,8 @@
TokenHighlightLayer: {
new (
container: HTMLElement,
- listener?: TokenHighlightListener
+ listener?: TokenHighlightListener,
+ getTokenQueryContainer?: () => HTMLElement
): DiffLayer;
};
};
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer.ts
index 19e0e22..1e5dd65 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer.ts
@@ -15,6 +15,7 @@
getLineNumberByChild,
lineNumberToNumber,
} from '../gr-diff/gr-diff-utils';
+import {GrDiff} from '../gr-diff/gr-diff';
const tokenMatcher = new RegExp(/[\w]+/g);
@@ -89,14 +90,43 @@
private updateTokenTask?: DelayedTask;
+ /**
+ * Container that contains all annotated tokens and contains no shadow root
+ * elements that would prevent tokens to be queryable by querySelectorAll.
+ */
+ private getTokenQueryContainer?: () => HTMLElement;
+
+ /**
+ * @param container for registering "deselect" click
+ * @param tokenHighlightListener method that is called,
+ * when token is highlighted.
+ * @param getTokenQueryContainer if specified, list of tokens to be
+ * highlighted are recalculated every time using querySelectorAll inside
+ * this element. Otherwise, the pointers calculated once at annotate() time
+ * and are reused.
+ */
constructor(
container: HTMLElement,
- tokenHighlightListener?: TokenHighlightListener
+ tokenHighlightListener?: TokenHighlightListener,
+ getTokenQueryContainer?: () => HTMLElement
) {
this.tokenHighlightListener = tokenHighlightListener;
container.addEventListener('click', e => {
this.handleContainerClick(e);
});
+ this.getTokenQueryContainer = getTokenQueryContainer;
+ }
+
+ static createTokenHighlightContainer(
+ container: HTMLElement,
+ getGrDiff: () => GrDiff,
+ tokenHighlightListener?: TokenHighlightListener
+ ): TokenHighlightLayer {
+ return new TokenHighlightLayer(
+ container,
+ tokenHighlightListener,
+ () => getGrDiff().diffTable!
+ );
}
annotate(el: HTMLElement, _1: HTMLElement, _2: GrDiffLine, _3: Side): void {
@@ -265,8 +295,19 @@
if (!token) {
return;
}
- const tokenEls = this.tokenToElements.get(token);
- if (!tokenEls) {
+ let tokenEls;
+ let tokenElsLength;
+ if (this.getTokenQueryContainer) {
+ tokenEls = this.getTokenQueryContainer().querySelectorAll(
+ `.${TOKEN_TEXT_PREFIX}${token}`
+ );
+ tokenElsLength = tokenEls.length;
+ } else {
+ tokenEls = this.tokenToElements.get(token);
+ tokenElsLength = tokenEls?.size;
+ }
+ if (!tokenEls || tokenElsLength === 0) {
+ console.warn(`No tokens have been found for '${token}'`);
return;
}
for (const el of tokenEls) {
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer_test.ts
index 0e2def0..1beed46 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer_test.ts
@@ -335,5 +335,44 @@
assert.equal(listener.pending, 0);
assert.isTrue(words1.classList.contains('token-highlight'));
});
+
+ test('query based highlighting', async () => {
+ highlighter = new TokenHighlightLayer(
+ container,
+ tokenHighlightListener,
+ /* getTokenQueryContainer=*/ () => container
+ );
+ const clock = sinon.useFakeTimers();
+ const line1 = createLine('two words');
+ annotate(line1);
+ const line2 = createLine('three words', 2);
+ annotate(line2, Side.RIGHT, 2);
+ // Invalidate pointers.
+ for (const child of line1.childNodes) {
+ line1.replaceChild(child.cloneNode(), child);
+ }
+ for (const child of line2.childNodes) {
+ line2.replaceChild(child.cloneNode(), child);
+ }
+
+ const words1 = queryAndAssert(line1, '.tk-text-words');
+ assert.isTrue(words1.classList.contains('token'));
+ dispatchMouseEvent('mouseover', words1);
+ assert.equal(tokenHighlightingCalls.length, 0);
+ clock.tick(HOVER_DELAY_MS);
+ assert.equal(tokenHighlightingCalls.length, 1);
+ assert.deepEqual(tokenHighlightingCalls[0].details, {
+ token: 'words',
+ side: Side.RIGHT,
+ element: words1,
+ range: {start_line: 1, start_column: 5, end_line: 1, end_column: 9},
+ });
+ assert.isTrue(words1.classList.contains('token-highlight'));
+
+ container.click();
+ assert.equal(tokenHighlightingCalls.length, 2);
+ assert.deepEqual(tokenHighlightingCalls[1].details, undefined);
+ assert.isFalse(words1.classList.contains('token-highlight'));
+ });
});
});