Skip to content

Commit 33ef9ff

Browse files
committed
Checkpoint embedded mode
Adds /embed as a variant of /run that hides the chrome and has other features that make it easier to iframe an instance. Updates #382
1 parent 71d2591 commit 33ef9ff

6 files changed

Lines changed: 126 additions & 70 deletions

File tree

src/App.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,16 @@ function App() {
6363
</AppearanceProvider>
6464
</Suspense>
6565
);
66-
footer = (
67-
<Footer
68-
onLogoClick={handleDone}
69-
showFullscreenButton={runDef.screenSize === "fullscreen"}
70-
/>
71-
);
66+
if (runDef.isEmbed) {
67+
footer = undefined;
68+
} else {
69+
footer = (
70+
<Footer
71+
onLogoClick={handleDone}
72+
showFullscreenButton={runDef.screenSize === "fullscreen"}
73+
/>
74+
);
75+
}
7276
} else {
7377
contents = (
7478
<React.StrictMode>

src/Mac.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ body.fullscreen {
99
background: black;
1010
}
1111

12+
body.embed {
13+
background: black;
14+
overflow: hidden;
15+
}
16+
1217
.Mac-Loading {
1318
position: absolute;
1419
top: 0;

src/Mac.tsx

Lines changed: 72 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export type MacProps = {
6565
machine: MachineDef;
6666
ramSize?: MachineDefRAMSize;
6767
screenSize: ScreenSize;
68+
screenScale?: number;
6869
ethernetProvider?: EmulatorEthernetProvider;
6970
customDate?: Date;
7071
debugFallback?: boolean;
@@ -87,6 +88,7 @@ export default function Mac({
8788
machine,
8889
ramSize,
8990
screenSize: screenSizeProp,
91+
screenScale: screenScaleProp,
9092
ethernetProvider,
9193
customDate,
9294
debugFallback,
@@ -98,7 +100,7 @@ export default function Mac({
98100
}: MacProps) {
99101
const screenRef = useRef<HTMLCanvasElement>(null);
100102
const [emulatorLoaded, setEmulatorLoaded] = useState(false);
101-
const [scale, setScale] = useState<number | undefined>(undefined);
103+
const [scale, setScale] = useState<number | undefined>(screenScaleProp);
102104
const [fullscreen, setFullscreen] = useState(false);
103105
const [emulatorLoadingProgress, setEmulatorLoadingProgress] = useState([
104106
0, 0,
@@ -134,11 +136,8 @@ export default function Mac({
134136

135137
const initialScreenSize = useMemo(
136138
() =>
137-
machine.fixedScreenSize ??
138-
(typeof screenSizeProp === "object"
139-
? screenSizeProp
140-
: SCREEN_SIZES[screenSizeProp](machine)),
141-
[machine, screenSizeProp]
139+
computeInitialScreenSize(machine, screenSizeProp, screenScaleProp),
140+
[machine, screenSizeProp, screenScaleProp]
142141
);
143142
const {width: initialScreenWidth, height: initialScreenHeight} =
144143
initialScreenSize;
@@ -204,8 +203,13 @@ export default function Mac({
204203
if (hasSavedHD) {
205204
emulatorDisks.push(SAVED_HD);
206205
}
207-
const useSharedMemory =
208-
typeof SharedArrayBuffer !== "undefined" && !debugFallback;
206+
const hasSharedArrayBuffer = typeof SharedArrayBuffer !== "undefined";
207+
if (!hasSharedArrayBuffer) {
208+
console.warn(
209+
"SharedArrayBuffer is not available, fallback more will be used. Performance may be degraded."
210+
);
211+
}
212+
const useSharedMemory = hasSharedArrayBuffer && !debugFallback;
209213
const emulator = new Emulator(
210214
{
211215
machine,
@@ -377,6 +381,10 @@ export default function Mac({
377381
handleMacLibraryProgress,
378382
]);
379383

384+
useEffect(() => {
385+
document.body.classList.toggle("embed", screenSizeProp === "embed");
386+
}, [screenSizeProp]);
387+
380388
const handleFullScreenClick = () => {
381389
// Make the entire page go fullscreen (instead of just the screen
382390
// canvas) because iOS Safari does not maintain the aspect ratio of the
@@ -688,7 +696,8 @@ export default function Mac({
688696
fullscreen ||
689697
// These screen sizes always want fullscreen-like bezels
690698
screenSizeProp === "fullscreen" ||
691-
screenSizeProp === "window"
699+
screenSizeProp === "window" ||
700+
screenSizeProp === "embed"
692701
}
693702
led={
694703
(!emulatorLoaded && !debugPaused) ||
@@ -781,14 +790,15 @@ export default function Mac({
781790
/>
782791
)}
783792
</ScreenFrame>
784-
{!fullscreen && disks[0]?.infiniteHdSubset !== "mfs" && (
793+
{!fullscreen && screenSizeProp !== "embed" && (
785794
<DrawersContainer>
786-
{emulatorSupportsCDROMs(machine.emulatorType) && (
787-
<MacCDROMs
788-
onRun={loadCDROM}
789-
platform={machine.platform}
790-
/>
791-
)}
795+
{emulatorSupportsCDROMs(machine.emulatorType) &&
796+
disks[0]?.infiniteHdSubset !== "mfs" && (
797+
<MacCDROMs
798+
onRun={loadCDROM}
799+
platform={machine.platform}
800+
/>
801+
)}
792802
{includeLibrary &&
793803
emulatorSupportsDownloadsFolder(
794804
machine.emulatorType
@@ -905,35 +915,54 @@ function uploadFiles(
905915
const SMALL_BEZEL_THRESHOLD = 80;
906916
const MEDIUM_BEZEL_THRESHOLD = 168;
907917

908-
const SCREEN_SIZES: {
909-
[id: string]: (machine: MachineDef) => {width: number; height: number};
910-
} = {
911-
"auto": machine => {
912-
const availableWidth = window.innerWidth - MEDIUM_BEZEL_THRESHOLD;
913-
const availableHeight = window.innerHeight - MEDIUM_BEZEL_THRESHOLD;
914-
const {
915-
supportedScreenSizes = [
916-
{width: 1600, height: 1200},
917-
{width: 1280, height: 1024},
918-
{width: 1152, height: 870},
919-
{width: 1024, height: 768},
920-
{width: 800, height: 600},
921-
{width: 640, height: 480},
922-
],
923-
} = machine;
924-
for (const {width, height} of supportedScreenSizes) {
925-
if (width <= availableWidth && height <= availableHeight) {
926-
return {width, height};
918+
function computeInitialScreenSize(
919+
machine: MachineDef,
920+
screenSizeProp?: ScreenSize,
921+
screenScaleProp?: number
922+
): {width: number; height: number} {
923+
if (machine.fixedScreenSize) {
924+
return machine.fixedScreenSize;
925+
}
926+
if (typeof screenSizeProp === "object") {
927+
return screenSizeProp;
928+
}
929+
let {innerWidth: windowWidth, innerHeight: windowHeight} = window;
930+
let {width: screenWidth, height: screenHeight} = window.screen;
931+
if (screenScaleProp) {
932+
windowWidth = Math.floor(windowWidth / screenScaleProp);
933+
windowHeight = Math.floor(windowHeight / screenScaleProp);
934+
screenWidth = Math.floor(screenWidth / screenScaleProp);
935+
screenHeight = Math.floor(screenHeight / screenScaleProp);
936+
}
937+
switch (screenSizeProp) {
938+
case undefined:
939+
case "auto": {
940+
const availableWidth = windowWidth - MEDIUM_BEZEL_THRESHOLD;
941+
const availableHeight = windowHeight - MEDIUM_BEZEL_THRESHOLD;
942+
const {
943+
supportedScreenSizes = [
944+
{width: 1600, height: 1200},
945+
{width: 1280, height: 1024},
946+
{width: 1152, height: 870},
947+
{width: 1024, height: 768},
948+
{width: 800, height: 600},
949+
{width: 640, height: 480},
950+
],
951+
} = machine;
952+
for (const {width, height} of supportedScreenSizes) {
953+
if (width <= availableWidth && height <= availableHeight) {
954+
return {width, height};
955+
}
927956
}
957+
return {width: 640, height: 480};
928958
}
929-
return {width: 640, height: 480};
930-
},
931-
"window": () => ({width: window.innerWidth, height: window.innerHeight}),
932-
"fullscreen": () => ({
933-
width: window.screen.width,
934-
height: window.screen.availHeight,
935-
}),
936-
};
959+
case "window":
960+
case "embed":
961+
return {width: windowWidth, height: windowHeight};
962+
case "fullscreen":
963+
return {width: screenWidth, height: screenHeight};
964+
}
965+
}
937966

938967
// Assume that mobile devices that can't do hover events also need an explicit
939968
// control for bringing up the on-screen keyboard.

src/RunDefMac.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export default function RunDefMac({runDef, onDone}: RunDefMacProps) {
4141
machine={runDef.machine}
4242
ramSize={runDef.ramSize}
4343
screenSize={runDef.screenSize}
44+
screenScale={runDef.screenScale}
4445
ethernetProvider={runDef.ethernetProvider}
4546
customDate={runDef.customDate ?? runDef.disks[0]?.customDate}
4647
debugFallback={runDef.debugFallback}

src/run-def.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type RunDef = {
2020
machine: MachineDef;
2121
ramSize?: MachineDefRAMSize;
2222
screenSize: ScreenSize;
23+
screenScale?: number;
2324
disks: SystemDiskDef[];
2425
diskFiles: DiskFile[];
2526
cdromURLs: string[];
@@ -36,12 +37,14 @@ export type RunDef = {
3637
debugTrackpad?: boolean;
3738
isCustom?: boolean;
3839
customDate?: Date;
40+
isEmbed?: boolean;
3941
};
4042

4143
export type ScreenSize =
4244
| "auto"
4345
| "fullscreen"
4446
| "window"
47+
| "embed"
4548
| {width: number; height: number};
4649

4750
export function runDefFromUrl(urlString: string): RunDef | undefined {
@@ -52,9 +55,10 @@ export function runDefFromUrl(urlString: string): RunDef | undefined {
5255
return undefined;
5356
}
5457

55-
const {searchParams} = url;
58+
const {searchParams, pathname} = url;
5659

5760
let isCustom = searchParams.has("edit");
61+
const isEmbed = pathname === "/embed";
5862
let includeInfiniteHD;
5963
let includeSavedHD;
6064
const includeLibrary = searchParams.get("library") !== "false";
@@ -98,7 +102,7 @@ export function runDefFromUrl(urlString: string): RunDef | undefined {
98102
ramSize = ramSizeParam;
99103
isCustom = true;
100104
}
101-
let screenSize: ScreenSize = "auto";
105+
let screenSize: ScreenSize = isEmbed ? "embed" : "auto";
102106
const screenSizeParam = searchParams.get("screenSize");
103107
switch (screenSizeParam) {
104108
case "auto":
@@ -118,6 +122,14 @@ export function runDefFromUrl(urlString: string): RunDef | undefined {
118122
break;
119123
}
120124
}
125+
let screenScale: number | undefined = undefined;
126+
const screenScaleParam = searchParams.get("screenScale");
127+
if (screenScaleParam) {
128+
const scale = parseFloat(screenScaleParam);
129+
if (!isNaN(scale)) {
130+
screenScale = scale;
131+
}
132+
}
121133

122134
const cdromURLs = [
123135
...searchParams.getAll("cdrom"),
@@ -154,6 +166,7 @@ export function runDefFromUrl(urlString: string): RunDef | undefined {
154166
machine,
155167
ramSize,
156168
screenSize,
169+
screenScale,
157170
ethernetProvider,
158171
cdromURLs,
159172
diskFiles: [],
@@ -164,6 +177,7 @@ export function runDefFromUrl(urlString: string): RunDef | undefined {
164177
debugLog,
165178
debugTrackpad,
166179
isCustom,
180+
isEmbed,
167181
};
168182
}
169183

@@ -218,6 +232,9 @@ export function runDefToUrl(runDef: RunDef): string {
218232
: runDef.screenSize
219233
);
220234
}
235+
if (runDef.screenScale && runDef.screenScale !== 1) {
236+
url.searchParams.set("screenScale", runDef.screenScale.toString());
237+
}
221238
if (ethernetProvider instanceof CloudflareWorkerEthernetProvider) {
222239
url.searchParams.set("appleTalk", ethernetProvider.zoneName());
223240
} else if (ethernetProvider instanceof BroadcastChannelEthernetProvider) {

workers-site/index.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,14 @@ async function handleRequest(
8686
}
8787

8888
response.headers.set("X-Content-Type-Options", "nosniff");
89-
response.headers.set("X-Frame-Options", "DENY");
89+
if (pathname === "/embed") {
90+
response.headers.set(
91+
"Cross-Origin-Resource-Policy",
92+
"cross-origin"
93+
);
94+
} else {
95+
response.headers.set("X-Frame-Options", "DENY");
96+
}
9097
// Allow SharedArrayBuffer to work
9198
response.headers.set("Cross-Origin-Opener-Policy", "same-origin");
9299
response.headers.set("Cross-Origin-Embedder-Policy", "require-corp");
@@ -131,27 +138,20 @@ function mapRequestToAsset(request: Request): Request {
131138
const parsedUrl = new URL(request.url);
132139
let {pathname} = parsedUrl;
133140

134-
if (pathname.endsWith("/")) {
135-
pathname = pathname.concat("index.html");
136-
}
137-
138-
const {hostname} = parsedUrl;
139141
// Let the client-side handle runDef URLs (minimal version of runDefFromUrl).
140-
if (
141-
hostname === "127.0.0.1" ||
142-
hostname === "localhost" ||
143-
hostname === "infinitemac.org" ||
144-
hostname.endsWith(".infinitemac.org")
145-
) {
146-
const pieces = parsedUrl.pathname.split("/");
142+
const pieces = pathname.split("/");
143+
if (pieces.length === 3 && parseInt(pieces[1]) >= 1984) {
147144
// Year paths
148-
if (pieces.length === 3 && parseInt(pieces[1]) >= 1984) {
149-
pathname = "/index.html";
150-
}
145+
pathname = "/index.html";
146+
} else if (
147+
pieces.length === 2 &&
148+
(pieces[1] === "run" || pieces[1] === "embed")
149+
) {
151150
// Custom run paths
152-
if (pieces.length === 2 && pieces[1] === "run") {
153-
pathname = "/index.html";
154-
}
151+
pathname = "/index.html";
152+
} else if (pathname.endsWith("/")) {
153+
// Regular index path
154+
pathname = pathname.concat("index.html");
155155
}
156156

157157
parsedUrl.pathname = pathname;

0 commit comments

Comments
 (0)