forked from mirrors/gecko-dev
Bug 1873147 - [devtools] Update token highlight and popup position when Popup updates. r=devtools-reviewers,ochameau.
In Popup, we were only adding/removing the highlight on hovered token on mount/unmount. But it can happen that the component gets updated (e.g. when the user moves quickly between valid tokens). In such case, we want to remove the highlighted class on all elements that it was set on, and only add it back on the "preview target". We also want to update the Popover position so it points to the actual token the preview was computed for. Differential Revision: https://phabricator.services.mozilla.com/D197582
This commit is contained in:
parent
b21894581e
commit
ff6752ce82
3 changed files with 133 additions and 12 deletions
|
|
@ -46,27 +46,39 @@ export class Popup extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.addHighlightToToken();
|
||||
this.addHighlightToToken(this.props.preview.target);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.removeHighlightFromToken();
|
||||
this.removeHighlightFromToken(this.props.preview.target);
|
||||
}
|
||||
|
||||
addHighlightToToken() {
|
||||
componentDidUpdate(prevProps) {
|
||||
const { target } = this.props.preview;
|
||||
if (target) {
|
||||
target.classList.add("preview-token");
|
||||
addHighlightToTargetSiblings(target, this.props);
|
||||
if (prevProps.target == target) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeHighlightFromToken(prevProps.preview.target);
|
||||
this.addHighlightToToken(target);
|
||||
}
|
||||
|
||||
removeHighlightFromToken() {
|
||||
const { target } = this.props.preview;
|
||||
if (target) {
|
||||
target.classList.remove("preview-token");
|
||||
removeHighlightForTargetSiblings(target);
|
||||
addHighlightToToken(target) {
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
target.classList.add("preview-token");
|
||||
addHighlightToTargetSiblings(target, this.props);
|
||||
}
|
||||
|
||||
removeHighlightFromToken(target) {
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
target.classList.remove("preview-token");
|
||||
removeHighlightForTargetSiblings(target);
|
||||
}
|
||||
|
||||
calculateMaxHeight = () => {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,10 @@ class Popover extends Component {
|
|||
|
||||
componentDidUpdate(prevProps) {
|
||||
// We have to update `coords` when the Popover type changes
|
||||
if (prevProps.type != this.props.type) {
|
||||
if (
|
||||
prevProps.type != this.props.type ||
|
||||
prevProps.target !== this.props.target
|
||||
) {
|
||||
const coords =
|
||||
this.props.type == "popover"
|
||||
? this.getPopoverCoords()
|
||||
|
|
|
|||
|
|
@ -103,6 +103,8 @@ add_task(async function () {
|
|||
ok(true, `"hello" was expanded`);
|
||||
await closePreviewForToken(dbg, tokenEl, "popup");
|
||||
await resume(dbg);
|
||||
|
||||
await testMovingFromATokenToAnother(dbg);
|
||||
});
|
||||
|
||||
async function testPreviews(dbg, fnName, previews) {
|
||||
|
|
@ -149,6 +151,110 @@ async function assertNoPreviews(dbg, expression, line, column) {
|
|||
is(result, "TIMEOUT", `No popup was displayed when hovering "${expression}"`);
|
||||
}
|
||||
|
||||
async function testMovingFromATokenToAnother(dbg) {
|
||||
info(
|
||||
"Check that moving the mouse to another token when popup is displayed updates highlighted token and popup position"
|
||||
);
|
||||
invokeInTab("classPreview");
|
||||
await waitForPaused(dbg);
|
||||
|
||||
info("Hover token `Foo` in `Foo.#privateStatic` expression");
|
||||
const fooTokenEl = getTokenElAtLine(dbg, "Foo", 50, 44);
|
||||
const cm = getCM(dbg);
|
||||
const onScrolled = waitForScrolling(cm);
|
||||
cm.scrollIntoView({ line: 49, ch: 0 }, 0);
|
||||
await onScrolled;
|
||||
const { element: fooPopupEl } = await tryHoverToken(dbg, fooTokenEl, "popup");
|
||||
ok(!!fooPopupEl, "popup is displayed");
|
||||
ok(
|
||||
fooTokenEl.classList.contains("preview-token"),
|
||||
"`Foo` token is highlighted"
|
||||
);
|
||||
|
||||
// store original position
|
||||
const originalPopupPosition = fooPopupEl.getBoundingClientRect().x;
|
||||
|
||||
info(
|
||||
"Move mouse over the `#privateStatic` token in `Foo.#privateStatic` expression"
|
||||
);
|
||||
const privateStaticTokenEl = getTokenElAtLine(dbg, "#privateStatic", 50, 48);
|
||||
|
||||
// The sequence of event to trigger the bug this is covering isn't easily reproducible
|
||||
// by firing a few chosen events (because of React async rendering), so we are going to
|
||||
// mimick moving the mouse from the `Foo` to `#privateStatic` in a given amount of time
|
||||
|
||||
// So get all the different token quads to compute their center
|
||||
const fooTokenQuad = fooTokenEl.getBoxQuads()[0];
|
||||
const privateStaticTokenQuad = privateStaticTokenEl.getBoxQuads()[0];
|
||||
const fooXCenter =
|
||||
fooTokenQuad.p1.x + (fooTokenQuad.p2.x - fooTokenQuad.p1.x) / 2;
|
||||
const fooYCenter =
|
||||
fooTokenQuad.p1.y + (fooTokenQuad.p3.y - fooTokenQuad.p1.y) / 2;
|
||||
const privateStaticXCenter =
|
||||
privateStaticTokenQuad.p1.x +
|
||||
(privateStaticTokenQuad.p2.x - privateStaticTokenQuad.p1.x) / 2;
|
||||
const privateStaticYCenter =
|
||||
privateStaticTokenQuad.p1.y +
|
||||
(privateStaticTokenQuad.p3.y - privateStaticTokenQuad.p1.y) / 2;
|
||||
|
||||
// we can then compute the distance to cover between the two token centers
|
||||
const xDistance = privateStaticXCenter - fooXCenter;
|
||||
const yDistance = privateStaticYCenter - fooYCenter;
|
||||
const movementDuration = 50;
|
||||
const xIncrements = xDistance / movementDuration;
|
||||
const yIncrements = yDistance / movementDuration;
|
||||
|
||||
// Finally, we're going to fire a mouseover event every ms
|
||||
info("Move mousecursor between the `Foo` token to the `#privateStatic` one");
|
||||
for (let i = 0; i < movementDuration; i++) {
|
||||
const x = fooXCenter + (yDistance + i * xIncrements);
|
||||
const y = fooYCenter + (yDistance + i * yIncrements);
|
||||
EventUtils.synthesizeMouseAtPoint(
|
||||
x,
|
||||
y,
|
||||
{
|
||||
type: "mouseover",
|
||||
},
|
||||
fooTokenEl.ownerGlobal
|
||||
);
|
||||
await wait(1);
|
||||
}
|
||||
|
||||
info("Wait for the popup to display the data for `#privateStatic`");
|
||||
await waitFor(() => {
|
||||
const popup = findElement(dbg, "popup");
|
||||
if (!popup) {
|
||||
return false;
|
||||
}
|
||||
// for `Foo`, the header text content is "Foo", so when it's "Object", we know the
|
||||
// popup was updated
|
||||
return (
|
||||
popup.querySelector(".preview-popup .node .objectBox")?.textContent ===
|
||||
"Object"
|
||||
);
|
||||
});
|
||||
ok(true, "Popup is displayed for #privateStatic");
|
||||
|
||||
ok(
|
||||
!fooTokenEl.classList.contains("preview-token"),
|
||||
"`Foo` token is not highlighted anymore"
|
||||
);
|
||||
ok(
|
||||
privateStaticTokenEl.classList.contains("preview-token"),
|
||||
"`#privateStatic` token is highlighted"
|
||||
);
|
||||
|
||||
const privateStaticPopupEl = await waitForElement(dbg, "popup");
|
||||
const newPopupPosition = privateStaticPopupEl.getBoundingClientRect().x;
|
||||
isnot(
|
||||
Math.round(newPopupPosition),
|
||||
Math.round(originalPopupPosition),
|
||||
`Popup position was updated`
|
||||
);
|
||||
|
||||
await resume(dbg);
|
||||
}
|
||||
|
||||
async function testBucketedArray(dbg) {
|
||||
invokeInTab("largeArray");
|
||||
await waitForPaused(dbg);
|
||||
|
|
|
|||
Loading…
Reference in a new issue