@archie/devtools 0.0.47 → 0.0.48
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.
- package/dist/inspector.standalone.js +492 -90
- package/package.json +1 -1
|
@@ -3069,25 +3069,254 @@
|
|
|
3069
3069
|
return result
|
|
3070
3070
|
}
|
|
3071
3071
|
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3072
|
+
var CSS_INLINE_TIMEOUT_MS = 2000
|
|
3073
|
+
var CSS_URL_RE = /url\(\s*(['"]?)([^'")]+)\1\s*\)/g
|
|
3074
|
+
|
|
3075
|
+
/** Resolves once `promise` settles, or once `ms` elapses — whichever
|
|
3076
|
+
* first — falling back to `fallbackValue` instead of rejecting. Used
|
|
3077
|
+
* to bound best-effort work (font/url inlining) that must never block
|
|
3078
|
+
* the capture past a short budget. */
|
|
3079
|
+
function withTimeout(promise, ms, fallbackValue) {
|
|
3080
|
+
return new Promise(function (resolve) {
|
|
3081
|
+
var settled = false
|
|
3082
|
+
var timer = setTimeout(function () {
|
|
3083
|
+
if (settled) return
|
|
3084
|
+
settled = true
|
|
3085
|
+
resolve(fallbackValue)
|
|
3086
|
+
}, ms)
|
|
3087
|
+
promise.then(
|
|
3088
|
+
function (value) {
|
|
3089
|
+
if (settled) return
|
|
3090
|
+
settled = true
|
|
3091
|
+
clearTimeout(timer)
|
|
3092
|
+
resolve(value)
|
|
3093
|
+
},
|
|
3094
|
+
function () {
|
|
3095
|
+
if (settled) return
|
|
3096
|
+
settled = true
|
|
3097
|
+
clearTimeout(timer)
|
|
3098
|
+
resolve(fallbackValue)
|
|
3099
|
+
}
|
|
3100
|
+
)
|
|
3101
|
+
})
|
|
3102
|
+
}
|
|
3103
|
+
|
|
3104
|
+
// Duck-typed against CSSOM's numeric `type` (MEDIA_RULE=4, IMPORT_RULE=3,
|
|
3105
|
+
// FONT_FACE_RULE=5) because CSSMediaRule/CSSImportRule/CSSFontFaceRule
|
|
3106
|
+
// are not guaranteed to exist in every test DOM (e.g. jsdom).
|
|
3107
|
+
function isMediaRule(rule) {
|
|
3108
|
+
if (typeof CSSMediaRule !== "undefined" && rule instanceof CSSMediaRule) {
|
|
3109
|
+
return true
|
|
3110
|
+
}
|
|
3111
|
+
return !!(rule && rule.media && rule.cssRules && rule.type === 4)
|
|
3112
|
+
}
|
|
3113
|
+
|
|
3114
|
+
function isImportRule(rule) {
|
|
3115
|
+
if (typeof CSSImportRule !== "undefined" && rule instanceof CSSImportRule) {
|
|
3116
|
+
return true
|
|
3117
|
+
}
|
|
3118
|
+
return !!(rule && rule.styleSheet && rule.type === 3)
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
function isFontFaceRule(rule) {
|
|
3122
|
+
if (typeof CSSFontFaceRule !== "undefined" && rule instanceof CSSFontFaceRule) {
|
|
3123
|
+
return true
|
|
3124
|
+
}
|
|
3125
|
+
return !!(rule && rule.type === 5)
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
/** The at-rule keyword of `rule.cssText` (e.g. "layer", "supports"),
|
|
3129
|
+
* lowercased. Used where a rule's numeric `.type` is unreliable — the
|
|
3130
|
+
* CSSOM spec never assigned legacy type constants to rules added
|
|
3131
|
+
* after `@supports` (`@layer`, `@container`, `@starting-style`, ...),
|
|
3132
|
+
* so they all report `type === 0`. */
|
|
3133
|
+
function ruleAtKeyword(rule) {
|
|
3134
|
+
var text = rule && rule.cssText ? rule.cssText : ""
|
|
3135
|
+
var match = /^\s*@(?:-webkit-|-moz-|-ms-|-o-)?([a-zA-Z-]+)/.exec(text)
|
|
3136
|
+
return match ? match[1].toLowerCase() : ""
|
|
3137
|
+
}
|
|
3138
|
+
|
|
3139
|
+
/** A rule that carries its own declarations/content and must always be
|
|
3140
|
+
* pushed verbatim, never treated as a group to recurse into — even
|
|
3141
|
+
* though some of these also expose `.cssRules`:
|
|
3142
|
+
* - `CSSStyleRule` (type 1) exposes `.cssRules` in real browsers for
|
|
3143
|
+
* CSS nesting (an empty list on an un-nested rule); treating that
|
|
3144
|
+
* as a grouping rule renders `.foo {}` with the declarations
|
|
3145
|
+
* dropped. Nested `@media` inside a nested style rule is a rare
|
|
3146
|
+
* case and is left as-is inside `cssText` rather than recursed.
|
|
3147
|
+
* - `@keyframes` / `@page` also expose `.cssRules`, but their nested
|
|
3148
|
+
* rules are keyframe selectors / margin boxes, not media-sensitive
|
|
3149
|
+
* content.
|
|
3150
|
+
* `.style` existing is a reliable signal across all of these (and
|
|
3151
|
+
* `CSSKeyframeRule`, type 8) even where `.type` duck-typing can't be
|
|
3152
|
+
* trusted. */
|
|
3153
|
+
function isStyleLikeLeaf(rule) {
|
|
3154
|
+
if (typeof CSSStyleRule !== "undefined" && rule instanceof CSSStyleRule) {
|
|
3155
|
+
return true
|
|
3156
|
+
}
|
|
3157
|
+
if (
|
|
3158
|
+
rule &&
|
|
3159
|
+
(rule.type === 1 || rule.type === 6 || rule.type === 7 || rule.type === 8)
|
|
3160
|
+
) {
|
|
3161
|
+
return true
|
|
3162
|
+
}
|
|
3163
|
+
return !!(rule && rule.style)
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3166
|
+
var GROUPING_AT_KEYWORDS = {
|
|
3167
|
+
layer: true,
|
|
3168
|
+
supports: true,
|
|
3169
|
+
container: true,
|
|
3170
|
+
scope: true,
|
|
3171
|
+
"starting-style": true,
|
|
3172
|
+
document: true,
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3175
|
+
/** Positive test for "safe to recurse into and rebuild": only at-rules
|
|
3176
|
+
* whose sole job is grouping other rules under a condition/namespace
|
|
3177
|
+
* (`@layer`, `@supports`, `@container`, `@scope`, `@starting-style`,
|
|
3178
|
+
* `@document`), and only when they actually carry nested rules — a
|
|
3179
|
+
* `@layer a, b;` statement form has no `cssRules` and falls through
|
|
3180
|
+
* to the verbatim leaf branch instead. Everything else defaults to
|
|
3181
|
+
* verbatim, rather than recursing by default, precisely because
|
|
3182
|
+
* `CSSStyleRule` also exposes `cssRules` (see `isStyleLikeLeaf`). */
|
|
3183
|
+
function isGroupingRule(rule) {
|
|
3184
|
+
if (!rule || !rule.cssRules) return false
|
|
3185
|
+
if (typeof CSSStyleRule !== "undefined" && rule instanceof CSSStyleRule) {
|
|
3186
|
+
return false
|
|
3187
|
+
}
|
|
3188
|
+
return !!GROUPING_AT_KEYWORDS[ruleAtKeyword(rule)]
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
/** The rule's header text up to (not including) its opening brace,
|
|
3192
|
+
* e.g. `@layer utilities ` or `@supports (display: grid) `. Used to
|
|
3193
|
+
* rebuild a grouping at-rule around its flattened children. */
|
|
3194
|
+
function headerTextOf(rule) {
|
|
3195
|
+
var text = rule && rule.cssText ? rule.cssText : ""
|
|
3196
|
+
var braceIndex = text.indexOf("{")
|
|
3197
|
+
return braceIndex === -1 ? text : text.slice(0, braceIndex)
|
|
3198
|
+
}
|
|
3199
|
+
|
|
3200
|
+
/** Renders a node produced by `flattenStyleRules` back into CSS text.
|
|
3201
|
+
* A "slot" defers to `pieces[index]`, which a font-face/cross-origin
|
|
3202
|
+
* inlining job may still be mutating when this runs — see
|
|
3203
|
+
* `collectCaptureCss`'s `CSS_INLINE_TIMEOUT_MS` race. */
|
|
3204
|
+
function renderNode(node, pieces) {
|
|
3205
|
+
if (typeof node === "string") return node
|
|
3206
|
+
if (node.kind === "slot") return pieces[node.index]
|
|
3207
|
+
if (node.kind === "group") {
|
|
3208
|
+
return node.header + "{" + renderNodes(node.children, pieces) + "}"
|
|
3209
|
+
}
|
|
3210
|
+
return ""
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
function renderNodes(nodes, pieces) {
|
|
3214
|
+
var texts = []
|
|
3215
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
3216
|
+
texts.push(renderNode(nodes[i], pieces))
|
|
3089
3217
|
}
|
|
3090
|
-
return
|
|
3218
|
+
return texts.join("\n")
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
/**
|
|
3222
|
+
* Flattens `rules` into a tree of plain-string leaves, font-face "slot"
|
|
3223
|
+
* placeholders (resolved later by URL-inlining — see `pieces` in
|
|
3224
|
+
* `collectCaptureCss`), and group nodes that preserve a non-media
|
|
3225
|
+
* at-rule's own semantics while still flattening any `@media` nested
|
|
3226
|
+
* inside it. This last case matters in practice: Tailwind v4 emits
|
|
3227
|
+
* responsive utilities as `@layer utilities { @media (min-width: ...)
|
|
3228
|
+
* { ... } }`, so a `@layer`/`@supports`/`@container` wrapper has to be
|
|
3229
|
+
* rebuilt around its recursed children rather than pushed as opaque
|
|
3230
|
+
* `cssText` — otherwise the nested `@media` inside it would still be
|
|
3231
|
+
* evaluated against the tiny rasterized SVG instead of the live
|
|
3232
|
+
* viewport.
|
|
3233
|
+
*
|
|
3234
|
+
* In a rasterized SVG, `@media` is evaluated against the SVG image's
|
|
3235
|
+
* own (tiny) intrinsic size, not the live viewport — so a non-matching
|
|
3236
|
+
* query is dropped up front and a matching one is inlined (unwrapped),
|
|
3237
|
+
* using the live `window.matchMedia` as the source of truth. `@import`
|
|
3238
|
+
* is followed into its target sheet the same way; a cross-origin
|
|
3239
|
+
* import throws on `.styleSheet.cssRules` and is dropped, same as any
|
|
3240
|
+
* other unreachable cross-origin CSS. A rule with no `cssRules` at all
|
|
3241
|
+
* (e.g. a plain style rule, or `@layer a, b;`'s statement form) is
|
|
3242
|
+
* pushed as a plain string leaf.
|
|
3243
|
+
*/
|
|
3244
|
+
function flattenStyleRules(rules, sheetHref, fetchCache, enhancementJobs, pieces) {
|
|
3245
|
+
var nodes = []
|
|
3246
|
+
if (!rules) return nodes
|
|
3247
|
+
for (var i = 0; i < rules.length; i++) {
|
|
3248
|
+
;(function (rule) {
|
|
3249
|
+
if (isMediaRule(rule)) {
|
|
3250
|
+
var conditionText =
|
|
3251
|
+
rule.conditionText || (rule.media && rule.media.mediaText) || ""
|
|
3252
|
+
var matches = true
|
|
3253
|
+
try {
|
|
3254
|
+
matches = window.matchMedia(conditionText).matches
|
|
3255
|
+
} catch (error) {
|
|
3256
|
+
matches = true
|
|
3257
|
+
}
|
|
3258
|
+
if (matches) {
|
|
3259
|
+
var mediaChildren = flattenStyleRules(
|
|
3260
|
+
rule.cssRules,
|
|
3261
|
+
sheetHref,
|
|
3262
|
+
fetchCache,
|
|
3263
|
+
enhancementJobs,
|
|
3264
|
+
pieces
|
|
3265
|
+
)
|
|
3266
|
+
for (var m = 0; m < mediaChildren.length; m++) {
|
|
3267
|
+
nodes.push(mediaChildren[m])
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
} else if (isImportRule(rule)) {
|
|
3271
|
+
try {
|
|
3272
|
+
var importedChildren = flattenStyleRules(
|
|
3273
|
+
rule.styleSheet.cssRules,
|
|
3274
|
+
rule.href || sheetHref,
|
|
3275
|
+
fetchCache,
|
|
3276
|
+
enhancementJobs,
|
|
3277
|
+
pieces
|
|
3278
|
+
)
|
|
3279
|
+
for (var k = 0; k < importedChildren.length; k++) {
|
|
3280
|
+
nodes.push(importedChildren[k])
|
|
3281
|
+
}
|
|
3282
|
+
} catch (error) {
|
|
3283
|
+
// cross-origin @import — best-effort, same as other
|
|
3284
|
+
// unreachable cross-origin CSS.
|
|
3285
|
+
}
|
|
3286
|
+
} else if (isFontFaceRule(rule)) {
|
|
3287
|
+
var index = pieces.length
|
|
3288
|
+
pieces.push(rule.cssText)
|
|
3289
|
+
enhancementJobs.push(
|
|
3290
|
+
inlineCssUrls(
|
|
3291
|
+
rule.cssText,
|
|
3292
|
+
sheetHref || document.baseURI,
|
|
3293
|
+
fetchCache
|
|
3294
|
+
).then(function (text) {
|
|
3295
|
+
pieces[index] = text
|
|
3296
|
+
})
|
|
3297
|
+
)
|
|
3298
|
+
nodes.push({ kind: "slot", index: index })
|
|
3299
|
+
} else if (isStyleLikeLeaf(rule)) {
|
|
3300
|
+
nodes.push(rule.cssText)
|
|
3301
|
+
} else if (isGroupingRule(rule)) {
|
|
3302
|
+
var groupChildren = flattenStyleRules(
|
|
3303
|
+
rule.cssRules,
|
|
3304
|
+
sheetHref,
|
|
3305
|
+
fetchCache,
|
|
3306
|
+
enhancementJobs,
|
|
3307
|
+
pieces
|
|
3308
|
+
)
|
|
3309
|
+
nodes.push({
|
|
3310
|
+
kind: "group",
|
|
3311
|
+
header: headerTextOf(rule),
|
|
3312
|
+
children: groupChildren,
|
|
3313
|
+
})
|
|
3314
|
+
} else {
|
|
3315
|
+
nodes.push(rule.cssText)
|
|
3316
|
+
}
|
|
3317
|
+
})(rules[i])
|
|
3318
|
+
}
|
|
3319
|
+
return nodes
|
|
3091
3320
|
}
|
|
3092
3321
|
|
|
3093
3322
|
function fetchAsDataUrl(url) {
|
|
@@ -3108,6 +3337,168 @@
|
|
|
3108
3337
|
})
|
|
3109
3338
|
}
|
|
3110
3339
|
|
|
3340
|
+
function fetchAsDataUrlCors(url) {
|
|
3341
|
+
return fetch(url, { mode: "cors" })
|
|
3342
|
+
.then(function (response) {
|
|
3343
|
+
if (!response.ok) throw new Error("bad status")
|
|
3344
|
+
return response.blob()
|
|
3345
|
+
})
|
|
3346
|
+
.then(function (blob) {
|
|
3347
|
+
return new Promise(function (resolve, reject) {
|
|
3348
|
+
var reader = new FileReader()
|
|
3349
|
+
reader.onload = function () {
|
|
3350
|
+
resolve(String(reader.result))
|
|
3351
|
+
}
|
|
3352
|
+
reader.onerror = reject
|
|
3353
|
+
reader.readAsDataURL(blob)
|
|
3354
|
+
})
|
|
3355
|
+
})
|
|
3356
|
+
.catch(function () {
|
|
3357
|
+
return null
|
|
3358
|
+
})
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
function collectCssUrls(cssText) {
|
|
3362
|
+
var urls = []
|
|
3363
|
+
var seen = {}
|
|
3364
|
+
var match
|
|
3365
|
+
CSS_URL_RE.lastIndex = 0
|
|
3366
|
+
while ((match = CSS_URL_RE.exec(cssText))) {
|
|
3367
|
+
var raw = match[2]
|
|
3368
|
+
if (!raw || raw.indexOf("data:") === 0 || seen[raw]) continue
|
|
3369
|
+
seen[raw] = true
|
|
3370
|
+
urls.push(raw)
|
|
3371
|
+
}
|
|
3372
|
+
return urls
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
/** Replaces every `url(...)` in `cssText` with a `data:` URL, fetched
|
|
3376
|
+
* cross-origin and deduped via `fetchCache` (shared across calls so
|
|
3377
|
+
* the same font file is only ever fetched once). A URL that fails to
|
|
3378
|
+
* resolve or fetch is left as-is rather than aborting the rest. */
|
|
3379
|
+
function inlineCssUrls(cssText, baseHref, fetchCache) {
|
|
3380
|
+
var urls = collectCssUrls(cssText)
|
|
3381
|
+
if (!urls.length) return Promise.resolve(cssText)
|
|
3382
|
+
|
|
3383
|
+
var resolved = {}
|
|
3384
|
+
var jobs = []
|
|
3385
|
+
for (var i = 0; i < urls.length; i++) {
|
|
3386
|
+
;(function (raw) {
|
|
3387
|
+
var absolute
|
|
3388
|
+
try {
|
|
3389
|
+
absolute = new URL(raw, baseHref).href
|
|
3390
|
+
} catch (error) {
|
|
3391
|
+
return
|
|
3392
|
+
}
|
|
3393
|
+
if (!fetchCache[absolute]) {
|
|
3394
|
+
fetchCache[absolute] = fetchAsDataUrlCors(absolute)
|
|
3395
|
+
}
|
|
3396
|
+
jobs.push(
|
|
3397
|
+
fetchCache[absolute].then(function (dataUrl) {
|
|
3398
|
+
resolved[absolute] = dataUrl
|
|
3399
|
+
})
|
|
3400
|
+
)
|
|
3401
|
+
})(urls[i])
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
return Promise.all(jobs).then(function () {
|
|
3405
|
+
return cssText.replace(CSS_URL_RE, function (full, quote, raw) {
|
|
3406
|
+
if (!raw || raw.indexOf("data:") === 0) return full
|
|
3407
|
+
var absolute
|
|
3408
|
+
try {
|
|
3409
|
+
absolute = new URL(raw, baseHref).href
|
|
3410
|
+
} catch (error) {
|
|
3411
|
+
return full
|
|
3412
|
+
}
|
|
3413
|
+
var dataUrl = resolved[absolute]
|
|
3414
|
+
return dataUrl ? "url(" + dataUrl + ")" : full
|
|
3415
|
+
})
|
|
3416
|
+
})
|
|
3417
|
+
}
|
|
3418
|
+
|
|
3419
|
+
/**
|
|
3420
|
+
* CSS text for the capture clone: same-origin sheets have their
|
|
3421
|
+
* `@media` flattened against the LIVE viewport (see `flattenStyleRules`)
|
|
3422
|
+
* and `:root` rewritten to `html`, because inside the rasterized SVG
|
|
3423
|
+
* the root element is the `<svg>`, not `<html>`. Cross-origin sheets
|
|
3424
|
+
* throw on `cssRules`, so their raw text is fetched instead and every
|
|
3425
|
+
* `url(...)` inlined as a `data:` URL (same-origin `@font-face` rules
|
|
3426
|
+
* get the same treatment); this fetching work is best-effort and
|
|
3427
|
+
* bounded by `CSS_INLINE_TIMEOUT_MS` so a slow or unreachable font
|
|
3428
|
+
* never delays the capture — rules still pending when the budget runs
|
|
3429
|
+
* out simply keep their original (uninlined) `url(...)`.
|
|
3430
|
+
*/
|
|
3431
|
+
function collectCaptureCss() {
|
|
3432
|
+
var fetchCache = {}
|
|
3433
|
+
var pieces = []
|
|
3434
|
+
var enhancementJobs = []
|
|
3435
|
+
var topNodes = []
|
|
3436
|
+
|
|
3437
|
+
for (var i = 0; i < document.styleSheets.length; i++) {
|
|
3438
|
+
;(function (sheet) {
|
|
3439
|
+
var rules = null
|
|
3440
|
+
try {
|
|
3441
|
+
rules = sheet.cssRules
|
|
3442
|
+
} catch (error) {
|
|
3443
|
+
rules = null
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
if (rules) {
|
|
3447
|
+
var nodes = flattenStyleRules(
|
|
3448
|
+
rules,
|
|
3449
|
+
sheet.href,
|
|
3450
|
+
fetchCache,
|
|
3451
|
+
enhancementJobs,
|
|
3452
|
+
pieces
|
|
3453
|
+
)
|
|
3454
|
+
for (var n = 0; n < nodes.length; n++) topNodes.push(nodes[n])
|
|
3455
|
+
return
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3458
|
+
if (!sheet.href) return
|
|
3459
|
+
|
|
3460
|
+
var index = pieces.length
|
|
3461
|
+
pieces.push("")
|
|
3462
|
+
topNodes.push({ kind: "slot", index: index })
|
|
3463
|
+
enhancementJobs.push(
|
|
3464
|
+
fetch(sheet.href, { mode: "cors" })
|
|
3465
|
+
.then(function (response) {
|
|
3466
|
+
if (!response.ok) throw new Error("bad status")
|
|
3467
|
+
return response.text()
|
|
3468
|
+
})
|
|
3469
|
+
.then(function (text) {
|
|
3470
|
+
return inlineCssUrls(text, sheet.href, fetchCache)
|
|
3471
|
+
})
|
|
3472
|
+
.then(function (text) {
|
|
3473
|
+
// Best-effort only: this raw fetched text is not run back
|
|
3474
|
+
// through flattenStyleRules, so any @media/@layer nesting
|
|
3475
|
+
// inside a blocked cross-origin sheet is left unflattened.
|
|
3476
|
+
pieces[index] = text
|
|
3477
|
+
})
|
|
3478
|
+
.catch(function () {
|
|
3479
|
+
// cross-origin sheet unreachable — left blank, same as
|
|
3480
|
+
// the previous best-effort behavior.
|
|
3481
|
+
})
|
|
3482
|
+
)
|
|
3483
|
+
})(document.styleSheets[i])
|
|
3484
|
+
}
|
|
3485
|
+
|
|
3486
|
+
return withTimeout(
|
|
3487
|
+
Promise.all(enhancementJobs),
|
|
3488
|
+
CSS_INLINE_TIMEOUT_MS,
|
|
3489
|
+
null
|
|
3490
|
+
).then(function () {
|
|
3491
|
+
return renderNodes(topNodes, pieces).replace(/:root\b/g, "html")
|
|
3492
|
+
})
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
// Test hook only — lets inspector.annotation.test.ts assert on the
|
|
3496
|
+
// collected CSS text directly, since the full capture pipeline needs a
|
|
3497
|
+
// browser Image decoder no test DOM provides.
|
|
3498
|
+
if (window.__ARCHIE_INSPECTOR__) {
|
|
3499
|
+
window.__ARCHIE_INSPECTOR__.collectCaptureCss = collectCaptureCss
|
|
3500
|
+
}
|
|
3501
|
+
|
|
3111
3502
|
/** Rasterizing an SVG cannot fetch subresources, so same-origin <img>
|
|
3112
3503
|
* sources are inlined first. Failures leave the image blank rather
|
|
3113
3504
|
* than aborting the whole capture. */
|
|
@@ -3141,15 +3532,22 @@
|
|
|
3141
3532
|
|
|
3142
3533
|
/**
|
|
3143
3534
|
* Rasterizes the CURRENT viewport into a PNG data URL via an
|
|
3144
|
-
* `<svg><foreignObject>` round-trip. Best-effort by construction:
|
|
3145
|
-
*
|
|
3146
|
-
*
|
|
3147
|
-
* the drawing
|
|
3535
|
+
* `<svg><foreignObject>` round-trip. Best-effort by construction:
|
|
3536
|
+
* cross-origin images and <canvas> pixels do not survive it, and web
|
|
3537
|
+
* fonts survive only within `CSS_INLINE_TIMEOUT_MS`. The host composes
|
|
3538
|
+
* the result with the user's drawing, and falls back to the drawing
|
|
3539
|
+
* alone when this rejects.
|
|
3148
3540
|
*/
|
|
3149
3541
|
function captureViewportDataUrl(pixelRatio) {
|
|
3150
3542
|
var width = window.innerWidth
|
|
3151
3543
|
var height = window.innerHeight
|
|
3152
3544
|
var ratio = Math.min(Math.max(Number(pixelRatio) || 1, 1), 2)
|
|
3545
|
+
// The rasterized SVG's root element is the `<svg>` itself, not
|
|
3546
|
+
// `<html>`, so `rem` would otherwise resolve against the UA default
|
|
3547
|
+
// instead of the live page's root font-size.
|
|
3548
|
+
var rootFontSize = window.getComputedStyle(
|
|
3549
|
+
document.documentElement
|
|
3550
|
+
).fontSize
|
|
3153
3551
|
|
|
3154
3552
|
var clone = document.documentElement.cloneNode(true)
|
|
3155
3553
|
var strip = clone.querySelectorAll(
|
|
@@ -3159,75 +3557,79 @@
|
|
|
3159
3557
|
if (strip[i].parentNode) strip[i].parentNode.removeChild(strip[i])
|
|
3160
3558
|
}
|
|
3161
3559
|
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
settled =
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
var canvas = document.createElement("canvas")
|
|
3213
|
-
canvas.width = Math.round(width * ratio)
|
|
3214
|
-
canvas.height = Math.round(height * ratio)
|
|
3215
|
-
var context = canvas.getContext("2d")
|
|
3216
|
-
context.fillStyle = "#ffffff"
|
|
3217
|
-
context.fillRect(0, 0, canvas.width, canvas.height)
|
|
3218
|
-
context.drawImage(image, 0, 0, canvas.width, canvas.height)
|
|
3219
|
-
finish(resolve, canvas.toDataURL("image/png"))
|
|
3220
|
-
} catch (error) {
|
|
3221
|
-
finish(reject, error)
|
|
3560
|
+
return Promise.all([collectCaptureCss(), inlineImages(clone)]).then(
|
|
3561
|
+
function (results) {
|
|
3562
|
+
var styleEl = document.createElement("style")
|
|
3563
|
+
styleEl.textContent = results[0]
|
|
3564
|
+
var head = clone.querySelector("head")
|
|
3565
|
+
if (head) head.appendChild(styleEl)
|
|
3566
|
+
else clone.insertBefore(styleEl, clone.firstChild)
|
|
3567
|
+
|
|
3568
|
+
var markup = new XMLSerializer().serializeToString(clone)
|
|
3569
|
+
var svg =
|
|
3570
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="' +
|
|
3571
|
+
width +
|
|
3572
|
+
'" height="' +
|
|
3573
|
+
height +
|
|
3574
|
+
'" style="font-size:' +
|
|
3575
|
+
rootFontSize +
|
|
3576
|
+
'">' +
|
|
3577
|
+
'<foreignObject x="0" y="0" width="' +
|
|
3578
|
+
width +
|
|
3579
|
+
'" height="' +
|
|
3580
|
+
height +
|
|
3581
|
+
'">' +
|
|
3582
|
+
'<div xmlns="http://www.w3.org/1999/xhtml" style="width:' +
|
|
3583
|
+
width +
|
|
3584
|
+
"px;transform:translate(" +
|
|
3585
|
+
-window.scrollX +
|
|
3586
|
+
"px," +
|
|
3587
|
+
-window.scrollY +
|
|
3588
|
+
'px)">' +
|
|
3589
|
+
markup +
|
|
3590
|
+
"</div></foreignObject></svg>"
|
|
3591
|
+
|
|
3592
|
+
return new Promise(function (resolve, reject) {
|
|
3593
|
+
var image = new Image()
|
|
3594
|
+
// An SVG the browser refuses to decode can fire NEITHER onload nor
|
|
3595
|
+
// onerror (malformed serialization, blocked subresource). Without
|
|
3596
|
+
// this guard the host would sit on the request until its own
|
|
3597
|
+
// timeout with no diagnostic; the deadline turns that into an
|
|
3598
|
+
// explicit VIEWPORT_CAPTURE_FAILED.
|
|
3599
|
+
var settled = false
|
|
3600
|
+
var deadline = setTimeout(function () {
|
|
3601
|
+
if (settled) return
|
|
3602
|
+
settled = true
|
|
3603
|
+
reject(new Error("svg rasterization timed out"))
|
|
3604
|
+
}, ANNOTATION_CAPTURE_TIMEOUT_MS)
|
|
3605
|
+
var finish = function (fn, value) {
|
|
3606
|
+
if (settled) return
|
|
3607
|
+
settled = true
|
|
3608
|
+
clearTimeout(deadline)
|
|
3609
|
+
fn(value)
|
|
3222
3610
|
}
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3611
|
+
image.onload = function () {
|
|
3612
|
+
try {
|
|
3613
|
+
var canvas = document.createElement("canvas")
|
|
3614
|
+
canvas.width = Math.round(width * ratio)
|
|
3615
|
+
canvas.height = Math.round(height * ratio)
|
|
3616
|
+
var context = canvas.getContext("2d")
|
|
3617
|
+
context.fillStyle = "#ffffff"
|
|
3618
|
+
context.fillRect(0, 0, canvas.width, canvas.height)
|
|
3619
|
+
context.drawImage(image, 0, 0, canvas.width, canvas.height)
|
|
3620
|
+
finish(resolve, canvas.toDataURL("image/png"))
|
|
3621
|
+
} catch (error) {
|
|
3622
|
+
finish(reject, error)
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
image.onerror = function () {
|
|
3626
|
+
finish(reject, new Error("svg rasterization failed"))
|
|
3627
|
+
}
|
|
3628
|
+
image.src =
|
|
3629
|
+
"data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg)
|
|
3630
|
+
})
|
|
3631
|
+
}
|
|
3632
|
+
)
|
|
3231
3633
|
}
|
|
3232
3634
|
|
|
3233
3635
|
function extractMetadata(element) {
|