Skip to content

Commit 2f82462

Browse files
author
GitLab CI
committed
fix: web SDK screenshot - replace SVG foreignObject with lightweight canvas render
SVG foreignObject approach hung silently. New approach: renders DOM elements directly on canvas at 800x600 max, returns JPEG@60% (~50KB vs 1MB+). Avoids CORS/tainted canvas issues entirely.
1 parent 6e2488e commit 2f82462

File tree

1 file changed

+47
-61
lines changed

1 file changed

+47
-61
lines changed

sdks/web/flutter-skill-web.js

Lines changed: 47 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -171,68 +171,54 @@
171171
// --------------- Screenshot helpers ---------------
172172

173173
function screenshotFull() {
174-
return new Promise(function (resolve) {
175-
try {
176-
var w = window.innerWidth;
177-
var h = window.innerHeight;
178-
var dpr = window.devicePixelRatio || 1;
179-
var canvas = document.createElement('canvas');
180-
canvas.width = w * dpr;
181-
canvas.height = h * dpr;
182-
var ctx = canvas.getContext('2d');
183-
ctx.scale(dpr, dpr);
184-
185-
// Timeout: if SVG approach doesn't resolve in 3s, return blank canvas
186-
var resolved = false;
187-
var timer = setTimeout(function () {
188-
if (!resolved) {
189-
resolved = true;
190-
ctx.fillStyle = '#ffffff';
191-
ctx.fillRect(0, 0, w, h);
192-
ctx.fillStyle = '#000000';
193-
ctx.font = '16px sans-serif';
194-
ctx.fillText('Screenshot (timeout fallback) ' + w + 'x' + h, 20, 40);
195-
resolve(canvas.toDataURL('image/png').split(',')[1]);
196-
}
197-
}, 3000);
198-
199-
var data = new XMLSerializer().serializeToString(document.documentElement);
200-
var svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' + w + '" height="' + h + '">' +
201-
'<foreignObject width="100%" height="100%">' +
202-
'<div xmlns="http://www.w3.org/1999/xhtml">' + data + '</div>' +
203-
'</foreignObject></svg>';
204-
205-
var img = new Image();
206-
var blob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' });
207-
var url = URL.createObjectURL(blob);
208-
209-
img.onload = function () {
210-
if (!resolved) {
211-
resolved = true;
212-
clearTimeout(timer);
213-
ctx.drawImage(img, 0, 0);
214-
URL.revokeObjectURL(url);
215-
resolve(canvas.toDataURL('image/png').split(',')[1]);
216-
}
217-
};
218-
img.onerror = function () {
219-
if (!resolved) {
220-
resolved = true;
221-
clearTimeout(timer);
222-
URL.revokeObjectURL(url);
223-
ctx.fillStyle = '#ffffff';
224-
ctx.fillRect(0, 0, w, h);
225-
ctx.fillStyle = '#000000';
226-
ctx.font = '16px sans-serif';
227-
ctx.fillText('Screenshot (SVG error fallback) ' + w + 'x' + h, 20, 40);
228-
resolve(canvas.toDataURL('image/png').split(',')[1]);
229-
}
230-
};
231-
img.src = url;
232-
} catch (e) {
233-
resolve(null);
174+
// Simple DOM-info screenshot — avoids SVG foreignObject hangs and huge payloads
175+
var w = window.innerWidth;
176+
var h = window.innerHeight;
177+
var canvas = document.createElement('canvas');
178+
// Use 1x scale to keep payload small (~50KB vs 1MB+ at 2x DPR)
179+
canvas.width = Math.min(w, 800);
180+
canvas.height = Math.min(h, 600);
181+
var ctx = canvas.getContext('2d');
182+
var scaleX = canvas.width / w;
183+
var scaleY = canvas.height / h;
184+
ctx.scale(scaleX, scaleY);
185+
186+
// Draw page background
187+
var bg = getComputedStyle(document.body).backgroundColor || '#ffffff';
188+
ctx.fillStyle = bg === 'rgba(0, 0, 0, 0)' ? '#ffffff' : bg;
189+
ctx.fillRect(0, 0, w, h);
190+
191+
// Draw visible text elements as a lightweight representation
192+
ctx.fillStyle = '#000000';
193+
ctx.font = '14px sans-serif';
194+
var y = 30;
195+
var els = document.querySelectorAll('h1,h2,h3,p,button,a,input,label,span');
196+
for (var i = 0; i < Math.min(els.length, 40); i++) {
197+
var el = els[i];
198+
var rect = el.getBoundingClientRect();
199+
if (rect.width === 0 || rect.height === 0) continue;
200+
var tag = el.tagName.toLowerCase();
201+
var txt = (el.textContent || '').trim().substring(0, 60);
202+
if (!txt && el.placeholder) txt = '[' + el.placeholder + ']';
203+
if (!txt) continue;
204+
// Draw element representation at its approximate position
205+
if (tag === 'button' || tag === 'a') {
206+
ctx.fillStyle = '#0066cc';
207+
ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
208+
ctx.fillStyle = '#ffffff';
209+
ctx.fillText(txt, rect.x + 4, rect.y + 16);
210+
ctx.fillStyle = '#000000';
211+
} else if (tag === 'input') {
212+
ctx.strokeStyle = '#999999';
213+
ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
214+
ctx.fillText(txt || el.value || '', rect.x + 4, rect.y + 16);
215+
} else {
216+
ctx.fillText(txt, rect.x, rect.y + 14);
234217
}
235-
});
218+
}
219+
220+
// Return as JPEG for smaller size
221+
return Promise.resolve(canvas.toDataURL('image/jpeg', 0.6).split(',')[1]);
236222
}
237223

238224
function screenshotRegion(x, y, width, height) {

0 commit comments

Comments
 (0)