Files
2026-01-01 12:38:03 +00:00

134 lines
39 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html><html lang=en><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><meta name=author content="Godot Engine"><meta name=description content="No need to " eval"! a whole new interface to interact with javascript from godot scripts in web exports, and a new api to prompt the user to download a file generated by godot."><script defer data-domain=godotengine.org src=https://plausible.godot.foundation/js/script.file-downloads.outbound-links.js></script><meta property="og:site_name" content="Godot Engine"><meta property="og:url" content="https://godotengine.org/article/godot-web-progress-report-9/"><meta property="og:type" content="website"><meta property="og:description" content="No need to " eval"! a whole new interface to interact with javascript from godot scripts in web exports, and a new api to prompt the user to download a file generated by godot."><meta property="og:image" content="https://godotengine.org/storage/app/uploads/public/60c/dfe/795/60cdfe7956fca023522866.png"><meta name=twitter:card content="summary_large_image"><meta property="twitter:domain" content="godotengine.org"><meta property="twitter:url" content="https://godotengine.org/article/godot-web-progress-report-9/"><meta property="og:title" content="Godot Web progress report #9: Godot Scripts <-> JavaScript Interface Godot Engine"><title>Godot Web progress report #9: Godot Scripts <-> JavaScript Interface Godot Engine</title>
<link rel=alternate type=application/rss+xml title="Godot News" href=/rss.xml><link rel=alternate type=application/json title="Godot News" href=/rss.json><link rel=alternate type=application/atom+xml title="Godot News" href=/atom.xml><link rel=icon href=/assets/favicon.png sizes=any><link rel=icon href=/assets/favicon.svg type=image/svg+xml><link rel=stylesheet href=/assets/css/main.css?121><link rel=stylesheet href=/assets/css/header.css?6><link rel=stylesheet href=/assets/css/tobii.min.css><link rel=preload as=font href=/assets/fonts/Montserrat-Italic-VariableFont_wght.woff2 crossorigin><link rel=preload as=font href=/assets/fonts/Montserrat-VariableFont_wght.woff2 crossorigin><link rel=me href=https://mastodon.gamedev.place/@godotengine><input type=checkbox id=nav_toggle_cb><header class="flex column"><div class="container flex align-center"><div id=nav_head><a href=/ id=logo-link><img class=nav-logo src=/assets/logo.svg width=136 height=48 alt="Godot Engine">
<img class="nav-logo dark-logo" src=/assets/logo_dark.svg width=136 height=48 alt="Godot Engine"></a><div class=mobile-links><span class="fund mobile"><a href=https://fund.godotengine.org><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="width:13px;fill:#fff;margin-right:4px"><path d="M47.6 300.4 228.3 469.1c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6.0 115.2.0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"/></svg> Donate</a></span>
<label for=nav_toggle_cb id=nav_toggle_btn><img src=/assets/icons/hamburger.svg width=24 height=24 alt="Main menu"></label></div></div><nav id=nav><ul class=left><li><a href=/features/ data-dropdown=features-dropdown>Features</a><li class=mobile-only><a href=/consoles/>Console support</a><li class=mobile-only><a href=/priorities/>Priorities</a><li><a href=/showcase/>Showcase</a><li><a href=/blog/>Blog</a><li><a href=/community/ data-dropdown=community-dropdown>Community</a><li><a href=https://godotengine.org/asset-library/asset>Assets</a></ul><ul class=right><li><a href=/download/windows/ class=set-os-download-url>Download</a><li><a href=https://docs.godotengine.org>Docs</a><li><a href=https://contributing.godotengine.org/en/latest/organization/how_to_contribute.html>Contribute</a><li class="fund desktop"><a href=https://fund.godotengine.org><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="width:13px;fill:#fff;margin-right:4px;top:1px;position:relative"><path d="M47.6 300.4 228.3 469.1c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6.0 115.2.0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"/></svg> Donate</a></ul></nav></div></header><div class=nav-dropdown-menu id=features-dropdown><a href=/features/ class=touch-only>Features</a>
<a href=/consoles/>Console support</a>
<a href=/priorities/>Priorities</a></div><div class=nav-dropdown-menu id=community-dropdown><a href=/community/ class=touch-only>Community</a>
<a href=https://forum.godotengine.org>Forum</a>
<a href=/events/>Events</a></div><script>document.addEventListener("DOMContentLoaded",function(){const e=document.querySelectorAll("[data-dropdown]");e.forEach(e=>{const n=e.getAttribute("data-dropdown"),t=document.getElementById(n);if(t){let n=-1;const i=()=>{if(n===-1)return;clearTimeout(n),n=-1},a=()=>t.style.display==="block",r=()=>{if(a())return;if(window.innerWidth<1200)return;i();const n=e.getBoundingClientRect();t.style.top=n.bottom+"px",t.style.left=n.left+"px",t.style.display="block",e.classList.add("dropdown-open")},s=({instant:s=!1}={})=>{if(!a())return;const o=()=>{i(),t.style.display="none",e.classList.remove("dropdown-open")};if(s){o();return}n=setTimeout(o,100)},l=e=>{if(window.innerWidth<1200)return;e.preventDefault(),a()?s({instant:!0}):r()},c=e=>t=>{t.pointerType==="touch"&&e(t)},o=e=>t=>{t.pointerType!=="touch"&&e(t)};e.addEventListener("pointerup",c(e=>l(e))),document.documentElement.addEventListener("pointerup",c(n=>{!e.contains(n.target)&&!t.contains(n.target)&&s({instant:!0})})),e.addEventListener("pointerenter",o(e=>r())),e.addEventListener("pointerleave",o(e=>s())),t.addEventListener("pointerenter",o(e=>i())),t.addEventListener("pointerleave",o(e=>s()))}})})</script><main><style>body{background-color:var(--background-color)}h1{margin-bottom:8px;margin-top:32px}:not(pre)>code{background:var(--code-background-color);padding:1px 4px;font-size:.95em;border-radius:3px}pre{background:var(--codeblock-background-color);color:var(--codeblock-color)}pre code{display:block;overflow-x:auto;padding:.5em}.date-big{line-height:2;margin-left:32px}article{background-color:var(--base-color);box-shadow:0 3px 2px rgba(0,0,0,.15)}figure{margin:0}figure img{margin:0}article img,article video{max-width:100%;height:auto;display:block;margin:auto;margin-top:16px;margin-bottom:16px}article h1{margin-top:64px}article h2,article h3,article h4{margin-top:42px}.article-info{display:flex;flex-direction:column;gap:8px}.article-metadata{display:flex;gap:24px;align-items:center;font-family:var(--header-font-family);margin-bottom:12px}@media(max-width:900px){.article-metadata{flex-direction:column;align-items:flex-start;gap:16px}}.article-author{color:var(--base-color-text-subtitle-date);font-weight:700;font-size:18px;flex-grow:1;display:flex;gap:12px;align-items:center}.article-author .avatar{border-radius:100%;margin:0;background:0 0}.article-author .by{color:var(--base-color-text-subtitle)}.article-metadata .date{color:var(--base-color-text-subtitle-date)}.article-metadata .date.post-recent-highlight{color:var(--post-recent-highlight-color);opacity:.8}.article-metadata .date.post-recent-highlight::after{font-size:80%;content:"NEW";border:2px solid var(--post-recent-highlight-color);padding:2px 3px;margin-left:8px}.tag.active{filter:saturate(.75)}@media screen and (min-width:900px){article .content{width:70%;margin:auto}}@media(max-width:900px){body{background-color:var(--base-color)}article{background-color:initial;box-shadow:none}article img:first-child,article video:first-child{max-width:100%}}.blog-navigation{display:grid;grid-template-columns:1fr 1fr;padding-top:30px;padding-bottom:60px}.blog-navigation .next{text-align:right}@media(max-width:900px){.blog-navigation{grid-template-columns:1fr;gap:20px;border-top:1px solid var(--code-background-color)}.blog-navigation .next{text-align:left}}.blog-navigation span{opacity:.6;font-weight:700;margin-bottom:5px;display:block}.blog-navigation a{display:inline-block;text-decoration:none;color:inherit;opacity:.6;transition:opacity .2s}.blog-navigation a:hover{opacity:1}</style><link rel=stylesheet href=/assets/css/highlight.obsidian.min.css><div class=container><article class=padded><div class="content article-container"><figure class=article-cover><img src=/storage/app/uploads/public/60c/dfe/795/60cdfe7956fca023522866.png title alt=" " class=rounded-lg style=width:100%;height:auto;background-color:initial></figure><div class=article-info><h1>Godot Web progress report #9: Godot Scripts <-> JavaScript Interface</h1><div class=article-metadata><div class=article-author><span>By: </span><img class=avatar width=25 height=25 src=/assets/images/authors/faless.webp alt="Fabio Alessandrelli" loading=lazy>
<span class=by>Fabio Alessandrelli</span></div><span class=date data-post-date="2021-06-30 16:20:00 +0000">30 June 2021</span></div><div class=tags><a href=/blog/progress-report><div class="tag active">Progress Report</div></a></div></div><div class="card card-warning"><p>This article is from <strong>June 2021</strong>, some of its contents might be outdated and no longer accurate.<br>You can find up-to-date information about the engine in the <a href=https://docs.godotengine.org/en/stable/>official documentation</a>.</div><div class=article-body><p>Howdy Godotters!<p>It hasnt been long since the <a href=https://godotengine.org/article/godot-web-progress-report-8>last Web progress report</a>, but its finally time for the blog entry you have probably been waiting for… time to talk about <strong>integrating Godot with third-party JavaScript APIs on the Web</strong>.<h3 id=motivation>Motivation</h3><p>Sometimes, when exporting Godot for the Web, it might be necessary to interface with external JavaScript code. Things like third-party SDKs, libraries, or simply accessing browser features that are not directly exposed by Godot.<p>Historically, this has been done in Godot via the <a href=https://docs.godotengine.org/en/stable/classes/class_javascript.html#class-javascript-method-eval><code class="language-plaintext highlighter-rouge">JavaScript.eval</code></a> method. This relied on the JavaScript <a href=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval><code class="language-plaintext highlighter-rouge">eval()</code> function</a>, which beside being dangerous when misused, is also <a href=https://docs.godotengine.org/en/3.3/getting_started/workflow/export/exporting_for_web.html#calling-javascript-from-script>quite cumbersome to use</a>.<p>For these reasons, a <strong>new interface</strong> has been developed. This new interface feels more natural in the context of Godot scripting (e.g. GDScript and C#).<h3 id=javascriptobject>JavaScriptObject</h3><p>A new <code class="language-plaintext highlighter-rouge">JavaScriptObject</code> class has been added that wraps around native JavaScript objects and allows to call JavaScript methods and retrieve object properties.<p>You can access a JavaScript object via two new methods in the <a href=https://docs.godotengine.org/en/latest/classes/class_javascript.html><code class="language-plaintext highlighter-rouge">JavaScript</code></a> singleton:<ul><li><code class="language-plaintext highlighter-rouge">JavaScript.get_interface()</code>: Retrieves and wraps around an object in the global scope by calling a new method.</ul><div class="language-gdscript highlighter-rouge"><div class=highlight><pre class=highlight><code><span class=k>extends</span> <span class=n>Node</span>
<span class=k>func</span> <span class=nf>_ready</span><span class=p>():</span>
<span class=c1># Retrieve the `window.console` object.</span>
<span class=k>var</span> <span class=n>console</span> <span class=o>=</span> <span class=n>JavaScript</span><span class=o>.</span><span class=n>get_interface</span><span class=p>(</span><span class=s2>"console"</span><span class=p>)</span>
<span class=c1># Call the `window.console.log()` method.</span>
<span class=n>console</span><span class=o>.</span><span class=nb>log</span><span class=p>(</span><span class=s2>"test"</span><span class=p>)</span>
</code></pre></div></div><ul><li><code class="language-plaintext highlighter-rouge">JavaScript.create_object()</code>: Create an object via the JavaScript <code class="language-plaintext highlighter-rouge">new</code> constructor.</ul><div class="language-gdscript highlighter-rouge"><div class=highlight><pre class=highlight><code><span class=k>extends</span> <span class=n>Node</span>
<span class=k>func</span> <span class=nf>_ready</span><span class=p>():</span>
<span class=c1># Call the JavaScript `new` operator on the `window.Array` object.</span>
<span class=c1># Passing 10 as argument to the constructor:</span>
<span class=c1># JS: `new Array(10);`</span>
<span class=k>var</span> <span class=n>arr</span> <span class=o>=</span> <span class=n>JavaScript</span><span class=o>.</span><span class=n>create_object</span><span class=p>(</span><span class=s2>"Array"</span><span class=p>,</span> <span class=mi>10</span><span class=p>)</span>
<span class=c1># Set the first element of the JavaScript array to the number 42.</span>
<span class=n>arr</span><span class=p>[</span><span class=mi>0</span><span class=p>]</span> <span class=o>=</span> <span class=mi>42</span>
<span class=c1># Call the `pop` function on the JavaScript array.</span>
<span class=n>arr</span><span class=o>.</span><span class=n>pop</span><span class=p>()</span>
<span class=c1># Print the value of the `length` property of the array (9 after the pop).</span>
<span class=nb>print</span><span class=p>(</span><span class=n>arr</span><span class=o>.</span><span class=n>length</span><span class=p>)</span>
</code></pre></div></div><p>As you can see, thanks to Godots internal <a href=https://docs.godotengine.org/en/stable/classes/class_object.html>Object</a> design, you can interact with JavaScript objects obtained that way like they were native Godot objects, calling their methods, and retrieving (or even setting) their properties.<p>Base types (int, floats, strings, booleans) are automatically converted (floats might lose precision when converted from JavaScript to Godot the first time). Anything else (i.e. objects, arrays, functions) are seen as <code class="language-plaintext highlighter-rouge">JavaScriptObject</code>s themselves.<p>If you wonder where the magic lies and dont mind digging into the C++ codebase, it was “just” a matter of creating a new <a href=https://github.com/godotengine/godot/blob/3.x/platform/javascript/javascript_singleton.cpp><code class="language-plaintext highlighter-rouge">Object</code> child class</a> and overriding the <code class="language-plaintext highlighter-rouge">_get</code>, <code class="language-plaintext highlighter-rouge">_set</code>, <code class="language-plaintext highlighter-rouge">setvar</code>, <code class="language-plaintext highlighter-rouge">getvar</code>, and <code class="language-plaintext highlighter-rouge">call</code> methods… plus a painful amount of <a href=https://github.com/godotengine/godot/blob/3.x/platform/javascript/js/libs/library_godot_javascript_singleton.js>JavaScript glue code</a>.<p>But theres more! Lets talk about…<h3 id=callbacks>Callbacks</h3><p>Calling JavaScript code from Godot is nice, but sometimes you need to call a Godot function from JavaScript instead.<p>This case is a bit more complicated. JavaScript relies on garbage collection, while Godot uses reference counting for memory management. This means you have to explicitly create callabacks (which are returned as <code class="language-plaintext highlighter-rouge">JavaScriptObject</code>s themselves) and you have to keep their reference.<p>Arguments passed by JavaScript to the callback will be passed as a single Godot <code class="language-plaintext highlighter-rouge">Array</code>.<p>But dont worry if this sounds a bit technical, its not too hard.<p>Here is an example to set the JavaScript <a href=https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers#properties><code class="language-plaintext highlighter-rouge">window.onbeforeunload</code></a> property which asks a user for confirmation when leaving the web page:<div class="language-gdscript highlighter-rouge"><div class=highlight><pre class=highlight><code><span class=k>extends</span> <span class=n>Node</span>
<span class=c1># Here create a reference to the `_my_callback` function (below).</span>
<span class=c1># This reference will be kept until the node is freed.</span>
<span class=k>var</span> <span class=n>_callback_ref</span> <span class=o>=</span> <span class=n>JavaScript</span><span class=o>.</span><span class=n>create_callback</span><span class=p>(</span><span class=bp>self</span><span class=p>,</span> <span class=s2>"_my_callback"</span><span class=p>)</span>
<span class=k>func</span> <span class=nf>_ready</span><span class=p>():</span>
<span class=c1># Get the JavaScript `window` object.</span>
<span class=k>var</span> <span class=n>window</span> <span class=o>=</span> <span class=n>JavaScript</span><span class=o>.</span><span class=n>get_interface</span><span class=p>(</span><span class=s2>"window"</span><span class=p>)</span>
<span class=c1># Set the `window.onbeforeunload` DOM event listener.</span>
<span class=n>window</span><span class=o>.</span><span class=n>onbeforeunload</span> <span class=o>=</span> <span class=n>_callback_ref</span>
<span class=k>func</span> <span class=nf>_my_callback</span><span class=p>(</span><span class=n>args</span><span class=p>):</span>
<span class=c1># Get the first argument (the DOM event in our case).</span>
<span class=k>var</span> <span class=n>js_event</span> <span class=o>=</span> <span class=n>args</span><span class=p>[</span><span class=mi>0</span><span class=p>]</span>
<span class=c1># Call preventDefault and set the `returnValue` property of the DOM event.</span>
<span class=n>js_event</span><span class=o>.</span><span class=n>preventDefault</span><span class=p>()</span>
<span class=n>js_event</span><span class=o>.</span><span class=n>returnValue</span> <span class=o>=</span> <span class=s2>'</span><span class=s1>'</span>
</code></pre></div></div><p>Here is another example that asks the user for the <a href=https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API>Notification permission</a> and waits asynchronously to deliver a notification if the permission is granted:<div class="language-gdscript highlighter-rouge"><div class=highlight><pre class=highlight><code><span class=k>extends</span> <span class=n>Node</span>
<span class=c1># Here create a reference to the `_on_permissions` function (below).</span>
<span class=c1># This reference will be kept until the node is freed.</span>
<span class=k>var</span> <span class=n>_permission_callback</span> <span class=o>=</span> <span class=n>JavaScript</span><span class=o>.</span><span class=n>create_callback</span><span class=p>(</span><span class=bp>self</span><span class=p>,</span> <span class=s2>"_on_permissions"</span><span class=p>)</span>
<span class=k>func</span> <span class=nf>_ready</span><span class=p>():</span>
<span class=c1># NOTE: This is done in `_ready` for semplicity, but SHOULD BE done in response</span>
<span class=c1># to user input instead (e.g. during `_input`, or `button_pressed` event, etc.),</span>
<span class=c1># otherwise it might not work.</span>
<span class=c1># Get the `window.Notification` JavaScript object.</span>
<span class=k>var</span> <span class=n>notification</span> <span class=o>=</span> <span class=n>JavaScript</span><span class=o>.</span><span class=n>get_interface</span><span class=p>(</span><span class=s2>"Notification"</span><span class=p>)</span>
<span class=c1># Call the `window.Notification.requestPermission` method which returns a JavaScript</span>
<span class=c1># Promise, and bind our callback to it.</span>
<span class=n>notification</span><span class=o>.</span><span class=n>requestPermission</span><span class=p>()</span><span class=o>.</span><span class=n>then</span><span class=p>(</span><span class=n>_permission_callback</span><span class=p>)</span>
<span class=k>func</span> <span class=nf>_on_permissions</span><span class=p>(</span><span class=n>args</span><span class=p>):</span>
<span class=c1># The first argument of this callback is the string "granted" if the permission is granted.</span>
<span class=k>var</span> <span class=n>permission</span> <span class=o>=</span> <span class=n>args</span><span class=p>[</span><span class=mi>0</span><span class=p>]</span>
<span class=k>if</span> <span class=n>permission</span> <span class=o>==</span> <span class=s2>"granted"</span><span class=p>:</span>
<span class=nb>print</span><span class=p>(</span><span class=s2>"Permission granted, sending notification."</span><span class=p>)</span>
<span class=c1># Create the notification: `new Notification("Hi there!")`</span>
<span class=n>JavaScript</span><span class=o>.</span><span class=n>create_object</span><span class=p>(</span><span class=s2>"Notification"</span><span class=p>,</span> <span class=s2>"Hi there!"</span><span class=p>)</span>
<span class=k>else</span><span class=p>:</span>
<span class=nb>print</span><span class=p>(</span><span class=s2>"No notification permission."</span><span class=p>)</span>
</code></pre></div></div><h3 id=but-can-i-use-library-x>But can I use library X?</h3><p>You most likely can, and it shouldnt be too cumbersome. First, you have to include your library in the page. You can simply customize the <code class="language-plaintext highlighter-rouge">Head Include</code> during export (see below), or even <a href=https://docs.godotengine.org/en/stable/tutorials/platform/customizing_html5_shell.html>write your own template</a>.<p>In the example below, we customize the <strong>Head Include</strong> to add an external library (<a href=https://axios-http.com/>axios</a>) from a content delivery network, and a second <code class="language-plaintext highlighter-rouge">&lt;script></code> tag to define our own custom function:<div class="language-html highlighter-rouge"><div class=highlight><pre class=highlight><code><span class=c>&lt;!-- Axios --&gt;</span>
<span class=nt>&lt;script </span><span class=na>src=</span><span class=s>"https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"</span><span class=nt>&gt;&lt;/script&gt;</span>
<span class=c>&lt;!-- Custom function --&gt;</span>
<span class=nt>&lt;script&gt;</span>
<span class=kd>function</span> <span class=nf>myFunc</span><span class=p>()</span> <span class=p>{</span>
<span class=nf>alert</span><span class=p>(</span><span class=dl>"</span><span class=s2>My func!</span><span class=dl>"</span><span class=p>);</span>
<span class=p>}</span>
<span class=nt>&lt;/script&gt;</span>
</code></pre></div></div><p><img src=/storage/app/uploads/public/60c/ddb/7a5/60cddb7a5c331340661868.png alt="HTML5 export head include setting"><p>We can then access both the library and the function from Godot, like we did in previous examples:<div class="language-gdscript highlighter-rouge"><div class=highlight><pre class=highlight><code><span class=k>extends</span> <span class=n>Node</span>
<span class=c1># Here create a reference to the `_on_get` function (below).</span>
<span class=c1># This reference will be kept until the node is freed.</span>
<span class=k>var</span> <span class=n>_callback</span> <span class=o>=</span> <span class=n>JavaScript</span><span class=o>.</span><span class=n>create_callback</span><span class=p>(</span><span class=bp>self</span><span class=p>,</span> <span class=s2>"_on_get"</span><span class=p>)</span>
<span class=k>func</span> <span class=nf>_ready</span><span class=p>():</span>
<span class=c1># Get the `window` object, where globally defined functions are.</span>
<span class=k>var</span> <span class=n>window</span> <span class=o>=</span> <span class=n>JavaScript</span><span class=o>.</span><span class=n>get_interface</span><span class=p>(</span><span class=s2>"window"</span><span class=p>)</span>
<span class=c1># Call the JavaScript `myFunc` function defined in the custom HTML head.</span>
<span class=n>window</span><span class=o>.</span><span class=n>myFunc</span><span class=p>()</span>
<span class=c1># Get the `axios` library (loaded from a CDN in the custom HTML head).</span>
<span class=k>var</span> <span class=n>axios</span> <span class=o>=</span> <span class=n>JavaScript</span><span class=o>.</span><span class=n>get_interface</span><span class=p>(</span><span class=s2>"axios"</span><span class=p>)</span>
<span class=c1># Make a GET request to the current location, and receive the callback when done.</span>
<span class=n>axios</span><span class=o>.</span><span class=n>get</span><span class=p>(</span><span class=n>window</span><span class=o>.</span><span class=n>location</span><span class=o>.</span><span class=n>toString</span><span class=p>())</span><span class=o>.</span><span class=n>then</span><span class=p>(</span><span class=n>_callback</span><span class=p>)</span>
<span class=k>func</span> <span class=nf>_on_get</span><span class=p>(</span><span class=n>args</span><span class=p>):</span>
<span class=n>OS</span><span class=o>.</span><span class=n>alert</span><span class=p>(</span><span class=s2>"On Get"</span><span class=p>)</span>
</code></pre></div></div><p>And one last treat…<h3 id=downloading-files>Downloading files</h3><p>A lot of you asked for an easy way to “download files” (e.g. game saves) from the Godot HTML5 export to the user computer.<p>This has been done with <code class="language-plaintext highlighter-rouge">eval()</code> before, and can also be done with the new interface described above.<p>Given it has a very common use case though, we decided to expose this functionality to scripting via a dedicated <code class="language-plaintext highlighter-rouge">JavaScript.download_buffer()</code> function which lets you download any generated buffer.<p>Here is a minimal example on how to use it:<div class="language-gdscript highlighter-rouge"><div class=highlight><pre class=highlight><code><span class=k>extends</span> <span class=n>Node</span>
<span class=k>func</span> <span class=nf>_ready</span><span class=p>():</span>
<span class=c1># Asks the user download a file called "hello.txt" whose content will be the string "Hello".</span>
<span class=n>JavaScript</span><span class=o>.</span><span class=n>download_buffer</span><span class=p>(</span><span class=s2>"Hello"</span><span class=o>.</span><span class=n>to_utf8</span><span class=p>(),</span> <span class=s2>"hello.txt"</span><span class=p>)</span>
</code></pre></div></div><p>And here is a more complete example on how to download a previously saved file:<div class="language-gdscript highlighter-rouge"><div class=highlight><pre class=highlight><code><span class=k>extends</span> <span class=n>Node</span>
<span class=c1># Open a file for reading and download it via the JavaScript singleton.</span>
<span class=k>func</span> <span class=nf>_download_file</span><span class=p>(</span><span class=n>path</span><span class=p>):</span>
<span class=k>var</span> <span class=n>file</span> <span class=o>=</span> <span class=n>File</span><span class=o>.</span><span class=n>new</span><span class=p>()</span>
<span class=k>if</span> <span class=n>file</span><span class=o>.</span><span class=n>open</span><span class=p>(</span><span class=n>path</span><span class=p>,</span> <span class=n>File</span><span class=o>.</span><span class=n>READ</span><span class=p>)</span> <span class=o>!=</span> <span class=n>OK</span><span class=p>:</span>
<span class=n>push_error</span><span class=p>(</span><span class=s2>"Failed to load file"</span><span class=p>)</span>
<span class=k>return</span>
<span class=c1># Get the file name.</span>
<span class=k>var</span> <span class=n>fname</span> <span class=o>=</span> <span class=n>path</span><span class=o>.</span><span class=n>get_file</span><span class=p>()</span>
<span class=c1># Read the whole file to memory.</span>
<span class=k>var</span> <span class=n>buffer</span> <span class=o>=</span> <span class=n>file</span><span class=o>.</span><span class=n>get_buffer</span><span class=p>(</span><span class=n>file</span><span class=o>.</span><span class=n>get_len</span><span class=p>())</span>
<span class=c1># Prompt the user to download the file (will have the same name as the input file).</span>
<span class=n>JavaScript</span><span class=o>.</span><span class=n>download_buffer</span><span class=p>(</span><span class=n>buffer</span><span class=p>,</span> <span class=n>fname</span><span class=p>)</span>
<span class=k>func</span> <span class=nf>_ready</span><span class=p>():</span>
<span class=c1># Create a temporary file.</span>
<span class=k>var</span> <span class=n>config</span> <span class=o>=</span> <span class=n>ConfigFile</span><span class=o>.</span><span class=n>new</span><span class=p>()</span>
<span class=n>config</span><span class=o>.</span><span class=n>set_value</span><span class=p>(</span><span class=s2>"option"</span><span class=p>,</span> <span class=s2>"one"</span><span class=p>,</span> <span class=bp>false</span><span class=p>)</span>
<span class=n>config</span><span class=o>.</span><span class=n>save</span><span class=p>(</span><span class=s2>"/tmp/test.cfg"</span><span class=p>)</span>
<span class=c1># Download it</span>
<span class=n>_download_file</span><span class=p>(</span><span class=s2>"/tmp/test.cfg"</span><span class=p>)</span>
</code></pre></div></div><h3 id=future-work>Future work</h3><p>Working on the Godot HTML5 export for more than a year has been a great experience, and I love seeing the many developers releasing their Godot games and apps on the Web, even with the <a href="https://www.youtube.com/watch?v=RCUj6Rlq2mw">limitations this platform still has</a>. I like to think Godot can help revitalize the web games ecosystem.<p>While I will keep maintaining the HTML5 platform, and there will be some more news in the next months beside the usual bug fixing, its time to get back at the other aspect of Godot thats been overlooked in the last year… Networking and Multiplayer! Those of you following GitHub development might have noticed something already, but theres much more to come.<p>So stay tuned! :)<h3 id=references>References</h3><ul><li><a href=https://github.com/godotengine/godot/pull/487190>Godot &lt;-> JavaScript interface</a> and <a href=https://github.com/godotengine/godot/pull/48691>3.x version</a> (<a href=https://github.com/godotengine/godot-proposals/issues/1852>original proposal</a>)<li><a href=https://github.com/godotengine/godot/pull/48881>Download API</a> and <a href=https://github.com/godotengine/godot/pull/48929>3.x version</a></ul></div></div></article><div class=blog-navigation><div class=previous><span>Previous</span>
<a rel=prev href=/article/tiles-editor-progress-4/>Tiles editor progress report #4</a></div><div class=next><span>Next</span>
<a rel=next href=/article/godotcon-july-2021-schedule/>GodotCon July 2021 - Schedule</a></div></div></div><link rel=stylesheet href=/assets/css/anchor-link.css?1><link rel=stylesheet href=/assets/css/article-cards.css?3><script src=/assets/js/anchor-link.js></script><script>document.addEventListener("DOMContentLoaded",()=>{window.applyAnchorLinks(".article-body"),document.querySelectorAll(".article-cover img, .article-body img").forEach(e=>{if(e.classList.contains("lightbox-ignore"))return;const t=document.createElement("a");t.href=e.src,t.classList.add("lightbox"),t.dataset.group="article",e.parentNode.appendChild(t),t.appendChild(e)})})</script></main><footer class=footer-global><div class=wrapper><div class=columns><div class=col><h2>Godot Engine</h2><ul><li><a class=set-os-download-url href=/download>Download</a><li><a href=https://docs.godotengine.org>Documentation</a><li><a href=/features/>Features</a><li><a href=https://editor.godotengine.org/releases/latest/>Web editor</a><li><a href=/download/archive/>Release archive</a><li><a href=https://github.com/godotengine>Source code</a></ul></div><div class=col><h2>Project</h2><ul><li><a href=/blog/>Blog</a><li><a href=/code-of-conduct/>Code of conduct</a><li><a href=/governance/>Governance</a><li><a href=/teams/>Teams</a><li><a href=/priorities/>Priorities</a><li><a href=/community/>Communities</a></ul></div><div class=col><h2>Resources</h2><ul><li><a href=https://godotengine.org/asset-library/asset>Asset library</a><li><a href=/press/>Press kit</a><li><a href=/showcase/>Showcase</a><li><a href=/education/>Education</a><li><a href=/consoles/>Console support</a></ul></div><div class=col><h2>Foundation</h2><ul><li><a href=https://godot.foundation/>About</a><li><a href=https://fund.godotengine.org>Donate</a><li><a href=/license/>License</a><li><a href=/privacy-policy/>Privacy policy</a><li><a href=/contact/>Contact us</a></ul></div></div><hr><div class=credits-and-socials><p>© 2007-2026 Juan Linietsky, Ariel Manzur and <a href=https://github.com/godotengine/godot/blob/master/AUTHORS.md target=_blank rel=noopener>contributors</a>. Hosted by the <a href=https://godot.foundation/ target=_blank rel=noopener>Godot Foundation</a>. Website <a href=https://github.com/godotengine/godot-website target=_blank rel=noopener>source code on GitHub</a>.<div class=social><a href=https://github.com/godotengine target=_blank rel=noopener title=GitHub><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6.0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6.0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3.0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1.0-6.2-.3-40.4-.3-61.4.0.0-70 15-84.7-29.8.0.0-11.4-29.1-27.8-36.6.0.0-22.9-15.7 1.6-15.4.0.0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5.0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9.0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4.0 33.7-.3 75.4-.3 83.6.0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6.0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9.0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
</a><a href=https://bsky.app/profile/godotengine.org target=_blank rel=noopener title=Bluesky><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M407.8 294.7c-3.3-.4-6.7-.8-10-1.3 3.4.4 6.7.9 10 1.3zM288 227.1C261.9 176.4 190.9 81.9 124.9 35.3 61.6-9.4 37.5-1.7 21.6 5.5 3.3 13.8.0 41.9.0 58.4S9.1 194 15 213.9c19.5 65.7 89.1 87.9 153.2 80.7 3.3-.5 6.6-.9 10-1.4-3.3.5-6.6 1-10 1.4-93.9 14-177.3 48.2-67.9 169.9C220.6 589.1 265.1 437.8 288 361.1c22.9 76.7 49.2 222.5 185.6 103.4 102.4-103.4 28.1-156-65.8-169.9-3.3-.4-6.7-.8-10-1.3 3.4.4 6.7.9 10 1.3 64.1 7.1 133.6-15.1 153.2-80.7C566.9 194 576 75 576 58.4s-3.3-44.7-21.6-52.9c-15.8-7.1-40-14.9-103.2 29.8C385.1 81.9 314.1 176.4 288 227.1z"/></svg>
</a><a href=https://mastodon.gamedev.place/@godotengine target=_blank rel=noopener title=Mastodon><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M433 179.1c0-97.2-63.7-125.7-63.7-125.7-62.5-28.7-228.6-28.4-290.5.0.0.0-63.7 28.5-63.7 125.7.0 115.7-6.6 259.4 105.6 289.1 40.5 10.7 75.3 13 103.3 11.4 50.8-2.8 79.3-18.1 79.3-18.1l-1.7-36.9s-36.3 11.4-77.1 10.1c-40.4-1.4-83-4.4-89.6-54a102.5 102.5.0 01-.9-13.9c85.6 20.9 158.7 9.1 178.8 6.7 56.1-6.7 105-41.3 111.2-72.9 9.8-49.8 9-121.5 9-121.5zm-75.1 125.2h-46.6V190.1c0-49.7-64-51.6-64 6.9v62.5H201V197c0-58.5-64-56.6-64-6.9v114.2H90.2c0-122.1-5.2-147.9 18.4-175 25.9-28.9 79.8-30.8 103.8 6.1l11.6 19.5 11.6-19.5c24.1-37.1 78.1-34.8 103.8-6.1 23.7 27.3 18.4 53 18.4 175z"/></svg>
</a><a href=https://discord.gg/godotengine target=_blank rel=noopener title=Discord><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M524.5 69.8a1.5 1.5.0 00-.8-.7A485.1 485.1.0 00404.1 32a1.8 1.8.0 00-1.9.9 337.5 337.5.0 00-14.9 30.6 447.8 447.8.0 00-134.4.0 309.5 309.5.0 00-15.1-30.6 1.9 1.9.0 00-1.9-.9A483.7 483.7.0 00116.1 69.1a1.7 1.7.0 00-.8.7C39.1 183.7 18.2 294.7 28.4 404.4a2 2 0 00.8 1.4A487.7 487.7.0 00176 479.9a1.9 1.9.0 002.1-.7 348.2 348.2.0 0030-48.8 1.9 1.9.0 00-1-2.6 321.2 321.2.0 01-45.9-21.9 1.9 1.9.0 01-.2-3.1c3.1-2.3 6.2-4.7 9.1-7.1a1.8 1.8.0 011.9-.3c96.2 43.9 200.4 43.9 295.5.0a1.8 1.8.0 011.9.2c2.9 2.4 6 4.9 9.1 7.2a1.9 1.9.0 01-.2 3.1 301.4 301.4.0 01-45.9 21.8 1.9 1.9.0 00-1 2.6 391.1 391.1.0 0030 48.8 1.9 1.9.0 002.1.7 486 486 0 00147.2-74.1 1.9 1.9.0 00.8-1.4c12.2-126.7-20.6-236.8-87-334.5zm-302 267.8c-29 0-52.8-26.6-52.8-59.2s23.4-59.3 52.8-59.3c29.7.0 53.3 26.8 52.8 59.2.0 32.7-23.4 59.3-52.8 59.3zm195.4.0c-29 0-52.8-26.6-52.8-59.2s23.3-59.3 52.8-59.3c29.7.0 53.3 26.8 52.8 59.2.0 32.7-23.2 59.3-52.8 59.3z"/></svg>
</a><a href=https://www.reddit.com/r/godot title=Reddit target=_blank rel=noopener><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 256C0 114.6 114.6.0 256 0S512 114.6 512 256 397.4 512 256 512H37.1c-13.7.0-20.5-16.5-10.9-26.2L75 437C28.7 390.7.0 326.7.0 256zM349.6 153.6c23.6.0 42.7-19.1 42.7-42.7s-19.1-42.7-42.7-42.7c-20.6.0-37.8 14.6-41.8 34-34.5 3.7-61.4 33-61.4 68.4v.2c-37.5 1.6-71.8 12.3-99 29.1-10.1-7.8-22.8-12.5-36.5-12.5-33 0-59.8 26.8-59.8 59.8.0 24 14.1 44.6 34.4 54.1 2 69.4 77.6 125.2 170.6 125.2s168.7-55.9 170.6-125.3c20.2-9.6 34.1-30.2 34.1-54 0-33-26.8-59.8-59.8-59.8-13.7.0-26.3 4.6-36.4 12.4-27.4-17-62.1-27.7-1e2-29.1v-.2c0-25.4 18.9-46.5 43.4-49.9 4.4 18.8 21.3 32.8 41.5 32.8zM177.1 246.9c16.7.0 29.5 17.6 28.5 39.3s-13.5 29.6-30.3 29.6-31.4-8.8-30.4-30.5S160.3 247 177 247zm190.1 38.3c1 21.7-13.7 30.5-30.4 30.5s-29.3-7.9-30.3-29.6c-1-21.7 11.8-39.3 28.5-39.3s31.2 16.6 32.1 38.3zm-48.1 56.7c-10.3 24.6-34.6 41.9-63 41.9s-52.7-17.3-63-41.9c-1.2-2.9.8-6.2 3.9-6.5 18.4-1.9 38.3-2.9 59.1-2.9s40.7 1 59.1 2.9c3.1.3 5.1 3.6 3.9 6.5z"/></svg>
</a><a href=/rss.xml title=RSS target=_blank rel=noopener><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M64 32C28.7 32 0 60.7.0 96V416c0 35.3 28.7 64 64 64h320c35.3.0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zM96 136c0-13.3 10.7-24 24-24 137 0 248 111 248 248 0 13.3-10.7 24-24 24s-24-10.7-24-24c0-110.5-89.5-2e2-2e2-2e2-13.3.0-24-10.7-24-24zm0 96c0-13.3 10.7-24 24-24 83.9.0 152 68.1 152 152 0 13.3-10.7 24-24 24s-24-10.7-24-24c0-57.4-46.6-104-104-104-13.3.0-24-10.7-24-24zm0 120a32 32 0 1164 0 32 32 0 11-64 0z"/></svg></a></div></div></div></footer><script defer src=/assets/js/localize.js?7></script><script defer src=/assets/js/tobii.min.js></script><script defer src=/assets/js/highlight.min.js?1></script><script defer src=/assets/js/highlight.gdscript.min.js?1></script><script>document.addEventListener("DOMContentLoaded",()=>{document.querySelectorAll("pre:not(.manual) code").forEach(e=>{hljs.highlightBlock(e)}),document.querySelectorAll("[data-post-date]").forEach(e=>{Date.parse(e.dataset.postDate)>Date.now()-1e3*60*60*48&&e.classList.add("post-recent-highlight")}),new Tobii({zoom:!1});const e=document.querySelectorAll(".set-os-download-url");for(let n=0;n<e.length;n++){const s=e[n];let o="download";"version"in s.dataset&&s.dataset.version==="3"&&(o="download/3.x");let t="windows";navigator.platform.indexOf("Mac")!==-1?t="macos":navigator.userAgent.indexOf("Android")!==-1?t="android":navigator.platform.indexOf("Linux")!==-1&&(t="linux"),s.href=`/${o}/${t}/`}})</script>