From c4c96a5aeb1b231ccb4bc7e85d595cc143f116c7 Mon Sep 17 00:00:00 2001 From: bambus80 Date: Mon, 22 Sep 2025 20:13:42 +0200 Subject: [PATCH 1/7] add iframe message blocks --- extensions/iframe.js | 48 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/extensions/iframe.js b/extensions/iframe.js index 68375ea344..45c8fec8c5 100644 --- a/extensions/iframe.js +++ b/extensions/iframe.js @@ -53,6 +53,7 @@ let height = -1; // negative means default let interactive = true; let resizeBehavior = "scale"; + let latestMessage = null; const updateFrameAttributes = () => { if (!iframe) { @@ -121,6 +122,12 @@ } }; + const handleFrameMessage = (e) => { + latestMessage = e.data; + Scratch.vm.runtime.startHats("iframe_whenMessage"); + }; + window.onmessage = handleFrameMessage; + Scratch.vm.on("STAGE_SIZE_CHANGED", updateFrameAttributes); Scratch.vm.runtime.on("RUNTIME_DISPOSED", closeFrame); @@ -247,6 +254,35 @@ }, }, }, + "---", + { + opcode: "sendMessage", + blockType: Scratch.BlockType.COMMAND, + text: Scratch.translate("send message [MESSAGE] to iframe"), + arguments: { + MESSAGE: { + type: Scratch.ArgumentType.STRING, + defaultValue: "hello", + }, + }, + }, + { + opcode: "clearMessage", + blockType: Scratch.BlockType.COMMAND, + text: Scratch.translate("clear iframe message"), + }, + { + opcode: "whenMessage", + blockType: Scratch.BlockType.EVENT, + text: Scratch.translate("when message from iframe is received"), + isEdgeActivated: false, + }, + { + opcode: "iframeMessage", + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate("iframe message"), + disableMonitor: true, + }, ], menus: { getMenu: { @@ -379,6 +415,18 @@ } } } + + sendMessage({ MESSAGE }) { + iframe.contentWindow.postMessage(MESSAGE, "*"); + } + + clearMessage() { + latestMessage = null; + } + + iframeMessage() { + return latestMessage; + } } Scratch.extensions.register(new IframeExtension()); From 97c956a5ca1d46ba7f83c02008fda3d66750d7c7 Mon Sep 17 00:00:00 2001 From: bambus80 Date: Mon, 22 Sep 2025 20:21:28 +0200 Subject: [PATCH 2/7] fix spelling mistake --- extensions/iframe.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/iframe.js b/extensions/iframe.js index 45c8fec8c5..f21ccfb585 100644 --- a/extensions/iframe.js +++ b/extensions/iframe.js @@ -274,7 +274,7 @@ { opcode: "whenMessage", blockType: Scratch.BlockType.EVENT, - text: Scratch.translate("when message from iframe is received"), + text: Scratch.translate("when message from iframe is sent"), isEdgeActivated: false, }, { From f07fa7db92b134be93bd9344b1d13baa27d10392 Mon Sep 17 00:00:00 2001 From: bambus80 Date: Mon, 22 Sep 2025 20:30:14 +0200 Subject: [PATCH 3/7] do not allow messages from 3rd-party websites --- extensions/iframe.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/extensions/iframe.js b/extensions/iframe.js index f21ccfb585..0beb0239d7 100644 --- a/extensions/iframe.js +++ b/extensions/iframe.js @@ -54,6 +54,7 @@ let interactive = true; let resizeBehavior = "scale"; let latestMessage = null; + let isTrusted = false; const updateFrameAttributes = () => { if (!iframe) { @@ -123,8 +124,10 @@ }; const handleFrameMessage = (e) => { - latestMessage = e.data; - Scratch.vm.runtime.startHats("iframe_whenMessage"); + if (isTrusted) { + latestMessage = e.data; + Scratch.vm.runtime.startHats("iframe_whenMessage"); + } }; window.onmessage = handleFrameMessage; @@ -326,6 +329,7 @@ async display({ URL }) { closeFrame(); if (await Scratch.canEmbed(URL)) { + isTrusted = false; createFrame(Scratch.Cast.toString(URL)); } } @@ -336,6 +340,7 @@ Scratch.Cast.toString(HTML) )}`; if (await Scratch.canEmbed(url)) { + isTrusted = true; createFrame(url); } } @@ -417,7 +422,9 @@ } sendMessage({ MESSAGE }) { - iframe.contentWindow.postMessage(MESSAGE, "*"); + if (isTrusted) { + iframe.contentWindow.postMessage(MESSAGE, "*"); + } } clearMessage() { From ba27bdbbe5f685db61755b9190b970fd7b4d1387 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Fri, 10 Oct 2025 23:29:16 -0500 Subject: [PATCH 4/7] improve it a lot, add example to hello.html --- extensions/iframe.js | 36 +++++++++++++----------------------- website/hello.html | 23 ++++++++++++++++++----- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/extensions/iframe.js b/extensions/iframe.js index 0beb0239d7..f1a67276a3 100644 --- a/extensions/iframe.js +++ b/extensions/iframe.js @@ -53,8 +53,8 @@ let height = -1; // negative means default let interactive = true; let resizeBehavior = "scale"; - let latestMessage = null; - let isTrusted = false; + /** @type {string|number|boolean} */ + let latestMessage = ""; const updateFrameAttributes = () => { if (!iframe) { @@ -123,13 +123,17 @@ } }; - const handleFrameMessage = (e) => { - if (isTrusted) { - latestMessage = e.data; + window.addEventListener("message", (e) => { + if (e.source === iframe.contentWindow) { + latestMessage = + typeof e.data === "string" || + typeof e.data === "number" || + typeof e.data === "boolean" + ? e.data + : JSON.stringify(e.data); Scratch.vm.runtime.startHats("iframe_whenMessage"); } - }; - window.onmessage = handleFrameMessage; + }); Scratch.vm.on("STAGE_SIZE_CHANGED", updateFrameAttributes); @@ -269,22 +273,16 @@ }, }, }, - { - opcode: "clearMessage", - blockType: Scratch.BlockType.COMMAND, - text: Scratch.translate("clear iframe message"), - }, { opcode: "whenMessage", blockType: Scratch.BlockType.EVENT, - text: Scratch.translate("when message from iframe is sent"), + text: Scratch.translate("when message received from iframe"), isEdgeActivated: false, }, { opcode: "iframeMessage", blockType: Scratch.BlockType.REPORTER, text: Scratch.translate("iframe message"), - disableMonitor: true, }, ], menus: { @@ -329,7 +327,6 @@ async display({ URL }) { closeFrame(); if (await Scratch.canEmbed(URL)) { - isTrusted = false; createFrame(Scratch.Cast.toString(URL)); } } @@ -340,7 +337,6 @@ Scratch.Cast.toString(HTML) )}`; if (await Scratch.canEmbed(url)) { - isTrusted = true; createFrame(url); } } @@ -422,13 +418,7 @@ } sendMessage({ MESSAGE }) { - if (isTrusted) { - iframe.contentWindow.postMessage(MESSAGE, "*"); - } - } - - clearMessage() { - latestMessage = null; + iframe.contentWindow.postMessage(MESSAGE, "*"); } iframeMessage() { diff --git a/website/hello.html b/website/hello.html index 5bd51d8c8f..6375da4764 100644 --- a/website/hello.html +++ b/website/hello.html @@ -32,18 +32,31 @@

It works!

-

Transparency is supported, so you can display the project behind the frame as long as the page doesn't explicitly set a background color.

-

You can also use JavaScript and most other Web APIs, like this:

+

Transparent backgrounds are supported, and JavaScript works.

-

Due to browser security measures, blocks like "mouse x" and "mouse down?" will not work while the cursor is over the frame unless you use the disable interactivity block.

-

Many websites also block other sites from embedding them for security reasons.

+

You can send messages between the extension and the iframe. Try sending this iframe a message.

+ +

+

Many websites block other sites from embedding them for security reasons. Blocks like "mouse x" and "mouse down?" will not work while the cursor is over the frame unless you use the disable interactivity block.

From fa1d6f3c779838c7e0ae52760d27817e4d577981 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Fri, 10 Oct 2025 23:32:31 -0500 Subject: [PATCH 5/7] don't get fooled by itself --- website/hello.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/hello.html b/website/hello.html index 6375da4764..a86b63ae62 100644 --- a/website/hello.html +++ b/website/hello.html @@ -53,7 +53,7 @@

It works!

}); window.addEventListener("message", (e) => { - if (e.source === window.parent) { + if (e.source === window.parent && e.source !== window) { document.querySelector(".received").textContent = `Last message received by iframe: ${e.data}`; } }); From 267a7f7887d4a1b242e18215625d185387749422 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Fri, 10 Oct 2025 23:32:56 -0500 Subject: [PATCH 6/7] iframe might be null --- extensions/iframe.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/iframe.js b/extensions/iframe.js index f1a67276a3..63e463658a 100644 --- a/extensions/iframe.js +++ b/extensions/iframe.js @@ -124,7 +124,7 @@ }; window.addEventListener("message", (e) => { - if (e.source === iframe.contentWindow) { + if (iframe && e.source === iframe.contentWindow) { latestMessage = typeof e.data === "string" || typeof e.data === "number" || From fca08720a9a721c33769d1cd31192d24c68a1cfa Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Fri, 10 Oct 2025 23:33:41 -0500 Subject: [PATCH 7/7] more null checks --- extensions/iframe.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/extensions/iframe.js b/extensions/iframe.js index 63e463658a..e39a89af4d 100644 --- a/extensions/iframe.js +++ b/extensions/iframe.js @@ -124,7 +124,7 @@ }; window.addEventListener("message", (e) => { - if (iframe && e.source === iframe.contentWindow) { + if (iframe && iframe.contentWindow && e.source === iframe.contentWindow) { latestMessage = typeof e.data === "string" || typeof e.data === "number" || @@ -418,7 +418,9 @@ } sendMessage({ MESSAGE }) { - iframe.contentWindow.postMessage(MESSAGE, "*"); + if (iframe && iframe.contentWindow) { + iframe.contentWindow.postMessage(MESSAGE, "*"); + } } iframeMessage() {