@gjsify/dom-elements 0.3.21 → 0.4.3

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 (82) hide show
  1. package/lib/esm/_virtual/_rolldown/runtime.js +1 -1
  2. package/lib/esm/attr.js +1 -1
  3. package/lib/esm/character-data.js +1 -1
  4. package/lib/esm/comment.js +1 -1
  5. package/lib/esm/document-fragment.js +1 -1
  6. package/lib/esm/document.js +1 -1
  7. package/lib/esm/dom-matrix.js +1 -1
  8. package/lib/esm/dom-token-list.js +1 -1
  9. package/lib/esm/element.js +1 -1
  10. package/lib/esm/font-face.js +1 -1
  11. package/lib/esm/gst-time.js +1 -1
  12. package/lib/esm/html-canvas-element.js +1 -1
  13. package/lib/esm/html-element.js +1 -1
  14. package/lib/esm/html-image-element.js +1 -1
  15. package/lib/esm/html-media-element.js +1 -1
  16. package/lib/esm/html-video-element.js +1 -1
  17. package/lib/esm/image.js +1 -1
  18. package/lib/esm/intersection-observer.js +1 -1
  19. package/lib/esm/location-stub.js +1 -1
  20. package/lib/esm/match-media.js +1 -1
  21. package/lib/esm/mutation-observer.js +1 -1
  22. package/lib/esm/named-node-map.js +1 -1
  23. package/lib/esm/node-list.js +1 -1
  24. package/lib/esm/node.js +1 -1
  25. package/lib/esm/register/canvas.js +1 -1
  26. package/lib/esm/register/document.js +1 -1
  27. package/lib/esm/register/helpers.js +1 -1
  28. package/lib/esm/resize-observer.js +1 -1
  29. package/lib/esm/text.js +1 -1
  30. package/lib/types/location-stub.d.ts +1 -1
  31. package/package.json +78 -75
  32. package/src/attr.ts +0 -61
  33. package/src/character-data.ts +0 -79
  34. package/src/comment.ts +0 -31
  35. package/src/document-fragment.ts +0 -137
  36. package/src/document.ts +0 -103
  37. package/src/dom-matrix.ts +0 -109
  38. package/src/dom-token-list.ts +0 -140
  39. package/src/element.ts +0 -316
  40. package/src/font-face.ts +0 -97
  41. package/src/gst-time.ts +0 -26
  42. package/src/html-canvas-element.ts +0 -90
  43. package/src/html-element.ts +0 -502
  44. package/src/html-image-element.spec.ts +0 -285
  45. package/src/html-image-element.ts +0 -295
  46. package/src/html-media-element.ts +0 -123
  47. package/src/html-video-element.ts +0 -143
  48. package/src/image.ts +0 -31
  49. package/src/index.spec.ts +0 -914
  50. package/src/index.ts +0 -39
  51. package/src/intersection-observer.ts +0 -42
  52. package/src/location-stub.ts +0 -20
  53. package/src/match-media.ts +0 -32
  54. package/src/mutation-observer.ts +0 -39
  55. package/src/named-node-map.ts +0 -159
  56. package/src/namespace-uri.ts +0 -11
  57. package/src/node-list.ts +0 -52
  58. package/src/node-type.ts +0 -14
  59. package/src/node.ts +0 -280
  60. package/src/property-symbol.ts +0 -23
  61. package/src/register/canvas.ts +0 -23
  62. package/src/register/document.ts +0 -64
  63. package/src/register/font-face.ts +0 -18
  64. package/src/register/helpers.ts +0 -15
  65. package/src/register/image.ts +0 -8
  66. package/src/register/location.ts +0 -6
  67. package/src/register/match-media.ts +0 -6
  68. package/src/register/navigator.ts +0 -6
  69. package/src/register/observers.ts +0 -10
  70. package/src/register.spec.ts +0 -136
  71. package/src/register.ts +0 -13
  72. package/src/resize-observer.ts +0 -28
  73. package/src/stubs.spec.ts +0 -284
  74. package/src/test.browser.mts +0 -686
  75. package/src/test.mts +0 -9
  76. package/src/text.ts +0 -67
  77. package/src/types/i-html-image-element.ts +0 -44
  78. package/src/types/image-data.ts +0 -12
  79. package/src/types/index.ts +0 -3
  80. package/src/types/predefined-color-space.ts +0 -1
  81. package/tsconfig.json +0 -37
  82. package/tsconfig.tsbuildinfo +0 -1
@@ -1,285 +0,0 @@
1
- // HTMLImageElement lifecycle tests — verifies the src → onload → pixbuf
2
- // chain Excalibur relies on, plus the native GJS extensions used by
3
- // @gjsify/canvas2d-core drawImage.
4
- //
5
- // Ported from refs/happy-dom/packages/happy-dom/test/nodes/html-image-element/
6
- // HTMLImageElement.test.ts (MIT, David Ortner) plus custom cases that
7
- // lock in compatibility with Excalibur's ImageSource.load pattern:
8
- // const image = new Image();
9
- // image.onload = () => futureResolve();
10
- // image.src = url;
11
- // await future;
12
- // (refs/excalibur/src/engine/graphics/image-source.ts:226-273)
13
-
14
- import { describe, it, expect } from '@gjsify/unit';
15
- import GLib from 'gi://GLib?version=2.0';
16
- import GdkPixbuf from 'gi://GdkPixbuf?version=2.0';
17
-
18
- import { HTMLImageElement, Image } from '@gjsify/dom-elements';
19
-
20
- // ── Test fixture setup ────────────────────────────────────────────────────
21
- //
22
- // Generates a 2×2 RGBA test PNG in /tmp at module load. Each pixel has a
23
- // distinct color (red/green/blue/white) so get-pixel tests can verify
24
- // byte order. Cleaned up by /tmp rotation; no explicit teardown needed.
25
-
26
- const FIXTURE_DIR = GLib.get_tmp_dir();
27
- const FIXTURE_PATH = GLib.build_filenamev([FIXTURE_DIR, 'gjsify-test-2x2.png']);
28
- const FIXTURE_URI = 'file://' + FIXTURE_PATH;
29
-
30
- function writeFixturePng(): void {
31
- // 2×2 RGBA raw pixel buffer:
32
- // (0,0) red (1,0) green
33
- // (0,1) blue (1,1) white
34
- const pixels = new Uint8Array([
35
- 255, 0, 0, 255, 0, 255, 0, 255,
36
- 0, 0, 255, 255, 255, 255, 255, 255,
37
- ]);
38
- // GdkPixbuf.Pixbuf.new_from_bytes expects row-major, w*4 stride for RGBA.
39
- const bytes = GLib.Bytes.new(pixels);
40
- const pixbuf = GdkPixbuf.Pixbuf.new_from_bytes(
41
- bytes,
42
- GdkPixbuf.Colorspace.RGB,
43
- true, // has_alpha
44
- 8, // bits_per_sample
45
- 2, // width
46
- 2, // height
47
- 8, // rowstride (2 px * 4 bytes)
48
- );
49
- pixbuf.savev(FIXTURE_PATH, 'png', [], []);
50
- }
51
-
52
- writeFixturePng();
53
-
54
- export default async () => {
55
- await describe('HTMLImageElement', async () => {
56
-
57
- await describe('constructor + static Image alias', async () => {
58
- await it('new HTMLImageElement() creates an empty image', async () => {
59
- const img = new HTMLImageElement();
60
- expect(img.complete).toBe(false);
61
- expect(img.naturalWidth).toBe(0);
62
- expect(img.naturalHeight).toBe(0);
63
- expect(img.src).toBe('');
64
- });
65
-
66
- await it('new Image() yields an HTMLImageElement instance', async () => {
67
- const img = new Image();
68
- expect(img instanceof HTMLImageElement).toBe(true);
69
- });
70
- });
71
-
72
- await describe('src setter — file:// loading', async () => {
73
- await it('loads a file:// URL synchronously and sets natural dimensions', async () => {
74
- const img = new HTMLImageElement();
75
- img.src = FIXTURE_URI;
76
- expect(img.complete).toBe(true);
77
- expect(img.naturalWidth).toBe(2);
78
- expect(img.naturalHeight).toBe(2);
79
- expect(img.isPixbuf()).toBe(true);
80
- });
81
-
82
- await it('fires the load event (sync dispatch)', async () => {
83
- const img = new HTMLImageElement();
84
- let loaded = false;
85
- img.addEventListener('load', () => { loaded = true; });
86
- img.src = FIXTURE_URI;
87
- expect(loaded).toBe(true);
88
- });
89
-
90
- await it('fires the error event for non-existent files', async () => {
91
- const img = new HTMLImageElement();
92
- let errored = false;
93
- img.addEventListener('error', () => { errored = true; });
94
- img.src = 'file:///nonexistent/definitely/not/here.png';
95
- expect(errored).toBe(true);
96
- expect(img.complete).toBe(true);
97
- expect(img.isPixbuf()).toBe(false);
98
- });
99
-
100
- await it('fires error immediately for http:// URLs (not supported in GJS)', async () => {
101
- const img = new HTMLImageElement();
102
- let errored = false;
103
- img.addEventListener('error', () => { errored = true; });
104
- img.src = 'http://example.com/img.png';
105
- expect(errored).toBe(true);
106
- expect(img.complete).toBe(true);
107
- });
108
- });
109
-
110
- await describe('Excalibur.ImageSource.load pattern — handler before src', async () => {
111
- // This is the canonical pattern Excalibur uses in
112
- // refs/excalibur/src/engine/graphics/image-source.ts:247-252.
113
- // The handler must be set BEFORE src so the sync dispatch fires
114
- // it and the awaited promise resolves correctly.
115
- await it('onload-set-then-src-set resolves the future', async () => {
116
- const img = new HTMLImageElement();
117
- const future = new Promise<void>((resolve) => {
118
- img.onload = () => resolve();
119
- });
120
- img.src = FIXTURE_URI;
121
- // Sync dispatch fires onload before returning from src=.
122
- await future;
123
- expect(img.complete).toBe(true);
124
- expect(img.naturalWidth).toBe(2);
125
- });
126
-
127
- await it('onload registered AFTER src is set does NOT fire', async () => {
128
- // Matches browser semantics: the load event is a one-shot
129
- // dispatch — registering a listener after it fires does not
130
- // retroactively invoke it.
131
- const img = new HTMLImageElement();
132
- img.src = FIXTURE_URI;
133
- expect(img.complete).toBe(true);
134
- let fired = false;
135
- img.onload = () => { fired = true; };
136
- // Give microtasks a chance to fire (they won't).
137
- await new Promise<void>((r) => r());
138
- expect(fired).toBe(false);
139
- });
140
- });
141
-
142
- await describe('getImageData — RGBA byte order', async () => {
143
- await it('extracts correct pixel bytes from the loaded pixbuf', async () => {
144
- const img = new HTMLImageElement();
145
- img.src = FIXTURE_URI;
146
- const data = img.getImageData();
147
- expect(data).not.toBeNull();
148
- expect(data!.width).toBe(2);
149
- expect(data!.height).toBe(2);
150
- // Pixel (0,0) — red
151
- expect(data!.data[0]).toBe(255);
152
- expect(data!.data[1]).toBe(0);
153
- expect(data!.data[2]).toBe(0);
154
- expect(data!.data[3]).toBe(255);
155
- // Pixel (1,0) — green
156
- expect(data!.data[4]).toBe(0);
157
- expect(data!.data[5]).toBe(255);
158
- expect(data!.data[6]).toBe(0);
159
- expect(data!.data[7]).toBe(255);
160
- // Pixel (0,1) — blue
161
- expect(data!.data[8]).toBe(0);
162
- expect(data!.data[9]).toBe(0);
163
- expect(data!.data[10]).toBe(255);
164
- expect(data!.data[11]).toBe(255);
165
- // Pixel (1,1) — white
166
- expect(data!.data[12]).toBe(255);
167
- expect(data!.data[13]).toBe(255);
168
- expect(data!.data[14]).toBe(255);
169
- expect(data!.data[15]).toBe(255);
170
- });
171
- });
172
-
173
- await describe('src setter — data: URI loading', async () => {
174
- // Excalibur's Loader.onBeforeLoad() sets img.src to a data:image/png;base64,...
175
- // URI for the loader logo. If this fires error instead of load, the
176
- // _imageLoaded promise never resolves and the loader hangs forever.
177
- // Regression test for the GJS fix using GLib.base64_decode + Gio.MemoryInputStream.
178
-
179
- function makeDataUri(): string {
180
- // Encode the 2×2 fixture PNG as a data URI using GLib.
181
- const [ok, bytes] = GLib.file_get_contents(FIXTURE_PATH);
182
- if (!ok) throw new Error('fixture PNG not found');
183
- const b64 = GLib.base64_encode(bytes as unknown as Uint8Array);
184
- return `data:image/png;base64,${b64}`;
185
- }
186
-
187
- await it('loads a base64 PNG data URI and sets natural dimensions', async () => {
188
- const img = new HTMLImageElement();
189
- const dataUri = makeDataUri();
190
- img.src = dataUri;
191
- expect(img.complete).toBe(true);
192
- expect(img.naturalWidth).toBe(2);
193
- expect(img.naturalHeight).toBe(2);
194
- expect(img.isPixbuf()).toBe(true);
195
- });
196
-
197
- await it('fires load event for a base64 PNG data URI', async () => {
198
- const img = new HTMLImageElement();
199
- let loaded = false;
200
- img.addEventListener('load', () => { loaded = true; });
201
- img.src = makeDataUri();
202
- expect(loaded).toBe(true);
203
- });
204
-
205
- await it('Excalibur pattern: onload-then-src resolves for data URIs', async () => {
206
- const img = new HTMLImageElement();
207
- const future = new Promise<void>((resolve) => {
208
- img.onload = () => resolve();
209
- });
210
- img.src = makeDataUri();
211
- await future;
212
- expect(img.complete).toBe(true);
213
- expect(img.naturalWidth).toBe(2);
214
- });
215
-
216
- await it('fires error for a malformed data URI (no comma)', async () => {
217
- const img = new HTMLImageElement();
218
- let errored = false;
219
- img.addEventListener('error', () => { errored = true; });
220
- img.src = 'data:image/png;base64';
221
- expect(errored).toBe(true);
222
- expect(img.complete).toBe(true);
223
- });
224
-
225
- await it('getImageData returns correct RGBA pixels from data URI', async () => {
226
- const img = new HTMLImageElement();
227
- img.src = makeDataUri();
228
- const data = img.getImageData();
229
- expect(data).not.toBeNull();
230
- expect(data!.width).toBe(2);
231
- expect(data!.height).toBe(2);
232
- // Pixel (0,0) — red
233
- expect(data!.data[0]).toBe(255);
234
- expect(data!.data[1]).toBe(0);
235
- expect(data!.data[2]).toBe(0);
236
- expect(data!.data[3]).toBe(255);
237
- });
238
- });
239
-
240
- await describe('dataset proxy — Excalibur data-original-src pattern', async () => {
241
- // Excalibur's ImageSource.load does:
242
- // image.setAttribute('data-original-src', this.path);
243
- // and TextureLoader.checkImageSizeSupportedAndLog reads
244
- // image.dataset.originalSrc
245
- // Our dataset Proxy must perform the kebab→camel case conversion.
246
- await it('reads a data-* attribute via camelCase dataset access', async () => {
247
- const img = new HTMLImageElement();
248
- img.setAttribute('data-original-src', 'sprites/hero.png');
249
- expect(img.dataset.originalSrc).toBe('sprites/hero.png');
250
- });
251
-
252
- await it('sets a data-* attribute via dataset assignment', async () => {
253
- const img = new HTMLImageElement();
254
- img.dataset.tileIndex = '42';
255
- expect(img.getAttribute('data-tile-index')).toBe('42');
256
- });
257
-
258
- await it('delete operator removes the data-* attribute', async () => {
259
- const img = new HTMLImageElement();
260
- img.setAttribute('data-foo', 'bar');
261
- delete img.dataset.foo;
262
- expect(img.hasAttribute('data-foo')).toBe(false);
263
- });
264
-
265
- await it('returns undefined for unset dataset keys', async () => {
266
- const img = new HTMLImageElement();
267
- expect(img.dataset.nothing).toBe(undefined);
268
- });
269
- });
270
-
271
- await describe('attribute-backed properties', async () => {
272
- await it('alt, title, crossOrigin round-trip through attributes', async () => {
273
- const img = new HTMLImageElement();
274
- img.alt = 'hero sprite';
275
- img.title = 'A jumping jelly';
276
- img.crossOrigin = 'anonymous';
277
- expect(img.getAttribute('alt')).toBe('hero sprite');
278
- expect(img.getAttribute('title')).toBe('A jumping jelly');
279
- expect(img.getAttribute('crossorigin')).toBe('anonymous');
280
- expect(img.alt).toBe('hero sprite');
281
- expect(img.crossOrigin).toBe('anonymous');
282
- });
283
- });
284
- });
285
- };
@@ -1,295 +0,0 @@
1
- // HTMLImageElement for GJS — original implementation using GdkPixbuf
2
- // Reference: refs/happy-dom/packages/happy-dom/src/nodes/html-image-element/HTMLImageElement.ts
3
-
4
- import GLib from '@girs/glib-2.0';
5
- import Gio from '@girs/gio-2.0';
6
- import GdkPixbuf from '@girs/gdkpixbuf-2.0';
7
- import { Event } from '@gjsify/dom-events';
8
- import { HTMLElement } from './html-element.js';
9
- import * as PropertySymbol from './property-symbol.js';
10
- import { NamespaceURI } from './namespace-uri.js';
11
- import System from 'system';
12
-
13
- import type { ImageData } from './types/index.js';
14
-
15
- /**
16
- * HTML Image Element.
17
- *
18
- * Reference: https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement
19
- */
20
- export class HTMLImageElement extends HTMLElement {
21
- private _complete = false;
22
- private _naturalHeight = 0;
23
- private _naturalWidth = 0;
24
- protected _pixbuf?: GdkPixbuf.Pixbuf;
25
-
26
- constructor() {
27
- super();
28
- this[PropertySymbol.tagName] = 'IMG';
29
- this[PropertySymbol.localName] = 'img';
30
- this[PropertySymbol.namespaceURI] = NamespaceURI.html;
31
- }
32
-
33
- // -- Read-only properties --
34
-
35
- get complete(): boolean {
36
- return this._complete;
37
- }
38
-
39
- get naturalHeight(): number {
40
- return this._naturalHeight;
41
- }
42
-
43
- get naturalWidth(): number {
44
- return this._naturalWidth;
45
- }
46
-
47
- get currentSrc(): string {
48
- return this.src;
49
- }
50
-
51
- get x(): number {
52
- return 0;
53
- }
54
-
55
- get y(): number {
56
- return 0;
57
- }
58
-
59
- // -- Attribute-backed string properties --
60
-
61
- get alt(): string {
62
- return this.getAttribute('alt') ?? '';
63
- }
64
-
65
- set alt(value: string) {
66
- this.setAttribute('alt', value);
67
- }
68
-
69
- get crossOrigin(): string | null {
70
- return this.getAttribute('crossorigin');
71
- }
72
-
73
- set crossOrigin(value: string | null) {
74
- if (value === null) {
75
- this.removeAttribute('crossorigin');
76
- } else {
77
- this.setAttribute('crossorigin', value);
78
- }
79
- }
80
-
81
- get decoding(): string {
82
- return this.getAttribute('decoding') ?? 'auto';
83
- }
84
-
85
- set decoding(value: string) {
86
- this.setAttribute('decoding', value);
87
- }
88
-
89
- get loading(): string {
90
- const value = this.getAttribute('loading');
91
- if (value === 'lazy' || value === 'eager') return value;
92
- return 'auto';
93
- }
94
-
95
- set loading(value: string) {
96
- this.setAttribute('loading', value);
97
- }
98
-
99
- get referrerPolicy(): string {
100
- return this.getAttribute('referrerpolicy') ?? '';
101
- }
102
-
103
- set referrerPolicy(value: string) {
104
- this.setAttribute('referrerpolicy', value);
105
- }
106
-
107
- get sizes(): string {
108
- return this.getAttribute('sizes') ?? '';
109
- }
110
-
111
- set sizes(value: string) {
112
- this.setAttribute('sizes', value);
113
- }
114
-
115
- get src(): string {
116
- return this.getAttribute('src') ?? '';
117
- }
118
-
119
- set src(src: string) {
120
- this.setAttribute('src', src);
121
-
122
- const DEBUG = (globalThis as any).__GJSIFY_DEBUG_IMG === true;
123
-
124
- // Handle data: URIs (e.g. base64 PNG logos from Excalibur's loader)
125
- if (src.startsWith('data:')) {
126
- const commaIdx = src.indexOf(',');
127
- if (commaIdx === -1) {
128
- this._complete = true;
129
- this.dispatchEvent(new Event('error'));
130
- return;
131
- }
132
- const meta = src.slice(5, commaIdx); // between 'data:' and ','
133
- const data = src.slice(commaIdx + 1);
134
- const isBase64 = meta.includes(';base64');
135
- try {
136
- let bytes: Uint8Array;
137
- if (isBase64) {
138
- // Use GLib.base64_decode — available in all GJS versions, no global needed
139
- bytes = GLib.base64_decode(data) as unknown as Uint8Array;
140
- } else {
141
- bytes = new TextEncoder().encode(decodeURIComponent(data));
142
- }
143
- const gbytes = GLib.Bytes.new(bytes);
144
- const stream = Gio.MemoryInputStream.new_from_bytes(gbytes);
145
- this._pixbuf = GdkPixbuf.Pixbuf.new_from_stream(stream, null);
146
- this._naturalWidth = this._pixbuf!.get_width();
147
- this._naturalHeight = this._pixbuf!.get_height();
148
- this._complete = true;
149
- if (DEBUG) console.log(`[img] ok data: (${this._naturalWidth}x${this._naturalHeight})`);
150
- this.dispatchEvent(new Event('load'));
151
- } catch (_error) {
152
- if (DEBUG) console.warn(`[img] error data:: ${(_error as any)?.message ?? _error}`);
153
- this._complete = true;
154
- this.dispatchEvent(new Event('error'));
155
- }
156
- return;
157
- }
158
-
159
- let filename: string;
160
- if (src.startsWith('file://')) {
161
- // GLib.filename_from_uri returns [localPath, hostname]
162
- filename = GLib.filename_from_uri(src)[0];
163
- } else if (src.startsWith('http://') || src.startsWith('https://')) {
164
- // Remote URLs are not supported in GJS — fire error
165
- this._complete = true;
166
- this.dispatchEvent(new Event('error'));
167
- return;
168
- } else {
169
- // Relative path — resolve against the directory of the running script
170
- const dir = GLib.path_get_dirname(System.programInvocationName);
171
- filename = GLib.build_filenamev([dir, src]);
172
- }
173
-
174
- try {
175
- if (DEBUG) console.log(`[img] load ${filename}`);
176
- this._pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename);
177
- this._naturalWidth = this._pixbuf.get_width();
178
- this._naturalHeight = this._pixbuf.get_height();
179
- this._complete = true;
180
- if (DEBUG) console.log(`[img] ok ${filename} (${this._naturalWidth}x${this._naturalHeight})`);
181
-
182
- this.dispatchEvent(new Event('load'));
183
- } catch (_error) {
184
- if (DEBUG) console.warn(`[img] error ${filename}: ${(_error as any)?.message ?? _error}`);
185
- this._complete = true;
186
- this.dispatchEvent(new Event('error'));
187
- }
188
- }
189
-
190
- get srcset(): string {
191
- return this.getAttribute('srcset') ?? '';
192
- }
193
-
194
- set srcset(value: string) {
195
- this.setAttribute('srcset', value);
196
- }
197
-
198
- get useMap(): string {
199
- return this.getAttribute('usemap') ?? '';
200
- }
201
-
202
- set useMap(value: string) {
203
- this.setAttribute('usemap', value);
204
- }
205
-
206
- // -- Attribute-backed numeric properties --
207
-
208
- get height(): number {
209
- if (this._pixbuf) {
210
- return this._pixbuf.get_height();
211
- }
212
- const attr = this.getAttribute('height');
213
- return attr !== null ? Number(attr) : 0;
214
- }
215
-
216
- set height(value: number) {
217
- this.setAttribute('height', String(value));
218
- }
219
-
220
- get width(): number {
221
- if (this._pixbuf) {
222
- return this._pixbuf.get_width();
223
- }
224
- const attr = this.getAttribute('width');
225
- return attr !== null ? Number(attr) : 0;
226
- }
227
-
228
- set width(value: number) {
229
- this.setAttribute('width', String(value));
230
- }
231
-
232
- // -- Attribute-backed boolean property --
233
-
234
- get isMap(): boolean {
235
- return this.hasAttribute('ismap');
236
- }
237
-
238
- set isMap(value: boolean) {
239
- if (value) {
240
- this.setAttribute('ismap', '');
241
- } else {
242
- this.removeAttribute('ismap');
243
- }
244
- }
245
-
246
- // -- Methods --
247
-
248
- /**
249
- * Decode the image. Returns a promise that resolves when the image is decoded.
250
- */
251
- decode(): Promise<void> {
252
- return Promise.resolve();
253
- }
254
-
255
- /**
256
- * Clone this node.
257
- */
258
- cloneNode(deep = false): HTMLImageElement {
259
- return super.cloneNode(deep) as HTMLImageElement;
260
- }
261
-
262
- // -- GJS-specific extensions --
263
-
264
- /**
265
- * Get the pixels of the loaded GdkPixbuf as ImageData.
266
- * Always returns RGBA (4 channels) — matches standard browser ImageData behaviour
267
- * and what WebGL expects for texSubImage2D with format=RGBA.
268
- * JPEG and other non-alpha formats are promoted to RGBA via add_alpha().
269
- */
270
- getImageData(): ImageData | null {
271
- if (!this._pixbuf) return null;
272
- // add_alpha() is a no-op when the pixbuf already has alpha; for RGB-only
273
- // pixbufs (e.g. JPEG) it appends a fully opaque alpha channel.
274
- const rgba = this._pixbuf.get_has_alpha()
275
- ? this._pixbuf
276
- : (this._pixbuf.add_alpha(false, 0, 0, 0) ?? this._pixbuf);
277
- return {
278
- colorSpace: 'srgb',
279
- data: new Uint8ClampedArray(rgba.get_pixels()),
280
- height: rgba.get_height(),
281
- width: rgba.get_width(),
282
- };
283
- }
284
-
285
- /**
286
- * Check if this image is backed by a GdkPixbuf.
287
- */
288
- isPixbuf(): boolean {
289
- return !!this._pixbuf;
290
- }
291
-
292
- get [Symbol.toStringTag](): string {
293
- return 'HTMLImageElement';
294
- }
295
- }
@@ -1,123 +0,0 @@
1
- // HTMLMediaElement for GJS — base class for HTMLVideoElement and HTMLAudioElement.
2
- // Reference: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
3
- // Reference: refs/happy-dom/packages/happy-dom/src/nodes/html-media-element/HTMLMediaElement.ts
4
- //
5
- // This is a pure DOM class — it stores media state and dispatches events but
6
- // does NOT interact with GStreamer. The bridge container (VideoBridge etc.)
7
- // listens for internal events and handles the actual pipeline.
8
-
9
- import { Event } from '@gjsify/dom-events';
10
-
11
- import { HTMLElement } from './html-element.js';
12
-
13
- // Readiness states
14
- export const HAVE_NOTHING = 0;
15
- export const HAVE_METADATA = 1;
16
- export const HAVE_CURRENT_DATA = 2;
17
- export const HAVE_FUTURE_DATA = 3;
18
- export const HAVE_ENOUGH_DATA = 4;
19
-
20
- // Network states
21
- export const NETWORK_EMPTY = 0;
22
- export const NETWORK_IDLE = 1;
23
- export const NETWORK_LOADING = 2;
24
- export const NETWORK_NO_SOURCE = 3;
25
-
26
- /**
27
- * Base class for media elements (video, audio).
28
- *
29
- * Stores media state and dispatches DOM events. Pipeline construction is
30
- * delegated to the bridge container via internal events.
31
- */
32
- export class HTMLMediaElement extends HTMLElement {
33
- // -- Source --
34
- private _src = '';
35
- private _srcObject: any = null;
36
-
37
- // -- Playback state --
38
- currentTime = 0;
39
- duration = NaN;
40
- paused = true;
41
- ended = false;
42
- volume = 1;
43
- muted = false;
44
- defaultMuted = false;
45
- loop = false;
46
- autoplay = false;
47
- preload: '' | 'none' | 'metadata' | 'auto' = '';
48
- playbackRate = 1;
49
- defaultPlaybackRate = 1;
50
-
51
- // -- Readiness --
52
- readyState = HAVE_NOTHING;
53
- networkState = NETWORK_EMPTY;
54
-
55
- // -- Buffered/seekable stubs --
56
- get buffered(): { length: number; start(index: number): number; end(index: number): number } {
57
- return { length: 0, start: () => 0, end: () => 0 };
58
- }
59
- get seekable(): { length: number; start(index: number): number; end(index: number): number } {
60
- return { length: 0, start: () => 0, end: () => 0 };
61
- }
62
- get played(): { length: number; start(index: number): number; end(index: number): number } {
63
- return { length: 0, start: () => 0, end: () => 0 };
64
- }
65
-
66
- // -- src property --
67
- get src(): string {
68
- return this._src;
69
- }
70
- set src(value: string) {
71
- this._src = value;
72
- this._srcObject = null;
73
- this.dispatchEvent(new Event('srcchange'));
74
- }
75
-
76
- // -- srcObject property (MediaStream) --
77
- get srcObject(): any {
78
- return this._srcObject;
79
- }
80
- set srcObject(stream: any) {
81
- this._srcObject = stream;
82
- this._src = '';
83
- this.dispatchEvent(new Event('srcobjectchange'));
84
- }
85
-
86
- // -- Playback control --
87
- play(): Promise<void> {
88
- this.paused = false;
89
- this.ended = false;
90
- this.dispatchEvent(new Event('play'));
91
- return Promise.resolve();
92
- }
93
-
94
- pause(): void {
95
- this.paused = true;
96
- this.dispatchEvent(new Event('pause'));
97
- }
98
-
99
- load(): void {
100
- this.readyState = HAVE_NOTHING;
101
- this.networkState = NETWORK_LOADING;
102
- this.dispatchEvent(new Event('loadstart'));
103
- }
104
-
105
- canPlayType(_type: string): '' | 'maybe' | 'probably' {
106
- return '';
107
- }
108
-
109
- // -- Static constants --
110
- static readonly HAVE_NOTHING = HAVE_NOTHING;
111
- static readonly HAVE_METADATA = HAVE_METADATA;
112
- static readonly HAVE_CURRENT_DATA = HAVE_CURRENT_DATA;
113
- static readonly HAVE_FUTURE_DATA = HAVE_FUTURE_DATA;
114
- static readonly HAVE_ENOUGH_DATA = HAVE_ENOUGH_DATA;
115
- static readonly NETWORK_EMPTY = NETWORK_EMPTY;
116
- static readonly NETWORK_IDLE = NETWORK_IDLE;
117
- static readonly NETWORK_LOADING = NETWORK_LOADING;
118
- static readonly NETWORK_NO_SOURCE = NETWORK_NO_SOURCE;
119
-
120
- get [Symbol.toStringTag](): string {
121
- return 'HTMLMediaElement';
122
- }
123
- }