Skip to content

Commit 1db6610

Browse files
committed
[frontend] screenshot through element capture api when available
1 parent f5e03a0 commit 1db6610

File tree

3 files changed

+85
-18
lines changed

3 files changed

+85
-18
lines changed

frontend/src/components/Shell.tsx

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { css, type Component } from "dreamland/core";
22
import { browser } from "../Browser";
33
import { forceScreenshot, popTab, pushTab } from "../Browser";
4+
import { takeScreenshotGDM } from "../screenshot";
45

56
export const Shell: Component = function (cx) {
67
pushTab.listen((tab) => {
@@ -53,41 +54,51 @@ export const Shell: Component = function (cx) {
5354
</div>
5455
</div>
5556
);
56-
57-
setInterval(() => forceScreenshot(tab), 1000);
5857
});
5958
popTab.listen((tab) => {
6059
const container = cx.root.querySelector(`[data-tab="${tab.id}"]`);
6160
if (!container) throw new Error(`No container found for tab ${tab.id}`);
6261
container.remove();
6362
});
64-
// forceScreenshot.listen(async (tab) => {
65-
// const container = cx.root.querySelector(
66-
// `[data-tab="${tab.id}"]`
67-
// ) as HTMLElement;
68-
// if (!container) throw new Error(`No container found for tab ${tab.id}`);
63+
forceScreenshot.listen(async (tab) => {
64+
const container = cx.root.querySelector(
65+
`[data-tab="${tab.id}"]`
66+
) as HTMLElement;
67+
if (!container) throw new Error(`No container found for tab ${tab.id}`);
6968

70-
// // tab.screenshot = URL.createObjectURL(await toBlob(container));
71-
// });
69+
let blob = await takeScreenshotGDM(container);
70+
if (blob) tab.screenshot = URL.createObjectURL(blob);
71+
});
7272

7373
return <div></div>;
7474
};
75+
7576
Shell.style = css`
7677
:scope {
7778
flex: 1;
7879
overflow: hidden;
7980
width: 100%;
81+
position: relative;
8082
}
8183
.unfocus {
8284
pointer-events: none;
8385
}
8486
.container {
87+
position: absolute;
8588
width: 100%;
8689
height: 100%;
87-
display: none;
90+
display: flex;
91+
top: 0;
92+
left: 0;
93+
z-index: -1;
94+
/*display: none;*/
95+
96+
/*https://screen-share.github.io/element-capture/#elements-eligible-for-restriction*/
97+
isolation: isolate;
98+
transform-style: flat;
8899
}
89100
.container.active {
90-
display: flex;
101+
z-index: 1;
91102
}
92103
.container .devtools {
93104
position: relative;

frontend/src/components/TabStrip.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { IconButton } from "./IconButton";
77
import type { Tab } from "../Tab";
88
// import html2canvas from "html2canvas";
99
import { setContextMenu } from "./Menu";
10-
import { browser } from "../Browser";
10+
import { browser, forceScreenshot } from "../Browser";
1111

1212
export const DragTab: Component<
1313
{
@@ -74,6 +74,7 @@ export const DragTab: Component<
7474
this.transitionend();
7575
}}
7676
on:mouseenter={() => {
77+
forceScreenshot(this.tab);
7778
if (hoverTimeout) clearTimeout(hoverTimeout);
7879
hoverTimeout = window.setTimeout(() => {
7980
this.tooltipActive = true;
@@ -87,11 +88,14 @@ export const DragTab: Component<
8788
<div class="tooltip" class:active={use(this.tooltipActive)}>
8889
<span class="title">{use(this.tab.title)}</span>
8990
<span class="hostname">{use(this.tab.url.hostname)}</span>
90-
{/*<img src={use(this.tab.screenshot)} class="img" />*/}
91-
<div
92-
style={use`background-image: -moz-element(#tab${this.tab.id})`}
93-
class="img"
94-
></div>
91+
{window.chrome ? (
92+
<img src={use(this.tab.screenshot)} class="img" />
93+
) : (
94+
<div
95+
style={use`background-image: -moz-element(#tab${this.tab.id})`}
96+
class="img"
97+
></div>
98+
)}
9599
</div>
96100
<div
97101
class="dragroot"
@@ -430,7 +434,9 @@ export const Tabs: Component<
430434

431435
calcDragPos(e, tab);
432436

433-
if (this.activetab != tab) this.activetab = tab;
437+
if (this.activetab != tab) {
438+
this.activetab = tab;
439+
}
434440
};
435441

436442
const transitionend = () => {

frontend/src/screenshot.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
let stream: MediaStream | null;
2+
let track: MediaStreamTrack | null;
3+
let video: HTMLVideoElement | null;
4+
5+
export async function startCapture() {
6+
if (stream) return;
7+
8+
stream = await navigator.mediaDevices.getDisplayMedia({
9+
video: {
10+
displaySurface: "window",
11+
},
12+
//@ts-expect-error untyped
13+
preferCurrentTab: true,
14+
audio: false,
15+
});
16+
track = stream.getVideoTracks()[0];
17+
18+
video = document.createElement("video");
19+
video.srcObject = stream;
20+
video.play();
21+
}
22+
23+
const canvas = document.createElement("canvas");
24+
const ctx = canvas.getContext("2d")!;
25+
export async function takeScreenshotGDM(
26+
container: HTMLElement
27+
): Promise<Blob | null> {
28+
if (!stream || !track || !video) return null;
29+
try {
30+
//@ts-expect-error untyped
31+
const restrictionTarget = await RestrictionTarget.fromElement(container);
32+
//@ts-expect-error untyped
33+
await track.restrictTo(restrictionTarget);
34+
35+
await new Promise((r) => setTimeout(r, 100));
36+
const settings = track.getSettings();
37+
canvas.width = settings.width!;
38+
canvas.height = settings.height!;
39+
ctx.drawImage(video, 0, 0);
40+
const blob: Blob | null = await new Promise((resolve) =>
41+
canvas.toBlob(resolve, "image/png")
42+
);
43+
44+
return blob;
45+
} catch (e) {
46+
console.error(e);
47+
48+
return null;
49+
}
50+
}

0 commit comments

Comments
 (0)