mirror of
https://github.com/musistudio/claude-code-router.git
synced 2026-08-29 03:12:10 +08:00
Fix deck-stage captures and Windows SDK path resolution
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { CONFIGDIR } from "@ccr/core/config/constants";
|
||||
import type {
|
||||
BotGatewayQrLoginCancelRequest,
|
||||
@@ -12,6 +11,7 @@ import type {
|
||||
BotGatewayQrLoginWaitResult,
|
||||
BotGatewayRuntimeConfig
|
||||
} from "@ccr/core/contracts/app";
|
||||
import { botGatewaySdkImportSpecifier } from "./sdk-import";
|
||||
|
||||
type BotGatewayClientWithRequest = {
|
||||
close?: () => Promise<void> | void;
|
||||
@@ -276,17 +276,6 @@ function resolveBundledBotGatewaySdkModule(): string {
|
||||
return candidates.find((candidate) => existsSync(candidate)) ?? "";
|
||||
}
|
||||
|
||||
function botGatewaySdkImportSpecifier(value: string): string {
|
||||
const trimmed = value.trim();
|
||||
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed)) {
|
||||
return trimmed;
|
||||
}
|
||||
if (path.isAbsolute(trimmed)) {
|
||||
return pathToFileURL(trimmed).href;
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
async function resolveWeixinQrIntegrationId(
|
||||
client: BotGatewayClientWithRequest,
|
||||
bot: BotGatewayRuntimeConfig,
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
const BOT_GATEWAY_SDK_PACKAGE = "@the-next-ai/bot-gateway-sdk";
|
||||
|
||||
export function botGatewaySdkImportSpecifier(value: string): string {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
return BOT_GATEWAY_SDK_PACKAGE;
|
||||
}
|
||||
if (path.isAbsolute(trimmed)) {
|
||||
return pathToFileURL(trimmed).href;
|
||||
}
|
||||
if (path.win32.isAbsolute(trimmed)) {
|
||||
return pathToFileURL(trimmed, { windows: true }).href;
|
||||
}
|
||||
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed)) {
|
||||
return trimmed;
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
@@ -5678,8 +5678,9 @@ function bundledBotGatewaySdkModule() {
|
||||
function botGatewaySdkImportSpecifier(value) {
|
||||
const trimmed = String(value || "").trim();
|
||||
if (!trimmed) return "@the-next-ai/bot-gateway-sdk";
|
||||
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed)) return trimmed;
|
||||
if (path.isAbsolute(trimmed)) return pathToFileURL(trimmed).href;
|
||||
if (path.win32.isAbsolute(trimmed)) return pathToFileURL(trimmed, { windows: true }).href;
|
||||
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed)) return trimmed;
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSy
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import test from "node:test";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { codexCliMiddlewareRuntimeScript } from "@ccr/core/agents/codex/cli-middleware-runtime.ts";
|
||||
|
||||
test("generated Codex CLI middleware runtime is valid JavaScript", () => {
|
||||
@@ -13,6 +14,18 @@ test("generated Codex CLI middleware runtime is valid JavaScript", () => {
|
||||
execFileSync(process.execPath, ["--check", file], { stdio: "pipe" });
|
||||
});
|
||||
|
||||
test("generated Codex CLI middleware converts Windows SDK paths before URL scheme detection", () => {
|
||||
const fn = evaluateRuntimeFunction("botGatewaySdkImportSpecifier");
|
||||
const windowsPath = "C:\\Users\\macao\\AppData\\Local\\Programs\\Claude Code Router\\resources\\app.asar\\dist\\main\\bot-gateway-sdk\\dist\\index.js";
|
||||
|
||||
assert.equal(
|
||||
fn(windowsPath),
|
||||
"file:///C:/Users/macao/AppData/Local/Programs/Claude%20Code%20Router/resources/app.asar/dist/main/bot-gateway-sdk/dist/index.js"
|
||||
);
|
||||
assert.equal(fn("file:///tmp/sdk/index.js"), "file:///tmp/sdk/index.js");
|
||||
assert.equal(fn("@the-next-ai/bot-gateway-sdk"), "@the-next-ai/bot-gateway-sdk");
|
||||
});
|
||||
|
||||
test("Codex app-server uses ChatGPT's bundled Node as a signed supervisor", { skip: process.platform !== "darwin" }, () => {
|
||||
const dir = mkdtempSync(path.join(os.tmpdir(), "ccr-runtime-signed-supervisor-"));
|
||||
const runtimeFile = writeRuntimeScript(dir);
|
||||
@@ -1042,6 +1055,33 @@ function writeRuntimeScript(dir) {
|
||||
return file;
|
||||
}
|
||||
|
||||
function evaluateRuntimeFunction(name) {
|
||||
const source = extractRuntimeFunctionSource(codexCliMiddlewareRuntimeScript(), name);
|
||||
return Function("path", "pathToFileURL", `${source}; return ${name};`)(path, pathToFileURL);
|
||||
}
|
||||
|
||||
function extractRuntimeFunctionSource(source, name) {
|
||||
const start = source.indexOf(`function ${name}(`);
|
||||
assert.notEqual(start, -1);
|
||||
const openBrace = source.indexOf("{", start);
|
||||
assert.notEqual(openBrace, -1);
|
||||
|
||||
let depth = 0;
|
||||
for (let index = openBrace; index < source.length; index += 1) {
|
||||
const character = source[index];
|
||||
if (character === "{") {
|
||||
depth += 1;
|
||||
} else if (character === "}") {
|
||||
depth -= 1;
|
||||
if (depth === 0) {
|
||||
return source.slice(start, index + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Unable to extract runtime function ${name}.`);
|
||||
}
|
||||
|
||||
function writeFakeClaudeCli(dir) {
|
||||
const fakeCli = path.join(dir, "fake-claude");
|
||||
const outputFile = path.join(dir, "fake-claude-output.json");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { botGatewayProfileEnv } from "@ccr/core/agents/bot-gateway/env.ts";
|
||||
import { botGatewaySdkImportSpecifier } from "@ccr/core/agents/bot-gateway/sdk-import.ts";
|
||||
|
||||
function botGateway(overrides = {}) {
|
||||
return {
|
||||
@@ -129,3 +130,14 @@ test("botGatewayProfileEnv defaults iMessage to local auth", () => {
|
||||
assert.equal(env.CCR_BOT_GATEWAY_AUTH_TYPE, "local");
|
||||
assert.equal(JSON.parse(env.CCR_BOT_GATEWAY_CONFIG_JSON).transport, "websocket");
|
||||
});
|
||||
|
||||
test("botGatewaySdkImportSpecifier converts Windows absolute paths before URL scheme detection", () => {
|
||||
const windowsPath = "C:\\Users\\macao\\AppData\\Local\\Programs\\Claude Code Router\\resources\\app.asar\\dist\\main\\bot-gateway-sdk\\dist\\index.js";
|
||||
|
||||
assert.equal(
|
||||
botGatewaySdkImportSpecifier(windowsPath),
|
||||
"file:///C:/Users/macao/AppData/Local/Programs/Claude%20Code%20Router/resources/app.asar/dist/main/bot-gateway-sdk/dist/index.js"
|
||||
);
|
||||
assert.equal(botGatewaySdkImportSpecifier("file:///tmp/sdk/index.js"), "file:///tmp/sdk/index.js");
|
||||
assert.equal(botGatewaySdkImportSpecifier("@the-next-ai/bot-gateway-sdk"), "@the-next-ai/bot-gateway-sdk");
|
||||
});
|
||||
|
||||
@@ -9960,7 +9960,30 @@ function serveProjectFileResponse(runtime, projectId, row, filePath) {
|
||||
const previewHtml = injectOmelettePreviewScripts(dependencySafeHtml, headers["x-ccr-preview-version"], previewPollUrl(projectId, filePath));
|
||||
return textResponse(200, previewHtml, headers);
|
||||
}
|
||||
return binaryResponse(200, body, headers);
|
||||
return binaryResponse(200, patchClaudeDesignProjectFileBody(body, filePath, contentType), headers);
|
||||
}
|
||||
|
||||
function patchClaudeDesignProjectFileBody(body, filePath, contentType) {
|
||||
if (!isDeckStageProjectScript(filePath, contentType)) {
|
||||
return body;
|
||||
}
|
||||
const source = body.toString("utf8");
|
||||
const patched = patchDeckStageThumbnailCloneVisibility(source);
|
||||
return patched === source ? body : Buffer.from(patched, "utf8");
|
||||
}
|
||||
|
||||
function isDeckStageProjectScript(filePath, contentType) {
|
||||
return /(?:^|\/)deck-stage\.js$/i.test(String(filePath || "")) &&
|
||||
(headerIncludes(contentType, "javascript") || headerIncludes(contentType, "text/plain") || !contentType);
|
||||
}
|
||||
|
||||
function patchDeckStageThumbnailCloneVisibility(source) {
|
||||
const current = "box-sizing:border-box;overflow:hidden;visibility:visible;opacity:1;";
|
||||
const fixed = "box-sizing:border-box;overflow:hidden;display:block!important;visibility:visible!important;opacity:1!important;";
|
||||
if (source.includes(fixed)) {
|
||||
return source;
|
||||
}
|
||||
return source.replace(current, fixed);
|
||||
}
|
||||
|
||||
function isHtmlProjectFile(filePath, contentType) {
|
||||
@@ -15013,6 +15036,418 @@ try {
|
||||
var frameIds = typeof WeakMap === 'function' ? new WeakMap() : null;
|
||||
var nextFrameId = 1;
|
||||
var blankPng = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=';
|
||||
function dataUrlToBytes(dataUrl) {
|
||||
try {
|
||||
var base64 = String(dataUrl || blankPng).split(',', 2)[1] || '';
|
||||
var raw = atob(base64);
|
||||
var bytes = new Uint8Array(raw.length);
|
||||
for (var i = 0; i < raw.length; i++) bytes[i] = raw.charCodeAt(i);
|
||||
return bytes;
|
||||
} catch (e) {
|
||||
return new Uint8Array(0);
|
||||
}
|
||||
}
|
||||
function nativeImageFromDataUrl(dataUrl) {
|
||||
var url = dataUrl || blankPng;
|
||||
return {
|
||||
toDataURL: function() { return url; },
|
||||
toPNG: function() { return dataUrlToBytes(url); }
|
||||
};
|
||||
}
|
||||
function serializableCaptureRect(rect) {
|
||||
return {
|
||||
height: Number(rect && rect.height) || 0,
|
||||
width: Number(rect && rect.width) || 0,
|
||||
x: Number(rect && rect.x) || 0,
|
||||
y: Number(rect && rect.y) || 0
|
||||
};
|
||||
}
|
||||
function isCapturedPngDataUrl(value) {
|
||||
return typeof value === 'string' && /^data:image\\/png;base64,/i.test(value) && value !== blankPng;
|
||||
}
|
||||
function numericRectValue(rect, key, fallback) {
|
||||
var value = rect && Number(rect[key]);
|
||||
return Number.isFinite(value) && value > 0 ? value : fallback;
|
||||
}
|
||||
function waitForFramePaint(win) {
|
||||
return new Promise(function(resolve) {
|
||||
try {
|
||||
var raf = win && win.requestAnimationFrame ? win.requestAnimationFrame.bind(win) : root.requestAnimationFrame && root.requestAnimationFrame.bind(root);
|
||||
if (!raf) {
|
||||
setTimeout(resolve, 0);
|
||||
return;
|
||||
}
|
||||
raf(function() { raf(function() { resolve(); }); });
|
||||
} catch (e) {
|
||||
setTimeout(resolve, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
function frameCaptureViewport(frame, win, doc, rect) {
|
||||
var rootElement = doc && doc.documentElement;
|
||||
var body = doc && doc.body;
|
||||
var frameRect;
|
||||
try { frameRect = frame.getBoundingClientRect(); } catch (e) { frameRect = null; }
|
||||
var viewportWidth = Math.max(
|
||||
1,
|
||||
Math.round(
|
||||
Number(win && win.innerWidth) ||
|
||||
Number(rootElement && rootElement.clientWidth) ||
|
||||
Number(body && body.clientWidth) ||
|
||||
Number(frame && frame.clientWidth) ||
|
||||
Number(frameRect && frameRect.width) ||
|
||||
1280
|
||||
)
|
||||
);
|
||||
var viewportHeight = Math.max(
|
||||
1,
|
||||
Math.round(
|
||||
Number(win && win.innerHeight) ||
|
||||
Number(rootElement && rootElement.clientHeight) ||
|
||||
Number(body && body.clientHeight) ||
|
||||
Number(frame && frame.clientHeight) ||
|
||||
Number(frameRect && frameRect.height) ||
|
||||
720
|
||||
)
|
||||
);
|
||||
var pageWidth = Math.max(
|
||||
viewportWidth,
|
||||
Math.round(Number(rootElement && rootElement.scrollWidth) || 0),
|
||||
Math.round(Number(body && body.scrollWidth) || 0)
|
||||
);
|
||||
var pageHeight = Math.max(
|
||||
viewportHeight,
|
||||
Math.round(Number(rootElement && rootElement.scrollHeight) || 0),
|
||||
Math.round(Number(body && body.scrollHeight) || 0)
|
||||
);
|
||||
var x = Math.max(0, Math.round(Number(rect && rect.x) || 0));
|
||||
var y = Math.max(0, Math.round(Number(rect && rect.y) || 0));
|
||||
var width = Math.max(1, Math.round(numericRectValue(rect, 'width', viewportWidth - x)));
|
||||
var height = Math.max(1, Math.round(numericRectValue(rect, 'height', viewportHeight - y)));
|
||||
return {
|
||||
height: Math.min(height, Math.max(1, pageHeight - y)),
|
||||
pageHeight: pageHeight,
|
||||
pageWidth: pageWidth,
|
||||
width: Math.min(width, Math.max(1, pageWidth - x)),
|
||||
x: x,
|
||||
y: y
|
||||
};
|
||||
}
|
||||
function prepareCaptureClone(doc, size) {
|
||||
var clone = doc.documentElement.cloneNode(true);
|
||||
clone.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
|
||||
try {
|
||||
var scripts = clone.querySelectorAll('script');
|
||||
for (var i = 0; i < scripts.length; i++) scripts[i].remove();
|
||||
} catch (e) {}
|
||||
var head = clone.querySelector('head');
|
||||
if (!head) {
|
||||
head = doc.createElement('head');
|
||||
clone.insertBefore(head, clone.firstChild);
|
||||
}
|
||||
var base = doc.createElement('base');
|
||||
base.setAttribute('href', doc.baseURI || doc.location && doc.location.href || '');
|
||||
head.insertBefore(base, head.firstChild);
|
||||
var style = doc.createElement('style');
|
||||
style.textContent = 'html,body{margin:0!important;width:' + size.pageWidth + 'px!important;min-width:' + size.pageWidth + 'px!important;height:' + size.pageHeight + 'px!important;min-height:' + size.pageHeight + 'px!important;overflow:hidden!important;}*{animation:none!important;transition:none!important;}';
|
||||
head.appendChild(style);
|
||||
return clone;
|
||||
}
|
||||
function capturedStyleSheetText(sheet) {
|
||||
try {
|
||||
var rules = sheet && sheet.cssRules;
|
||||
if (!rules) return '';
|
||||
var text = '';
|
||||
for (var i = 0; i < rules.length; i++) {
|
||||
text += rules[i].cssText + '\\n';
|
||||
}
|
||||
return text;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
function capturedShadowStyleText(shadowRoot) {
|
||||
var text = '';
|
||||
try {
|
||||
var sheets = shadowRoot && shadowRoot.adoptedStyleSheets || [];
|
||||
for (var i = 0; i < sheets.length; i++) {
|
||||
text += capturedStyleSheetText(sheets[i]);
|
||||
}
|
||||
} catch (e) {}
|
||||
return text
|
||||
.replace(/:host\\(([^)]*)\\)/g, '[data-ccr-shadow-host]$1')
|
||||
.replace(/:host\\b/g, '[data-ccr-shadow-host]');
|
||||
}
|
||||
function appendCapturedShadowStyle(shadowRoot, cloneHost, doc) {
|
||||
var text = capturedShadowStyleText(shadowRoot);
|
||||
if (!text) return;
|
||||
var style = doc.createElement('style');
|
||||
style.setAttribute('data-ccr-captured-shadow-styles', 'true');
|
||||
style.textContent = text;
|
||||
cloneHost.appendChild(style);
|
||||
}
|
||||
function inlineOpenShadowRoots(sourceNode, cloneNode, doc) {
|
||||
if (!sourceNode || !cloneNode) return;
|
||||
if (sourceNode.nodeType === 1 && sourceNode.shadowRoot) {
|
||||
while (cloneNode.firstChild) cloneNode.removeChild(cloneNode.firstChild);
|
||||
try { cloneNode.setAttribute('data-ccr-shadow-host', 'true'); } catch (e) {}
|
||||
appendCapturedShadowStyle(sourceNode.shadowRoot, cloneNode, doc);
|
||||
var shadowChildren = Array.prototype.slice.call(sourceNode.shadowRoot.childNodes || []);
|
||||
var shadowClones = [];
|
||||
for (var i = 0; i < shadowChildren.length; i++) {
|
||||
var shadowClone = shadowChildren[i].cloneNode(true);
|
||||
shadowClones.push(shadowClone);
|
||||
cloneNode.appendChild(shadowClone);
|
||||
}
|
||||
for (var j = 0; j < shadowChildren.length; j++) {
|
||||
inlineOpenShadowRoots(shadowChildren[j], shadowClones[j], doc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var sourceChildren = Array.prototype.slice.call(sourceNode.childNodes || []);
|
||||
var cloneChildren = Array.prototype.slice.call(cloneNode.childNodes || []);
|
||||
var count = Math.min(sourceChildren.length, cloneChildren.length);
|
||||
for (var k = 0; k < count; k++) {
|
||||
inlineOpenShadowRoots(sourceChildren[k], cloneChildren[k], doc);
|
||||
}
|
||||
}
|
||||
function captureFrameDataUrl(frame, rect) {
|
||||
return captureFrameDataUrlFromParent(frame, rect).then(function(dataUrl) {
|
||||
if (isCapturedPngDataUrl(dataUrl)) return dataUrl;
|
||||
return captureFrameDataUrlViaEval(frame, rect).then(function(evalDataUrl) {
|
||||
return isCapturedPngDataUrl(evalDataUrl) ? evalDataUrl : dataUrl;
|
||||
}, function() {
|
||||
return dataUrl;
|
||||
});
|
||||
}, function() {
|
||||
return captureFrameDataUrlViaEval(frame, rect);
|
||||
});
|
||||
}
|
||||
function captureFrameDataUrlViaEval(frame, rect) {
|
||||
var code = '(' + captureCurrentDocumentDataUrl.toString() + ')(' +
|
||||
JSON.stringify(serializableCaptureRect(rect || {})) + ',' +
|
||||
JSON.stringify(blankPng) +
|
||||
')';
|
||||
return postEval(frame, code, 5000);
|
||||
}
|
||||
function captureFrameDataUrlFromParent(frame, rect) {
|
||||
return new Promise(function(resolve) {
|
||||
try {
|
||||
var win = frameSource(frame);
|
||||
var doc = win && win.document;
|
||||
if (!doc || !doc.documentElement) {
|
||||
resolve(blankPng);
|
||||
return;
|
||||
}
|
||||
var size = frameCaptureViewport(frame, win, doc, rect || {});
|
||||
var clone = prepareCaptureClone(doc, size);
|
||||
inlineOpenShadowRoots(doc.documentElement, clone, doc);
|
||||
var serialized = new XMLSerializer().serializeToString(clone);
|
||||
var svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' + size.width + '" height="' + size.height + '" viewBox="0 0 ' + size.width + ' ' + size.height + '"><foreignObject x="' + (-size.x) + '" y="' + (-size.y) + '" width="' + size.pageWidth + '" height="' + size.pageHeight + '">' + serialized + '</foreignObject></svg>';
|
||||
var image = new Image();
|
||||
var settled = false;
|
||||
function finish(value) {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
resolve(value || blankPng);
|
||||
}
|
||||
var timer = setTimeout(function() { finish(blankPng); }, 3000);
|
||||
image.onload = function() {
|
||||
clearTimeout(timer);
|
||||
try {
|
||||
var scale = Math.max(1, Math.min(2, Number(root.devicePixelRatio) || 1));
|
||||
var maxPixels = 2000000;
|
||||
while (size.width * size.height * scale * scale > maxPixels && scale > 1) {
|
||||
scale = Math.max(1, scale / 2);
|
||||
}
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = Math.max(1, Math.round(size.width * scale));
|
||||
canvas.height = Math.max(1, Math.round(size.height * scale));
|
||||
var ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
finish(blankPng);
|
||||
return;
|
||||
}
|
||||
ctx.scale(scale, scale);
|
||||
ctx.drawImage(image, 0, 0);
|
||||
finish(canvas.toDataURL('image/png'));
|
||||
} catch (e) {
|
||||
finish(blankPng);
|
||||
}
|
||||
};
|
||||
image.onerror = function() {
|
||||
clearTimeout(timer);
|
||||
finish(blankPng);
|
||||
};
|
||||
image.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg);
|
||||
} catch (e) {
|
||||
resolve(blankPng);
|
||||
}
|
||||
});
|
||||
}
|
||||
function captureCurrentDocumentDataUrl(rect, fallbackPng) {
|
||||
return new Promise(function(resolve) {
|
||||
try {
|
||||
function cssTextFromStyleSheet(sheet) {
|
||||
try {
|
||||
var rules = sheet && sheet.cssRules;
|
||||
if (!rules) return '';
|
||||
var text = '';
|
||||
for (var i = 0; i < rules.length; i++) {
|
||||
text += rules[i].cssText + '\\n';
|
||||
}
|
||||
return text;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
function shadowStyleText(shadowRoot) {
|
||||
var text = '';
|
||||
try {
|
||||
var sheets = shadowRoot && shadowRoot.adoptedStyleSheets || [];
|
||||
for (var i = 0; i < sheets.length; i++) {
|
||||
text += cssTextFromStyleSheet(sheets[i]);
|
||||
}
|
||||
} catch (e) {}
|
||||
return text
|
||||
.replace(/:host\\(([^)]*)\\)/g, '[data-ccr-shadow-host]$1')
|
||||
.replace(/:host\\b/g, '[data-ccr-shadow-host]');
|
||||
}
|
||||
function appendShadowStyle(shadowRoot, cloneHost) {
|
||||
var text = shadowStyleText(shadowRoot);
|
||||
if (!text) return;
|
||||
var style = doc.createElement('style');
|
||||
style.setAttribute('data-ccr-captured-shadow-styles', 'true');
|
||||
style.textContent = text;
|
||||
cloneHost.appendChild(style);
|
||||
}
|
||||
function inlineShadows(sourceNode, cloneNode) {
|
||||
if (!sourceNode || !cloneNode) return;
|
||||
if (sourceNode.nodeType === 1 && sourceNode.shadowRoot) {
|
||||
while (cloneNode.firstChild) cloneNode.removeChild(cloneNode.firstChild);
|
||||
try { cloneNode.setAttribute('data-ccr-shadow-host', 'true'); } catch (e) {}
|
||||
appendShadowStyle(sourceNode.shadowRoot, cloneNode);
|
||||
var shadowChildren = Array.prototype.slice.call(sourceNode.shadowRoot.childNodes || []);
|
||||
var shadowClones = [];
|
||||
for (var i = 0; i < shadowChildren.length; i++) {
|
||||
var shadowClone = shadowChildren[i].cloneNode(true);
|
||||
shadowClones.push(shadowClone);
|
||||
cloneNode.appendChild(shadowClone);
|
||||
}
|
||||
for (var j = 0; j < shadowChildren.length; j++) {
|
||||
inlineShadows(shadowChildren[j], shadowClones[j]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var sourceChildren = Array.prototype.slice.call(sourceNode.childNodes || []);
|
||||
var cloneChildren = Array.prototype.slice.call(cloneNode.childNodes || []);
|
||||
var count = Math.min(sourceChildren.length, cloneChildren.length);
|
||||
for (var k = 0; k < count; k++) {
|
||||
inlineShadows(sourceChildren[k], cloneChildren[k]);
|
||||
}
|
||||
}
|
||||
var doc = document;
|
||||
var rootElement = doc && doc.documentElement;
|
||||
var body = doc && doc.body;
|
||||
if (!rootElement) {
|
||||
resolve(fallbackPng);
|
||||
return;
|
||||
}
|
||||
var viewportWidth = Math.max(
|
||||
1,
|
||||
Math.round(
|
||||
Number(window.innerWidth) ||
|
||||
Number(rootElement.clientWidth) ||
|
||||
Number(body && body.clientWidth) ||
|
||||
1280
|
||||
)
|
||||
);
|
||||
var viewportHeight = Math.max(
|
||||
1,
|
||||
Math.round(
|
||||
Number(window.innerHeight) ||
|
||||
Number(rootElement.clientHeight) ||
|
||||
Number(body && body.clientHeight) ||
|
||||
720
|
||||
)
|
||||
);
|
||||
var pageWidth = Math.max(
|
||||
viewportWidth,
|
||||
Math.round(Number(rootElement.scrollWidth) || 0),
|
||||
Math.round(Number(body && body.scrollWidth) || 0)
|
||||
);
|
||||
var pageHeight = Math.max(
|
||||
viewportHeight,
|
||||
Math.round(Number(rootElement.scrollHeight) || 0),
|
||||
Math.round(Number(body && body.scrollHeight) || 0)
|
||||
);
|
||||
var x = Math.max(0, Math.round(Number(rect && rect.x) || 0));
|
||||
var y = Math.max(0, Math.round(Number(rect && rect.y) || 0));
|
||||
var width = Math.max(1, Math.round(Number(rect && rect.width) > 0 ? Number(rect.width) : viewportWidth - x));
|
||||
var height = Math.max(1, Math.round(Number(rect && rect.height) > 0 ? Number(rect.height) : viewportHeight - y));
|
||||
width = Math.min(width, Math.max(1, pageWidth - x));
|
||||
height = Math.min(height, Math.max(1, pageHeight - y));
|
||||
var clone = rootElement.cloneNode(true);
|
||||
clone.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
|
||||
try {
|
||||
var scripts = clone.querySelectorAll('script');
|
||||
for (var i = 0; i < scripts.length; i++) scripts[i].remove();
|
||||
} catch (e) {}
|
||||
var head = clone.querySelector('head');
|
||||
if (!head) {
|
||||
head = doc.createElement('head');
|
||||
clone.insertBefore(head, clone.firstChild);
|
||||
}
|
||||
var base = doc.createElement('base');
|
||||
base.setAttribute('href', doc.baseURI || location.href || '');
|
||||
head.insertBefore(base, head.firstChild);
|
||||
var style = doc.createElement('style');
|
||||
style.textContent = 'html,body{margin:0!important;width:' + pageWidth + 'px!important;min-width:' + pageWidth + 'px!important;height:' + pageHeight + 'px!important;min-height:' + pageHeight + 'px!important;overflow:hidden!important;}*{animation:none!important;transition:none!important;}';
|
||||
head.appendChild(style);
|
||||
inlineShadows(rootElement, clone);
|
||||
var serialized = new XMLSerializer().serializeToString(clone);
|
||||
var svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' + width + '" height="' + height + '" viewBox="0 0 ' + width + ' ' + height + '"><foreignObject x="' + (-x) + '" y="' + (-y) + '" width="' + pageWidth + '" height="' + pageHeight + '">' + serialized + '</foreignObject></svg>';
|
||||
var image = new Image();
|
||||
var settled = false;
|
||||
function finish(value) {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
resolve(value || fallbackPng);
|
||||
}
|
||||
var timer = setTimeout(function() { finish(fallbackPng); }, 3000);
|
||||
image.onload = function() {
|
||||
clearTimeout(timer);
|
||||
try {
|
||||
var scale = Math.max(1, Math.min(2, Number(window.devicePixelRatio) || 1));
|
||||
var maxPixels = 2000000;
|
||||
while (width * height * scale * scale > maxPixels && scale > 1) {
|
||||
scale = Math.max(1, scale / 2);
|
||||
}
|
||||
var canvas = doc.createElement('canvas');
|
||||
canvas.width = Math.max(1, Math.round(width * scale));
|
||||
canvas.height = Math.max(1, Math.round(height * scale));
|
||||
var ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
finish(fallbackPng);
|
||||
return;
|
||||
}
|
||||
ctx.scale(scale, scale);
|
||||
ctx.drawImage(image, 0, 0);
|
||||
finish(canvas.toDataURL('image/png'));
|
||||
} catch (e) {
|
||||
finish(fallbackPng);
|
||||
}
|
||||
};
|
||||
image.onerror = function() {
|
||||
clearTimeout(timer);
|
||||
finish(fallbackPng);
|
||||
};
|
||||
image.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg);
|
||||
} catch (e) {
|
||||
resolve(fallbackPng);
|
||||
}
|
||||
});
|
||||
}
|
||||
function isFrame(node) {
|
||||
if (!node || node.nodeType !== 1) return false;
|
||||
var tag = String(node.tagName || '').toLowerCase();
|
||||
@@ -15281,17 +15716,12 @@ try {
|
||||
};
|
||||
}
|
||||
if (typeof proto.capturePage !== 'function') {
|
||||
proto.capturePage = function() {
|
||||
return Promise.resolve({
|
||||
toDataURL: function() { return blankPng; },
|
||||
toPNG: function() {
|
||||
try {
|
||||
var raw = atob(blankPng.split(',')[1]);
|
||||
var bytes = new Uint8Array(raw.length);
|
||||
for (var i = 0; i < raw.length; i++) bytes[i] = raw.charCodeAt(i);
|
||||
return bytes;
|
||||
} catch (e) { return new Uint8Array(0); }
|
||||
}
|
||||
proto.capturePage = function(rect) {
|
||||
var frame = this;
|
||||
return waitForFramePaint(frameSource(frame)).then(function() {
|
||||
return captureFrameDataUrl(frame, rect);
|
||||
}).then(nativeImageFromDataUrl, function() {
|
||||
return nativeImageFromDataUrl(blankPng);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user