@marimo-team/frontend 0.23.3-dev13 → 0.23.3-dev16
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/assets/{JsonOutput-BGumwadB.js → JsonOutput-ChlI2Rcq.js} +11 -11
- package/dist/assets/{add-connection-dialog-DW9MujC-.js → add-connection-dialog-D4egHycB.js} +1 -1
- package/dist/assets/{agent-panel-C_laGojQ.js → agent-panel-B9qfQkjK.js} +1 -1
- package/dist/assets/{cell-editor-bOGguH6G.js → cell-editor-BciNtNBM.js} +1 -1
- package/dist/assets/{column-preview-lJCZcp8j.js → column-preview-DZGGZYAY.js} +1 -1
- package/dist/assets/{command-palette-CMX2_mdR.js → command-palette-C7NnRcfP.js} +1 -1
- package/dist/assets/{edit-page-c_cb8Sc-.js → edit-page-BX4jv2i-.js} +3 -3
- package/dist/assets/{file-explorer-panel-DLFVjtxQ.js → file-explorer-panel-rRNjUnK5.js} +1 -1
- package/dist/assets/{form-DEDyV6Nr.js → form-B5Zfx4cn.js} +1 -1
- package/dist/assets/{hooks-BkJSzslE.js → hooks-toE2zzDy.js} +1 -1
- package/dist/assets/{index-D-M3NRDk.js → index-DyhAORlA.js} +3 -3
- package/dist/assets/{layout-BFa1cfaZ.js → layout-CX1oKyFB.js} +3 -3
- package/dist/assets/{panels-lV4EyUmM.js → panels-CMep1Kk9.js} +1 -1
- package/dist/assets/{reveal-component-Cu8nMU5_.js → reveal-component-DNVS81N_.js} +1 -1
- package/dist/assets/{run-page-Dl8g4faj.js → run-page-fS3bsdcc.js} +1 -1
- package/dist/assets/{scratchpad-panel-D-v2WrEb.js → scratchpad-panel-2N56xPu4.js} +1 -1
- package/dist/assets/{session-panel-C2UpyfiB.js → session-panel-DTU0bHW4.js} +1 -1
- package/dist/assets/{slide-BNis4O9h.js → slide-CHcKvgFE.js} +1 -1
- package/dist/assets/state-wy7oHhYg.js +3 -0
- package/dist/assets/{useNotebookActions-DPwf9nYC.js → useNotebookActions-xdSJIXJ7.js} +1 -1
- package/dist/index.html +5 -5
- package/package.json +1 -1
- package/src/components/editor/output/JsonOutput.tsx +157 -4
- package/src/components/editor/output/__tests__/JsonOutput-mimetype.test.tsx +80 -0
- package/src/components/editor/output/__tests__/json-output.test.ts +147 -2
- package/src/core/islands/__tests__/bridge.test.ts +116 -5
- package/src/core/islands/bridge.ts +5 -1
- package/src/core/static/export-context.ts +43 -0
- package/src/plugins/core/__test__/trusted-url.test.ts +130 -18
- package/src/plugins/core/trusted-url.ts +23 -10
- package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +29 -1
- package/src/plugins/impl/mpl-interactive/__tests__/MplInteractivePlugin.test.tsx +34 -0
- package/src/plugins/impl/panel/__tests__/PanelPlugin.test.ts +35 -2
- package/dist/assets/state-BapA2ATY.js +0 -3
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
export interface MarimoExportContext {
|
|
4
|
+
trusted: true;
|
|
5
|
+
notebookCode?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
declare global {
|
|
9
|
+
interface Window {
|
|
10
|
+
__MARIMO_EXPORT_CONTEXT__?: Readonly<MarimoExportContext>;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isMarimoExportContext(
|
|
15
|
+
value: unknown,
|
|
16
|
+
): value is Readonly<MarimoExportContext> {
|
|
17
|
+
if (typeof value !== "object" || value === null) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const candidate = value as MarimoExportContext;
|
|
22
|
+
if (candidate.trusted !== true) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (
|
|
26
|
+
candidate.notebookCode !== undefined &&
|
|
27
|
+
typeof candidate.notebookCode !== "string"
|
|
28
|
+
) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getMarimoExportContext():
|
|
35
|
+
| Readonly<MarimoExportContext>
|
|
36
|
+
| undefined {
|
|
37
|
+
const context = window?.__MARIMO_EXPORT_CONTEXT__;
|
|
38
|
+
return isMarimoExportContext(context) ? context : undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function hasTrustedExportContext(): boolean {
|
|
42
|
+
return getMarimoExportContext()?.trusted === true;
|
|
43
|
+
}
|
|
@@ -1,10 +1,66 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
3
3
|
import { hasRunAnyCellAtom } from "@/components/editor/cell/useRunCells";
|
|
4
|
+
import { userConfigAtom } from "@/core/config/config";
|
|
5
|
+
import { parseUserConfig } from "@/core/config/config-schema";
|
|
6
|
+
import { initialModeAtom } from "@/core/mode";
|
|
4
7
|
import { store } from "@/core/state/jotai";
|
|
5
8
|
import { isTrustedVirtualFileUrl } from "../trusted-url";
|
|
6
9
|
|
|
10
|
+
type ExportContextWindow = Window & {
|
|
11
|
+
__MARIMO_EXPORT_CONTEXT__?: {
|
|
12
|
+
trusted: true;
|
|
13
|
+
notebookCode?: string;
|
|
14
|
+
};
|
|
15
|
+
__MARIMO_STATIC__?: unknown;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function snapshotTrustState() {
|
|
19
|
+
return {
|
|
20
|
+
hasRunAnyCell: store.get(hasRunAnyCellAtom),
|
|
21
|
+
userConfig: store.get(userConfigAtom),
|
|
22
|
+
initialMode: store.get(initialModeAtom),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function restoreTrustState(snapshot: ReturnType<typeof snapshotTrustState>) {
|
|
27
|
+
store.set(hasRunAnyCellAtom, snapshot.hasRunAnyCell);
|
|
28
|
+
store.set(userConfigAtom, snapshot.userConfig);
|
|
29
|
+
store.set(initialModeAtom, snapshot.initialMode);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function setAutoInstantiate(value: boolean) {
|
|
33
|
+
const cleared = parseUserConfig({});
|
|
34
|
+
store.set(userConfigAtom, {
|
|
35
|
+
...cleared,
|
|
36
|
+
runtime: { ...cleared.runtime, auto_instantiate: value },
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function clearTrustSignals() {
|
|
41
|
+
store.set(hasRunAnyCellAtom, false);
|
|
42
|
+
setAutoInstantiate(false);
|
|
43
|
+
store.set(initialModeAtom, "edit");
|
|
44
|
+
}
|
|
45
|
+
|
|
7
46
|
describe("isTrustedVirtualFileUrl", () => {
|
|
47
|
+
let windowWithExportContext: ExportContextWindow;
|
|
48
|
+
let trustStateSnapshot: ReturnType<typeof snapshotTrustState>;
|
|
49
|
+
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
windowWithExportContext = window as ExportContextWindow;
|
|
52
|
+
trustStateSnapshot = snapshotTrustState();
|
|
53
|
+
clearTrustSignals();
|
|
54
|
+
delete windowWithExportContext.__MARIMO_EXPORT_CONTEXT__;
|
|
55
|
+
delete windowWithExportContext.__MARIMO_STATIC__;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
restoreTrustState(trustStateSnapshot);
|
|
60
|
+
delete windowWithExportContext.__MARIMO_EXPORT_CONTEXT__;
|
|
61
|
+
delete windowWithExportContext.__MARIMO_STATIC__;
|
|
62
|
+
});
|
|
63
|
+
|
|
8
64
|
it.each([
|
|
9
65
|
"./@file/123-mpl.js",
|
|
10
66
|
"./@file/456-mpl.css",
|
|
@@ -48,40 +104,96 @@ describe("isTrustedVirtualFileUrl", () => {
|
|
|
48
104
|
expect(isTrustedVirtualFileUrl({})).toBe(false);
|
|
49
105
|
});
|
|
50
106
|
|
|
51
|
-
|
|
52
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Data URLs are the WASM / Pyodide fallback shape (see
|
|
109
|
+
* `virtual_file.py`: when `virtual_files_supported=False`, files are
|
|
110
|
+
* emitted directly as base64 data URLs). The tests below cover each
|
|
111
|
+
* supported and unsupported trust signal.
|
|
112
|
+
*/
|
|
113
|
+
describe("data URL acceptance", () => {
|
|
114
|
+
const SAFE_DATA_URLS = [
|
|
115
|
+
"data:text/javascript;base64,ZXhwb3J0IGRlZmF1bHQge30=",
|
|
116
|
+
"data:application/javascript;base64,ZXhwb3J0IGRlZmF1bHQge30=",
|
|
117
|
+
"data:text/css;base64,Ym9keXt9",
|
|
118
|
+
];
|
|
53
119
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
120
|
+
it.each(SAFE_DATA_URLS)(
|
|
121
|
+
"accepts %s once the user has run a cell",
|
|
122
|
+
(url) => {
|
|
123
|
+
store.set(hasRunAnyCellAtom, true);
|
|
124
|
+
expect(isTrustedVirtualFileUrl(url)).toBe(true);
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
it("accepts safe data URL when trusted export context is present", () => {
|
|
129
|
+
windowWithExportContext.__MARIMO_EXPORT_CONTEXT__ = {
|
|
130
|
+
trusted: true,
|
|
131
|
+
notebookCode: "import marimo\napp = marimo.App()",
|
|
132
|
+
};
|
|
133
|
+
expect(
|
|
134
|
+
isTrustedVirtualFileUrl(
|
|
135
|
+
"data:text/javascript;base64,ZXhwb3J0IGRlZmF1bHQge30=",
|
|
136
|
+
),
|
|
137
|
+
).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("rejects safe data URL when only read mode is present", () => {
|
|
141
|
+
store.set(initialModeAtom, "read");
|
|
142
|
+
expect(
|
|
143
|
+
isTrustedVirtualFileUrl(
|
|
144
|
+
"data:text/javascript;base64,ZXhwb3J0IGRlZmF1bHQge30=",
|
|
145
|
+
),
|
|
146
|
+
).toBe(false);
|
|
57
147
|
});
|
|
58
148
|
|
|
59
|
-
|
|
60
|
-
|
|
149
|
+
it("rejects safe data URL when only auto_instantiate is enabled", () => {
|
|
150
|
+
setAutoInstantiate(true);
|
|
151
|
+
expect(
|
|
152
|
+
isTrustedVirtualFileUrl(
|
|
153
|
+
"data:text/javascript;base64,ZXhwb3J0IGRlZmF1bHQge30=",
|
|
154
|
+
),
|
|
155
|
+
).toBe(false);
|
|
61
156
|
});
|
|
62
157
|
|
|
63
|
-
it
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
158
|
+
it("rejects safe data URL when only a marimo-code tag is present", () => {
|
|
159
|
+
const tag = document.createElement("marimo-code");
|
|
160
|
+
tag.textContent = encodeURIComponent("import marimo\napp = marimo.App()");
|
|
161
|
+
document.body.appendChild(tag);
|
|
162
|
+
try {
|
|
163
|
+
expect(
|
|
164
|
+
isTrustedVirtualFileUrl(
|
|
165
|
+
"data:text/javascript;base64,ZXhwb3J0IGRlZmF1bHQge30=",
|
|
166
|
+
),
|
|
167
|
+
).toBe(false);
|
|
168
|
+
} finally {
|
|
169
|
+
tag.remove();
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("rejects safe data URL when only __MARIMO_STATIC__ is present", () => {
|
|
174
|
+
windowWithExportContext.__MARIMO_STATIC__ = { files: {} };
|
|
175
|
+
expect(
|
|
176
|
+
isTrustedVirtualFileUrl(
|
|
177
|
+
"data:text/javascript;base64,ZXhwb3J0IGRlZmF1bHQge30=",
|
|
178
|
+
),
|
|
179
|
+
).toBe(false);
|
|
69
180
|
});
|
|
70
181
|
|
|
71
182
|
it.each([
|
|
72
|
-
// Non-base64 data URLs are refused
|
|
183
|
+
// Non-base64 data URLs are refused because the unencoded payload
|
|
184
|
+
// broadens the parsing/loading surface for attacker-controlled content.
|
|
73
185
|
"data:text/javascript,alert(1)",
|
|
74
186
|
"data:text/javascript;charset=utf-8,alert(1)",
|
|
75
|
-
// HTML / SVG / arbitrary types are refused
|
|
187
|
+
// HTML / SVG / arbitrary types are refused even when trusted.
|
|
76
188
|
"data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==",
|
|
77
189
|
"data:image/svg+xml;base64,PHN2Zy8+",
|
|
78
190
|
"data:application/octet-stream;base64,AAA=",
|
|
79
|
-
])("still rejects unsafe data URL %s", (url) => {
|
|
191
|
+
])("still rejects unsafe data URL %s in trusted context", (url) => {
|
|
192
|
+
store.set(hasRunAnyCellAtom, true);
|
|
80
193
|
expect(isTrustedVirtualFileUrl(url)).toBe(false);
|
|
81
194
|
});
|
|
82
195
|
|
|
83
|
-
it("rejects data
|
|
84
|
-
store.set(hasRunAnyCellAtom, false);
|
|
196
|
+
it("rejects data URLs when no trust signal is set", () => {
|
|
85
197
|
expect(
|
|
86
198
|
isTrustedVirtualFileUrl(
|
|
87
199
|
"data:text/javascript;base64,ZXhwb3J0IGRlZmF1bHQge30=",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { hasRunAnyCellAtom } from "@/components/editor/cell/useRunCells";
|
|
4
|
+
import { hasTrustedExportContext } from "@/core/static/export-context";
|
|
4
5
|
import { store } from "@/core/state/jotai";
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -15,13 +16,13 @@ import { store } from "@/core/state/jotai";
|
|
|
15
16
|
* attacker-controlled JavaScript at same origin, since the HTML sanitizer
|
|
16
17
|
* lets arbitrary marimo custom elements and attributes through.
|
|
17
18
|
*
|
|
18
|
-
* Some runtimes (WASM, VS Code
|
|
19
|
-
*
|
|
19
|
+
* Some runtimes (WASM, VS Code, and trusted exported notebook contexts such as
|
|
20
|
+
* Quarto islands) have no backend to serve virtual files, so `VirtualFile`
|
|
21
|
+
* falls back to inline base64 data URLs (see `virtual_file.py`).
|
|
20
22
|
* We accept those only once the user has explicitly run a cell in the current
|
|
21
|
-
* notebook
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* new attack surface.
|
|
23
|
+
* notebook, or when a first-party export script has installed a trusted
|
|
24
|
+
* notebook export context. Both cases already imply trust in notebook-authored
|
|
25
|
+
* code, so loading the matching data URL is not a new attack surface.
|
|
25
26
|
*/
|
|
26
27
|
export function isTrustedVirtualFileUrl(url: unknown): url is string {
|
|
27
28
|
if (typeof url !== "string" || url.length === 0) {
|
|
@@ -30,16 +31,28 @@ export function isTrustedVirtualFileUrl(url: unknown): url is string {
|
|
|
30
31
|
if (/^(\.?\/)?@file\/[^?#]+$/.test(url)) {
|
|
31
32
|
return true;
|
|
32
33
|
}
|
|
33
|
-
if (isSafeDataUrl(url)
|
|
34
|
+
if (isSafeDataUrl(url)) {
|
|
34
35
|
return true;
|
|
35
36
|
}
|
|
36
37
|
return false;
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
function hasNotebookTrustedDataUrlContext(): boolean {
|
|
41
|
+
return store.get(hasRunAnyCellAtom) || hasTrustedExportContext();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Safe data URL formats: JS/CSS inlined as base64. Non-base64 data URLs and
|
|
46
|
+
* other MIME types (HTML, SVG, octet-stream, etc.) are refused because they
|
|
47
|
+
* broaden the surface for attacker-controlled inline content.
|
|
48
|
+
*/
|
|
39
49
|
function isSafeDataUrl(url: string): boolean {
|
|
40
|
-
|
|
50
|
+
const isSafeKind =
|
|
41
51
|
url.startsWith("data:text/javascript;base64,") ||
|
|
42
52
|
url.startsWith("data:application/javascript;base64,") ||
|
|
43
|
-
url.startsWith("data:text/css;base64,")
|
|
44
|
-
)
|
|
53
|
+
url.startsWith("data:text/css;base64,");
|
|
54
|
+
if (!isSafeKind) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return hasNotebookTrustedDataUrlContext();
|
|
45
58
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
import {
|
|
2
|
+
import type { ExtractAtomValue } from "jotai";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { hasRunAnyCellAtom } from "@/components/editor/cell/useRunCells";
|
|
5
|
+
import { userConfigAtom } from "@/core/config/config";
|
|
6
|
+
import { parseUserConfig } from "@/core/config/config-schema";
|
|
7
|
+
import { initialModeAtom } from "@/core/mode";
|
|
8
|
+
import { store } from "@/core/state/jotai";
|
|
3
9
|
import { Model } from "../model";
|
|
4
10
|
import type { ModelState, WidgetModelId } from "../types";
|
|
5
11
|
import { visibleForTesting } from "../widget-binding";
|
|
@@ -18,9 +24,31 @@ function createMockComm() {
|
|
|
18
24
|
|
|
19
25
|
describe("WidgetDefRegistry", () => {
|
|
20
26
|
let registry: InstanceType<typeof WidgetDefRegistry>;
|
|
27
|
+
let previousConfig: ExtractAtomValue<typeof userConfigAtom>;
|
|
28
|
+
let previousMode: ExtractAtomValue<typeof initialModeAtom>;
|
|
29
|
+
let previousHasRunAnyCell: ExtractAtomValue<typeof hasRunAnyCellAtom>;
|
|
21
30
|
|
|
22
31
|
beforeEach(() => {
|
|
23
32
|
registry = new WidgetDefRegistry();
|
|
33
|
+
// Force "no notebook trust" so the `data:` rejection test below
|
|
34
|
+
// exercises the untrusted branch. The positive trust path is covered
|
|
35
|
+
// centrally in trusted-url.test.ts.
|
|
36
|
+
previousConfig = store.get(userConfigAtom);
|
|
37
|
+
previousMode = store.get(initialModeAtom);
|
|
38
|
+
previousHasRunAnyCell = store.get(hasRunAnyCellAtom);
|
|
39
|
+
store.set(hasRunAnyCellAtom, false);
|
|
40
|
+
const cleared = parseUserConfig({});
|
|
41
|
+
store.set(userConfigAtom, {
|
|
42
|
+
...cleared,
|
|
43
|
+
runtime: { ...cleared.runtime, auto_instantiate: false },
|
|
44
|
+
});
|
|
45
|
+
store.set(initialModeAtom, "edit");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
afterEach(() => {
|
|
49
|
+
store.set(userConfigAtom, previousConfig);
|
|
50
|
+
store.set(initialModeAtom, previousMode);
|
|
51
|
+
store.set(hasRunAnyCellAtom, previousHasRunAnyCell);
|
|
24
52
|
});
|
|
25
53
|
|
|
26
54
|
it("should cache modules by jsHash and return same promise", () => {
|
|
@@ -1,12 +1,41 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import type { ExtractAtomValue } from "jotai";
|
|
2
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { hasRunAnyCellAtom } from "@/components/editor/cell/useRunCells";
|
|
5
|
+
import { userConfigAtom } from "@/core/config/config";
|
|
6
|
+
import { parseUserConfig } from "@/core/config/config-schema";
|
|
7
|
+
import { initialModeAtom } from "@/core/mode";
|
|
8
|
+
import { store } from "@/core/state/jotai";
|
|
3
9
|
import { Logger } from "@/utils/Logger";
|
|
4
10
|
import { visibleForTesting } from "../MplInteractivePlugin";
|
|
5
11
|
|
|
6
12
|
const { ensureMplJs, injectCss, resetMplJsLoading } = visibleForTesting;
|
|
7
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Clear every "notebook trust" signal `isTrustedVirtualFileUrl` consults so
|
|
16
|
+
* the rejection cases below test the actually-untrusted branch. Positive
|
|
17
|
+
* export-context trust is covered centrally in trusted-url.test.ts.
|
|
18
|
+
*/
|
|
19
|
+
function clearTrustSignals() {
|
|
20
|
+
store.set(hasRunAnyCellAtom, false);
|
|
21
|
+
const cleared = parseUserConfig({});
|
|
22
|
+
store.set(userConfigAtom, {
|
|
23
|
+
...cleared,
|
|
24
|
+
runtime: { ...cleared.runtime, auto_instantiate: false },
|
|
25
|
+
});
|
|
26
|
+
store.set(initialModeAtom, "edit");
|
|
27
|
+
}
|
|
28
|
+
|
|
8
29
|
describe("MplInteractivePlugin URL validation", () => {
|
|
30
|
+
let previousConfig: ExtractAtomValue<typeof userConfigAtom>;
|
|
31
|
+
let previousMode: ExtractAtomValue<typeof initialModeAtom>;
|
|
32
|
+
let previousHasRunAnyCell: ExtractAtomValue<typeof hasRunAnyCellAtom>;
|
|
33
|
+
|
|
9
34
|
beforeEach(() => {
|
|
35
|
+
previousConfig = store.get(userConfigAtom);
|
|
36
|
+
previousMode = store.get(initialModeAtom);
|
|
37
|
+
previousHasRunAnyCell = store.get(hasRunAnyCellAtom);
|
|
38
|
+
clearTrustSignals();
|
|
10
39
|
// Reset module-level script-loading state and any stubs.
|
|
11
40
|
delete (window as { mpl?: unknown }).mpl;
|
|
12
41
|
resetMplJsLoading();
|
|
@@ -20,6 +49,9 @@ describe("MplInteractivePlugin URL validation", () => {
|
|
|
20
49
|
|
|
21
50
|
afterEach(() => {
|
|
22
51
|
vi.restoreAllMocks();
|
|
52
|
+
store.set(userConfigAtom, previousConfig);
|
|
53
|
+
store.set(initialModeAtom, previousMode);
|
|
54
|
+
store.set(hasRunAnyCellAtom, previousHasRunAnyCell);
|
|
23
55
|
});
|
|
24
56
|
|
|
25
57
|
describe("ensureMplJs", () => {
|
|
@@ -35,6 +67,8 @@ describe("MplInteractivePlugin URL validation", () => {
|
|
|
35
67
|
"https://evil.example.com/x.js",
|
|
36
68
|
"//evil.example.com/x.js",
|
|
37
69
|
"javascript:alert(1)",
|
|
70
|
+
// Data URL is rejected only in an untrusted context. WASM/autoInstantiate
|
|
71
|
+
// intentionally accepts it — covered by trusted-url.test.ts.
|
|
38
72
|
"data:text/javascript;base64,YWxlcnQoMSk=",
|
|
39
73
|
"./@file/x.js?redirect=http://evil.com",
|
|
40
74
|
])("rejects %s", async (url) => {
|
|
@@ -1,10 +1,39 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import type { ExtractAtomValue } from "jotai";
|
|
2
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { hasRunAnyCellAtom } from "@/components/editor/cell/useRunCells";
|
|
5
|
+
import { userConfigAtom } from "@/core/config/config";
|
|
6
|
+
import { parseUserConfig } from "@/core/config/config-schema";
|
|
7
|
+
import { initialModeAtom } from "@/core/mode";
|
|
8
|
+
import { store } from "@/core/state/jotai";
|
|
3
9
|
import { Logger } from "@/utils/Logger";
|
|
4
10
|
import { loadPanelExtension } from "../PanelPlugin";
|
|
5
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Force the "no notebook trust" branch so the `data:` URL rejection below
|
|
14
|
+
* actually exercises the untrusted path. Positive export-context trust is
|
|
15
|
+
* covered centrally in trusted-url.test.ts.
|
|
16
|
+
*/
|
|
17
|
+
function clearTrustSignals() {
|
|
18
|
+
store.set(hasRunAnyCellAtom, false);
|
|
19
|
+
const cleared = parseUserConfig({});
|
|
20
|
+
store.set(userConfigAtom, {
|
|
21
|
+
...cleared,
|
|
22
|
+
runtime: { ...cleared.runtime, auto_instantiate: false },
|
|
23
|
+
});
|
|
24
|
+
store.set(initialModeAtom, "edit");
|
|
25
|
+
}
|
|
26
|
+
|
|
6
27
|
describe("loadPanelExtension", () => {
|
|
28
|
+
let previousConfig: ExtractAtomValue<typeof userConfigAtom>;
|
|
29
|
+
let previousMode: ExtractAtomValue<typeof initialModeAtom>;
|
|
30
|
+
let previousHasRunAnyCell: ExtractAtomValue<typeof hasRunAnyCellAtom>;
|
|
31
|
+
|
|
7
32
|
beforeEach(() => {
|
|
33
|
+
previousConfig = store.get(userConfigAtom);
|
|
34
|
+
previousMode = store.get(initialModeAtom);
|
|
35
|
+
previousHasRunAnyCell = store.get(hasRunAnyCellAtom);
|
|
36
|
+
clearTrustSignals();
|
|
8
37
|
for (const el of document.head.querySelectorAll("script")) {
|
|
9
38
|
el.remove();
|
|
10
39
|
}
|
|
@@ -12,6 +41,9 @@ describe("loadPanelExtension", () => {
|
|
|
12
41
|
|
|
13
42
|
afterEach(() => {
|
|
14
43
|
vi.restoreAllMocks();
|
|
44
|
+
store.set(userConfigAtom, previousConfig);
|
|
45
|
+
store.set(initialModeAtom, previousMode);
|
|
46
|
+
store.set(hasRunAnyCellAtom, previousHasRunAnyCell);
|
|
15
47
|
});
|
|
16
48
|
|
|
17
49
|
it("does nothing and returns false for null URL", () => {
|
|
@@ -35,8 +67,9 @@ describe("loadPanelExtension", () => {
|
|
|
35
67
|
it.each([
|
|
36
68
|
"https://evil.example.com/x.js",
|
|
37
69
|
"//evil.example.com/x.js",
|
|
38
|
-
//
|
|
39
|
-
//
|
|
70
|
+
// Data URL is rejected only in an untrusted context — the WASM fallback
|
|
71
|
+
// legitimately produces these, so trusted-url.test.ts covers the
|
|
72
|
+
// positive path when a notebook trust signal is set.
|
|
40
73
|
"data:text/javascript;base64,YWxlcnQoMSk=",
|
|
41
74
|
"javascript:alert(1)",
|
|
42
75
|
"./@file/x.js#http://evil.com",
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
var $t=Object.defineProperty;var Oe=f=>{throw TypeError(f)};var zt=(f,h,y)=>h in f?$t(f,h,{enumerable:!0,configurable:!0,writable:!0,value:y}):f[h]=y;var n=(f,h,y)=>zt(f,typeof h!="symbol"?h+"":h,y),ue=(f,h,y)=>h.has(f)||Oe("Cannot "+y);var o=(f,h,y)=>(ue(f,h,"read from private field"),y?y.call(f):h.get(f)),_=(f,h,y)=>h.has(f)?Oe("Cannot add the same private member more than once"):h instanceof WeakSet?h.add(f):h.set(f,y),C=(f,h,y,Q)=>(ue(f,h,"write to private field"),Q?Q.call(f,y):h.set(f,y),y),A=(f,h,y)=>(ue(f,h,"access private method"),y);import{i as z,p as de,u as Vt}from"./useEvent-D91BmmQi.js";import{Ci as G,Si as Qt,_i as Yt,b as Gt,fi as Jt,it as He,jn as Kt,li as Xt,mi as Zt,x as er,__tla as tr}from"./cells-_QgKZKXb.js";import{t as rr}from"./get-C-qh_et5.js";import{t as sr}from"./debounce-DhnxH9Rh.js";import{t as nr}from"./_baseSet-CxV9N1bc.js";import{_ as p,y as g}from"./useEventListener-BR0C1MaI.js";import{t as ar}from"./invariant-BUdrueMv.js";import{S as ir}from"./utils-DIGrmLDO.js";import{S as or,n as lr}from"./config-BwY7xK-l.js";import{n as ur}from"./switch-QpkpQKr6.js";import{n as $e}from"./globals-fN2Lim68.js";import{ot as ce,st as ze,__tla as dr}from"./JsonOutput-BGumwadB.js";import{t as cr}from"./createReducer-1ePoj7v6.js";import{r as pr}from"./strings-md4mFbOQ.js";import{t as pe}from"./requests-DIwGYs0l.js";import{a as Ve,l as fr,r as Qe,__tla as hr}from"./markdown-renderer-Cown6yO8.js";import{t as mr}from"./DeferredRequestRegistry-Bobqy1XF.js";import{t as gr}from"./preload-helper-DXnewu3N.js";import{t as Ye}from"./Deferred-CcWopv8N.js";import{t as Ge}from"./uuid-D_ujsK2Y.js";import{t as yr}from"./use-toast-DZOInYtM.js";import{t as wr}from"./useRunCells-Ce0N_5Wc.js";import{n as br,r as vr,t as _r}from"./share-C7-yIyYZ.js";import{t as Cr}from"./blob-Gsfvuteh.js";import{i as Je,r as Rr,t as xr}from"./events-CKqlBGzI.js";let J,Ke,Xe,fe,Ze,et,he,tt,V,rt,st,me,ge,nt,at,it,ot,ne,lt,ut,ye,dt,we,qr=Promise.all([(()=>{try{return tr}catch{}})(),(()=>{try{return dr}catch{}})(),(()=>{try{return hr}catch{}})()]).then(async()=>{var U,ee,pt,Le,M,j,S,Ne,P,Ae,F,Y,D,ae,Ue,je,te,I,L,O,R,W,N,ft,ht,mt,re;function f(t,e,r){return t==null?t:nr(t,e,r)}J=f,Ze=de(null);var{valueAtom:h,useActions:y}=cr(()=>({banners:[]}),{addBanner:(t,e)=>({...t,banners:[...t.banners,{...e,id:Ge()}]}),removeBanner:(t,e)=>({...t,banners:t.banners.filter(r=>r.id!==e)}),clearBanners:t=>({...t,banners:[]})});dt=()=>Vt(h),Ke=function(){return y()};let Q,ve,_e;Q=class{constructor(){n(this,"subscriptions",new Map)}addSubscription(t,e){var r;this.subscriptions.has(t)||this.subscriptions.set(t,new Set),(r=this.subscriptions.get(t))==null||r.add(e)}removeSubscription(t,e){var r;(r=this.subscriptions.get(t))==null||r.delete(e)}notify(t,e){for(let r of this.subscriptions.get(t)??[])r(e)}},we=class be{constructor(e){n(this,"subscriptions",new Q);this.producer=e}static withProducerCallback(e){return new be(e)}static empty(){return new be}startProducer(){this.producer&&this.producer(e=>{this.subscriptions.notify("message",e)})}connect(){return new Promise(e=>setTimeout(e,0)).then(()=>{this.subscriptions.notify("open",new Event("open"))})}get readyState(){return WebSocket.OPEN}reconnect(e,r){this.close(),this.connect()}close(){this.subscriptions.notify("close",new Event("close"))}send(e){return this.subscriptions.notify("message",new MessageEvent("message",{data:e})),Promise.resolve()}addEventListener(e,r){this.subscriptions.addSubscription(e,r),e==="open"&&r(new Event("open")),e==="message"&&this.startProducer()}removeEventListener(e,r){this.subscriptions.removeSubscription(e,r)}},ve=1e10,_e=1e3;function K(t,e){let r=t.map(s=>`"${s}"`).join(", ");return Error(`This RPC instance cannot ${e} because the transport did not provide one or more of these methods: ${r}`)}function yt(t={}){let e={};function r(l){e=l}let s={};function a(l){var c;s.unregisterHandler&&s.unregisterHandler(),s=l,(c=s.registerHandler)==null||c.call(s,Ht)}let i;function u(l){if(typeof l=="function"){i=l;return}i=(c,w)=>{let m=l[c];if(m)return m(w);let b=l._;if(!b)throw Error(`The requested method has no handler: ${c}`);return b(c,w)}}let{maxRequestTime:d=_e}=t;t.transport&&a(t.transport),t.requestHandler&&u(t.requestHandler),t._debugHooks&&r(t._debugHooks);let v=0;function x(){return v<=ve?++v:v=0}let q=new Map,E=new Map;function H(l,...c){let w=c[0];return new Promise((m,b)=>{var $;if(!s.send)throw K(["send"],"make requests");let k=x(),B={type:"request",id:k,method:l,params:w};q.set(k,{resolve:m,reject:b}),d!==1/0&&E.set(k,setTimeout(()=>{E.delete(k),b(Error("RPC request timed out."))},d)),($=e.onSend)==null||$.call(e,B),s.send(B)})}let se=new Proxy(H,{get:(l,c,w)=>c in l?Reflect.get(l,c,w):m=>H(c,m)}),We=se;function Te(l,...c){var b;let w=c[0];if(!s.send)throw K(["send"],"send messages");let m={type:"message",id:l,payload:w};(b=e.onSend)==null||b.call(e,m),s.send(m)}let Be=new Proxy(Te,{get:(l,c,w)=>c in l?Reflect.get(l,c,w):m=>Te(c,m)}),De=Be,T=new Map,le=new Set;function Dt(l,c){var w;if(!s.registerHandler)throw K(["registerHandler"],"register message listeners");if(l==="*"){le.add(c);return}T.has(l)||T.set(l,new Set),(w=T.get(l))==null||w.add(c)}function Ot(l,c){var w,m;if(l==="*"){le.delete(c);return}(w=T.get(l))==null||w.delete(c),((m=T.get(l))==null?void 0:m.size)===0&&T.delete(l)}async function Ht(l){var c,w;if((c=e.onReceive)==null||c.call(e,l),!("type"in l))throw Error("Message does not contain a type.");if(l.type==="request"){if(!s.send||!i)throw K(["send","requestHandler"],"handle requests");let{id:m,method:b,params:k}=l,B;try{B={type:"response",id:m,success:!0,payload:await i(b,k)}}catch($){if(!($ instanceof Error))throw $;B={type:"response",id:m,success:!1,error:$.message}}(w=e.onSend)==null||w.call(e,B),s.send(B);return}if(l.type==="response"){let m=E.get(l.id);m!=null&&clearTimeout(m);let{resolve:b,reject:k}=q.get(l.id)??{};l.success?b==null||b(l.payload):k==null||k(Error(l.error));return}if(l.type==="message"){for(let b of le)b(l.id,l.payload);let m=T.get(l.id);if(!m)return;for(let b of m)b(l.payload);return}throw Error(`Unexpected RPC message type: ${l.type}`)}return{setTransport:a,setRequestHandler:u,request:se,requestProxy:We,send:Be,sendProxy:De,addMessageListener:Dt,removeMessageListener:Ot,proxy:{send:De,request:We},_setDebugHooks:r}}function wt(t){return yt(t)}var Ce="[transport-id]";function bt(t,e){let{transportId:r}=e;return r==null?t:{[Ce]:r,data:t}}function vt(t,e){let{transportId:r,filter:s}=e,a=s==null?void 0:s();if(r!=null&&a!=null)throw Error("Cannot use both `transportId` and `filter` at the same time");let i=t;if(r){if(t[Ce]!==r)return[!0];i=t.data}return a===!1?[!0]:[!1,i]}function _t(t,e={}){let{transportId:r,filter:s,remotePort:a}=e,i=t,u=a??t,d;return{send(v){u.postMessage(bt(v,{transportId:r}))},registerHandler(v){d=x=>{let q=x.data,[E,H]=vt(q,{transportId:r,filter:()=>s==null?void 0:s(x)});E||v(H)},i.addEventListener("message",d)},unregisterHandler(){d&&i.removeEventListener("message",d)}}}function Ct(t,e){return _t(t,e)}function Re(t){return wt({transport:Ct(t,{transportId:"marimo-transport"}),maxRequestTime:2e4,_debugHooks:{onSend:e=>{p.debug("[rpc] Parent -> Worker",e)},onReceive:e=>{p.debug("[rpc] Worker -> Parent",e)}}})}ye=de("Initializing..."),Xe=de(t=>{let e=t(Gt),r=Object.values(e.cellRuntime);return r.some(s=>!fr(s.output))?!0:r.every(s=>s.status==="idle")});var xe=br(),qe="marimo:file",Ee=new Xt(null);const Rt={saveFile(t){Ee.set(qe,t)},readFile(){return Ee.get(qe)}};var xt={saveFile(t){G.setCodeForHash((0,xe.compressToEncodedURIComponent)(t))},readFile(){let t=G.getCodeFromHash()||G.getCodeFromSearchParam();return t?(0,xe.decompressFromEncodedURIComponent)(t):null}};const qt={saveFile(t){},readFile(){let t=document.querySelector("marimo-code");return t?decodeURIComponent(t.textContent||"").trim():null}};var Et={saveFile(t){},readFile(){if(window.location.hostname!=="marimo.app")return null;let t=new URL("files/wasm-intro.py",document.baseURI);return fetch(t.toString()).then(e=>e.ok?e.text():null).catch(()=>null)}};const St={saveFile(t){},readFile(){return z.get(Jt)??null}};var kt={saveFile(t){},readFile(){return["import marimo","app = marimo.App()","","@app.cell","def __():"," return","",'if __name__ == "__main__":'," app.run()"].join(`
|
|
2
|
-
`)}},Se=class{constructor(t){this.stores=t}insert(t,e){this.stores.splice(t,0,e)}saveFile(t){this.stores.forEach(e=>e.saveFile(t))}readFile(){for(let t of this.stores){let e=t.readFile();if(e)return e}return null}};let X;V=new Se([St,qt,xt]),X=new Se([Rt,Et,kt]),ge=class ct{constructor(){n(this,"initialized",new Ye);n(this,"sendRename",async({filename:e})=>(e===null||(G.setFilename(e),await this.rpc.proxy.request.bridge({functionName:"rename_file",payload:e})),null));n(this,"sendSave",async e=>{if(!this.saveRpc)return p.warn("Save RPC not initialized"),null;await this.saveRpc.saveNotebook(e);let r=await this.readCode();return r.contents&&(V.saveFile(r.contents),X.saveFile(r.contents)),this.rpc.proxy.request.saveNotebook(e).catch(s=>{p.error(s)}),null});n(this,"sendCopy",async()=>{g()});n(this,"sendStdin",async e=>(await this.rpc.proxy.request.bridge({functionName:"put_input",payload:e.text}),null));n(this,"sendPdb",async()=>{g()});n(this,"sendRun",async e=>(await this.rpc.proxy.request.loadPackages(e.codes.join(`
|
|
3
|
-
`)),await this.putControlRequest({type:"execute-cells",...e}),null));n(this,"sendRunScratchpad",async e=>(await this.rpc.proxy.request.loadPackages(e.code),await this.putControlRequest({type:"execute-scratchpad",...e}),null));n(this,"sendInterrupt",async()=>(this.interruptBuffer!==void 0&&(this.interruptBuffer[0]=2),null));n(this,"sendShutdown",async()=>(window.close(),null));n(this,"sendFormat",async e=>await this.rpc.proxy.request.bridge({functionName:"format",payload:e}));n(this,"sendDeleteCell",async e=>(await this.putControlRequest({type:"delete-cell",...e}),null));n(this,"sendInstallMissingPackages",async e=>(this.putControlRequest({type:"install-packages",...e}),null));n(this,"sendCodeCompletionRequest",async e=>(z.get(er)||await this.rpc.proxy.request.bridge({functionName:"code_complete",payload:e}),null));n(this,"saveUserConfig",async e=>(await this.rpc.proxy.request.bridge({functionName:"save_user_config",payload:e}),ur.post("/kernel/save_user_config",e,{baseUrl:"/"}).catch(r=>(p.error(r),null))));n(this,"saveAppConfig",async e=>(await this.rpc.proxy.request.bridge({functionName:"save_app_config",payload:e}),null));n(this,"saveCellConfig",async e=>(await this.putControlRequest({type:"update-cell-config",...e}),null));n(this,"sendRestart",async()=>{let e=await this.readCode();return e.contents&&(V.saveFile(e.contents),X.saveFile(e.contents)),vr(),null});n(this,"readCode",async()=>this.saveRpc?{contents:await this.saveRpc.readNotebook()}:(p.warn("Save RPC not initialized"),{contents:""}));n(this,"readSnippets",async()=>await this.rpc.proxy.request.bridge({functionName:"read_snippets",payload:void 0}));n(this,"openFile",async({path:e})=>{let r=_r({code:null,baseUrl:window.location.origin});return window.open(r,"_blank"),null});n(this,"sendListFiles",async e=>await this.rpc.proxy.request.bridge({functionName:"list_files",payload:e}));n(this,"sendSearchFiles",async e=>await this.rpc.proxy.request.bridge({functionName:"search_files",payload:e}));n(this,"sendComponentValues",async e=>(await this.putControlRequest({type:"update-ui-element",...e,token:Ge()}),null));n(this,"sendInstantiate",async e=>null);n(this,"sendFunctionRequest",async e=>(await this.putControlRequest({type:"invoke-function",...e}),null));n(this,"sendCreateFileOrFolder",async e=>await this.rpc.proxy.request.bridge({functionName:"create_file_or_directory",payload:e}));n(this,"sendDeleteFileOrFolder",async e=>await this.rpc.proxy.request.bridge({functionName:"delete_file_or_directory",payload:e}));n(this,"sendCopyFileOrFolder",async e=>await this.rpc.proxy.request.bridge({functionName:"copy_file_or_directory",payload:e}));n(this,"sendRenameFileOrFolder",async e=>await this.rpc.proxy.request.bridge({functionName:"move_file_or_directory",payload:e}));n(this,"sendUpdateFile",async e=>await this.rpc.proxy.request.bridge({functionName:"update_file",payload:e}));n(this,"sendFileDetails",async e=>await this.rpc.proxy.request.bridge({functionName:"file_details",payload:e}));n(this,"exportAsHTML",async e=>await this.rpc.proxy.request.bridge({functionName:"export_html",payload:e}));n(this,"exportAsMarkdown",async e=>await this.rpc.proxy.request.bridge({functionName:"export_markdown",payload:e}));n(this,"previewDatasetColumn",async e=>(await this.putControlRequest({type:"preview-dataset-column",...e}),null));n(this,"previewSQLTable",async e=>(await this.putControlRequest({type:"preview-sql-table",...e}),null));n(this,"previewSQLTableList",async e=>(await this.putControlRequest({type:"list-sql-tables",...e}),null));n(this,"previewSQLSchemaList",async e=>(await this.putControlRequest({type:"list-sql-schemas",...e}),null));n(this,"previewDataSourceConnection",async e=>(await this.putControlRequest({type:"list-data-source-connection",...e}),null));n(this,"validateSQL",async e=>(await this.putControlRequest({type:"validate-sql",...e}),null));n(this,"sendModelValue",async e=>(await this.putControlRequest({type:"model",...e}),null));n(this,"sendDocumentTransaction",()=>Promise.resolve(null));n(this,"addPackage",async e=>this.rpc.proxy.request.addPackage(e));n(this,"removePackage",async e=>this.rpc.proxy.request.removePackage(e));n(this,"getPackageList",async()=>await this.rpc.proxy.request.listPackages());n(this,"getDependencyTree",async()=>({tree:{dependencies:[],name:"",tags:[],version:null}}));n(this,"listSecretKeys",async e=>(await this.putControlRequest({type:"list-secret-keys",...e}),null));n(this,"getUsageStats",g);n(this,"openTutorial",g);n(this,"getRecentFiles",g);n(this,"getWorkspaceFiles",g);n(this,"getRunningNotebooks",g);n(this,"shutdownSession",g);n(this,"exportAsIPYNB",g);n(this,"exportAsPDF",g);n(this,"autoExportAsHTML",g);n(this,"autoExportAsMarkdown",g);n(this,"autoExportAsIPYNB",g);n(this,"updateCellOutputs",g);n(this,"writeSecret",g);n(this,"invokeAiTool",g);n(this,"clearCache",g);n(this,"getCacheInfo",g);n(this,"listStorageEntries",g);n(this,"downloadStorage",g);or()&&(this.rpc=Re(new Worker(new URL(""+new URL("worker-ztl1wuLb.js",import.meta.url).href,""+import.meta.url),{type:"module",name:$e()})),this.rpc.addMessageListener("ready",()=>{this.startSession()}),this.rpc.addMessageListener("initialized",()=>{this.saveRpc=this.getSaveWorker(),this.setInterruptBuffer(),this.initialized.resolve()}),this.rpc.addMessageListener("initializingMessage",({message:e})=>{z.set(ye,e)}),this.rpc.addMessageListener("initializedError",({error:e})=>{this.initialized.status==="resolved"&&(p.error(e),yr({title:"Error initializing",description:e,variant:"danger"})),this.initialized.reject(Error(e))}),this.rpc.addMessageListener("kernelMessage",({message:e})=>{var r;(r=this.messageConsumer)==null||r.call(this,new MessageEvent("message",{data:e}))}))}static get INSTANCE(){let e="_marimo_private_PyodideBridge";return window[e]||(window[e]=new ct),window[e]}getSaveWorker(){return He()==="read"?(p.debug("Using partially disabled SaveWorker in read-mode"),{readFile:g,readNotebook:async()=>await V.readFile()??"",saveNotebook:g}):Re(new Worker(new URL(""+new URL("save-worker-CtJsIYIM.js",import.meta.url).href,""+import.meta.url),{type:"module",name:$e()})).proxy.request}async startSession(){let e=await V.readFile(),r=await X.readFile(),s=z.get(Zt)??G.getFilename(),a=z.get(ir),i={},u=new URLSearchParams(window.location.search);for(let d of u.keys()){let v=u.getAll(d);i[d]=v.length===1?v[0]:v}await this.rpc.proxy.request.startSession({queryParameters:i,code:e||r||"",filename:s,userConfig:{...a,runtime:{...a.runtime,auto_instantiate:He()==="read"?!0:a.runtime.auto_instantiate}}})}setInterruptBuffer(){crossOriginIsolated?(this.interruptBuffer=new Uint8Array(new SharedArrayBuffer(1)),this.rpc.proxy.request.setInterruptBuffer(this.interruptBuffer)):p.warn("Not running in a secure context; interrupts are not available.")}attachMessageConsumer(e){this.messageConsumer=e,this.rpc.proxy.send.consumerReady({})}async putControlRequest(e){await this.rpc.proxy.request.bridge({functionName:"put_control_request",payload:e})}},rt=function(){return we.withProducerCallback(t=>{ge.INSTANCE.attachMessageConsumer(t)})},tt=function(t=ce()){let e=window.fetch;return window.fetch=async(r,s)=>{let a=r instanceof Request?r.url:r.toString();if(a.startsWith("data:"))return e(r,s);try{let i=Z(a,t);if(i){let u=await(await e(i)).arrayBuffer();return new Response(u,{headers:{"Content-Type":Mt(a)}})}return e(r,s)}catch(i){return p.error("Error parsing URL",i),e(r,s)}},()=>{window.fetch=e}};function Mt(t){return t.endsWith(".csv")?"text/csv":t.endsWith(".json")?"application/json":t.endsWith(".txt")?"text/plain":"application/octet-stream"}it=function(t,e=ce()){let r=t.http.bind(t),s=t.load.bind(t);return t.http=async a=>{let i=Z(a,e);if(i)return await window.fetch(i).then(u=>u.text());try{return await r(a)}catch(u){if(a.startsWith("data:"))return await window.fetch(a).then(d=>d.text());throw u}},t.load=async a=>{let i=Z(a,e);if(i)return await window.fetch(i).then(u=>u.text());try{return await s(a)}catch(u){if(a.startsWith("data:"))return await window.fetch(a).then(d=>d.text());throw u}},()=>{t.http=r,t.load=s}};function ke(t){return t.startsWith(".")?t.slice(1):t}function Ft(t,e=ce()){let r=Z(t,e);if(!r)return t;let s=Cr(r);return URL.createObjectURL(s)}function Z(t,e){let r=document.baseURI;r.startsWith("blob:")&&(r=r.replace("blob:",""));let s=new URL(t,r).pathname,a=Me(t),i=Me(s);return e[t]||e[ke(t)]||e[s]||e[ke(s)]||a&&e[a]||i&&e[i]}function Me(t){let e=t.indexOf("/@file/");return e===-1?null:t.slice(e)}function ie(t,e=[]){let r=[];if(t instanceof DataView)r.push(e);else if(Array.isArray(t))for(let[s,a]of t.entries())r.push(...ie(a,[...e,s]));else if(typeof t=="object"&&t)for(let[s,a]of Object.entries(t))r.push(...ie(a,[...e,s]));return r}function Pt(t){let e=ie(t);if(e.length===0)return{state:t,buffers:[],bufferPaths:[]};let r=structuredClone(t),s=[],a=[];for(let i of e){let u=rr(t,i);if(u instanceof DataView){let d=Ve(u);s.push(d),a.push(i),J(r,i,d)}}return{state:r,buffers:s,bufferPaths:a}}function Fe(t){let{state:e,bufferPaths:r,buffers:s}=t;if(!r||r.length===0)return e;s&&ar(s.length===r.length,"Buffers and buffer paths not the same length");let a=e;for(let[i,u]of r.entries()){let d=s==null?void 0:s[i];if(d==null){p.warn("[anywidget] Could not find buffer at path",u);continue}typeof d=="string"?J(a,u,Qe(d)):J(a,u,d)}return a}he=function(t){return typeof t!="string"||t.length===0?!1:!!(/^(\.?\/)?@file\/[^?#]+$/.test(t)||It(t)&&z.get(wr))};function It(t){return t.startsWith("data:text/javascript;base64,")||t.startsWith("data:application/javascript;base64,")||t.startsWith("data:text/css;base64,")}const Pe={invoke:async()=>{let t="anywidget.invoke not supported in marimo. Please file an issue at https://github.com/marimo-team/marimo/issues";throw p.warn(t),Error(t)}};function Lt(t){if(typeof AbortSignal.any=="function")return AbortSignal.any(t);let e=new AbortController;for(let r of t){if(r.aborted)return e.abort(r.reason),e.signal;r.addEventListener("abort",()=>e.abort(r.reason),{once:!0})}return e.signal}var Nt=(Le=class{constructor(){_(this,ee);_(this,U,new Map)}getModule(t,e){let r=o(this,U).get(e);if(r)return r;let s=A(this,ee,pt).call(this,t).catch(a=>{throw o(this,U).delete(e),a});return o(this,U).set(e,s),s}invalidate(t){p.debug(`[WidgetDefRegistry] Invalidating module cache for hash=${t}`),o(this,U).delete(t)}},U=new WeakMap,ee=new WeakSet,pt=async function(t){if(!he(t))throw Error(`Refusing to load anywidget module from untrusted URL: ${String(t)}`);let e=lr(t).toString();return ze()&&(e=Ft(e)),gr(()=>import(e).then(async r=>(await r.__tla,r)),[],import.meta.url)},Le),At=(Ne=class{constructor(){_(this,M);_(this,j);_(this,S)}async bind(t,e){var i,u;if(o(this,S)&&o(this,j)===t)return o(this,S);o(this,S)&&o(this,j)!==t&&(p.debug("[WidgetBinding] Hot-reload detected, aborting previous binding"),(i=o(this,M))==null||i.abort(),C(this,M,void 0),C(this,S,void 0)),C(this,j,t),C(this,M,new AbortController);let r=o(this,M).signal,s=typeof t=="function"?await t():t,a=await((u=s.initialize)==null?void 0:u.call(s,{model:e,experimental:Pe}));return a&&r.addEventListener("abort",a),C(this,S,async(d,v)=>{var q;let x=await((q=s.render)==null?void 0:q.call(s,{model:e,el:d,experimental:Pe}));x&&Lt([v,r]).addEventListener("abort",()=>{let E=v.aborted?"view unmount":"binding destroyed";p.debug(`[WidgetBinding] Render cleanup triggered (reason: ${E})`),x()})}),o(this,S)}destroy(){var t;p.debug("[WidgetBinding] Destroying binding, aborting initialize lifecycle"),(t=o(this,M))==null||t.abort(),C(this,M,void 0),C(this,j,void 0),C(this,S,void 0)}},M=new WeakMap,j=new WeakMap,S=new WeakMap,Ne),Ut=(Ae=class{constructor(){_(this,P,new Map)}getOrCreate(t){let e=o(this,P).get(t);return e||(e=new At,o(this,P).set(t,e)),e}destroy(t){let e=o(this,P).get(t);e&&(p.debug(`[BindingManager] Destroying binding for model=${t}`),e.destroy(),o(this,P).delete(t))}has(t){return o(this,P).has(t)}},P=new WeakMap,Ae);ut=new Nt,me=new Ut;var jt=(Ue=class{constructor(t=1e4){_(this,D);_(this,F,new Map);_(this,Y);C(this,Y,t)}get(t){let e=A(this,D,ae).call(this,t);return e.deferred.status==="pending"&&setTimeout(()=>{e.deferred.status==="pending"&&(e.deferred.reject(Error(`Model not found for key: ${t}`)),o(this,F).delete(t))},o(this,Y)),e.deferred.promise}create(t,e){let r=A(this,D,ae).call(this,t);r.deferred.resolve(e(r.controller.signal))}set(t,e){A(this,D,ae).call(this,t).deferred.resolve(e)}getSync(t){let e=o(this,F).get(t);if(e&&e.deferred.status==="resolved")return e.deferred.value}delete(t){var e;p.debug(`[ModelManager] Deleting model=${t}, aborting lifecycle signal`),(e=o(this,F).get(t))==null||e.controller.abort(),o(this,F).delete(t)}},F=new WeakMap,Y=new WeakMap,D=new WeakSet,ae=function(t){let e=o(this,F).get(t);return e||(e={deferred:new Ye,controller:new AbortController},o(this,F).set(t,e)),e},Ue),Ie=Symbol("marimo");function oe(t){return t[Ie]}ne=new jt;var Wt=(je=Ie,W=class{constructor(e,r,s){_(this,N);_(this,te,"change");_(this,I);_(this,L);_(this,O);_(this,R,{});n(this,je,{updateAndEmitDiffs:e=>A(this,N,ht).call(this,e),emitCustomMessage:(e,r)=>A(this,N,mt).call(this,e,r)});n(this,"widget_manager",{async get_model(e){let r=await W._modelManager.get(e);if(!r)throw Error(`Model not found with id: ${e}. This is likely because the model was not registered.`);return r}});_(this,re,sr(()=>{let e=o(this,R)[o(this,te)];if(e)for(let r of e)try{r()}catch(s){p.error("Error emitting event",s)}},0));C(this,L,e),C(this,O,r),C(this,I,new Map),s&&s.addEventListener("abort",()=>{p.debug("[Model] Signal aborted, clearing all listeners"),C(this,R,{})})}off(e,r){var s;if(!e){C(this,R,{});return}if(!r){o(this,R)[e]=new Set;return}(s=o(this,R)[e])==null||s.delete(r)}send(e,r,s){let a=(s??[]).map(i=>i instanceof ArrayBuffer?new DataView(i):new DataView(i.buffer,i.byteOffset,i.byteLength));return o(this,O).sendCustomMessage(e,a).then(()=>r==null?void 0:r())}get(e){return o(this,L)[e]}set(e,r){C(this,L,{...o(this,L),[e]:r}),o(this,I).set(e,r),A(this,N,ft).call(this,`change:${e}`,r),o(this,re).call(this)}save_changes(){if(o(this,I).size===0)return;let e=Object.fromEntries(o(this,I).entries());o(this,I).clear(),o(this,O).sendUpdate(e)}on(e,r){o(this,R)[e]||(o(this,R)[e]=new Set),o(this,R)[e].add(r)}},te=new WeakMap,I=new WeakMap,L=new WeakMap,O=new WeakMap,R=new WeakMap,N=new WeakSet,ft=function(e,r){if(!o(this,R)[e])return;let s=o(this,R)[e];for(let a of s)try{a(r)}catch(i){p.error("Error emitting event",i)}},ht=function(e){e!=null&&Object.keys(e).forEach(r=>{let s=r;o(this,L)[s]!==e[s]&&this.set(s,e[s])})},mt=function(e,r=[]){let s=o(this,R)["msg:custom"];if(s)for(let a of s)try{a(e.content,r)}catch(i){p.error("Error emitting event",i)}},re=new WeakMap,n(W,"_modelManager",ne),W);et=async function(t,e){let r=e.model_id,s=e.message,a=("buffers"in s?s.buffers:[]).map(Qe);switch(s.method){case"open":{let{state:i,buffer_paths:u=[]}=s,d=Fe({state:i,bufferPaths:u,buffers:a}),v=t.getSync(r);if(v){oe(v).updateAndEmitDiffs(d);return}t.create(r,x=>new Wt(d,ze()?{sendUpdate:async()=>{},sendCustomMessage:async()=>{}}:{async sendUpdate(q){if(x.aborted){p.debug(`[Model] sendUpdate suppressed for model=${r} (signal aborted)`);return}let{state:E,buffers:H,bufferPaths:se}=Pt(q);await pe().sendModelValue({modelId:r,message:{method:"update",state:E,bufferPaths:se},buffers:H})},async sendCustomMessage(q,E){if(x.aborted){p.debug(`[Model] sendCustomMessage suppressed for model=${r} (signal aborted)`);return}await pe().sendModelValue({modelId:r,message:{method:"custom",content:q},buffers:E.map(Ve)})}},x));return}case"custom":oe(await t.get(r)).emitCustomMessage({method:"custom",content:s.content},a);return;case"close":me.destroy(r),t.delete(r);return;case"update":{let{state:i,buffer_paths:u=[]}=s,d=Fe({state:i,bufferPaths:u,buffers:a});oe(await t.get(r)).updateAndEmitDiffs(d);return}default:pr(s)}},Kt(ne,"MODEL_MANAGER");function Tt(t){return typeof t!="object"||!t?!1:"type"in t&&t.type==="marimo-ui-value-update"}fe=class gt{static get INSTANCE(){let e="_marimo_private_UIElementRegistry";return window[e]||(window[e]=new gt),window[e]}constructor(){this.entries=new Map}has(e){return this.entries.has(e)}set(e,r){this.entries.has(e)&&p.debug("UIElementRegistry overwriting entry for objectId.",e),this.entries.set(e,{objectId:e,value:r,elements:new Set})}registerInstance(e,r){let s=this.entries.get(e);s===void 0?this.entries.set(e,{objectId:e,value:Qt(r,this),elements:new Set([r])}):s.elements.add(r)}removeInstance(e,r){let s=this.entries.get(e);s!=null&&s.elements.has(r)&&s.elements.delete(r)}removeElementsByCell(e){[...this.entries.keys()].filter(r=>r.startsWith(`${e}-`)).forEach(r=>{this.entries.delete(r)})}lookupValue(e){let r=this.entries.get(e);return r===void 0?void 0:r.value}broadcastMessage(e,r,s){let a=this.entries.get(e);if(a===void 0){p.warn("UIElementRegistry missing entry",e);return}if(Tt(r)){a.value=r.value,a.elements.forEach(i=>{i.dispatchEvent(Je.create({bubbles:!1,composed:!0,detail:{value:r.value,element:i}}))});return}a.elements.forEach(i=>{i.dispatchEvent(xr.create({bubbles:!1,composed:!0,detail:{objectId:e,message:r,buffers:s}}))})}broadcastValueUpdate(e,r,s){let a=this.entries.get(r);a===void 0?p.warn("UIElementRegistry missing entry",r):(a.value=s,a.elements.forEach(i=>{i!==e&&i.dispatchEvent(Je.create({bubbles:!1,composed:!0,detail:{value:s,element:i}}))}),document.dispatchEvent(Rr.create({bubbles:!0,composed:!0,detail:{objectId:r}})))}},at=fe.INSTANCE,st=new mr("function-call-result",async(t,e)=>{await pe().sendFunctionRequest({functionCallId:t,...e})}),lt="68px";var Bt="288px";nt=t=>t?/^\d+$/.test(t)?`${t}px`:t:Bt,ot=Yt({isOpen:!0},(t,e)=>{if(!e)return t;switch(e.type){case"toggle":return{...t,isOpen:e.isOpen??t.isOpen};case"setWidth":return{...t,width:e.width};default:return t}})});export{J as C,Ke as S,Xe as _,qr as __tla,fe as a,Ze as b,et as c,he as d,tt as f,V as g,rt as h,st as i,me as l,ge as m,nt as n,at as o,it as p,ot as r,ne as s,lt as t,ut as u,ye as v,dt as x,we as y};
|