Separate bbox tracking from dependencies tracking

When recording bboxes for images, it's enough to record their
clip box / bounding box without needing to run the full bbox
tracking of the image's dependencies.
This commit is contained in:
Nicolò Ribaudo 2026-02-25 15:23:22 +01:00
parent 886c90d1a5
commit 4f7a025e21
No known key found for this signature in database
GPG Key ID: AAFDA9101C58F338
2 changed files with 414 additions and 237 deletions

View File

@ -39,6 +39,7 @@ import {
SerializableEmpty, SerializableEmpty,
} from "./annotation_storage.js"; } from "./annotation_storage.js";
import { import {
CanvasBBoxTracker,
CanvasDependencyTracker, CanvasDependencyTracker,
CanvasImagesTracker, CanvasImagesTracker,
} from "./canvas_dependency_tracker.js"; } from "./canvas_dependency_tracker.js";
@ -1594,20 +1595,28 @@ class PDFPageProxy {
} }
}; };
let dependencyTracker = null;
let bboxTracker = null;
if (shouldRecordOperations || shouldRecordImages) {
bboxTracker = new CanvasBBoxTracker(
canvas,
intentState.operatorList.length
);
}
if (shouldRecordOperations) {
dependencyTracker = new CanvasDependencyTracker(
bboxTracker,
recordForDebugger
);
}
const internalRenderTask = new InternalRenderTask({ const internalRenderTask = new InternalRenderTask({
callback: complete, callback: complete,
// Only include the required properties, and *not* the entire object. // Only include the required properties, and *not* the entire object.
params: { params: {
canvas, canvas,
canvasContext, canvasContext,
dependencyTracker: dependencyTracker: dependencyTracker ?? bboxTracker,
shouldRecordOperations || shouldRecordImages
? new CanvasDependencyTracker(
canvas,
intentState.operatorList.length,
recordForDebugger
)
: null,
imagesTracker: shouldRecordImages imagesTracker: shouldRecordImages
? new CanvasImagesTracker(canvas) ? new CanvasImagesTracker(canvas)
: null, : null,

View File

@ -71,6 +71,323 @@ const ensureDebugMetadata = (map, key) =>
isRenderingOperation: false, isRenderingOperation: false,
})); }));
// NOTE: CanvasBBoxTracker, CanvasDependencyTracker and
// CanvasNestedDependencyTracker must all have the same interface.
class CanvasBBoxTracker {
#baseTransformStack = [[1, 0, 0, 1, 0, 0]];
#clipBox = [-Infinity, -Infinity, Infinity, Infinity];
// Float32Array<minX, minY, maxX, maxY>
#pendingBBox = new Float64Array([Infinity, Infinity, -Infinity, -Infinity]);
_pendingBBoxIdx = -1;
#canvasWidth;
#canvasHeight;
// Uint8ClampedArray<minX, minY, maxX, maxY>
#bboxesCoords;
#bboxes;
_savesStack = [];
_markedContentStack = [];
constructor(canvas, operationsCount) {
this.#canvasWidth = canvas.width;
this.#canvasHeight = canvas.height;
this.#initializeBBoxes(operationsCount);
}
growOperationsCount(operationsCount) {
if (operationsCount >= this.#bboxes.length) {
this.#initializeBBoxes(operationsCount, this.#bboxes);
}
}
#initializeBBoxes(operationsCount, oldBBoxes) {
const buffer = new ArrayBuffer(operationsCount * 4);
this.#bboxesCoords = new Uint8ClampedArray(buffer);
this.#bboxes = new Uint32Array(buffer);
if (oldBBoxes && oldBBoxes.length > 0) {
this.#bboxes.set(oldBBoxes);
this.#bboxes.fill(EMPTY_BBOX, oldBBoxes.length);
} else {
this.#bboxes.fill(EMPTY_BBOX);
}
}
get clipBox() {
return this.#clipBox;
}
save(opIdx) {
this.#clipBox = { __proto__: this.#clipBox };
this._savesStack.push(opIdx);
return this;
}
restore(opIdx, onSavePopped) {
const previous = Object.getPrototypeOf(this.#clipBox);
if (previous === null) {
// Sometimes we call more .restore() than .save(), for
// example when using CanvasGraphics' #restoreInitialState()
return this;
}
this.#clipBox = previous;
const lastSave = this._savesStack.pop();
if (lastSave !== undefined) {
onSavePopped?.(lastSave, opIdx);
this.#bboxes[opIdx] = this.#bboxes[lastSave];
}
return this;
}
/**
* @param {number} idx
*/
recordOpenMarker(idx) {
this._savesStack.push(idx);
return this;
}
getOpenMarker() {
if (this._savesStack.length === 0) {
return null;
}
return this._savesStack.at(-1);
}
recordCloseMarker(opIdx, onSavePopped) {
const lastSave = this._savesStack.pop();
if (lastSave !== undefined) {
onSavePopped?.(lastSave, opIdx);
this.#bboxes[opIdx] = this.#bboxes[lastSave];
}
return this;
}
// Marked content needs a separate stack from save/restore, because they
// form two independent trees.
beginMarkedContent(opIdx) {
this._markedContentStack.push(opIdx);
return this;
}
endMarkedContent(opIdx, onSavePopped) {
const lastSave = this._markedContentStack.pop();
if (lastSave !== undefined) {
onSavePopped?.(lastSave, opIdx);
this.#bboxes[opIdx] = this.#bboxes[lastSave];
}
return this;
}
pushBaseTransform(ctx) {
this.#baseTransformStack.push(
Util.multiplyByDOMMatrix(
this.#baseTransformStack.at(-1),
ctx.getTransform()
)
);
return this;
}
popBaseTransform() {
if (this.#baseTransformStack.length > 1) {
this.#baseTransformStack.pop();
}
return this;
}
resetBBox(idx) {
if (this._pendingBBoxIdx !== idx) {
this._pendingBBoxIdx = idx;
this.#pendingBBox[0] = Infinity;
this.#pendingBBox[1] = Infinity;
this.#pendingBBox[2] = -Infinity;
this.#pendingBBox[3] = -Infinity;
}
return this;
}
recordClipBox(idx, ctx, minX, maxX, minY, maxY) {
const transform = Util.multiplyByDOMMatrix(
this.#baseTransformStack.at(-1),
ctx.getTransform()
);
const clipBox = [Infinity, Infinity, -Infinity, -Infinity];
Util.axialAlignedBoundingBox([minX, minY, maxX, maxY], transform, clipBox);
const intersection = Util.intersect(this.#clipBox, clipBox);
if (intersection) {
this.#clipBox[0] = intersection[0];
this.#clipBox[1] = intersection[1];
this.#clipBox[2] = intersection[2];
this.#clipBox[3] = intersection[3];
} else {
this.#clipBox[0] = this.#clipBox[1] = Infinity;
this.#clipBox[2] = this.#clipBox[3] = -Infinity;
}
return this;
}
recordBBox(idx, ctx, minX, maxX, minY, maxY) {
const clipBox = this.#clipBox;
if (clipBox[0] === Infinity) {
return this;
}
const transform = Util.multiplyByDOMMatrix(
this.#baseTransformStack.at(-1),
ctx.getTransform()
);
if (clipBox[0] === -Infinity) {
Util.axialAlignedBoundingBox(
[minX, minY, maxX, maxY],
transform,
this.#pendingBBox
);
return this;
}
const bbox = [Infinity, Infinity, -Infinity, -Infinity];
Util.axialAlignedBoundingBox([minX, minY, maxX, maxY], transform, bbox);
this.#pendingBBox[0] = Math.min(
this.#pendingBBox[0],
Math.max(bbox[0], clipBox[0])
);
this.#pendingBBox[1] = Math.min(
this.#pendingBBox[1],
Math.max(bbox[1], clipBox[1])
);
this.#pendingBBox[2] = Math.max(
this.#pendingBBox[2],
Math.min(bbox[2], clipBox[2])
);
this.#pendingBBox[3] = Math.max(
this.#pendingBBox[3],
Math.min(bbox[3], clipBox[3])
);
return this;
}
recordFullPageBBox(idx) {
this.#pendingBBox[0] = Math.max(0, this.#clipBox[0]);
this.#pendingBBox[1] = Math.max(0, this.#clipBox[1]);
this.#pendingBBox[2] = Math.min(this.#canvasWidth, this.#clipBox[2]);
this.#pendingBBox[3] = Math.min(this.#canvasHeight, this.#clipBox[3]);
return this;
}
/**
* @param {number} idx
*/
recordOperation(idx, preserve = false, dependencyLists) {
if (this._pendingBBoxIdx !== idx) {
return this;
}
const minX = floor((this.#pendingBBox[0] * 256) / this.#canvasWidth);
const minY = floor((this.#pendingBBox[1] * 256) / this.#canvasHeight);
const maxX = ceil((this.#pendingBBox[2] * 256) / this.#canvasWidth);
const maxY = ceil((this.#pendingBBox[3] * 256) / this.#canvasHeight);
expandBBox(this.#bboxesCoords, idx, minX, minY, maxX, maxY);
if (dependencyLists) {
for (const dependencies of dependencyLists) {
for (const depIdx of dependencies) {
if (depIdx !== idx) {
expandBBox(this.#bboxesCoords, depIdx, minX, minY, maxX, maxY);
}
}
}
}
if (!preserve) {
this._pendingBBoxIdx = -1;
}
return this;
}
bboxToClipBoxDropOperation(idx) {
if (this._pendingBBoxIdx === idx) {
this._pendingBBoxIdx = -1;
this.#clipBox[0] = Math.max(this.#clipBox[0], this.#pendingBBox[0]);
this.#clipBox[1] = Math.max(this.#clipBox[1], this.#pendingBBox[1]);
this.#clipBox[2] = Math.min(this.#clipBox[2], this.#pendingBBox[2]);
this.#clipBox[3] = Math.min(this.#clipBox[3], this.#pendingBBox[3]);
}
return this;
}
take() {
return new BBoxReader(this.#bboxes, this.#bboxesCoords);
}
takeDebugMetadata() {
throw new Error("Unreachable");
}
recordSimpleData(name, idx) {
return this;
}
recordIncrementalData(name, idx) {
return this;
}
resetIncrementalData(name, idx) {
return this;
}
recordNamedData(name, idx) {
return this;
}
recordSimpleDataFromNamed(name, depName, fallbackIdx) {
return this;
}
recordFutureForcedDependency(name, idx) {
return this;
}
inheritSimpleDataAsFutureForcedDependencies(names) {
return this;
}
inheritPendingDependenciesAsFutureForcedDependencies() {
return this;
}
recordCharacterBBox(idx, ctx, font, scale = 1, x = 0, y = 0, getMeasure) {
return this;
}
getSimpleIndex(dependencyName) {
return undefined;
}
recordDependencies(idx, dependencyNames) {
return this;
}
recordNamedDependency(idx, name) {
return this;
}
recordShowTextOperation(idx, preserve = false) {
return this;
}
}
/** /**
* @typedef {"lineWidth" | "lineCap" | "lineJoin" | "miterLimit" | "dash" | * @typedef {"lineWidth" | "lineCap" | "lineJoin" | "miterLimit" | "dash" |
* "strokeAlpha" | "fillColor" | "fillAlpha" | "globalCompositeOperation" | * "strokeAlpha" | "fillColor" | "fillAlpha" | "globalCompositeOperation" |
@ -100,65 +417,34 @@ class CanvasDependencyTracker {
#namedDependencies = new Map(); #namedDependencies = new Map();
#savesStack = [];
#markedContentStack = [];
#baseTransformStack = [[1, 0, 0, 1, 0, 0]];
#clipBox = [-Infinity, -Infinity, Infinity, Infinity];
// Float32Array<minX, minY, maxX, maxY>
#pendingBBox = new Float64Array([Infinity, Infinity, -Infinity, -Infinity]);
#pendingBBoxIdx = -1;
#pendingDependencies = new Set(); #pendingDependencies = new Set();
#operations = new Map();
#fontBBoxTrustworthy = new Map(); #fontBBoxTrustworthy = new Map();
#canvasWidth;
#canvasHeight;
// Uint8ClampedArray<minX, minY, maxX, maxY>
#bboxesCoords;
#bboxes;
#debugMetadata; #debugMetadata;
constructor(canvas, operationsCount, recordDebugMetadata = false) { #recordDebugMetadataDepenencyAfterRestore;
this.#canvasWidth = canvas.width;
this.#canvasHeight = canvas.height; #bboxTracker;
this.#initializeBBoxes(operationsCount);
constructor(bboxTracker, recordDebugMetadata = false) {
this.#bboxTracker = bboxTracker;
if (recordDebugMetadata) { if (recordDebugMetadata) {
this.#debugMetadata = new Map(); this.#debugMetadata = new Map();
this.#recordDebugMetadataDepenencyAfterRestore = (lastSave, opIdx) => {
ensureDebugMetadata(this.#debugMetadata, opIdx).dependencies.add(
lastSave
);
};
} }
} }
get clipBox() { get clipBox() {
return this.#clipBox; return this.#bboxTracker.clipBox;
} }
growOperationsCount(operationsCount) { growOperationsCount(operationsCount) {
if (operationsCount >= this.#bboxes.length) { this.#bboxTracker.growOperationsCount(operationsCount);
this.#initializeBBoxes(operationsCount, this.#bboxes);
}
}
#initializeBBoxes(operationsCount, oldBBoxes) {
const buffer = new ArrayBuffer(operationsCount * 4);
this.#bboxesCoords = new Uint8ClampedArray(buffer);
this.#bboxes = new Uint32Array(buffer);
if (oldBBoxes && oldBBoxes.length > 0) {
this.#bboxes.set(oldBBoxes);
this.#bboxes.fill(EMPTY_BBOX, oldBBoxes.length);
} else {
this.#bboxes.fill(EMPTY_BBOX);
}
} }
save(opIdx) { save(opIdx) {
@ -172,13 +458,17 @@ class CanvasDependencyTracker {
__proto__: this.#incremental[FORCED_DEPENDENCY_LABEL], __proto__: this.#incremental[FORCED_DEPENDENCY_LABEL],
}, },
}; };
this.#clipBox = { __proto__: this.#clipBox }; this.#bboxTracker.save(opIdx);
this.#savesStack.push(opIdx);
return this; return this;
} }
restore(opIdx) { restore(opIdx) {
this.#bboxTracker.restore(
opIdx,
this.#recordDebugMetadataDepenencyAfterRestore
);
const previous = Object.getPrototypeOf(this.#simple); const previous = Object.getPrototypeOf(this.#simple);
if (previous === null) { if (previous === null) {
// Sometimes we call more .restore() than .save(), for // Sometimes we call more .restore() than .save(), for
@ -187,77 +477,53 @@ class CanvasDependencyTracker {
} }
this.#simple = previous; this.#simple = previous;
this.#incremental = Object.getPrototypeOf(this.#incremental); this.#incremental = Object.getPrototypeOf(this.#incremental);
this.#clipBox = Object.getPrototypeOf(this.#clipBox);
const lastSave = this.#savesStack.pop();
if (lastSave !== undefined) {
ensureDebugMetadata(this.#debugMetadata, opIdx)?.dependencies.add(
lastSave
);
this.#bboxes[opIdx] = this.#bboxes[lastSave];
}
return this; return this;
} }
/** recordOpenMarker(opIdx) {
* @param {number} idx this.#bboxTracker.recordOpenMarker(
*/ opIdx,
recordOpenMarker(idx) { this.#recordDebugMetadataDepenencyAfterRestore
this.#savesStack.push(idx);
return this;
}
getOpenMarker() {
if (this.#savesStack.length === 0) {
return null;
}
return this.#savesStack.at(-1);
}
recordCloseMarker(opIdx) {
const lastSave = this.#savesStack.pop();
if (lastSave !== undefined) {
ensureDebugMetadata(this.#debugMetadata, opIdx)?.dependencies.add(
lastSave
);
this.#bboxes[opIdx] = this.#bboxes[lastSave];
}
return this;
}
// Marked content needs a separate stack from save/restore, because they
// form two independent trees.
beginMarkedContent(opIdx) {
this.#markedContentStack.push(opIdx);
return this;
}
endMarkedContent(opIdx) {
const lastSave = this.#markedContentStack.pop();
if (lastSave !== undefined) {
ensureDebugMetadata(this.#debugMetadata, opIdx)?.dependencies.add(
lastSave
);
this.#bboxes[opIdx] = this.#bboxes[lastSave];
}
return this;
}
pushBaseTransform(ctx) {
this.#baseTransformStack.push(
Util.multiplyByDOMMatrix(
this.#baseTransformStack.at(-1),
ctx.getTransform()
)
); );
return this; return this;
} }
getOpenMarker() {
return this.#bboxTracker.getOpenMarker();
}
recordCloseMarker(opIdx) {
this.#bboxTracker.recordCloseMarker(
opIdx,
this.#recordDebugMetadataDepenencyAfterRestore
);
return this;
}
/**
* @param {number} opIdx
*/
beginMarkedContent(opIdx) {
this.#bboxTracker.beginMarkedContent(opIdx);
return this;
}
endMarkedContent(opIdx) {
this.#bboxTracker.endMarkedContent(
opIdx,
this.#recordDebugMetadataDepenencyAfterRestore
);
return this;
}
pushBaseTransform(ctx) {
this.#bboxTracker.pushBaseTransform(ctx);
return this;
}
popBaseTransform() { popBaseTransform() {
if (this.#baseTransformStack.length > 1) { this.#bboxTracker.popBaseTransform();
this.#baseTransformStack.pop();
}
return this; return this;
} }
@ -327,73 +593,17 @@ class CanvasDependencyTracker {
} }
resetBBox(idx) { resetBBox(idx) {
if (this.#pendingBBoxIdx !== idx) { this.#bboxTracker.resetBBox(idx);
this.#pendingBBoxIdx = idx;
this.#pendingBBox[0] = Infinity;
this.#pendingBBox[1] = Infinity;
this.#pendingBBox[2] = -Infinity;
this.#pendingBBox[3] = -Infinity;
}
return this; return this;
} }
recordClipBox(idx, ctx, minX, maxX, minY, maxY) { recordClipBox(idx, ctx, minX, maxX, minY, maxY) {
const transform = Util.multiplyByDOMMatrix( this.#bboxTracker.recordClipBox(idx, ctx, minX, maxX, minY, maxY);
this.#baseTransformStack.at(-1),
ctx.getTransform()
);
const clipBox = [Infinity, Infinity, -Infinity, -Infinity];
Util.axialAlignedBoundingBox([minX, minY, maxX, maxY], transform, clipBox);
const intersection = Util.intersect(this.#clipBox, clipBox);
if (intersection) {
this.#clipBox[0] = intersection[0];
this.#clipBox[1] = intersection[1];
this.#clipBox[2] = intersection[2];
this.#clipBox[3] = intersection[3];
} else {
this.#clipBox[0] = this.#clipBox[1] = Infinity;
this.#clipBox[2] = this.#clipBox[3] = -Infinity;
}
return this; return this;
} }
recordBBox(idx, ctx, minX, maxX, minY, maxY) { recordBBox(idx, ctx, minX, maxX, minY, maxY) {
const clipBox = this.#clipBox; this.#bboxTracker.recordBBox(idx, ctx, minX, maxX, minY, maxY);
if (clipBox[0] === Infinity) {
return this;
}
const transform = Util.multiplyByDOMMatrix(
this.#baseTransformStack.at(-1),
ctx.getTransform()
);
if (clipBox[0] === -Infinity) {
Util.axialAlignedBoundingBox(
[minX, minY, maxX, maxY],
transform,
this.#pendingBBox
);
return this;
}
const bbox = [Infinity, Infinity, -Infinity, -Infinity];
Util.axialAlignedBoundingBox([minX, minY, maxX, maxY], transform, bbox);
this.#pendingBBox[0] = Math.min(
this.#pendingBBox[0],
Math.max(bbox[0], clipBox[0])
);
this.#pendingBBox[1] = Math.min(
this.#pendingBBox[1],
Math.max(bbox[1], clipBox[1])
);
this.#pendingBBox[2] = Math.max(
this.#pendingBBox[2],
Math.min(bbox[2], clipBox[2])
);
this.#pendingBBox[3] = Math.max(
this.#pendingBBox[3],
Math.min(bbox[3], clipBox[3])
);
return this; return this;
} }
@ -475,11 +685,7 @@ class CanvasDependencyTracker {
} }
recordFullPageBBox(idx) { recordFullPageBBox(idx) {
this.#pendingBBox[0] = Math.max(0, this.#clipBox[0]); this.#bboxTracker.recordFullPageBBox(idx);
this.#pendingBBox[1] = Math.max(0, this.#clipBox[1]);
this.#pendingBBox[2] = Math.min(this.#canvasWidth, this.#clipBox[2]);
this.#pendingBBox[3] = Math.min(this.#canvasHeight, this.#clipBox[3]);
return this; return this;
} }
@ -520,39 +726,25 @@ class CanvasDependencyTracker {
const metadata = ensureDebugMetadata(this.#debugMetadata, idx); const metadata = ensureDebugMetadata(this.#debugMetadata, idx);
const { dependencies } = metadata; const { dependencies } = metadata;
this.#pendingDependencies.forEach(dependencies.add, dependencies); this.#pendingDependencies.forEach(dependencies.add, dependencies);
this.#savesStack.forEach(dependencies.add, dependencies); this.#bboxTracker._savesStack.forEach(dependencies.add, dependencies);
this.#markedContentStack.forEach(dependencies.add, dependencies); this.#bboxTracker._markedContentStack.forEach(
dependencies.add,
dependencies
);
dependencies.delete(idx); dependencies.delete(idx);
metadata.isRenderingOperation = true; metadata.isRenderingOperation = true;
} }
if (this.#pendingBBoxIdx === idx) { const needsCleanup = !preserve && idx === this.#bboxTracker._pendingBBoxIdx;
const minX = floor((this.#pendingBBox[0] * 256) / this.#canvasWidth);
const minY = floor((this.#pendingBBox[1] * 256) / this.#canvasHeight);
const maxX = ceil((this.#pendingBBox[2] * 256) / this.#canvasWidth);
const maxY = ceil((this.#pendingBBox[3] * 256) / this.#canvasHeight);
expandBBox(this.#bboxesCoords, idx, minX, minY, maxX, maxY); this.#bboxTracker.recordOperation(idx, preserve, [
for (const depIdx of this.#pendingDependencies) { this.#pendingDependencies,
if (depIdx !== idx) { this.#bboxTracker._savesStack,
expandBBox(this.#bboxesCoords, depIdx, minX, minY, maxX, maxY); this.#bboxTracker._markedContentStack,
} ]);
}
for (const saveIdx of this.#savesStack) {
if (saveIdx !== idx) {
expandBBox(this.#bboxesCoords, saveIdx, minX, minY, maxX, maxY);
}
}
for (const saveIdx of this.#markedContentStack) {
if (saveIdx !== idx) {
expandBBox(this.#bboxesCoords, saveIdx, minX, minY, maxX, maxY);
}
}
if (!preserve) { if (needsCleanup) {
this.#pendingDependencies.clear(); this.#pendingDependencies.clear();
this.#pendingBBoxIdx = -1;
}
} }
return this; return this;
@ -569,42 +761,17 @@ class CanvasDependencyTracker {
} }
bboxToClipBoxDropOperation(idx, preserve = false) { bboxToClipBoxDropOperation(idx, preserve = false) {
if (this.#pendingBBoxIdx === idx) { const needsCleanup = !preserve && idx === this.#bboxTracker._pendingBBoxIdx;
this.#pendingBBoxIdx = -1; this.#bboxTracker.bboxToClipBoxDropOperation(idx);
if (needsCleanup) {
this.#clipBox[0] = Math.max(this.#clipBox[0], this.#pendingBBox[0]); this.#pendingDependencies.clear();
this.#clipBox[1] = Math.max(this.#clipBox[1], this.#pendingBBox[1]);
this.#clipBox[2] = Math.min(this.#clipBox[2], this.#pendingBBox[2]);
this.#clipBox[3] = Math.min(this.#clipBox[3], this.#pendingBBox[3]);
if (!preserve) {
this.#pendingDependencies.clear();
}
} }
return this; return this;
} }
_takePendingDependencies() {
const pendingDependencies = this.#pendingDependencies;
this.#pendingDependencies = new Set();
return pendingDependencies;
}
_extractOperation(idx) {
const operation = this.#operations.get(idx);
this.#operations.delete(idx);
return operation;
}
_pushPendingDependencies(dependencies) {
for (const dep of dependencies) {
this.#pendingDependencies.add(dep);
}
}
take() { take() {
this.#fontBBoxTrustworthy.clear(); this.#fontBBoxTrustworthy.clear();
return new BBoxReader(this.#bboxes, this.#bboxesCoords); return this.#bboxTracker.take();
} }
takeDebugMetadata() { takeDebugMetadata() {
@ -1073,6 +1240,7 @@ class CanvasImagesTracker {
} }
export { export {
CanvasBBoxTracker,
CanvasDependencyTracker, CanvasDependencyTracker,
CanvasImagesTracker, CanvasImagesTracker,
CanvasNestedDependencyTracker, CanvasNestedDependencyTracker,