Skip to content

Commit

Permalink
Adapt to damaged rects (#5)
Browse files Browse the repository at this point in the history
* Expose damagedRects as a member of PerformanceContainerTiming events

As native implementation exposes damagedRects in PerformanceContainerTiming
for obtaining the set of rectangles representing the damaged area, do the
same in the polyfill. As we already calculated that information in paintedRects
convert the old implementation to match the new API.

* Ignore paintDebugOverlay generated divs in ContainerTiming

Add the containertiming-ignore property to the divs for the
debug overlay so they are not accounted in any of the container
timing entries, even if it is the toplevel html or body.

* Support native case paint highlight

To support painting the damaged areas in the native case, call
the method directly from the test cases, and remove automatic
ctDebug for those cases.
  • Loading branch information
jdapena authored Nov 1, 2024
1 parent 47546e9 commit 557c779
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 15 deletions.
18 changes: 16 additions & 2 deletions docs/polyfill.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,22 @@ For more info on nested containers, see [Nested Containers](../README.md#nested-

## Debug Mode

You can set a global `ctDebug` flag to true in order to see paint rectangles from the collection of paints when a container has updated.
(set `window.ctDebug` or `globalThis.ctDebug` to true)
You can set a global `ctDebug` flag to true in order to see paint rectangles from the collection of paints when a container has updated. This will work only when using the polyfill and not the native implementation.
(set `window.ctDebug` or `globalThis.ctDebug` to true).

In case you want to handle directly the painting of the rectangles, you can use directly the API provided by the polyfill,
`ContainerPerformanceObserver.paintDebugOverlay(rects)`.

An example:
```js
const nativeObserver = new PerformanceObserver((v) => {
const entries = v.getEntries();
entries.forEach((entry) => {
const rects = entry?.damagedRects;
ContainerPerformanceObserver.paintDebugOverlay(rects);
});
});
```
## Performance Impact of a native implementation
Expand Down
8 changes: 6 additions & 2 deletions examples/adding-content/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
window.ctDebug = true;
const queryString = window.location.search;
const urlParams = new URLSearchParams(window.location.search);
const nestedStrategy = urlParams.get("nestedStrategy") || "ignore"

const nativeObserver = new PerformanceObserver((v) => {
console.log(v.getEntries());
const entries = v.getEntries();
console.log(entries);
entries.forEach((entry) => {
const rects = entry?.damagedRects;
ContainerPerformanceObserver.paintDebugOverlay(rects);
});
});

nativeObserver.observe({ type: "container", nestedStrategy: nestedStrategy });
Expand Down
3 changes: 2 additions & 1 deletion examples/table/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
window.ctDebug = true;
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
console.log(entry);
const rects = entry.damagedRects;
ContainerPerformanceObserver.paintDebugOverlay(rects);
});
});

Expand Down
23 changes: 13 additions & 10 deletions polyfill/polyfill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ interface PerformanceElementTiming extends PerformanceEntry {
}

interface ResolvedRootData extends PerformanceContainerTiming {
/** Keep track of all the paintedRects */
paintedRects: Set<DOMRectReadOnly>;
/** For aggregated paints keep track of the union painted rect */
coordData?: any;
}
Expand All @@ -35,7 +33,7 @@ const containerRootDataMap = new Map<Element, ResolvedRootData>();
const containerRootUpdates = new Set<Element>();
// Keep track of the last set of resolved data so it can be shown in debug mode
let lastResolvedData: Partial<{
paintedRects: Set<DOMRectReadOnly>;
damagedRects: Set<DOMRectReadOnly>;
intersectionRect: DOMRectReadOnly;
}>;

Expand Down Expand Up @@ -113,19 +111,22 @@ class PerformanceContainerTiming implements PerformanceEntry {
firstRenderTime: number;
size: number;
lastPaintedElement: Element | null;
damagedRects: Set<DOMRectReadOnly>;

constructor(
startTime: number,
identifier: string | null,
size: number,
firstRenderTime: number,
lastPaintedElement: Element | null,
damagedRects: Set<DOMRectReadOnly>
) {
this.identifier = identifier;
this.size = size;
this.startTime = startTime;
this.firstRenderTime = firstRenderTime;
this.lastPaintedElement = lastPaintedElement;
this.damagedRects = damagedRects;
}

toJSON(): void {}
Expand Down Expand Up @@ -205,7 +206,7 @@ class ContainerPerformanceObserver implements PerformanceObserver {
entryType: "container",
name: "",
duration: 0,
paintedRects: new Set(),
damagedRects: new Set(),
identifier: "",
size: 0,
startTime: 0,
Expand Down Expand Up @@ -233,11 +234,12 @@ class ContainerPerformanceObserver implements PerformanceObserver {
div.style.left = `${rectData.left}px`;
div.style.position = "absolute";
div.style.transition = "background-color 1s";
div.setAttribute("containertiming-ignore", '');
document.body.appendChild(div);
divCol.add(div);
};

if (rectData instanceof Set) {
if ((rectData instanceof Set) || (Array.isArray(rectData))) {
rectData?.forEach((rect) => {
addOverlayToRect(rect);
});
Expand Down Expand Up @@ -329,15 +331,15 @@ class ContainerPerformanceObserver implements PerformanceObserver {
}

// TODO: We should look into better ways to combine rectangles or detect overlapping rectangles such as R-Tree or Quad Tree algorithms
for (const rect of resolvedRootData.paintedRects) {
for (const rect of resolvedRootData.damagedRects) {
if (ContainerPerformanceObserver.overlaps(entry.intersectionRect, rect)) {
return;
}
}

resolvedRootData.lastPaintedElement = entry.element;
resolvedRootData.startTime = entry.startTime; // For images this will either be the load time or render time
resolvedRootData.paintedRects?.add(entry.intersectionRect);
resolvedRootData.damagedRects?.add(entry.intersectionRect);
// size won't be super accurate as it doesn't take into account overlaps
resolvedRootData.size += incomingEntrySize;
resolvedRootData.identifier ||= closestRoot.getAttribute("containertiming");
Expand All @@ -346,7 +348,7 @@ class ContainerPerformanceObserver implements PerformanceObserver {
// Update States
containerRootDataMap.set(closestRoot, resolvedRootData);
containerRootUpdates.add(closestRoot);
lastResolvedData.paintedRects?.add(entry.intersectionRect);
lastResolvedData.damagedRects?.add(entry.intersectionRect);

// If nested update any parents
this.updateParentIfExists(closestRoot);
Expand Down Expand Up @@ -425,7 +427,7 @@ class ContainerPerformanceObserver implements PerformanceObserver {
containerRootUpdates.clear();

// Reset last resolved data state, we want to re-use coordinate and size if aggregated
lastResolvedData = { paintedRects: new Set() };
lastResolvedData = { damagedRects: new Set() };

const processEntries = (entry: PerformanceEntry): void => {
// This should ensure we're dealing with a PerformanceElementTiming instance
Expand Down Expand Up @@ -465,12 +467,13 @@ class ContainerPerformanceObserver implements PerformanceObserver {
resolvedRootData.size,
resolvedRootData.firstRenderTime,
resolvedRootData.lastPaintedElement,
resolvedRootData.damagedRects
);

containerEntries.push(containerCandidate);

if (this.debug) {
const rects = lastResolvedData?.paintedRects;
const rects = lastResolvedData?.damagedRects;
ContainerPerformanceObserver.paintDebugOverlay(rects);
}
});
Expand Down

0 comments on commit 557c779

Please sign in to comment.