Skip to content

Commit e41356a

Browse files
committed
Model drawing buffer tracking by source
1 parent e13b0c9 commit e41356a

8 files changed

Lines changed: 474 additions & 342 deletions

File tree

dev-docs/RFCs/vNext/canvas-context-configuration-rfc.md

Lines changed: 169 additions & 101 deletions
Large diffs are not rendered by default.

modules/core/src/adapter/canvas-context.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
export type {
66
CanvasContextProps,
7-
DrawingBufferSizingMode,
7+
DrawingBufferSizeTracking,
88
MutableCanvasContextProps
99
} from './canvas-surface';
1010
import {CanvasSurface} from './canvas-surface';

modules/core/src/adapter/canvas-surface.ts

Lines changed: 176 additions & 126 deletions
Large diffs are not rendered by default.

modules/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export {Device, DeviceFeatures, DeviceLimits, isHTMLInCanvasSupported} from './a
2222

2323
export type {
2424
CanvasContextProps,
25-
DrawingBufferSizingMode,
25+
DrawingBufferSizeTracking,
2626
MutableCanvasContextProps
2727
} from './adapter/canvas-context';
2828
export {CanvasContext} from './adapter/canvas-context';

modules/core/test/adapter/canvas-context.spec.ts

Lines changed: 94 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -343,16 +343,13 @@ test('CanvasContext#_handleResize keeps numeric useDevicePixels override across
343343
t.end();
344344
});
345345

346-
test('CanvasContext#drawingBufferSizingMode supports explicit automatic and manual sizing', t => {
346+
test('CanvasContext#drawingBufferSizeTracking supports canvas, external, and no tracking', t => {
347347
if (!isBrowser()) {
348348
t.end();
349349
return;
350350
}
351351

352-
const exactCanvasContext = new TestCanvasContext(
353-
{drawingBufferSizingMode: 'track-device-pixels'},
354-
false
355-
);
352+
const exactCanvasContext = new TestCanvasContext({drawingBufferSizeTracking: 'canvas'}, false);
356353
(exactCanvasContext as any)._handleResize([
357354
{
358355
target: exactCanvasContext.canvas,
@@ -363,13 +360,10 @@ test('CanvasContext#drawingBufferSizingMode supports explicit automatic and manu
363360
t.deepEqual(
364361
exactCanvasContext.getDrawingBufferSize(),
365362
[151, 76],
366-
'track-device-pixels mode uses exact physical dimensions'
363+
'canvas tracking uses exact physical dimensions when available'
367364
);
368365

369-
const fallbackCanvasContext = new TestCanvasContext(
370-
{drawingBufferSizingMode: 'track-device-pixels'},
371-
false
372-
);
366+
const fallbackCanvasContext = new TestCanvasContext({drawingBufferSizeTracking: 'canvas'}, false);
373367
(fallbackCanvasContext as any)._handleResize([
374368
{
375369
target: fallbackCanvasContext.canvas,
@@ -379,29 +373,11 @@ test('CanvasContext#drawingBufferSizingMode supports explicit automatic and manu
379373
t.deepEqual(
380374
fallbackCanvasContext.getDrawingBufferSize(),
381375
[Math.floor(100.4 * window.devicePixelRatio), Math.floor(50.4 * window.devicePixelRatio)],
382-
'track-device-pixels falls back to CSS dimensions times browser DPR'
383-
);
384-
385-
const contentBoxCanvasContext = new TestCanvasContext(
386-
{drawingBufferSizingMode: 'track-css-pixels'},
387-
false
388-
);
389-
contentBoxCanvasContext.getDevicePixelRatio = () => 1.5;
390-
(contentBoxCanvasContext as any)._handleResize([
391-
{
392-
target: contentBoxCanvasContext.canvas,
393-
contentBoxSize: [{inlineSize: 100.4, blockSize: 50.4}],
394-
devicePixelContentBoxSize: [{inlineSize: 151, blockSize: 76}]
395-
}
396-
]);
397-
t.deepEqual(
398-
contentBoxCanvasContext.getDrawingBufferSize(),
399-
[150, 75],
400-
'track-css-pixels mode uses CSS dimensions times browser DPR'
376+
'canvas tracking falls back to CSS dimensions times browser DPR'
401377
);
402378

403379
const fixedRatioCanvasContext = new TestCanvasContext(
404-
{drawingBufferSizingMode: 'track-css-pixels', pixelRatio: 1},
380+
{drawingBufferSizeTracking: 'canvas', pixelRatio: 1},
405381
false
406382
);
407383
fixedRatioCanvasContext.getDevicePixelRatio = () => 2;
@@ -414,10 +390,10 @@ test('CanvasContext#drawingBufferSizingMode supports explicit automatic and manu
414390
t.deepEqual(
415391
fixedRatioCanvasContext.getDrawingBufferSize(),
416392
[100, 50],
417-
'pixelRatio overrides browser DPR in track-css-pixels mode'
393+
'pixelRatio overrides browser DPR in canvas tracking mode'
418394
);
419395

420-
const manualCanvasContext = new TestCanvasContext({drawingBufferSizingMode: 'manual'}, false);
396+
const manualCanvasContext = new TestCanvasContext({drawingBufferSizeTracking: 'none'}, false);
421397
const {calls, device} = createCanvasContextSpyDevice();
422398
// @ts-expect-error read only
423399
manualCanvasContext.device = device;
@@ -439,7 +415,7 @@ test('CanvasContext#drawingBufferSizingMode supports explicit automatic and manu
439415
t.end();
440416
});
441417

442-
test('CanvasContext#trackCanvas mirrors source dimensions before drawing', t => {
418+
test('CanvasContext#external-canvas tracking mirrors source dimensions before drawing', t => {
443419
if (!isBrowser()) {
444420
t.end();
445421
return;
@@ -448,12 +424,18 @@ test('CanvasContext#trackCanvas mirrors source dimensions before drawing', t =>
448424
const sourceCanvas = document.createElement('canvas');
449425
sourceCanvas.width = 320;
450426
sourceCanvas.height = 180;
451-
const canvasContext = new TestCanvasContext({trackCanvas: sourceCanvas}, false);
427+
const canvasContext = new TestCanvasContext(
428+
{
429+
drawingBufferSizeTracking: 'external-canvas',
430+
drawingBufferSizeSource: sourceCanvas
431+
},
432+
false
433+
);
452434

453435
t.equal(
454-
canvasContext.props.drawingBufferSizingMode,
455-
'manual',
456-
'tracking a canvas selects manual observer sizing'
436+
canvasContext.props.drawingBufferSizeTracking,
437+
'external-canvas',
438+
'external canvas tracking is explicit'
457439
);
458440
t.deepEqual(
459441
canvasContext.getDrawingBufferSize(),
@@ -488,7 +470,11 @@ test('CanvasContext#trackCanvas mirrors source dimensions before drawing', t =>
488470

489471
const attachedCanvas = document.createElement('canvas');
490472
const attachedCanvasContext = new TestCanvasContext(
491-
{canvas: attachedCanvas, trackCanvas: attachedCanvas},
473+
{
474+
canvas: attachedCanvas,
475+
drawingBufferSizeTracking: 'external-canvas',
476+
drawingBufferSizeSource: attachedCanvas
477+
},
492478
false
493479
);
494480
attachedCanvas.width = 512;
@@ -503,18 +489,36 @@ test('CanvasContext#trackCanvas mirrors source dimensions before drawing', t =>
503489
() =>
504490
new TestCanvasContext(
505491
{
506-
trackCanvas: sourceCanvas,
507-
drawingBufferSizingMode: 'track-css-pixels'
492+
drawingBufferSizeSource: sourceCanvas,
493+
drawingBufferSizeTracking: 'canvas'
508494
},
509495
false
510496
),
511497
/assertion failed/,
512-
'trackCanvas rejects conflicting automatic sizing'
498+
'canvas tracking rejects an external source'
513499
);
514500
t.throws(
515-
() => new TestCanvasContext({trackCanvas: sourceCanvas, pixelRatio: 2}, false),
501+
() =>
502+
new TestCanvasContext(
503+
{
504+
drawingBufferSizeTracking: 'external-canvas',
505+
drawingBufferSizeSource: sourceCanvas,
506+
pixelRatio: 2
507+
},
508+
false
509+
),
516510
/assertion failed/,
517-
'trackCanvas rejects a conflicting pixel ratio'
511+
'external canvas tracking rejects a conflicting pixel ratio'
512+
);
513+
t.throws(
514+
() => new TestCanvasContext({drawingBufferSizeTracking: 'external-canvas'}, false),
515+
/assertion failed/,
516+
'external canvas tracking requires a source'
517+
);
518+
t.throws(
519+
() => new TestCanvasContext({drawingBufferSizeSource: sourceCanvas}, false),
520+
/assertion failed/,
521+
'an external source requires an explicit tracking behavior'
518522
);
519523

520524
t.end();
@@ -527,7 +531,7 @@ test('CanvasContext#setProps updates drawing buffer sizing and observer mode', t
527531
}
528532

529533
const canvasContext = new TestCanvasContext(
530-
{drawingBufferSizingMode: 'track-css-pixels', pixelRatio: 1},
534+
{drawingBufferSizeTracking: 'canvas', pixelRatio: 1},
531535
false
532536
);
533537
canvasContext.getDevicePixelRatio = () => 1.5;
@@ -549,14 +553,12 @@ test('CanvasContext#setProps updates drawing buffer sizing and observer mode', t
549553
t.deepEqual(
550554
canvasContext.getDrawingBufferSize(),
551555
[150, 75],
552-
'clearing the fixed ratio resumes browser DPR'
556+
'clearing the fixed ratio resumes exact device-pixel tracking'
553557
);
554-
555-
canvasContext.setProps({drawingBufferSizingMode: 'track-device-pixels'});
556558
t.equal(
557559
(canvasContext as any)._canvasObserver.props.resizeObserverBox,
558560
'device-pixel-content-box',
559-
'switching mode reconfigures the observer box'
561+
'clearing pixelRatio reconfigures the observer box'
560562
);
561563
(canvasContext as any)._handleResize([
562564
{
@@ -571,7 +573,7 @@ test('CanvasContext#setProps updates drawing buffer sizing and observer mode', t
571573
'exact observer dimensions are used after switching mode'
572574
);
573575

574-
canvasContext.setProps({drawingBufferSizingMode: 'manual'});
576+
canvasContext.setProps({drawingBufferSizeTracking: 'none'});
575577
(canvasContext as any)._handleResize([
576578
{
577579
target: canvasContext.canvas,
@@ -588,23 +590,30 @@ test('CanvasContext#setProps updates drawing buffer sizing and observer mode', t
588590
const sourceCanvas = document.createElement('canvas');
589591
sourceCanvas.width = 240;
590592
sourceCanvas.height = 120;
591-
canvasContext.setProps({trackCanvas: sourceCanvas});
593+
canvasContext.setProps({
594+
drawingBufferSizeTracking: 'external-canvas',
595+
drawingBufferSizeSource: sourceCanvas
596+
});
592597
t.deepEqual(
593598
canvasContext.getDrawingBufferSize(),
594599
[240, 120],
595-
'trackCanvas can take ownership dynamically'
600+
'external canvas tracking can take ownership dynamically'
596601
);
597602

598603
canvasContext.setProps({
599-
trackCanvas: null,
600-
drawingBufferSizingMode: 'track-css-pixels',
604+
drawingBufferSizeSource: null,
605+
drawingBufferSizeTracking: 'canvas',
601606
pixelRatio: 1
602607
});
603-
t.equal(canvasContext.props.trackCanvas, null, 'tracked canvas can be cleared dynamically');
604608
t.equal(
605-
canvasContext.props.drawingBufferSizingMode,
606-
'track-css-pixels',
607-
'automatic sizing can resume when trackCanvas is cleared'
609+
canvasContext.props.drawingBufferSizeSource,
610+
null,
611+
'external size source can be cleared dynamically'
612+
);
613+
t.equal(
614+
canvasContext.props.drawingBufferSizeTracking,
615+
'canvas',
616+
'canvas tracking can resume when the external source is cleared'
608617
);
609618

610619
t.end();
@@ -618,17 +627,17 @@ test('CanvasContext#new drawing buffer sizing props override and validate legacy
618627

619628
const legacyCSSCanvasContext = new TestCanvasContext({useDevicePixels: false}, false);
620629
t.equal(
621-
legacyCSSCanvasContext.props.drawingBufferSizingMode,
622-
'track-css-pixels',
623-
'legacy false normalizes to track-css-pixels mode'
630+
legacyCSSCanvasContext.props.drawingBufferSizeTracking,
631+
'canvas',
632+
'legacy false normalizes to canvas tracking'
624633
);
625634
t.equal(legacyCSSCanvasContext.props.pixelRatio, 1, 'legacy false normalizes to ratio 1');
626635

627636
const legacyNumericCanvasContext = new TestCanvasContext({useDevicePixels: 1.5}, false);
628637
t.equal(
629-
legacyNumericCanvasContext.props.drawingBufferSizingMode,
630-
'track-css-pixels',
631-
'legacy numeric ratio normalizes to track-css-pixels mode'
638+
legacyNumericCanvasContext.props.drawingBufferSizeTracking,
639+
'canvas',
640+
'legacy numeric ratio normalizes to canvas tracking'
632641
);
633642
t.equal(legacyNumericCanvasContext.props.pixelRatio, 1.5, 'legacy numeric ratio is preserved');
634643

@@ -637,19 +646,19 @@ test('CanvasContext#new drawing buffer sizing props override and validate legacy
637646
false
638647
);
639648
t.equal(
640-
legacyExactCanvasContext.props.drawingBufferSizingMode,
641-
'track-device-pixels',
642-
'legacy exact sizing normalizes to track-device-pixels mode'
649+
legacyExactCanvasContext.props.drawingBufferSizeTracking,
650+
'canvas',
651+
'legacy exact sizing normalizes to canvas tracking'
643652
);
644653

645654
const legacyCSSDPRCanvasContext = new TestCanvasContext(
646655
{useDevicePixels: true, pixelSizeSource: 'css-dpr'},
647656
false
648657
);
649658
t.equal(
650-
legacyCSSDPRCanvasContext.props.drawingBufferSizingMode,
651-
'track-css-pixels',
652-
'legacy CSS-DPR sizing normalizes to track-css-pixels mode'
659+
legacyCSSDPRCanvasContext.props.drawingBufferSizeTracking,
660+
'canvas',
661+
'legacy CSS-DPR sizing normalizes to canvas tracking'
653662
);
654663
t.equal(
655664
legacyCSSDPRCanvasContext.props.pixelRatio,
@@ -659,14 +668,14 @@ test('CanvasContext#new drawing buffer sizing props override and validate legacy
659668

660669
const legacyManualCanvasContext = new TestCanvasContext({autoResize: false}, false);
661670
t.equal(
662-
legacyManualCanvasContext.props.drawingBufferSizingMode,
663-
'manual',
664-
'legacy autoResize false normalizes to manual mode'
671+
legacyManualCanvasContext.props.drawingBufferSizeTracking,
672+
'none',
673+
'legacy autoResize false normalizes to no tracking'
665674
);
666675

667676
const newCanvasContext = new TestCanvasContext(
668677
{
669-
drawingBufferSizingMode: 'track-css-pixels',
678+
drawingBufferSizeTracking: 'canvas',
670679
autoResize: false,
671680
useDevicePixels: false,
672681
pixelSizeSource: 'exact'
@@ -677,13 +686,14 @@ test('CanvasContext#new drawing buffer sizing props override and validate legacy
677686
(newCanvasContext as any)._handleResize([
678687
{
679688
target: newCanvasContext.canvas,
680-
contentBoxSize: [{inlineSize: 100, blockSize: 50}]
689+
contentBoxSize: [{inlineSize: 100, blockSize: 50}],
690+
devicePixelContentBoxSize: [{inlineSize: 200, blockSize: 100}]
681691
}
682692
]);
683693
t.deepEqual(
684694
newCanvasContext.getDrawingBufferSize(),
685695
[200, 100],
686-
'new track-css-pixels mode ignores conflicting legacy sizing props'
696+
'new canvas tracking ignores conflicting legacy sizing props'
687697
);
688698
newCanvasContext.setProps({useDevicePixels: 1});
689699
t.equal(
@@ -693,23 +703,22 @@ test('CanvasContext#new drawing buffer sizing props override and validate legacy
693703
);
694704

695705
t.throws(
696-
() =>
697-
new TestCanvasContext({drawingBufferSizingMode: 'track-device-pixels', pixelRatio: 2}, false),
706+
() => new TestCanvasContext({drawingBufferSizeTracking: 'none', pixelRatio: 2}, false),
698707
/assertion failed/,
699-
'pixelRatio is rejected outside track-css-pixels mode'
708+
'pixelRatio is rejected outside canvas tracking'
700709
);
701710
t.throws(
702-
() =>
703-
new TestCanvasContext({drawingBufferSizingMode: 'track-css-pixels', pixelRatio: 0}, false),
711+
() => new TestCanvasContext({pixelRatio: 2}, false),
712+
/assertion failed/,
713+
'pixelRatio requires explicit canvas tracking'
714+
);
715+
t.throws(
716+
() => new TestCanvasContext({drawingBufferSizeTracking: 'canvas', pixelRatio: 0}, false),
704717
/assertion failed/,
705718
'non-positive pixelRatio is rejected'
706719
);
707720
t.throws(
708-
() =>
709-
new TestCanvasContext(
710-
{drawingBufferSizingMode: 'track-css-pixels', pixelRatio: Infinity},
711-
false
712-
),
721+
() => new TestCanvasContext({drawingBufferSizeTracking: 'canvas', pixelRatio: Infinity}, false),
713722
/assertion failed/,
714723
'non-finite pixelRatio is rejected'
715724
);

0 commit comments

Comments
 (0)