@doenet/doenetml-iframe 0.7.20-dev.320 → 0.7.20-dev.323

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/index.d.ts CHANGED
@@ -75,7 +75,7 @@ declare type DoenetEditorProps = Omit<React.ComponentProps<typeof DoenetEditor_2
75
75
  * DoenetML component. Instead you must use the message passing interface of `DoenetViewer` to communicate
76
76
  * with the underlying DoenetML component.
77
77
  */
78
- export declare function DoenetViewer({ doenetML, standaloneUrl: specifiedStandaloneUrl, cssUrl: specifiedCssUrl, doenetmlVersion: specifiedDoenetmlVersion, autodetectVersion, ...doenetViewerProps }: DoenetViewerIframeProps): default_2.JSX.Element | null;
78
+ export declare function DoenetViewer({ doenetML, standaloneUrl: specifiedStandaloneUrl, cssUrl: specifiedCssUrl, doenetmlVersion: specifiedDoenetmlVersion, autodetectVersion, useSharedCoreWorker, ...doenetViewerProps }: DoenetViewerIframeProps): default_2.JSX.Element | null;
79
79
 
80
80
  export declare type DoenetViewerIframeProps = DoenetViewerProps & {
81
81
  doenetML: string;
@@ -100,6 +100,17 @@ export declare type DoenetViewerIframeProps = DoenetViewerProps & {
100
100
  * overwriting any doenetmlVersion or urls provided
101
101
  */
102
102
  autodetectVersion?: boolean;
103
+ /**
104
+ * Opt-in (#1466): multiplex this viewer's core onto a shared core worker
105
+ * owned by this (parent) page, instead of the iframe booting its own
106
+ * ~100 MB dedicated worker. Viewers on the same page with the same
107
+ * standalone version share workers (up to a pool cap per worker), which
108
+ * is the dominant memory saving on pages embedding many documents.
109
+ * Trade-off: a worker-level hang or crash affects every document on that
110
+ * worker (per-core teardown stays individual; suspect workers are
111
+ * quarantined so retries boot fresh ones). Default off.
112
+ */
113
+ useSharedCoreWorker?: boolean;
103
114
  };
104
115
 
105
116
  declare type DoenetViewerProps = Omit<React.ComponentProps<typeof DoenetViewer_2>, "doenetML" | "externalVirtualKeyboardProvided">;
package/index.js CHANGED
@@ -25618,7 +25618,7 @@ function requireCssesc$1() {
25618
25618
  return cssesc_1$1;
25619
25619
  }
25620
25620
  requireCssesc$1();
25621
- const viewerIframeJsSource = '(function() {\n "use strict";\n async function waitForStandaloneBundle(timeoutMs) {\n if (typeof window.renderDoenetViewerToContainer === "function") {\n return true;\n }\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n await new Promise((resolve) => setTimeout(resolve, 50));\n if (typeof window.renderDoenetViewerToContainer === "function") {\n return true;\n }\n }\n return false;\n }\n ComlinkViewer.expose(\n { renderViewerWithFunctionProps },\n ComlinkViewer.windowEndpoint(globalThis.parent)\n );\n function renderViewerWithFunctionProps(...args) {\n const augmentedDoenetViewerProps = { ...doenetViewerProps };\n augmentedDoenetViewerProps.externalVirtualKeyboardProvided = true;\n for (const propName of doenetViewerPropsSpecified) {\n if (!(propName in doenetViewerProps)) {\n const idx = args.indexOf(propName);\n if (idx !== -1) {\n augmentedDoenetViewerProps[propName] = args[idx + 1];\n }\n }\n }\n if (!doenetViewerPropsSpecified.includes("requestScrollTo")) {\n augmentedDoenetViewerProps.requestScrollTo = (offset) => {\n messageParentFromViewer({ type: "scrollTo", offset });\n };\n }\n window.renderDoenetViewerToContainer(\n document.getElementById("root"),\n void 0,\n augmentedDoenetViewerProps\n );\n }\n (async () => {\n if (await waitForStandaloneBundle(6e4)) {\n messageParentFromViewer({ iframeReady: true });\n } else {\n messageParentFromViewer({\n error: "Invalid DoenetML version or DoenetML package not found"\n });\n }\n })().catch((err) => {\n console.error(\n "iframe DoenetViewer: unexpected failure while signalling iframeReady",\n err\n );\n try {\n messageParentFromViewer({\n error: "iframe viewer failed to initialize"\n });\n } catch {\n }\n });\n function messageParentFromViewer(data) {\n window.parent.postMessage(\n {\n origin: viewerId,\n data\n },\n window.parent.origin\n );\n }\n})();\n';
25621
+ const viewerIframeJsSource = '(function() {\n "use strict";\n async function waitForStandaloneBundle(timeoutMs) {\n if (typeof window.renderDoenetViewerToContainer === "function") {\n return true;\n }\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n await new Promise((resolve) => setTimeout(resolve, 50));\n if (typeof window.renderDoenetViewerToContainer === "function") {\n return true;\n }\n }\n return false;\n }\n ComlinkViewer.expose(\n { renderViewerWithFunctionProps },\n ComlinkViewer.windowEndpoint(globalThis.parent)\n );\n function renderViewerWithFunctionProps(...args) {\n const augmentedDoenetViewerProps = { ...doenetViewerProps };\n augmentedDoenetViewerProps.externalVirtualKeyboardProvided = true;\n for (const propName of doenetViewerPropsSpecified) {\n if (!(propName in doenetViewerProps)) {\n const idx = args.indexOf(propName);\n if (idx !== -1) {\n augmentedDoenetViewerProps[propName] = args[idx + 1];\n }\n }\n }\n if (!doenetViewerPropsSpecified.includes("requestScrollTo")) {\n augmentedDoenetViewerProps.requestScrollTo = (offset) => {\n messageParentFromViewer({ type: "scrollTo", offset });\n };\n }\n window.renderDoenetViewerToContainer(\n document.getElementById("root"),\n void 0,\n augmentedDoenetViewerProps\n );\n }\n function installSharedCorePortProvider() {\n let coreCounter = 0;\n window.doenetGlobalConfig.createExternalCoreWorkerPort = () => {\n const coreId = `${viewerId}-core-${++coreCounter}`;\n const channel = new MessageChannel();\n window.parent.postMessage(\n { origin: viewerId, data: { type: "createSharedCore", coreId } },\n window.parent.origin,\n [channel.port2]\n );\n return {\n port: channel.port1,\n destroy: (suspectWedge) => {\n messageParentFromViewer({\n type: "destroySharedCore",\n coreId,\n suspectWedge: Boolean(suspectWedge)\n });\n }\n };\n };\n }\n (async () => {\n if (await waitForStandaloneBundle(6e4)) {\n if (typeof doenetSharedCoreWorker !== "undefined" && doenetSharedCoreWorker) {\n installSharedCorePortProvider();\n }\n messageParentFromViewer({ iframeReady: true });\n } else {\n messageParentFromViewer({\n error: "Invalid DoenetML version or DoenetML package not found"\n });\n }\n })().catch((err) => {\n console.error(\n "iframe DoenetViewer: unexpected failure while signalling iframeReady",\n err\n );\n try {\n messageParentFromViewer({\n error: "iframe viewer failed to initialize"\n });\n } catch {\n }\n });\n function messageParentFromViewer(data) {\n window.parent.postMessage(\n {\n origin: viewerId,\n data\n },\n window.parent.origin\n );\n }\n})();\n';
25622
25622
  const editorIframeJsSource = `(function() {
25623
25623
  "use strict";
25624
25624
  let editorControlHandle = null;
@@ -25856,7 +25856,7 @@ function createIframeHead(standaloneUrl, cssUrl) {
25856
25856
  function createIframeBodyOpenTag(darkMode) {
25857
25857
  return `<body style="margin:0" ${BODY_BACKGROUND_ATTRIBUTE}="${bodyBackgroundMode(darkMode)}">`;
25858
25858
  }
25859
- function createHtmlForDoenetViewer(id2, doenetML, doenetViewerProps, standaloneUrl, cssUrl) {
25859
+ function createHtmlForDoenetViewer(id2, doenetML, doenetViewerProps, standaloneUrl, cssUrl, useSharedCoreWorker = false) {
25860
25860
  const doenetViewerPropsSpecified = Object.keys(doenetViewerProps);
25861
25861
  return `
25862
25862
  <html style="overflow:hidden">
@@ -25866,6 +25866,7 @@ function createHtmlForDoenetViewer(id2, doenetML, doenetViewerProps, standaloneU
25866
25866
  const viewerId = "${id2}";
25867
25867
  const doenetViewerProps = ${JSON.stringify(doenetViewerProps)};
25868
25868
  const doenetViewerPropsSpecified = ${JSON.stringify(doenetViewerPropsSpecified)};
25869
+ const doenetSharedCoreWorker = ${JSON.stringify(!!useSharedCoreWorker)};
25869
25870
  import * as ComlinkViewer from "https://unpkg.com/comlink/dist/esm/comlink.mjs";
25870
25871
 
25871
25872
  // This source code has been compiled by vite and should be directly included.
@@ -25907,6 +25908,124 @@ function createHtmlForDoenetEditor(id2, doenetML, width, doenetEditorProps, stan
25907
25908
  </html>
25908
25909
  `;
25909
25910
  }
25911
+ const MAX_CORES_PER_WORKER = 12;
25912
+ const hostsByWorkerUrl = /* @__PURE__ */ new Map();
25913
+ const coreRecords = /* @__PURE__ */ new Map();
25914
+ function resolveWorkerUrl(standaloneUrl) {
25915
+ try {
25916
+ return new URL("./doenetml-worker/index.js", standaloneUrl).href;
25917
+ } catch {
25918
+ }
25919
+ try {
25920
+ return new URL("/doenetml-worker/index.js", document.baseURI).href;
25921
+ } catch {
25922
+ return null;
25923
+ }
25924
+ }
25925
+ function workerCreationUrl(workerUrl) {
25926
+ try {
25927
+ if (new URL(workerUrl).origin === window.location.origin) {
25928
+ return workerUrl;
25929
+ }
25930
+ } catch {
25931
+ }
25932
+ const bootstrap = `importScripts(${JSON.stringify(workerUrl)});`;
25933
+ return URL.createObjectURL(
25934
+ new Blob([bootstrap], { type: "text/javascript" })
25935
+ );
25936
+ }
25937
+ function quarantineHost(workerUrl, host) {
25938
+ host.quarantined = true;
25939
+ if (host.liveCores <= 0) {
25940
+ try {
25941
+ host.worker.terminate();
25942
+ } catch {
25943
+ }
25944
+ const pool = hostsByWorkerUrl.get(workerUrl);
25945
+ const index2 = pool?.indexOf(host) ?? -1;
25946
+ if (pool && index2 !== -1) {
25947
+ pool.splice(index2, 1);
25948
+ }
25949
+ }
25950
+ }
25951
+ function getHost(workerUrl) {
25952
+ let pool = hostsByWorkerUrl.get(workerUrl);
25953
+ if (!pool) {
25954
+ pool = [];
25955
+ hostsByWorkerUrl.set(workerUrl, pool);
25956
+ }
25957
+ let host = pool.find(
25958
+ (h2) => !h2.quarantined && h2.liveCores < MAX_CORES_PER_WORKER
25959
+ );
25960
+ if (!host) {
25961
+ const worker = new Worker(workerCreationUrl(workerUrl), {
25962
+ type: "classic"
25963
+ });
25964
+ const newHost = {
25965
+ worker,
25966
+ remote: wrap$3(worker),
25967
+ liveCores: 0,
25968
+ quarantined: false
25969
+ };
25970
+ worker.addEventListener("error", () => {
25971
+ quarantineHost(workerUrl, newHost);
25972
+ });
25973
+ pool.push(newHost);
25974
+ host = newHost;
25975
+ }
25976
+ return host;
25977
+ }
25978
+ function handleCreateSharedCore({
25979
+ viewerId,
25980
+ coreId,
25981
+ standaloneUrl,
25982
+ port
25983
+ }) {
25984
+ const workerUrl = resolveWorkerUrl(standaloneUrl);
25985
+ if (workerUrl === null) {
25986
+ console.warn(
25987
+ "doenetml-iframe: could not resolve a shared core worker URL for",
25988
+ standaloneUrl
25989
+ );
25990
+ return;
25991
+ }
25992
+ const host = getHost(workerUrl);
25993
+ host.liveCores++;
25994
+ const idPromise = host.remote.createCore(transfer(port, [port]));
25995
+ idPromise.catch(() => {
25996
+ });
25997
+ coreRecords.set(coreId, {
25998
+ viewerId,
25999
+ host,
26000
+ workerUrl,
26001
+ idPromise,
26002
+ destroyed: false
26003
+ });
26004
+ }
26005
+ function handleDestroySharedCore({
26006
+ coreId,
26007
+ suspectWedge
26008
+ }) {
26009
+ const record = coreRecords.get(coreId);
26010
+ if (!record || record.destroyed) {
26011
+ return;
26012
+ }
26013
+ record.destroyed = true;
26014
+ coreRecords.delete(coreId);
26015
+ record.host.liveCores--;
26016
+ record.idPromise.then((id2) => record.host.remote.destroyCore(id2)).catch(() => {
26017
+ });
26018
+ if (suspectWedge || record.host.quarantined) {
26019
+ quarantineHost(record.workerUrl, record.host);
26020
+ }
26021
+ }
26022
+ function destroySharedCoresForViewer(viewerId) {
26023
+ for (const [coreId, record] of [...coreRecords]) {
26024
+ if (record.viewerId === viewerId) {
26025
+ handleDestroySharedCore({ coreId, suspectWedge: false });
26026
+ }
26027
+ }
26028
+ }
25910
26029
  function findNodesWithPositionInfo(nodes) {
25911
26030
  if (typeof nodes !== "object" || nodes === null) {
25912
26031
  return [];
@@ -63053,7 +63172,7 @@ function ExternalVirtualKeyboard({
63053
63172
  }
63054
63173
  );
63055
63174
  }
63056
- const version = "0.7.20-dev.320";
63175
+ const version = "0.7.20-dev.323";
63057
63176
  const latestDoenetmlVersion = version;
63058
63177
  function subscribeToPinnedTheme() {
63059
63178
  return () => {
@@ -63074,6 +63193,7 @@ function DoenetViewer({
63074
63193
  cssUrl: specifiedCssUrl,
63075
63194
  doenetmlVersion: specifiedDoenetmlVersion,
63076
63195
  autodetectVersion = true,
63196
+ useSharedCoreWorker = false,
63077
63197
  ...doenetViewerProps
63078
63198
  }) {
63079
63199
  const [id2, _2] = React__default__default.useState(() => Math.random().toString(36).slice(2));
@@ -63106,6 +63226,16 @@ function DoenetViewer({
63106
63226
  } else {
63107
63227
  cssUrl = `https://cdn.jsdelivr.net/npm/@doenet/standalone@${selectedDoenetmlVersion}/style.css`;
63108
63228
  }
63229
+ const standaloneUrlRef = React__default__default.useRef(standaloneUrl);
63230
+ standaloneUrlRef.current = standaloneUrl;
63231
+ React__default__default.useEffect(() => {
63232
+ if (!useSharedCoreWorker) {
63233
+ return;
63234
+ }
63235
+ return () => {
63236
+ destroySharedCoresForViewer(id2);
63237
+ };
63238
+ }, []);
63109
63239
  React__default__default.useEffect(() => {
63110
63240
  const listener = (event) => {
63111
63241
  if (event.origin !== window.location.origin) {
@@ -63127,6 +63257,22 @@ function DoenetViewer({
63127
63257
  return;
63128
63258
  }
63129
63259
  const data = event.data.data;
63260
+ if (data.type === "createSharedCore" && event.ports[0]) {
63261
+ handleCreateSharedCore({
63262
+ viewerId: id2,
63263
+ coreId: String(data.coreId),
63264
+ standaloneUrl: standaloneUrlRef.current,
63265
+ port: event.ports[0]
63266
+ });
63267
+ return;
63268
+ }
63269
+ if (data.type === "destroySharedCore") {
63270
+ handleDestroySharedCore({
63271
+ coreId: String(data.coreId),
63272
+ suspectWedge: Boolean(data.suspectWedge)
63273
+ });
63274
+ return;
63275
+ }
63130
63276
  if (ref.current && data.type === "scrollTo" && typeof data.offset === "number") {
63131
63277
  const iframeTop = ref.current.getBoundingClientRect().top + window.scrollY;
63132
63278
  const targetAbsoluteTop = iframeTop + data.offset;
@@ -63208,7 +63354,8 @@ function DoenetViewer({
63208
63354
  doenetML,
63209
63355
  doenetViewerProps,
63210
63356
  standaloneUrl,
63211
- cssUrl
63357
+ cssUrl,
63358
+ useSharedCoreWorker
63212
63359
  ),
63213
63360
  style: {
63214
63361
  width: "100%",