buninu-browser 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/README.md +9 -3
  2. package/lib/audio/pcm.js +11 -0
  3. package/lib/audio/pulse-client.js +7 -2
  4. package/lib/cdp-server.js +31 -7
  5. package/lib/happy-dom/document-base.js +29 -1
  6. package/lib/happy-dom/inert-link.js +26 -0
  7. package/lib/happy-dom/mutation-observer.js +157 -0
  8. package/lib/happy-dom/node-iterator-removal.js +36 -0
  9. package/lib/happy-dom/node-replacement.js +60 -0
  10. package/lib/happy-dom/parser.js +6 -1
  11. package/lib/headless-shell.js +49 -9
  12. package/lib/layout/repaint.js +89 -0
  13. package/lib/layout/text-layout.js +4 -2
  14. package/lib/media/mp4-keyframes.js +144 -0
  15. package/lib/media/video-keyframe.js +29 -1
  16. package/lib/media/webm-snapshots.js +282 -0
  17. package/lib/network/cookie-jar.js +18 -2
  18. package/lib/paint/display-list.js +4 -3
  19. package/lib/paint/frame.js +12 -5
  20. package/lib/render-tree/index.js +3 -0
  21. package/lib/renderer/animation-frames.js +76 -0
  22. package/lib/renderer/bindings.js +107 -3
  23. package/lib/renderer/compositor-channel.js +95 -10
  24. package/lib/renderer/compositor.js +54 -18
  25. package/lib/renderer/document-cookies.js +87 -0
  26. package/lib/renderer/font-loading.js +60 -16
  27. package/lib/renderer/host.js +48 -19
  28. package/lib/renderer/idle-callbacks.js +109 -0
  29. package/lib/renderer/main.js +5 -1
  30. package/lib/renderer/media-source.js +4 -0
  31. package/lib/renderer/page-realm.js +159 -49
  32. package/lib/renderer/page-renderer.js +577 -201
  33. package/lib/renderer/resource-elements.js +88 -0
  34. package/lib/renderer/script-rewrite.js +18 -1
  35. package/lib/renderer/seccomp.js +6 -4
  36. package/lib/renderer/timers.js +71 -0
  37. package/lib/renderer/video-channel.js +94 -0
  38. package/lib/renderer/video-mask.js +115 -0
  39. package/lib/renderer/video-presentation.js +304 -39
  40. package/lib/renderer/video-worker.js +46 -0
  41. package/lib/renderer/workers.js +7 -17
  42. package/lib/style/computed-style.js +257 -61
  43. package/package.json +1 -1
  44. package/test/animation-frames.test.js +67 -0
  45. package/test/canvas-style-snapshot.test.js +18 -0
  46. package/test/cdp-stream.test.js +96 -3
  47. package/test/compare-node-iterator.js +42 -0
  48. package/test/compositor-channel-ownership.test.js +194 -0
  49. package/test/compositor.test.js +27 -1
  50. package/test/computed-style-binding.test.js +49 -0
  51. package/test/computed-style-connectivity.test.js +13 -0
  52. package/test/computed-style.test.js +86 -0
  53. package/test/cookie-sync.test.js +82 -0
  54. package/test/document-base-cache.test.js +103 -0
  55. package/test/document-cookies.test.js +117 -0
  56. package/test/duplicate-style-keys.test.js +11 -0
  57. package/test/fixtures/animation-frames.js +56 -0
  58. package/test/fixtures/canvas-style-snapshot.js +81 -0
  59. package/test/fixtures/computed-style-connectivity.js +101 -0
  60. package/test/fixtures/computed-style-lazy.js +135 -0
  61. package/test/fixtures/duplicate-style-keys.js +97 -0
  62. package/test/fixtures/fake-renderer.js +12 -0
  63. package/test/fixtures/font-definition-invalidation.js +97 -0
  64. package/test/fixtures/font-metrics-invalidation.js +87 -0
  65. package/test/fixtures/fractional-text-scale.js +38 -0
  66. package/test/fixtures/full-style-identity.js +36 -0
  67. package/test/fixtures/hidden-style-engine.js +260 -0
  68. package/test/fixtures/hidden-style-reentry.js +232 -0
  69. package/test/fixtures/hidden-style-renderer.js +203 -0
  70. package/test/fixtures/layout-repaint.js +69 -0
  71. package/test/fixtures/media-presentation-continuation.js +141 -0
  72. package/test/fixtures/media-presentation-refill.js +113 -0
  73. package/test/fixtures/media-probe.js +148 -0
  74. package/test/fixtures/media-refill-cancel.js +79 -0
  75. package/test/fixtures/media-repaint.js +48 -0
  76. package/test/fixtures/media-resource-reselection.js +94 -0
  77. package/test/fixtures/media-source-attachment.js +136 -0
  78. package/test/fixtures/media-source-no-decoder.js +17 -0
  79. package/test/fixtures/media-track-refill.js +132 -0
  80. package/test/fixtures/media-webm-seek.js +51 -0
  81. package/test/fixtures/mutation-observer.js +201 -0
  82. package/test/fixtures/node-iterator-cases.js +59 -0
  83. package/test/fixtures/node-iterator-chromium.json +315 -0
  84. package/test/fixtures/observer-invalidation.js +105 -0
  85. package/test/fixtures/prepaint-scheduling.js +73 -0
  86. package/test/fixtures/resource-discovery.js +32 -0
  87. package/test/fixtures/spare-renderer-scheduling.js +124 -0
  88. package/test/fixtures/style-propagation.js +26 -0
  89. package/test/fixtures/svg-node-reuse.js +83 -0
  90. package/test/fixtures/video-black-replay.js +140 -0
  91. package/test/fixtures/video-controls-invalidation.js +71 -0
  92. package/test/fixtures/viewport-bounds.js +148 -0
  93. package/test/font-loading.test.js +77 -0
  94. package/test/font-metrics-invalidation.test.js +60 -0
  95. package/test/fractional-text-scale.test.js +6 -0
  96. package/test/full-style-identity.test.js +191 -0
  97. package/test/hidden-style-lazy.test.js +31 -0
  98. package/test/hidden-style-reentry.test.js +20 -0
  99. package/test/idle-callback.test.js +89 -0
  100. package/test/idle-scheduler.test.js +148 -0
  101. package/test/inert-link.test.js +37 -0
  102. package/test/inline-style-cascade.test.js +63 -0
  103. package/test/layout-repaint-renderer.test.js +9 -0
  104. package/test/layout-repaint.test.js +222 -0
  105. package/test/media-presentation-continuation.test.js +14 -0
  106. package/test/media-presentation-refill.test.js +8 -0
  107. package/test/media-probe.test.js +16 -0
  108. package/test/media-repaint.test.js +8 -0
  109. package/test/media-source-attachment.test.js +34 -0
  110. package/test/media-track-refill.test.js +26 -0
  111. package/test/media-webm-seek.test.js +11 -0
  112. package/test/mp4-keyframes.test.js +36 -0
  113. package/test/mutation-observer.test.js +91 -0
  114. package/test/node-iterator.test.js +81 -0
  115. package/test/observer-invalidation.test.js +12 -0
  116. package/test/page-scripts.test.js +151 -1
  117. package/test/page-timers.test.js +67 -0
  118. package/test/paint-frame.test.js +18 -1
  119. package/test/pcm.test.js +63 -0
  120. package/test/prepaint-scheduling.test.js +13 -0
  121. package/test/pulse-client.test.js +165 -0
  122. package/test/raw-capture.test.js +34 -5
  123. package/test/renderer-host.test.js +51 -0
  124. package/test/resource-discovery.test.js +12 -0
  125. package/test/resource-elements.test.js +234 -0
  126. package/test/root-self-style.test.js +104 -0
  127. package/test/script-rewrite.test.js +35 -0
  128. package/test/seccomp.test.js +45 -0
  129. package/test/spare-renderer-scheduling.test.js +8 -0
  130. package/test/style-propagation.test.js +225 -0
  131. package/test/style-sheets.test.js +19 -0
  132. package/test/svg.test.js +45 -0
  133. package/test/text-layout.test.js +15 -0
  134. package/test/timers.test.js +58 -0
  135. package/test/video-black-replay.test.js +34 -0
  136. package/test/video-capture.test.js +170 -0
  137. package/test/video-channel.test.js +253 -0
  138. package/test/video-continuation.test.js +57 -0
  139. package/test/video-controls-invalidation.test.js +12 -0
  140. package/test/video-keyframe.test.js +104 -4
  141. package/test/video-mask.test.js +94 -0
  142. package/test/video-presentation.test.js +239 -1
  143. package/test/video-timing-presentation.test.js +115 -0
  144. package/test/viewport-bounds.test.js +13 -0
  145. package/test/webm-snapshots.test.js +372 -0
  146. package/test/window-resize.test.js +29 -0
@@ -0,0 +1,59 @@
1
+ // Runs unchanged in the renderer and Chromium; keep this function standalone.
2
+ export function nodeIteratorCases() {
3
+ const results = [];
4
+ const label = n => n ? n.id || n.nodeName : null;
5
+ for (const tag of ['div', 'form', 'select'])
6
+ for (const operation of ['remove', 'replace', 'replaceWith', 'self', 'sibling', 'fragment', 'descendant', 'multiple'])
7
+ for (const position of [0, 1, 2, 3]) for (const before of [false, true]) {
8
+ const root = document.createElement(tag);root.id = 'root';
9
+ for (const id of ['a', 'b']) {
10
+ const node = document.createElement('option');node.id = id;root.append(node);
11
+ }
12
+ const nested = document.createElement('span');nested.id = 'nested';root.firstChild.append(nested);
13
+ const it = document.createNodeIterator(root, 1);
14
+ for (let i = 0; i <= position; i++) it.nextNode();
15
+ if (before) it.previousNode();
16
+ const old = root.firstChild, x = document.createElement('em');x.id = 'x';
17
+ if (operation === 'remove') old.remove();
18
+ if (operation === 'replace') root.replaceChild(x, old);
19
+ if (operation === 'replaceWith') old.replaceWith(x);
20
+ if (operation === 'self') root.replaceChild(old, old);
21
+ if (operation === 'sibling') root.replaceChild(root.lastChild, old);
22
+ if (operation === 'descendant') root.replaceChild(nested, old);
23
+ if (operation === 'fragment') {
24
+ const fragment = document.createDocumentFragment();fragment.append(x);
25
+ const y = document.createElement('em');y.id = 'y';fragment.append(y);
26
+ root.replaceChild(fragment, old);
27
+ }
28
+ if (operation === 'multiple') old.replaceWith(x, old, root.lastChild);
29
+ results.push([tag, operation, position, before, label(it.referenceNode),
30
+ it.pointerBeforeReferenceNode, label(it.nextNode()), label(it.previousNode()),
31
+ [...root.childNodes].map(label).join(',')]);
32
+ }
33
+ for (const whatToShow of [0, 1, -1, 4294967297, NaN, Infinity, '1', null, 1n, Object(1n), Symbol('x')]) {
34
+ try { results.push(['mask', String(whatToShow), document.createNodeIterator(document.body, whatToShow).whatToShow]); }
35
+ catch (e) { results.push(['mask', String(whatToShow), e.name]); }
36
+ }
37
+ for (const verdict of [1, 65537, -65535, NaN, Infinity, '1', null, 1n, Object(1n), Symbol('x')]) {
38
+ try { results.push(['filter', String(verdict), !!document.createNodeIterator(document.body, 1, () => verdict).nextNode()]); }
39
+ catch (e) { results.push(['filter', String(verdict), e.name]); }
40
+ }
41
+ // Filtering and root types: rejection must not prune descendants.
42
+ for (const rootKind of ['element', 'fragment', 'text', 'comment', 'doctype'])
43
+ for (const mask of [0, 1, 4, 128, 0xffffffff]) for (const mode of ['accept', 'reject', 'skip', 'alternating']) {
44
+ const root = rootKind === 'fragment' ? document.createDocumentFragment()
45
+ : rootKind === 'text' ? document.createTextNode('text')
46
+ : rootKind === 'comment' ? document.createComment('comment')
47
+ : rootKind === 'doctype' ? document.implementation.createDocumentType('html', '', '') : document.createElement('div');
48
+ if (rootKind === 'element' || rootKind === 'fragment') {
49
+ const child = document.createElement('b');child.append('text');root.append(child, document.createComment('comment'));
50
+ }
51
+ const it = document.createNodeIterator(root, mask, n => mode === 'accept' ? 1
52
+ : mode === 'reject' ? 2 : mode === 'skip' ? 3 : n.nodeType === 1 ? 2 : 1);
53
+ const forward = [], backward = [];
54
+ for (let n; (n = it.nextNode());) forward.push(n.nodeType);
55
+ for (let n; (n = it.previousNode());) backward.push(n.nodeType);
56
+ results.push(['walk', rootKind, mask, mode, forward.join(','), backward.join(',')]);
57
+ }
58
+ return results;
59
+ }
@@ -0,0 +1,315 @@
1
+ [
2
+ ["div","remove",0,false,"root",false,"b","b","b"],
3
+ ["div","remove",0,true,"root",true,"root","root","b"],
4
+ ["div","remove",1,false,"root",false,"b","b","b"],
5
+ ["div","remove",1,true,"b",true,"b","b","b"],
6
+ ["div","remove",2,false,"root",false,"b","b","b"],
7
+ ["div","remove",2,true,"b",true,"b","b","b"],
8
+ ["div","remove",3,false,"b",false,null,"b","b"],
9
+ ["div","remove",3,true,"b",true,"b","b","b"],
10
+ ["div","replace",0,false,"root",false,"x","x","x,b"],
11
+ ["div","replace",0,true,"root",true,"root","root","x,b"],
12
+ ["div","replace",1,false,"root",false,"x","x","x,b"],
13
+ ["div","replace",1,true,"b",true,"b","b","x,b"],
14
+ ["div","replace",2,false,"root",false,"x","x","x,b"],
15
+ ["div","replace",2,true,"b",true,"b","b","x,b"],
16
+ ["div","replace",3,false,"b",false,null,"b","x,b"],
17
+ ["div","replace",3,true,"b",true,"b","b","x,b"],
18
+ ["div","replaceWith",0,false,"root",false,"x","x","x,b"],
19
+ ["div","replaceWith",0,true,"root",true,"root","root","x,b"],
20
+ ["div","replaceWith",1,false,"root",false,"x","x","x,b"],
21
+ ["div","replaceWith",1,true,"b",true,"b","b","x,b"],
22
+ ["div","replaceWith",2,false,"root",false,"x","x","x,b"],
23
+ ["div","replaceWith",2,true,"b",true,"b","b","x,b"],
24
+ ["div","replaceWith",3,false,"b",false,null,"b","x,b"],
25
+ ["div","replaceWith",3,true,"b",true,"b","b","x,b"],
26
+ ["div","self",0,false,"root",false,"a","a","a,b"],
27
+ ["div","self",0,true,"root",true,"root","root","a,b"],
28
+ ["div","self",1,false,"root",false,"a","a","a,b"],
29
+ ["div","self",1,true,"b",true,"b","b","a,b"],
30
+ ["div","self",2,false,"root",false,"a","a","a,b"],
31
+ ["div","self",2,true,"b",true,"b","b","a,b"],
32
+ ["div","self",3,false,"b",false,null,"b","a,b"],
33
+ ["div","self",3,true,"b",true,"b","b","a,b"],
34
+ ["div","sibling",0,false,"root",false,"b","b","b"],
35
+ ["div","sibling",0,true,"root",true,"root","root","b"],
36
+ ["div","sibling",1,false,"root",false,"b","b","b"],
37
+ ["div","sibling",1,true,"a",true,"a","a","b"],
38
+ ["div","sibling",2,false,"root",false,"b","b","b"],
39
+ ["div","sibling",2,true,"nested",true,"nested","nested","b"],
40
+ ["div","sibling",3,false,"root",false,"b","b","b"],
41
+ ["div","sibling",3,true,"root",false,"b","b","b"],
42
+ ["div","fragment",0,false,"root",false,"x","x","x,y,b"],
43
+ ["div","fragment",0,true,"root",true,"root","root","x,y,b"],
44
+ ["div","fragment",1,false,"root",false,"x","x","x,y,b"],
45
+ ["div","fragment",1,true,"b",true,"b","b","x,y,b"],
46
+ ["div","fragment",2,false,"root",false,"x","x","x,y,b"],
47
+ ["div","fragment",2,true,"b",true,"b","b","x,y,b"],
48
+ ["div","fragment",3,false,"b",false,null,"b","x,y,b"],
49
+ ["div","fragment",3,true,"b",true,"b","b","x,y,b"],
50
+ ["div","descendant",0,false,"root",false,"nested","nested","nested,b"],
51
+ ["div","descendant",0,true,"root",true,"root","root","nested,b"],
52
+ ["div","descendant",1,false,"root",false,"nested","nested","nested,b"],
53
+ ["div","descendant",1,true,"b",true,"b","b","nested,b"],
54
+ ["div","descendant",2,false,"root",false,"nested","nested","nested,b"],
55
+ ["div","descendant",2,true,"b",true,"b","b","nested,b"],
56
+ ["div","descendant",3,false,"b",false,null,"b","nested,b"],
57
+ ["div","descendant",3,true,"b",true,"b","b","nested,b"],
58
+ ["div","multiple",0,false,"root",false,"x","x","x,a,b"],
59
+ ["div","multiple",0,true,"root",true,"root","root","x,a,b"],
60
+ ["div","multiple",1,false,"root",false,"x","x","x,a,b"],
61
+ ["div","multiple",1,true,"root",false,"x","x","x,a,b"],
62
+ ["div","multiple",2,false,"root",false,"x","x","x,a,b"],
63
+ ["div","multiple",2,true,"root",false,"x","x","x,a,b"],
64
+ ["div","multiple",3,false,"root",false,"x","x","x,a,b"],
65
+ ["div","multiple",3,true,"root",false,"x","x","x,a,b"],
66
+ ["form","remove",0,false,"root",false,"b","b","b"],
67
+ ["form","remove",0,true,"root",true,"root","root","b"],
68
+ ["form","remove",1,false,"root",false,"b","b","b"],
69
+ ["form","remove",1,true,"b",true,"b","b","b"],
70
+ ["form","remove",2,false,"root",false,"b","b","b"],
71
+ ["form","remove",2,true,"b",true,"b","b","b"],
72
+ ["form","remove",3,false,"b",false,null,"b","b"],
73
+ ["form","remove",3,true,"b",true,"b","b","b"],
74
+ ["form","replace",0,false,"root",false,"x","x","x,b"],
75
+ ["form","replace",0,true,"root",true,"root","root","x,b"],
76
+ ["form","replace",1,false,"root",false,"x","x","x,b"],
77
+ ["form","replace",1,true,"b",true,"b","b","x,b"],
78
+ ["form","replace",2,false,"root",false,"x","x","x,b"],
79
+ ["form","replace",2,true,"b",true,"b","b","x,b"],
80
+ ["form","replace",3,false,"b",false,null,"b","x,b"],
81
+ ["form","replace",3,true,"b",true,"b","b","x,b"],
82
+ ["form","replaceWith",0,false,"root",false,"x","x","x,b"],
83
+ ["form","replaceWith",0,true,"root",true,"root","root","x,b"],
84
+ ["form","replaceWith",1,false,"root",false,"x","x","x,b"],
85
+ ["form","replaceWith",1,true,"b",true,"b","b","x,b"],
86
+ ["form","replaceWith",2,false,"root",false,"x","x","x,b"],
87
+ ["form","replaceWith",2,true,"b",true,"b","b","x,b"],
88
+ ["form","replaceWith",3,false,"b",false,null,"b","x,b"],
89
+ ["form","replaceWith",3,true,"b",true,"b","b","x,b"],
90
+ ["form","self",0,false,"root",false,"a","a","a,b"],
91
+ ["form","self",0,true,"root",true,"root","root","a,b"],
92
+ ["form","self",1,false,"root",false,"a","a","a,b"],
93
+ ["form","self",1,true,"b",true,"b","b","a,b"],
94
+ ["form","self",2,false,"root",false,"a","a","a,b"],
95
+ ["form","self",2,true,"b",true,"b","b","a,b"],
96
+ ["form","self",3,false,"b",false,null,"b","a,b"],
97
+ ["form","self",3,true,"b",true,"b","b","a,b"],
98
+ ["form","sibling",0,false,"root",false,"b","b","b"],
99
+ ["form","sibling",0,true,"root",true,"root","root","b"],
100
+ ["form","sibling",1,false,"root",false,"b","b","b"],
101
+ ["form","sibling",1,true,"a",true,"a","a","b"],
102
+ ["form","sibling",2,false,"root",false,"b","b","b"],
103
+ ["form","sibling",2,true,"nested",true,"nested","nested","b"],
104
+ ["form","sibling",3,false,"root",false,"b","b","b"],
105
+ ["form","sibling",3,true,"root",false,"b","b","b"],
106
+ ["form","fragment",0,false,"root",false,"x","x","x,y,b"],
107
+ ["form","fragment",0,true,"root",true,"root","root","x,y,b"],
108
+ ["form","fragment",1,false,"root",false,"x","x","x,y,b"],
109
+ ["form","fragment",1,true,"b",true,"b","b","x,y,b"],
110
+ ["form","fragment",2,false,"root",false,"x","x","x,y,b"],
111
+ ["form","fragment",2,true,"b",true,"b","b","x,y,b"],
112
+ ["form","fragment",3,false,"b",false,null,"b","x,y,b"],
113
+ ["form","fragment",3,true,"b",true,"b","b","x,y,b"],
114
+ ["form","descendant",0,false,"root",false,"nested","nested","nested,b"],
115
+ ["form","descendant",0,true,"root",true,"root","root","nested,b"],
116
+ ["form","descendant",1,false,"root",false,"nested","nested","nested,b"],
117
+ ["form","descendant",1,true,"b",true,"b","b","nested,b"],
118
+ ["form","descendant",2,false,"root",false,"nested","nested","nested,b"],
119
+ ["form","descendant",2,true,"b",true,"b","b","nested,b"],
120
+ ["form","descendant",3,false,"b",false,null,"b","nested,b"],
121
+ ["form","descendant",3,true,"b",true,"b","b","nested,b"],
122
+ ["form","multiple",0,false,"root",false,"x","x","x,a,b"],
123
+ ["form","multiple",0,true,"root",true,"root","root","x,a,b"],
124
+ ["form","multiple",1,false,"root",false,"x","x","x,a,b"],
125
+ ["form","multiple",1,true,"root",false,"x","x","x,a,b"],
126
+ ["form","multiple",2,false,"root",false,"x","x","x,a,b"],
127
+ ["form","multiple",2,true,"root",false,"x","x","x,a,b"],
128
+ ["form","multiple",3,false,"root",false,"x","x","x,a,b"],
129
+ ["form","multiple",3,true,"root",false,"x","x","x,a,b"],
130
+ ["select","remove",0,false,"root",false,"b","b","b"],
131
+ ["select","remove",0,true,"root",true,"root","root","b"],
132
+ ["select","remove",1,false,"root",false,"b","b","b"],
133
+ ["select","remove",1,true,"b",true,"b","b","b"],
134
+ ["select","remove",2,false,"root",false,"b","b","b"],
135
+ ["select","remove",2,true,"b",true,"b","b","b"],
136
+ ["select","remove",3,false,"b",false,null,"b","b"],
137
+ ["select","remove",3,true,"b",true,"b","b","b"],
138
+ ["select","replace",0,false,"root",false,"x","x","x,b"],
139
+ ["select","replace",0,true,"root",true,"root","root","x,b"],
140
+ ["select","replace",1,false,"root",false,"x","x","x,b"],
141
+ ["select","replace",1,true,"b",true,"b","b","x,b"],
142
+ ["select","replace",2,false,"root",false,"x","x","x,b"],
143
+ ["select","replace",2,true,"b",true,"b","b","x,b"],
144
+ ["select","replace",3,false,"b",false,null,"b","x,b"],
145
+ ["select","replace",3,true,"b",true,"b","b","x,b"],
146
+ ["select","replaceWith",0,false,"root",false,"x","x","x,b"],
147
+ ["select","replaceWith",0,true,"root",true,"root","root","x,b"],
148
+ ["select","replaceWith",1,false,"root",false,"x","x","x,b"],
149
+ ["select","replaceWith",1,true,"b",true,"b","b","x,b"],
150
+ ["select","replaceWith",2,false,"root",false,"x","x","x,b"],
151
+ ["select","replaceWith",2,true,"b",true,"b","b","x,b"],
152
+ ["select","replaceWith",3,false,"b",false,null,"b","x,b"],
153
+ ["select","replaceWith",3,true,"b",true,"b","b","x,b"],
154
+ ["select","self",0,false,"root",false,"a","a","a,b"],
155
+ ["select","self",0,true,"root",true,"root","root","a,b"],
156
+ ["select","self",1,false,"root",false,"a","a","a,b"],
157
+ ["select","self",1,true,"b",true,"b","b","a,b"],
158
+ ["select","self",2,false,"root",false,"a","a","a,b"],
159
+ ["select","self",2,true,"b",true,"b","b","a,b"],
160
+ ["select","self",3,false,"b",false,null,"b","a,b"],
161
+ ["select","self",3,true,"b",true,"b","b","a,b"],
162
+ ["select","sibling",0,false,"root",false,"b","b","b"],
163
+ ["select","sibling",0,true,"root",true,"root","root","b"],
164
+ ["select","sibling",1,false,"root",false,"b","b","b"],
165
+ ["select","sibling",1,true,"a",true,"a","a","b"],
166
+ ["select","sibling",2,false,"root",false,"b","b","b"],
167
+ ["select","sibling",2,true,"nested",true,"nested","nested","b"],
168
+ ["select","sibling",3,false,"root",false,"b","b","b"],
169
+ ["select","sibling",3,true,"root",false,"b","b","b"],
170
+ ["select","fragment",0,false,"root",false,"x","x","x,y,b"],
171
+ ["select","fragment",0,true,"root",true,"root","root","x,y,b"],
172
+ ["select","fragment",1,false,"root",false,"x","x","x,y,b"],
173
+ ["select","fragment",1,true,"b",true,"b","b","x,y,b"],
174
+ ["select","fragment",2,false,"root",false,"x","x","x,y,b"],
175
+ ["select","fragment",2,true,"b",true,"b","b","x,y,b"],
176
+ ["select","fragment",3,false,"b",false,null,"b","x,y,b"],
177
+ ["select","fragment",3,true,"b",true,"b","b","x,y,b"],
178
+ ["select","descendant",0,false,"root",false,"nested","nested","nested,b"],
179
+ ["select","descendant",0,true,"root",true,"root","root","nested,b"],
180
+ ["select","descendant",1,false,"root",false,"nested","nested","nested,b"],
181
+ ["select","descendant",1,true,"b",true,"b","b","nested,b"],
182
+ ["select","descendant",2,false,"root",false,"nested","nested","nested,b"],
183
+ ["select","descendant",2,true,"b",true,"b","b","nested,b"],
184
+ ["select","descendant",3,false,"b",false,null,"b","nested,b"],
185
+ ["select","descendant",3,true,"b",true,"b","b","nested,b"],
186
+ ["select","multiple",0,false,"root",false,"x","x","x,a,b"],
187
+ ["select","multiple",0,true,"root",true,"root","root","x,a,b"],
188
+ ["select","multiple",1,false,"root",false,"x","x","x,a,b"],
189
+ ["select","multiple",1,true,"root",false,"x","x","x,a,b"],
190
+ ["select","multiple",2,false,"root",false,"x","x","x,a,b"],
191
+ ["select","multiple",2,true,"root",false,"x","x","x,a,b"],
192
+ ["select","multiple",3,false,"root",false,"x","x","x,a,b"],
193
+ ["select","multiple",3,true,"root",false,"x","x","x,a,b"],
194
+ ["mask","0",0],
195
+ ["mask","1",1],
196
+ ["mask","-1",4294967295],
197
+ ["mask","4294967297",1],
198
+ ["mask","NaN",0],
199
+ ["mask","Infinity",0],
200
+ ["mask","1",1],
201
+ ["mask","null",0],
202
+ ["mask","1","TypeError"],
203
+ ["mask","1","TypeError"],
204
+ ["mask","Symbol(x)","TypeError"],
205
+ ["filter","1",true],
206
+ ["filter","65537",true],
207
+ ["filter","-65535",true],
208
+ ["filter","NaN",false],
209
+ ["filter","Infinity",false],
210
+ ["filter","1",true],
211
+ ["filter","null",false],
212
+ ["filter","1","TypeError"],
213
+ ["filter","1","TypeError"],
214
+ ["filter","Symbol(x)","TypeError"],
215
+ ["walk","element",0,"accept","",""],
216
+ ["walk","element",0,"reject","",""],
217
+ ["walk","element",0,"skip","",""],
218
+ ["walk","element",0,"alternating","",""],
219
+ ["walk","element",1,"accept","1,1","1,1"],
220
+ ["walk","element",1,"reject","",""],
221
+ ["walk","element",1,"skip","",""],
222
+ ["walk","element",1,"alternating","",""],
223
+ ["walk","element",4,"accept","3","3"],
224
+ ["walk","element",4,"reject","",""],
225
+ ["walk","element",4,"skip","",""],
226
+ ["walk","element",4,"alternating","3","3"],
227
+ ["walk","element",128,"accept","8","8"],
228
+ ["walk","element",128,"reject","",""],
229
+ ["walk","element",128,"skip","",""],
230
+ ["walk","element",128,"alternating","8","8"],
231
+ ["walk","element",4294967295,"accept","1,1,3,8","8,3,1,1"],
232
+ ["walk","element",4294967295,"reject","",""],
233
+ ["walk","element",4294967295,"skip","",""],
234
+ ["walk","element",4294967295,"alternating","3,8","8,3"],
235
+ ["walk","fragment",0,"accept","",""],
236
+ ["walk","fragment",0,"reject","",""],
237
+ ["walk","fragment",0,"skip","",""],
238
+ ["walk","fragment",0,"alternating","",""],
239
+ ["walk","fragment",1,"accept","1","1"],
240
+ ["walk","fragment",1,"reject","",""],
241
+ ["walk","fragment",1,"skip","",""],
242
+ ["walk","fragment",1,"alternating","",""],
243
+ ["walk","fragment",4,"accept","3","3"],
244
+ ["walk","fragment",4,"reject","",""],
245
+ ["walk","fragment",4,"skip","",""],
246
+ ["walk","fragment",4,"alternating","3","3"],
247
+ ["walk","fragment",128,"accept","8","8"],
248
+ ["walk","fragment",128,"reject","",""],
249
+ ["walk","fragment",128,"skip","",""],
250
+ ["walk","fragment",128,"alternating","8","8"],
251
+ ["walk","fragment",4294967295,"accept","11,1,3,8","8,3,1,11"],
252
+ ["walk","fragment",4294967295,"reject","",""],
253
+ ["walk","fragment",4294967295,"skip","",""],
254
+ ["walk","fragment",4294967295,"alternating","11,3,8","8,3,11"],
255
+ ["walk","text",0,"accept","",""],
256
+ ["walk","text",0,"reject","",""],
257
+ ["walk","text",0,"skip","",""],
258
+ ["walk","text",0,"alternating","",""],
259
+ ["walk","text",1,"accept","",""],
260
+ ["walk","text",1,"reject","",""],
261
+ ["walk","text",1,"skip","",""],
262
+ ["walk","text",1,"alternating","",""],
263
+ ["walk","text",4,"accept","3","3"],
264
+ ["walk","text",4,"reject","",""],
265
+ ["walk","text",4,"skip","",""],
266
+ ["walk","text",4,"alternating","3","3"],
267
+ ["walk","text",128,"accept","",""],
268
+ ["walk","text",128,"reject","",""],
269
+ ["walk","text",128,"skip","",""],
270
+ ["walk","text",128,"alternating","",""],
271
+ ["walk","text",4294967295,"accept","3","3"],
272
+ ["walk","text",4294967295,"reject","",""],
273
+ ["walk","text",4294967295,"skip","",""],
274
+ ["walk","text",4294967295,"alternating","3","3"],
275
+ ["walk","comment",0,"accept","",""],
276
+ ["walk","comment",0,"reject","",""],
277
+ ["walk","comment",0,"skip","",""],
278
+ ["walk","comment",0,"alternating","",""],
279
+ ["walk","comment",1,"accept","",""],
280
+ ["walk","comment",1,"reject","",""],
281
+ ["walk","comment",1,"skip","",""],
282
+ ["walk","comment",1,"alternating","",""],
283
+ ["walk","comment",4,"accept","",""],
284
+ ["walk","comment",4,"reject","",""],
285
+ ["walk","comment",4,"skip","",""],
286
+ ["walk","comment",4,"alternating","",""],
287
+ ["walk","comment",128,"accept","8","8"],
288
+ ["walk","comment",128,"reject","",""],
289
+ ["walk","comment",128,"skip","",""],
290
+ ["walk","comment",128,"alternating","8","8"],
291
+ ["walk","comment",4294967295,"accept","8","8"],
292
+ ["walk","comment",4294967295,"reject","",""],
293
+ ["walk","comment",4294967295,"skip","",""],
294
+ ["walk","comment",4294967295,"alternating","8","8"],
295
+ ["walk","doctype",0,"accept","",""],
296
+ ["walk","doctype",0,"reject","",""],
297
+ ["walk","doctype",0,"skip","",""],
298
+ ["walk","doctype",0,"alternating","",""],
299
+ ["walk","doctype",1,"accept","",""],
300
+ ["walk","doctype",1,"reject","",""],
301
+ ["walk","doctype",1,"skip","",""],
302
+ ["walk","doctype",1,"alternating","",""],
303
+ ["walk","doctype",4,"accept","",""],
304
+ ["walk","doctype",4,"reject","",""],
305
+ ["walk","doctype",4,"skip","",""],
306
+ ["walk","doctype",4,"alternating","",""],
307
+ ["walk","doctype",128,"accept","",""],
308
+ ["walk","doctype",128,"reject","",""],
309
+ ["walk","doctype",128,"skip","",""],
310
+ ["walk","doctype",128,"alternating","",""],
311
+ ["walk","doctype",4294967295,"accept","10","10"],
312
+ ["walk","doctype",4294967295,"reject","",""],
313
+ ["walk","doctype",4294967295,"skip","",""],
314
+ ["walk","doctype",4294967295,"alternating","10","10"]
315
+ ]
@@ -0,0 +1,105 @@
1
+ // Host scripting and the style-read counter stay outside the test runner.
2
+ import { strict as assert } from "node:assert";
3
+ import { createPageRenderer } from "../../lib/renderer/page-renderer.js";
4
+ import { nodeOf } from "../../lib/renderer/bindings.js";
5
+ import { StyleEngine } from "../../lib/style/computed-style.js";
6
+
7
+ const font = await Bun.file(new URL("../../usr/share/fonts/DejaVuSansMono.ttf", import.meta.url)).bytes();
8
+ const sleep = Bun.sleep;
9
+ let finishFont;
10
+ const fontResponse = new Promise(resolve => { finishFont = resolve; });
11
+ const renderer = await createPageRenderer({
12
+ fetchResource: async url => ({ ok: false, status: 404, url, body: new Uint8Array() }),
13
+ pageFetch: () => fontResponse,
14
+ });
15
+ const evaluate = code => renderer.evaluate(code);
16
+ const eventually = async (code, check, message) => {
17
+ for (let attempt = 0; attempt < 100; attempt++) {
18
+ const value = await evaluate(code);
19
+ if (check(value)) return value;
20
+ await sleep(10);
21
+ }
22
+ assert.fail(message);
23
+ };
24
+ const originalGet = StyleEngine.prototype.get;
25
+ try {
26
+ renderer.resize(400, 300, 1);
27
+ renderer.setScripting("host");
28
+ await renderer.loadDocument({ url: "https://observer.test/", contentType: "text/html", source: `<!doctype html>
29
+ <style id=sheet>body{margin:0}#box{width:100px;height:50px}
30
+ body[data-wide] #box{width:175px}
31
+ #viewport{width:50%;height:10px}
32
+ #scroller{position:absolute;top:80px;width:100px;height:100px;overflow:auto}
33
+ #inside{position:absolute;top:150px;width:20px;height:20px}
34
+ #below{position:absolute;top:500px;width:20px;height:20px}</style>
35
+ <div id=box></div><div id=viewport></div>
36
+ <div id=scroller><div style="position:relative;height:300px"><div id=inside></div></div></div>
37
+ <div id=below></div><div style="height:1000px"></div>` });
38
+ await renderer.whenLoaded();
39
+ await evaluate(`window.box=document.getElementById('box');window.sizes=[];window.intersections=[];
40
+ window.ro=new ResizeObserver(entries=>sizes.push(entries[0].contentRect.width));ro.observe(box);
41
+ window.io=new IntersectionObserver(entries=>intersections.push(entries[0].isIntersecting));io.observe(box);`);
42
+ await eventually("sizes.length===1&&intersections.length===1", Boolean, "initial observers should be delivered");
43
+ // Consume initial focus bookkeeping, then count actual style measurements
44
+ // of the observed box. Reading body geometry itself cannot touch this box.
45
+ await evaluate("document.body.setAttribute('data-warm','1');document.body.clientWidth");
46
+ await sleep(40);
47
+ const target = nodeOf(globalThis.document.getElementById("box"));
48
+ let measurements = 0;
49
+ StyleEngine.prototype.get = function (element) {
50
+ if (element === target) measurements++;
51
+ return originalGet.call(this, element);
52
+ };
53
+ for (let i = 0; i < 5; i++) {
54
+ await evaluate(`document.body.setAttribute('data-counter','${i}');document.body.clientWidth`);
55
+ await sleep(30);
56
+ }
57
+ assert.equal(measurements, 0, "metadata flushes must not remeasure unchanged observer targets");
58
+ assert.deepEqual(await evaluate("[sizes,intersections]"), [[100], [true]]);
59
+ StyleEngine.prototype.get = originalGet;
60
+
61
+ // observe() itself still queues a first measurement without a new version.
62
+ await evaluate("window.newSizes=[];window.newRO=new ResizeObserver(e=>newSizes.push(e[0].contentRect.width));newRO.observe(box)");
63
+ await eventually("newSizes[0]", value => value === 100, "new observers must not wait for invalidation");
64
+ await evaluate("box.style.width='150px';document.body.clientWidth");
65
+ await eventually("sizes.at(-1)", value => value === 150, "inline size changes must notify");
66
+ await evaluate("box.style.width='';document.body.setAttribute('data-wide','');document.body.clientWidth");
67
+ await eventually("sizes.at(-1)", value => value === 175, "selector-observed metadata must still invalidate");
68
+ await evaluate("document.getElementById('sheet').textContent+='body[data-wide] #box{width:190px}';document.body.clientWidth");
69
+ await eventually("sizes.at(-1)", value => value === 190, "stylesheet changes must notify");
70
+
71
+ await evaluate(`window.insideChanges=[];window.belowChanges=[];window.viewportSizes=[];window.deviceSizes=[];
72
+ window.insideIO=new IntersectionObserver(e=>insideChanges.push(e[0].isIntersecting));insideIO.observe(document.getElementById('inside'));
73
+ window.belowIO=new IntersectionObserver(e=>belowChanges.push(e[0].isIntersecting));belowIO.observe(document.getElementById('below'));
74
+ window.viewportRO=new ResizeObserver(e=>viewportSizes.push(e[0].contentRect.width));viewportRO.observe(document.getElementById('viewport'));
75
+ window.deviceRO=new ResizeObserver(e=>deviceSizes.push(e[0].devicePixelContentBoxSize[0].inlineSize));deviceRO.observe(box,{box:'device-pixel-content-box'});`);
76
+ await eventually("insideChanges.length===1&&belowChanges.length===1&&viewportSizes.length===1&&deviceSizes.length===1", Boolean,
77
+ "scroll/viewport observers should be initialized");
78
+ assert.deepEqual(await evaluate("[insideChanges,belowChanges,viewportSizes,deviceSizes]"), [[false], [false], [200], [190]]);
79
+ await evaluate("document.getElementById('scroller').scrollTop=100");
80
+ await eventually("insideChanges.at(-1)", Boolean, "element scrolling must notify");
81
+ renderer.scrollTo(0, 450);
82
+ await eventually("belowChanges.at(-1)", Boolean, "viewport scrolling must notify");
83
+ renderer.scrollTo(0, 0);
84
+ renderer.resize(400, 300, 2);
85
+ await eventually("deviceSizes.at(-1)", value => value === 380, "DPR-only changes must notify without layout invalidation");
86
+ renderer.resize(600, 300, 2);
87
+ await eventually("viewportSizes.at(-1)", value => value === 300, "viewport resizing must notify");
88
+ await evaluate("box.remove();document.body.clientWidth");
89
+ await eventually("sizes.at(-1)===0&&intersections.at(-1)===false", Boolean, "removing a target must notify");
90
+ await evaluate("document.body.prepend(box);document.body.clientWidth");
91
+ await eventually("sizes.at(-1)===190&&intersections.at(-1)===true", Boolean, "reattaching a target must notify");
92
+
93
+ // Font completion is a resource invalidation even with no DOM mutation.
94
+ await evaluate(`document.body.insertAdjacentHTML('beforeend',
95
+ '<span id=text style="display:inline-block;font-family:ObserverMono,sans-serif;font-size:30px">iiii</span>')`);
96
+ await evaluate("window.fontSizes=[];window.fontRO=new ResizeObserver(e=>fontSizes.push(e[0].contentRect.width));fontRO.observe(document.getElementById('text'))");
97
+ const before = await eventually("fontSizes[0]", value => value > 0, "fallback font should be measured");
98
+ await evaluate(`document.getElementById('sheet').textContent+='@font-face{font-family:ObserverMono;src:url(/font.ttf)}';document.body.clientWidth`);
99
+ finishFont({ status: 200, body: font });
100
+ await eventually("fontSizes.at(-1)", value => value > before * 2, "loaded font metrics must notify");
101
+ process.stdout.write("observer invalidation integration passed\n");
102
+ } finally {
103
+ StyleEngine.prototype.get = originalGet;
104
+ renderer.dispose();
105
+ }
@@ -0,0 +1,73 @@
1
+ // Host scripting installs browser globals, so each scenario runs separately.
2
+ import {strict as assert} from 'node:assert';
3
+ import {createPageRenderer} from '../../lib/renderer/page-renderer.js';
4
+
5
+ const scenario = process.argv[2];
6
+ const frames = [], errors = [], videos = [];
7
+ let finishScript;
8
+ const pendingScript = new Promise(resolve => { finishScript = resolve; });
9
+ const renderer = await createPageRenderer({
10
+ renderMinInterval: 180,
11
+ onFrame: ({tile}) => frames.push(tile),
12
+ onVideo: message => videos.push(message),
13
+ reportError: error => errors.push(String(error)),
14
+ fetchResource: async url => {
15
+ if (url.endsWith('slow.js')) await pendingScript;
16
+ return {ok:true, url, body:new Uint8Array([32]), contentType:url.endsWith('.js') ? 'text/javascript' : 'video/mp4'};
17
+ },
18
+ videoDecoder: {
19
+ probe: async () => ({duration:10, video:{width:20, height:12}}),
20
+ decode: async () => ({width:20, height:12, fps:30,
21
+ nextFrame(w,h) { return new Uint8Array(w*h*4).fill(255); }, seek() {}, close() {}}),
22
+ },
23
+ });
24
+
25
+ try {
26
+ renderer.resize(64,36,1);
27
+ renderer.setScripting('host');
28
+ frames.length = 0;
29
+ const loading = scenario.startsWith('loading');
30
+ const media = scenario === 'video';
31
+ await renderer.loadDocument({url:'https://prepaint.test/', contentType:'text/html',
32
+ source:'<style>body{margin:0}#marker{height:500px;background:red}video{display:block;width:20px;height:12px}</style>'
33
+ + (media ? '<video id=v src="clip.mp4"></video>' : '') + '<div id=marker></div>'
34
+ + (loading ? '<script src="slow.js"></script>' : '')});
35
+
36
+ if (loading) {
37
+ await Bun.sleep(400);
38
+ assert(frames.length > 0);
39
+ assert(frames.every(tile => tile.height === 36), 'pending scripts must not expand an already visible tile');
40
+ if (scenario === 'loading-scroll') {
41
+ renderer.applyCompositorScroll(0,140);
42
+ await Bun.sleep(90);
43
+ assert(frames.some(tile => tile.y <= 140 && tile.y+tile.height >= 176),
44
+ 'scrolling beyond the old tile must still paint while loading');
45
+ }
46
+ finishScript();
47
+ await renderer.whenLoaded();
48
+ // The script has no visual effect: refresh need not call render. The
49
+ // deferred prepaint still needs to wake and expand the unchanged tile.
50
+ await Bun.sleep(550);
51
+ assert(frames.some(tile => tile.height > 36), 'prepaint resumes after a no-op load settles');
52
+ } else {
53
+ await renderer.whenLoaded();
54
+ if (media) {
55
+ await renderer.evaluate('document.getElementById("v").play()');
56
+ await Bun.sleep(400);
57
+ assert(videos.some(message => message.type === 'video-open'));
58
+ assert(frames.some(tile => tile.height > 36 && tile.videoLayer),
59
+ 'independent video playback alone must not block idle prepaint');
60
+ } else {
61
+ await renderer.evaluate('setTimeout(()=>document.documentElement.setAttribute("data-bookkeeping","changed"),1)');
62
+ await Bun.sleep(80);
63
+ assert(frames.every(tile => tile.height === 36), 'pending frame takes priority over speculative raster');
64
+ await Bun.sleep(550);
65
+ assert(frames.some(tile => tile.height > 36), 'prepaint resumes after a queued no-op frame');
66
+ }
67
+ }
68
+ assert.deepEqual(errors, []);
69
+ process.stdout.write(`prepaint ${scenario} passed\n`);
70
+ } finally {
71
+ finishScript();
72
+ renderer.dispose();
73
+ }
@@ -0,0 +1,32 @@
1
+ // Host scripting is isolated from the test runner's globals.
2
+ import { strict as assert } from "node:assert";
3
+ import { createPageRenderer } from "../../lib/renderer/page-renderer.js";
4
+ import { nodeOf } from "../../lib/renderer/bindings.js";
5
+ const renderer = await createPageRenderer({
6
+ fetchResource: async url => ({ ok: false, status: 404, url, body: new Uint8Array() }),
7
+ });
8
+ try {
9
+ renderer.resize(400, 300, 1);
10
+ renderer.setScripting("host");
11
+ await renderer.loadDocument({ url: "https://resource.test/", contentType: "text/html",
12
+ source: '<!doctype html><style>body{color:red}</style><body>' + '<div>Item</div>'.repeat(100) + '</body>' });
13
+ await renderer.whenLoaded();
14
+ await renderer.evaluate("document.body.setAttribute('data-warm','1');document.body.clientWidth");
15
+ const document = nodeOf(globalThis.document), query = document.querySelectorAll.bind(document);
16
+ const watched = new Set(["video, audio", "img", "style, link[rel~='stylesheet']", "iframe"]);
17
+ const queries = [];
18
+ document.querySelectorAll = selector => {
19
+ if (watched.has(selector)) queries.push(selector);
20
+ return query(selector);
21
+ };
22
+ await renderer.evaluate(`for(let i=0;i<30;i++) {
23
+ document.body.setAttribute('data-counter',String(i));
24
+ document.body.setAttribute('aria-label',String(i));
25
+ document.body.clientWidth;
26
+ }`);
27
+ assert.deepEqual(queries, [], "metadata-only geometry flushes retain resource discovery");
28
+ await renderer.evaluate("document.body.insertAdjacentHTML('beforeend','<video></video>');document.body.clientWidth");
29
+ assert.ok(queries.includes("video, audio"), "structural insertion refreshes media discovery synchronously");
30
+ assert.equal(await renderer.evaluate("document.querySelector('video').offsetWidth"), 300);
31
+ process.stdout.write("resource discovery integration passed\n");
32
+ } finally { renderer.dispose(); }