wasm-html-to-image 1.0.7 → 1.0.8

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.
@@ -69,6 +69,14 @@ export declare function limitCodeForReason(reason: string): string;
69
69
  * together, so this bounds the connection burst for many-page documents.
70
70
  */
71
71
  export declare const FETCH_CONCURRENCY = 16;
72
+ /**
73
+ * Resolve the relative `url(...)` and `@import` references of a fetched stylesheet against
74
+ * the stylesheet's own URL. The engine resolves them against the page, which is wrong as soon
75
+ * as the sheet lives in another directory (`url(../fonts/a.woff)` in `/static/css/a.css`).
76
+ * Absolute, protocol-relative, `data:`, `#` and root-relative references are left alone, and a
77
+ * root-relative sheet URL keeps its root-relative form.
78
+ */
79
+ export declare function absolutizeCssUrls(css: string, cssUrl: string): string;
72
80
  /** Run `fn` over `items` with at most `limit` calls in flight (`fn` must not reject). */
73
81
  export declare function mapWithLimit<T>(items: readonly T[], limit: number, fn: (item: T) => Promise<void>): Promise<void>;
74
82
  /**
@@ -270,6 +270,50 @@ export function limitCodeForReason(reason) {
270
270
  * together, so this bounds the connection burst for many-page documents.
271
271
  */
272
272
  export const FETCH_CONCURRENCY = 16;
273
+ /**
274
+ * Resolve the relative `url(...)` and `@import` references of a fetched stylesheet against
275
+ * the stylesheet's own URL. The engine resolves them against the page, which is wrong as soon
276
+ * as the sheet lives in another directory (`url(../fonts/a.woff)` in `/static/css/a.css`).
277
+ * Absolute, protocol-relative, `data:`, `#` and root-relative references are left alone, and a
278
+ * root-relative sheet URL keeps its root-relative form.
279
+ */
280
+ export function absolutizeCssUrls(css, cssUrl) {
281
+ if (!css.includes("url(") && !css.includes("@import"))
282
+ return css;
283
+ const dummy = "http://css-base.invalid/";
284
+ let base;
285
+ try {
286
+ base = new URL(cssUrl, dummy);
287
+ }
288
+ catch {
289
+ return css;
290
+ }
291
+ const keepRootRelative = cssUrl.startsWith("/");
292
+ const absoluteCssUrl = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(cssUrl);
293
+ const resolve = (ref) => {
294
+ const t = ref.trim();
295
+ if (t === "" ||
296
+ t.startsWith("#") ||
297
+ t.startsWith("/") ||
298
+ /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(t))
299
+ return ref;
300
+ try {
301
+ const u = new URL(t, base);
302
+ if (absoluteCssUrl)
303
+ return u.href;
304
+ if (u.origin !== base.origin)
305
+ return u.href;
306
+ const path = u.pathname + u.search + u.hash;
307
+ return keepRootRelative ? path : path.slice(1);
308
+ }
309
+ catch {
310
+ return ref;
311
+ }
312
+ };
313
+ return css
314
+ .replace(/url\(\s*(['"]?)([^'")]*)\1\s*\)/g, (_m, q, ref) => `url(${q}${resolve(ref)}${q})`)
315
+ .replace(/@import\s+(['"])([^'"]+)\1/g, (_m, q, ref) => `@import ${q}${resolve(ref)}${q}`);
316
+ }
273
317
  /** Run `fn` over `items` with at most `limit` calls in flight (`fn` must not reject). */
274
318
  export async function mapWithLimit(items, limit, fn) {
275
319
  let next = 0;
@@ -392,6 +436,10 @@ export async function resolveHtmlResources(module, satoruInst, htmls, width, hei
392
436
  const pending = parsePendingResources(bin instanceof Uint8Array ? bin : undefined);
393
437
  if (pending.length === 0)
394
438
  break;
439
+ // Stylesheets cascade by document order, not by fetch completion order: a small
440
+ // later sheet must not land before (and so lose to) a large earlier one. They are
441
+ // buffered here and injected in discovery order once the whole round has settled.
442
+ const loadedCss = [];
395
443
  await mapWithLimit(pending, FETCH_CONCURRENCY, async (r) => {
396
444
  const diag = ctx.diag;
397
445
  const entry = diag
@@ -470,7 +518,11 @@ export async function resolveHtmlResources(module, satoruInst, htmls, width, hei
470
518
  }
471
519
  ctx.usage.totalResourceBytes += bytes.length;
472
520
  settle("loaded", bytes.length);
473
- (await addRes(satoruInst, r.url, r.type === "image" ? 2 : r.type === "css" ? 3 : 1, bytes));
521
+ if (r.type === "css") {
522
+ loadedCss.push({ index: pending.indexOf(r), url: r.url, bytes });
523
+ return;
524
+ }
525
+ (await addRes(satoruInst, r.url, r.type === "image" ? 2 : 1, bytes));
474
526
  if (r.type === "font") {
475
527
  diag?.fonts.push({
476
528
  family: r.name || r.url,
@@ -486,6 +538,13 @@ export async function resolveHtmlResources(module, satoruInst, htmls, width, hei
486
538
  diag?.warnings.push(d);
487
539
  }
488
540
  });
541
+ loadedCss.sort((a, b) => a.index - b.index);
542
+ for (const css of loadedCss) {
543
+ const text = new TextDecoder().decode(css.bytes);
544
+ const fixed = absolutizeCssUrls(text, css.url);
545
+ const bytes = fixed === text ? css.bytes : new TextEncoder().encode(fixed);
546
+ (await addRes(satoruInst, css.url, 3, bytes));
547
+ }
489
548
  }
490
549
  // Merge the C++ collect-phase breakdown into the JS timings.
491
550
  if (profileEnabled && ctx.diag) {