Skip to content

Commit 9a62934

Browse files
committed
stable html: switch to ttl cache; only use for trusted content; process math and links
1 parent ea0c1ce commit 9a62934

File tree

3 files changed

+40
-32
lines changed

3 files changed

+40
-32
lines changed

src/packages/frontend/jupyter/output-messages/mime-types/iframe.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import register from "./register";
22
import IFrame from "../iframe";
33

4-
register("iframe", 7, ({ id, project_id, value, index }) => {
4+
register("iframe", 7, ({ id, project_id, value, index, trust }) => {
55
if (value == null || project_id == null) {
66
return <pre>iframe must specify project_id and sha1</pre>;
77
}
@@ -11,6 +11,7 @@ register("iframe", 7, ({ id, project_id, value, index }) => {
1111
sha1={value}
1212
project_id={project_id}
1313
index={index}
14+
trust={trust}
1415
/>
1516
);
1617
});

src/packages/frontend/jupyter/output-messages/stable-unsafe-html.tsx

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,49 @@
11
/*
22
Create stable unsafe HTML DOM node. This is a way to render HTML that stays stable
33
irregardless of it being unmounted/remounted.
4-
This supports virtualization, window splitting, etc., without loss of state...
4+
5+
This supports virtualization, window splitting, etc., without loss of state,
56
unless there are too many of them, then we delete the oldest.
67
8+
By default, the HTML is just directly put into the DOM exactly as is, except that
9+
we *do* process links so internal references work and math using katex.
10+
711
Unsafe is in the name since there is NO SANITIZATION. Only use this on trusted
812
documents.
13+
14+
Elements only get re-rendered when for IDLE_TIMEOUT_S, both:
15+
16+
- the underlying react element does not exist, AND
17+
- the parent is not scrolled at all.
18+
19+
OR
20+
21+
- if there are more than MAX_ELEMENTS, then the oldest are removed (to avoid catastrophic memory usage).
22+
23+
If for any reason the react element exists or the parent is scrolled, then
24+
the idle timeout is reset.
925
*/
1026

1127
import { useCallback, useEffect, useRef } from "react";
1228
import $ from "jquery";
1329
import { useFrameContext } from "@cocalc/frontend/frame-editors/frame-tree/frame-context";
1430
import { useIFrameContext } from "@cocalc/frontend/jupyter/cell-list";
1531
import { sha1 } from "@cocalc/util/misc";
32+
import TTL from "@isaacs/ttlcache";
1633

17-
interface Props {
18-
docId: string;
19-
html: string;
20-
zIndex?: number;
21-
}
22-
23-
import LRU from "lru-cache";
34+
const IDLE_TIMEOUT_S = 10 * 60; // 10 minutes
35+
const MAX_ELEMENTS = 500; // max items
2436

25-
const CACHE_SIZE = 100;
26-
27-
const immortals = new LRU<string, any>({
28-
max: CACHE_SIZE,
37+
const cache = new TTL<string, any>({
38+
ttl: IDLE_TIMEOUT_S * 1000,
39+
max: MAX_ELEMENTS,
2940
updateAgeOnGet: true,
30-
updateAgeOnHas: true,
3141
dispose: (elt) => {
3242
elt.empty();
3343
elt.remove();
3444
},
3545
});
36-
// const immortals: { [globalKey: string]: any } = {};
46+
// const cache: { [globalKey: string]: any } = {};
3747

3848
const Z_INDEX = 1;
3949

@@ -52,6 +62,12 @@ const SCROLL_COUNT = 25;
5262
const PADDING = 0;
5363
const STYLE = {} as const;
5464

65+
interface Props {
66+
docId: string;
67+
html: string;
68+
zIndex?: number;
69+
}
70+
5571
export default function StableUnsafeHtml({
5672
docId,
5773
html,
@@ -134,18 +150,19 @@ export default function StableUnsafeHtml({
134150
}, []);
135151

136152
const getElt = () => {
137-
console.log("size", immortals.size);
138-
if (!immortals.has(globalKey)) {
139-
console.log("cache miss", globalKey);
153+
if (!cache.has(globalKey)) {
140154
const elt = $(
141155
`<div id="${globalKey}" style="border:0;position:absolute;overflow-y:hidden;z-index:${zIndex}"/>${html}</div>`,
142156
);
143-
immortals.set(globalKey, elt);
157+
// @ts-ignore
158+
elt.process_smc_links();
159+
// @ts-ignore
160+
elt.katex({ preProcess: true });
161+
cache.set(globalKey, elt);
144162
$("body").append(elt);
145163
return elt;
146164
} else {
147-
console.log("cache hit", globalKey);
148-
return immortals.get(globalKey);
165+
return cache.get(globalKey);
149166
}
150167
};
151168

src/packages/jupyter/blobs/iframe.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,7 @@ export function is_likely_iframe(content: string): boolean {
4141
// it'll just break anyways if we don't use an iframe -- if we do, there is hope.
4242
return true;
4343
}
44-
return (
45-
content.includes("bk-notebook-logo") ||
46-
content.startsWith("<iframe") ||
47-
content.includes("<!doctype html>") ||
48-
(content.includes("<html>") && content.includes("<head>")) ||
49-
// this gets really serious -- we sanitize out script tags in non-iframe html,
50-
// and a LOT of interesting jupyter outputs are self contained html + script tags... so
51-
// by rendering them all in iframes (1) they suddenly all work, which is great, and
52-
// (2) if they are large (which is common) they work even better, by far!
53-
content.includes("<script")
54-
);
44+
return content.startsWith("<iframe");
5545
}
5646

5747
export function process(

0 commit comments

Comments
 (0)