Skip to content

Commit

Permalink
Polyfill Fixes (#9)
Browse files Browse the repository at this point in the history
- Now supports containertiming-ignore
- Now only shows damagedRects when in debug mode

Co-authored-by: jwilliams720 <[email protected]>
  • Loading branch information
jasonwilliams and jwilliams720 authored Nov 20, 2024
1 parent d860c32 commit 6937043
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 17 deletions.
4 changes: 2 additions & 2 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
"tasks": [
{
"type": "npm",
"script": "watch",
"script": "build",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [],
"label": "npm: watch",
"label": "npm: build",
"detail": "tsc",
"path": "/polyfill"
}
Expand Down
4 changes: 2 additions & 2 deletions docs/polyfill.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ For more info on nested containers, see [Nested Containers](../README.md#nested-
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)`.
Debug mode will not only show the rect overlays but it will also expose a `damagedRects` property on the `PerformanceContainerTimingDebug` class.

An example:

```js
const nativeObserver = new PerformanceObserver((v) => {
const entries = v.getEntries();
Expand Down
71 changes: 58 additions & 13 deletions polyfill/polyfill.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const native_implementation_available = ("PerformanceContainerTiming" in window);
const native_implementation_available = "PerformanceContainerTiming" in window;

// https://wicg.github.io/element-timing/#performanceelementtiming
interface PerformanceElementTiming extends PerformanceEntry {
Expand All @@ -15,6 +15,7 @@ interface PerformanceElementTiming extends PerformanceEntry {
}

interface ResolvedRootData extends PerformanceContainerTiming {
damagedRects: Set<DOMRectReadOnly>;
/** For aggregated paints keep track of the union painted rect */
coordData?: any;
}
Expand All @@ -25,6 +26,7 @@ type NestedStrategy = "ignore" | "transparent" | "shadowed";
// Otherwise no elements will be observed
const INTERNAL_ATTR_NAME = "POLYFILL-ELEMENTTIMING";
const containerTimingAttrSelector = "[containertiming]";
const containerTimingIgnoreSelector = "[containertiming-ignore]";
const NativePerformanceObserver = window.PerformanceObserver;
// containerRoots needs to be set before "observe" has initiated due to the fact new elements could have been injected earlier
const containerRoots = new Set<Element>();
Expand Down Expand Up @@ -65,6 +67,11 @@ const mutationObserverCallback = (mutationList: MutationRecord[]) => {
// At this point we can be certain we're dealing with an element
const element = node as Element;

// If the element is a descendent of an ignored container we should skip
if (element.closest(containerTimingIgnoreSelector)) {
continue;
}

// Theres a chance the new sub-tree injected is a descendent of a container that was already in the DOM
// Go through the container have currently and check..
if (element.closest(containerTimingAttrSelector)) {
Expand All @@ -82,10 +89,9 @@ const mutationObserverCallback = (mutationList: MutationRecord[]) => {
}
};


// Wait until the DOM is ready then start collecting elements needed to be timed.
if (!native_implementation_available) {
console.debug("Enabling polyfill")
console.debug("Enabling polyfill");
document.addEventListener("DOMContentLoaded", () => {
mutationObserver = new window.MutationObserver(mutationObserverCallback);

Expand All @@ -99,7 +105,7 @@ if (!native_implementation_available) {
});
});
} else {
console.debug("Native implementation of Container Timing available")
console.debug("Native implementation of Container Timing available");
}

class PerformanceContainerTiming implements PerformanceEntry {
Expand All @@ -111,27 +117,55 @@ 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>
_: Set<DOMRectReadOnly> | undefined,
) {
this.identifier = identifier;
this.size = size;
this.startTime = startTime;
this.firstRenderTime = firstRenderTime;
this.lastPaintedElement = lastPaintedElement;
this.damagedRects = damagedRects;
}

toJSON(): void {}
}

// The debug version of PerformanceContainerTiming, this will give some more detail
class PerformanceContainerTimingDebug extends PerformanceContainerTiming {
damagedRects: Set<DOMRectReadOnly> | undefined;

constructor(
startTime: number,
identifier: string | null,
size: number,
firstRenderTime: number,
lastPaintedElement: Element | null,
damagedRects: Set<DOMRectReadOnly> | undefined,
) {
super(
startTime,
identifier,
size,
firstRenderTime,
lastPaintedElement,
damagedRects,
);

this.identifier = identifier;
this.size = size;
this.startTime = startTime;
this.firstRenderTime = firstRenderTime;
this.lastPaintedElement = lastPaintedElement;
this.damagedRects = damagedRects;
}
}

/**
* Container Performance Observer is a superset of Performance Observer which can augment element-timing to work on containers
*/
Expand Down Expand Up @@ -163,6 +197,11 @@ class ContainerPerformanceObserver implements PerformanceObserver {
const walkChildren = ({ children }: Element) => {
const normalizedChildren = Array.from(children);
normalizedChildren.forEach((child) => {
// If we see a containertiming-ignore we should stop traversing
if (child.matches && child.matches(containerTimingIgnoreSelector)) {
return;
}

callback(child);
if (child.children.length) {
walkChildren(child);
Expand Down Expand Up @@ -234,12 +273,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", '');
div.setAttribute("containertiming-ignore", "");
document.body.appendChild(div);
divCol.add(div);
};

if ((rectData instanceof Set) || (Array.isArray(rectData))) {
if (rectData instanceof Set || Array.isArray(rectData)) {
rectData?.forEach((rect) => {
addOverlayToRect(rect);
});
Expand Down Expand Up @@ -294,7 +333,7 @@ class ContainerPerformanceObserver implements PerformanceObserver {
this.entryTypes = resolvedTypes;
this.nestedStrategy ??= options?.nestedStrategy || "ignore";
// If we only have 1 type its preferred to use the type property, otherwise use entryTypes
// This is to make sure buffered still works when we only have "elmeent" set.
// This is to make sure buffered still works when we only have "element" set.
this.nativePerformanceObserver.observe({
type: resolvedTypes.length === 1 ? resolvedTypes[0] : undefined,
entryTypes: resolvedTypes.length > 1 ? resolvedTypes : undefined,
Expand Down Expand Up @@ -326,6 +365,8 @@ class ContainerPerformanceObserver implements PerformanceObserver {
);

// There's a weird bug where we sometimes get a load of empty rects (all zero'd out)
// https://issues.chromium.org/issues/379844652
// https://github.com/bloomberg/container-timing/issues/8
if (ContainerPerformanceObserver.isEmptyRect(entry.intersectionRect)) {
return;
}
Expand Down Expand Up @@ -461,13 +502,17 @@ class ContainerPerformanceObserver implements PerformanceObserver {
if (!resolvedRootData) {
return;
}
const containerCandidate: any = new PerformanceContainerTiming(

const PerfContainerTimingClass = this.debug
? PerformanceContainerTimingDebug
: PerformanceContainerTiming;
const containerCandidate: any = new PerfContainerTimingClass(
resolvedRootData.startTime,
resolvedRootData.identifier,
resolvedRootData.size,
resolvedRootData.firstRenderTime,
resolvedRootData.lastPaintedElement,
resolvedRootData.damagedRects
resolvedRootData.damagedRects,
);

containerEntries.push(containerCandidate);
Expand Down Expand Up @@ -517,4 +562,4 @@ class ContainerPerformanceObserver implements PerformanceObserver {

if (!native_implementation_available) {
window.PerformanceObserver = ContainerPerformanceObserver;
}
}

0 comments on commit 6937043

Please sign in to comment.