Skip to content

Commit a4a5736

Browse files
committed
Model drawing buffer tracking by source
1 parent 5159306 commit a4a5736

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
@@ -361,16 +361,13 @@ test('CanvasContext#_handleResize keeps numeric useDevicePixels override across
361361
t.end();
362362
});
363363

364-
test('CanvasContext#drawingBufferSizingMode supports explicit automatic and manual sizing', t => {
364+
test('CanvasContext#drawingBufferSizeTracking supports canvas, external, and no tracking', t => {
365365
if (!isBrowser()) {
366366
t.end();
367367
return;
368368
}
369369

370-
const exactCanvasContext = new TestCanvasContext(
371-
{drawingBufferSizingMode: 'track-device-pixels'},
372-
false
373-
);
370+
const exactCanvasContext = new TestCanvasContext({drawingBufferSizeTracking: 'canvas'}, false);
374371
(exactCanvasContext as any)._handleResize([
375372
{
376373
target: exactCanvasContext.canvas,
@@ -381,13 +378,10 @@ test('CanvasContext#drawingBufferSizingMode supports explicit automatic and manu
381378
t.deepEqual(
382379
exactCanvasContext.getDrawingBufferSize(),
383380
[151, 76],
384-
'track-device-pixels mode uses exact physical dimensions'
381+
'canvas tracking uses exact physical dimensions when available'
385382
);
386383

387-
const fallbackCanvasContext = new TestCanvasContext(
388-
{drawingBufferSizingMode: 'track-device-pixels'},
389-
false
390-
);
384+
const fallbackCanvasContext = new TestCanvasContext({drawingBufferSizeTracking: 'canvas'}, false);
391385
(fallbackCanvasContext as any)._handleResize([
392386
{
393387
target: fallbackCanvasContext.canvas,
@@ -397,29 +391,11 @@ test('CanvasContext#drawingBufferSizingMode supports explicit automatic and manu
397391
t.deepEqual(
398392
fallbackCanvasContext.getDrawingBufferSize(),
399393
[Math.floor(100.4 * window.devicePixelRatio), Math.floor(50.4 * window.devicePixelRatio)],
400-
'track-device-pixels falls back to CSS dimensions times browser DPR'
401-
);
402-
403-
const contentBoxCanvasContext = new TestCanvasContext(
404-
{drawingBufferSizingMode: 'track-css-pixels'},
405-
false
406-
);
407-
contentBoxCanvasContext.getDevicePixelRatio = () => 1.5;
408-
(contentBoxCanvasContext as any)._handleResize([
409-
{
410-
target: contentBoxCanvasContext.canvas,
411-
contentBoxSize: [{inlineSize: 100.4, blockSize: 50.4}],
412-
devicePixelContentBoxSize: [{inlineSize: 151, blockSize: 76}]
413-
}
414-
]);
415-
t.deepEqual(
416-
contentBoxCanvasContext.getDrawingBufferSize(),
417-
[150, 75],
418-
'track-css-pixels mode uses CSS dimensions times browser DPR'
394+
'canvas tracking falls back to CSS dimensions times browser DPR'
419395
);
420396

421397
const fixedRatioCanvasContext = new TestCanvasContext(
422-
{drawingBufferSizingMode: 'track-css-pixels', pixelRatio: 1},
398+
{drawingBufferSizeTracking: 'canvas', pixelRatio: 1},
423399
false
424400
);
425401
fixedRatioCanvasContext.getDevicePixelRatio = () => 2;
@@ -432,10 +408,10 @@ test('CanvasContext#drawingBufferSizingMode supports explicit automatic and manu
432408
t.deepEqual(
433409
fixedRatioCanvasContext.getDrawingBufferSize(),
434410
[100, 50],
435-
'pixelRatio overrides browser DPR in track-css-pixels mode'
411+
'pixelRatio overrides browser DPR in canvas tracking mode'
436412
);
437413

438-
const manualCanvasContext = new TestCanvasContext({drawingBufferSizingMode: 'manual'}, false);
414+
const manualCanvasContext = new TestCanvasContext({drawingBufferSizeTracking: 'none'}, false);
439415
const {calls, device} = createCanvasContextSpyDevice();
440416
// @ts-expect-error read only
441417
manualCanvasContext.device = device;
@@ -457,7 +433,7 @@ test('CanvasContext#drawingBufferSizingMode supports explicit automatic and manu
457433
t.end();
458434
});
459435

460-
test('CanvasContext#trackCanvas mirrors source dimensions before drawing', t => {
436+
test('CanvasContext#external-canvas tracking mirrors source dimensions before drawing', t => {
461437
if (!isBrowser()) {
462438
t.end();
463439
return;
@@ -466,12 +442,18 @@ test('CanvasContext#trackCanvas mirrors source dimensions before drawing', t =>
466442
const sourceCanvas = document.createElement('canvas');
467443
sourceCanvas.width = 320;
468444
sourceCanvas.height = 180;
469-
const canvasContext = new TestCanvasContext({trackCanvas: sourceCanvas}, false);
445+
const canvasContext = new TestCanvasContext(
446+
{
447+
drawingBufferSizeTracking: 'external-canvas',
448+
drawingBufferSizeSource: sourceCanvas
449+
},
450+
false
451+
);
470452

471453
t.equal(
472-
canvasContext.props.drawingBufferSizingMode,
473-
'manual',
474-
'tracking a canvas selects manual observer sizing'
454+
canvasContext.props.drawingBufferSizeTracking,
455+
'external-canvas',
456+
'external canvas tracking is explicit'
475457
);
476458
t.deepEqual(
477459
canvasContext.getDrawingBufferSize(),
@@ -506,7 +488,11 @@ test('CanvasContext#trackCanvas mirrors source dimensions before drawing', t =>
506488

507489
const attachedCanvas = document.createElement('canvas');
508490
const attachedCanvasContext = new TestCanvasContext(
509-
{canvas: attachedCanvas, trackCanvas: attachedCanvas},
491+
{
492+
canvas: attachedCanvas,
493+
drawingBufferSizeTracking: 'external-canvas',
494+
drawingBufferSizeSource: attachedCanvas
495+
},
510496
false
511497
);
512498
attachedCanvas.width = 512;
@@ -521,18 +507,36 @@ test('CanvasContext#trackCanvas mirrors source dimensions before drawing', t =>
521507
() =>
522508
new TestCanvasContext(
523509
{
524-
trackCanvas: sourceCanvas,
525-
drawingBufferSizingMode: 'track-css-pixels'
510+
drawingBufferSizeSource: sourceCanvas,
511+
drawingBufferSizeTracking: 'canvas'
526512
},
527513
false
528514
),
529515
/assertion failed/,
530-
'trackCanvas rejects conflicting automatic sizing'
516+
'canvas tracking rejects an external source'
531517
);
532518
t.throws(
533-
() => new TestCanvasContext({trackCanvas: sourceCanvas, pixelRatio: 2}, false),
519+
() =>
520+
new TestCanvasContext(
521+
{
522+
drawingBufferSizeTracking: 'external-canvas',
523+
drawingBufferSizeSource: sourceCanvas,
524+
pixelRatio: 2
525+
},
526+
false
527+
),
534528
/assertion failed/,
535-
'trackCanvas rejects a conflicting pixel ratio'
529+
'external canvas tracking rejects a conflicting pixel ratio'
530+
);
531+
t.throws(
532+
() => new TestCanvasContext({drawingBufferSizeTracking: 'external-canvas'}, false),
533+
/assertion failed/,
534+
'external canvas tracking requires a source'
535+
);
536+
t.throws(
537+
() => new TestCanvasContext({drawingBufferSizeSource: sourceCanvas}, false),
538+
/assertion failed/,
539+
'an external source requires an explicit tracking behavior'
536540
);
537541

538542
t.end();
@@ -545,7 +549,7 @@ test('CanvasContext#setProps updates drawing buffer sizing and observer mode', t
545549
}
546550

547551
const canvasContext = new TestCanvasContext(
548-
{drawingBufferSizingMode: 'track-css-pixels', pixelRatio: 1},
552+
{drawingBufferSizeTracking: 'canvas', pixelRatio: 1},
549553
false
550554
);
551555
canvasContext.getDevicePixelRatio = () => 1.5;
@@ -567,14 +571,12 @@ test('CanvasContext#setProps updates drawing buffer sizing and observer mode', t
567571
t.deepEqual(
568572
canvasContext.getDrawingBufferSize(),
569573
[150, 75],
570-
'clearing the fixed ratio resumes browser DPR'
574+
'clearing the fixed ratio resumes exact device-pixel tracking'
571575
);
572-
573-
canvasContext.setProps({drawingBufferSizingMode: 'track-device-pixels'});
574576
t.equal(
575577
(canvasContext as any)._canvasObserver.props.resizeObserverBox,
576578
'device-pixel-content-box',
577-
'switching mode reconfigures the observer box'
579+
'clearing pixelRatio reconfigures the observer box'
578580
);
579581
(canvasContext as any)._handleResize([
580582
{
@@ -589,7 +591,7 @@ test('CanvasContext#setProps updates drawing buffer sizing and observer mode', t
589591
'exact observer dimensions are used after switching mode'
590592
);
591593

592-
canvasContext.setProps({drawingBufferSizingMode: 'manual'});
594+
canvasContext.setProps({drawingBufferSizeTracking: 'none'});
593595
(canvasContext as any)._handleResize([
594596
{
595597
target: canvasContext.canvas,
@@ -606,23 +608,30 @@ test('CanvasContext#setProps updates drawing buffer sizing and observer mode', t
606608
const sourceCanvas = document.createElement('canvas');
607609
sourceCanvas.width = 240;
608610
sourceCanvas.height = 120;
609-
canvasContext.setProps({trackCanvas: sourceCanvas});
611+
canvasContext.setProps({
612+
drawingBufferSizeTracking: 'external-canvas',
613+
drawingBufferSizeSource: sourceCanvas
614+
});
610615
t.deepEqual(
611616
canvasContext.getDrawingBufferSize(),
612617
[240, 120],
613-
'trackCanvas can take ownership dynamically'
618+
'external canvas tracking can take ownership dynamically'
614619
);
615620

616621
canvasContext.setProps({
617-
trackCanvas: null,
618-
drawingBufferSizingMode: 'track-css-pixels',
622+
drawingBufferSizeSource: null,
623+
drawingBufferSizeTracking: 'canvas',
619624
pixelRatio: 1
620625
});
621-
t.equal(canvasContext.props.trackCanvas, null, 'tracked canvas can be cleared dynamically');
622626
t.equal(
623-
canvasContext.props.drawingBufferSizingMode,
624-
'track-css-pixels',
625-
'automatic sizing can resume when trackCanvas is cleared'
627+
canvasContext.props.drawingBufferSizeSource,
628+
null,
629+
'external size source can be cleared dynamically'
630+
);
631+
t.equal(
632+
canvasContext.props.drawingBufferSizeTracking,
633+
'canvas',
634+
'canvas tracking can resume when the external source is cleared'
626635
);
627636

628637
t.end();
@@ -636,17 +645,17 @@ test('CanvasContext#new drawing buffer sizing props override and validate legacy
636645

637646
const legacyCSSCanvasContext = new TestCanvasContext({useDevicePixels: false}, false);
638647
t.equal(
639-
legacyCSSCanvasContext.props.drawingBufferSizingMode,
640-
'track-css-pixels',
641-
'legacy false normalizes to track-css-pixels mode'
648+
legacyCSSCanvasContext.props.drawingBufferSizeTracking,
649+
'canvas',
650+
'legacy false normalizes to canvas tracking'
642651
);
643652
t.equal(legacyCSSCanvasContext.props.pixelRatio, 1, 'legacy false normalizes to ratio 1');
644653

645654
const legacyNumericCanvasContext = new TestCanvasContext({useDevicePixels: 1.5}, false);
646655
t.equal(
647-
legacyNumericCanvasContext.props.drawingBufferSizingMode,
648-
'track-css-pixels',
649-
'legacy numeric ratio normalizes to track-css-pixels mode'
656+
legacyNumericCanvasContext.props.drawingBufferSizeTracking,
657+
'canvas',
658+
'legacy numeric ratio normalizes to canvas tracking'
650659
);
651660
t.equal(legacyNumericCanvasContext.props.pixelRatio, 1.5, 'legacy numeric ratio is preserved');
652661

@@ -655,19 +664,19 @@ test('CanvasContext#new drawing buffer sizing props override and validate legacy
655664
false
656665
);
657666
t.equal(
658-
legacyExactCanvasContext.props.drawingBufferSizingMode,
659-
'track-device-pixels',
660-
'legacy exact sizing normalizes to track-device-pixels mode'
667+
legacyExactCanvasContext.props.drawingBufferSizeTracking,
668+
'canvas',
669+
'legacy exact sizing normalizes to canvas tracking'
661670
);
662671

663672
const legacyCSSDPRCanvasContext = new TestCanvasContext(
664673
{useDevicePixels: true, pixelSizeSource: 'css-dpr'},
665674
false
666675
);
667676
t.equal(
668-
legacyCSSDPRCanvasContext.props.drawingBufferSizingMode,
669-
'track-css-pixels',
670-
'legacy CSS-DPR sizing normalizes to track-css-pixels mode'
677+
legacyCSSDPRCanvasContext.props.drawingBufferSizeTracking,
678+
'canvas',
679+
'legacy CSS-DPR sizing normalizes to canvas tracking'
671680
);
672681
t.equal(
673682
legacyCSSDPRCanvasContext.props.pixelRatio,
@@ -677,14 +686,14 @@ test('CanvasContext#new drawing buffer sizing props override and validate legacy
677686

678687
const legacyManualCanvasContext = new TestCanvasContext({autoResize: false}, false);
679688
t.equal(
680-
legacyManualCanvasContext.props.drawingBufferSizingMode,
681-
'manual',
682-
'legacy autoResize false normalizes to manual mode'
689+
legacyManualCanvasContext.props.drawingBufferSizeTracking,
690+
'none',
691+
'legacy autoResize false normalizes to no tracking'
683692
);
684693

685694
const newCanvasContext = new TestCanvasContext(
686695
{
687-
drawingBufferSizingMode: 'track-css-pixels',
696+
drawingBufferSizeTracking: 'canvas',
688697
autoResize: false,
689698
useDevicePixels: false,
690699
pixelSizeSource: 'exact'
@@ -695,13 +704,14 @@ test('CanvasContext#new drawing buffer sizing props override and validate legacy
695704
(newCanvasContext as any)._handleResize([
696705
{
697706
target: newCanvasContext.canvas,
698-
contentBoxSize: [{inlineSize: 100, blockSize: 50}]
707+
contentBoxSize: [{inlineSize: 100, blockSize: 50}],
708+
devicePixelContentBoxSize: [{inlineSize: 200, blockSize: 100}]
699709
}
700710
]);
701711
t.deepEqual(
702712
newCanvasContext.getDrawingBufferSize(),
703713
[200, 100],
704-
'new track-css-pixels mode ignores conflicting legacy sizing props'
714+
'new canvas tracking ignores conflicting legacy sizing props'
705715
);
706716
newCanvasContext.setProps({useDevicePixels: 1});
707717
t.equal(
@@ -711,23 +721,22 @@ test('CanvasContext#new drawing buffer sizing props override and validate legacy
711721
);
712722

713723
t.throws(
714-
() =>
715-
new TestCanvasContext({drawingBufferSizingMode: 'track-device-pixels', pixelRatio: 2}, false),
724+
() => new TestCanvasContext({drawingBufferSizeTracking: 'none', pixelRatio: 2}, false),
716725
/assertion failed/,
717-
'pixelRatio is rejected outside track-css-pixels mode'
726+
'pixelRatio is rejected outside canvas tracking'
718727
);
719728
t.throws(
720-
() =>
721-
new TestCanvasContext({drawingBufferSizingMode: 'track-css-pixels', pixelRatio: 0}, false),
729+
() => new TestCanvasContext({pixelRatio: 2}, false),
730+
/assertion failed/,
731+
'pixelRatio requires explicit canvas tracking'
732+
);
733+
t.throws(
734+
() => new TestCanvasContext({drawingBufferSizeTracking: 'canvas', pixelRatio: 0}, false),
722735
/assertion failed/,
723736
'non-positive pixelRatio is rejected'
724737
);
725738
t.throws(
726-
() =>
727-
new TestCanvasContext(
728-
{drawingBufferSizingMode: 'track-css-pixels', pixelRatio: Infinity},
729-
false
730-
),
739+
() => new TestCanvasContext({drawingBufferSizeTracking: 'canvas', pixelRatio: Infinity}, false),
731740
/assertion failed/,
732741
'non-finite pixelRatio is rejected'
733742
);

0 commit comments

Comments
 (0)