-
Notifications
You must be signed in to change notification settings - Fork 138
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
Improve paint/repaint behavior #10449
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import { openDevToolsTab, startTest } from "../helpers"; | ||
import { findConsoleMessage, seekToConsoleMessage } from "../helpers/console-panel"; | ||
import { stepOver } from "../helpers/pause-information-panel"; | ||
import { getGraphicsDataUrl, waitForRepaintGraphicsToLoad } from "../helpers/screenshot"; | ||
import { debugPrint } from "../helpers/utils"; | ||
import { expect, test } from "../testFixture"; | ||
|
||
test.use({ exampleKey: "doc_control_flow.html" }); | ||
|
||
test.skip("repaint-06: repaints the screen screen when stepping over code that modifies the DOM", async ({ | ||
pageWithMeta: { page, recordingId }, | ||
exampleKey, | ||
}) => { | ||
await startTest(page, recordingId); | ||
await openDevToolsTab(page); | ||
|
||
const printedStrings = [ | ||
"catch", | ||
"afterCatch", | ||
"finally", | ||
"yield 1", | ||
"generated 1", | ||
"yield 2", | ||
"generated 2", | ||
"after timer 1", | ||
"after timer 2", | ||
"after throw timer 1", | ||
"after throw timer 2", | ||
"within iterator", | ||
]; | ||
|
||
const paints: { | ||
[key: string]: { | ||
before: string | null; | ||
after: string | null; | ||
}; | ||
} = {}; | ||
|
||
{ | ||
await debugPrint(page, "Testing repaint graphics"); | ||
|
||
let graphicsDataUrl: string | null = null; | ||
let previousGraphicsDataUrl: string | null = null; | ||
|
||
for (let index = 0; index < printedStrings.length; index++) { | ||
const string = printedStrings[index]; | ||
const text = `updateText("${string}")`; | ||
|
||
const locator = await findConsoleMessage(page, text, "console-log"); | ||
await seekToConsoleMessage(page, locator); | ||
|
||
// Step to "Element text before..." | ||
await stepOver(page); | ||
await stepOver(page); | ||
|
||
await waitForRepaintGraphicsToLoad(page); | ||
|
||
graphicsDataUrl = await getGraphicsDataUrl(page); | ||
expect( | ||
graphicsDataUrl, | ||
`Expected graphics before "${string}" not to match previous after graphics` | ||
).not.toEqual(previousGraphicsDataUrl); | ||
previousGraphicsDataUrl = graphicsDataUrl; | ||
|
||
const cachedPaints = { | ||
before: graphicsDataUrl, | ||
after: "" as string | null, | ||
}; | ||
|
||
// Step to "Element text after..." | ||
await stepOver(page); | ||
await stepOver(page); | ||
|
||
await waitForRepaintGraphicsToLoad(page); | ||
|
||
graphicsDataUrl = await getGraphicsDataUrl(page); | ||
expect( | ||
graphicsDataUrl, | ||
`Expected graphics after "${string}" not to match the graphics before` | ||
).not.toEqual(previousGraphicsDataUrl); | ||
previousGraphicsDataUrl = graphicsDataUrl; | ||
|
||
cachedPaints.after = graphicsDataUrl; | ||
|
||
paints[string] = cachedPaints; | ||
} | ||
} | ||
|
||
{ | ||
await debugPrint(page, "Testing screenshot caching"); | ||
|
||
let graphicsDataUrl: string | null = null; | ||
|
||
for (let index = 0; index < printedStrings.length; index++) { | ||
const string = printedStrings[index]; | ||
const text = `updateText("${string}")`; | ||
|
||
const locator = await findConsoleMessage(page, text, "console-log"); | ||
await seekToConsoleMessage(page, locator); | ||
|
||
// Step to "Element text before..." | ||
await stepOver(page); | ||
await stepOver(page); | ||
|
||
await waitForRepaintGraphicsToLoad(page); | ||
|
||
graphicsDataUrl = await getGraphicsDataUrl(page); | ||
expect( | ||
graphicsDataUrl, | ||
`Expected graphics before "${string}" to match previous cached graphics` | ||
).toEqual(paints[string]?.before); | ||
|
||
// Step to "Element text after..." | ||
await stepOver(page); | ||
await stepOver(page); | ||
|
||
await waitForRepaintGraphicsToLoad(page); | ||
|
||
graphicsDataUrl = await getGraphicsDataUrl(page); | ||
expect( | ||
graphicsDataUrl, | ||
`Expected graphics after "${string}" to match previous cached graphics` | ||
).toEqual(paints[string]?.after); | ||
} | ||
} | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
import { PauseId, repaintGraphicsResult } from "@replayio/protocol"; | ||
import { Cache, createCache } from "suspense"; | ||
|
||
import { paintHashCache } from "replay-next/src/suspense/ScreenshotCache"; | ||
import { ReplayClientInterface } from "shared/client/types"; | ||
|
||
export const RepaintGraphicsCache: Cache< | ||
|
@@ -11,6 +12,20 @@ export const RepaintGraphicsCache: Cache< | |
debugLabel: "RepaintGraphicsCache", | ||
getKey: ([replayClient, pauseId]) => pauseId, | ||
load: async ([replayClient, pauseId]) => { | ||
return replayClient.repaintGraphics(pauseId); | ||
const result = await replayClient.repaintGraphics(pauseId); | ||
|
||
let { description, screenShot } = result; | ||
|
||
// The backend won't return a screenshot for a given hash more than once; see FE-2357 | ||
if (screenShot) { | ||
paintHashCache.cacheValue(screenShot, description.hash); | ||
} else { | ||
screenShot = paintHashCache.getValueIfCached(description.hash); | ||
} | ||
Comment on lines
+19
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
return { | ||
description, | ||
screenShot, | ||
}; | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
import { ExecutionPoint, ScreenShot } from "@replayio/protocol"; | ||
import { createCache } from "suspense"; | ||
import { createCache, createExternallyManagedCache } from "suspense"; | ||
|
||
import { ReplayClientInterface } from "shared/client/types"; | ||
|
||
|
@@ -10,5 +10,18 @@ export const screenshotCache = createCache< | |
config: { immutable: true }, | ||
debugLabel: "ScreenshotCache", | ||
getKey: ([client, point, paintHash]) => paintHash, | ||
load: ([client, point]) => client.getScreenshot(point), | ||
load: async ([client, point]) => { | ||
const screenShot = await client.getScreenshot(point); | ||
|
||
paintHashCache.cacheValue(screenShot, screenShot.hash); | ||
|
||
return screenShot; | ||
Comment on lines
+13
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if this is prone to the same case mentioned in the repaint cache. Seems like it could be, depending on how RUN keys screenshots (by point or by hash)? Edit I don't think this is a problem, after re-reading the docs for |
||
}, | ||
}); | ||
|
||
// The backend won't return a screenshot for a given hash more than once; see FE-2357 | ||
export const paintHashCache = createExternallyManagedCache<[paintHash: string], ScreenShot>({ | ||
config: { immutable: true }, | ||
debugLabel: "paintHashCache", | ||
getKey: ([paintHash]) => paintHash, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ interface Rect { | |
} | ||
|
||
export type Status = "failed" | "loaded" | "loading"; | ||
export type ScreenShotType = "cached-paint" | "repaint"; | ||
|
||
export interface State { | ||
currentExecutionPoint: ExecutionPoint | null; | ||
|
@@ -33,6 +34,7 @@ export interface State { | |
localScale: number; | ||
recordingScale: number; | ||
screenShot: ScreenShot | undefined; | ||
screenShotType: ScreenShotType | undefined; | ||
status: Status; | ||
} | ||
|
||
|
@@ -48,25 +50,33 @@ export const state = createState<State>({ | |
localScale: 1, | ||
recordingScale: 1, | ||
screenShot: undefined, | ||
screenShotType: undefined, | ||
status: "loading", | ||
}); | ||
|
||
let lock = new Object(); | ||
|
||
export async function updateState( | ||
containerElement: HTMLElement, | ||
options: Partial<{ | ||
didResize?: boolean; | ||
executionPoint: ExecutionPoint | null; | ||
screenShot: ScreenShot | null; | ||
screenShotType: ScreenShotType | null; | ||
status: Status; | ||
time: number; | ||
}> = {} | ||
) { | ||
const localLock = new Object(); | ||
lock = localLock; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a potential race here before when e.g. scrubbing the timeline. |
||
|
||
const prevState = state.read(); | ||
|
||
let { | ||
didResize, | ||
executionPoint = prevState.currentExecutionPoint, | ||
screenShot = prevState.screenShot, | ||
screenShotType = prevState.screenShotType, | ||
status = prevState.status, | ||
time = prevState.currentTime, | ||
} = options; | ||
|
@@ -76,6 +86,10 @@ export async function updateState( | |
let recordingScale = prevState.recordingScale; | ||
if (screenShot && (screenShot != prevState.screenShot || didResize)) { | ||
const naturalDimensions = await getDimensions(screenShot.data, screenShot.mimeType); | ||
if (lock !== localLock) { | ||
return; | ||
} | ||
|
||
const naturalHeight = naturalDimensions.height; | ||
const naturalWidth = naturalDimensions.width; | ||
|
||
|
@@ -86,6 +100,10 @@ export async function updateState( | |
imageHeight: naturalHeight, | ||
imageWidth: naturalWidth, | ||
}); | ||
if (lock !== localLock) { | ||
return; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: |
||
} | ||
|
||
const clientHeight = scaledDimensions.height; | ||
const clientWidth = scaledDimensions.width; | ||
|
||
|
@@ -107,6 +125,7 @@ export async function updateState( | |
localScale, | ||
recordingScale, | ||
screenShot: screenShot || undefined, | ||
screenShotType: screenShotType || undefined, | ||
status, | ||
}; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is correct: why should the screenshot change in between updates?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you're right about that.