diff --git a/web/extension/build.sh b/web/extension/build.sh new file mode 100644 index 000000000..cfb5fb70b --- /dev/null +++ b/web/extension/build.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +#Really hacky way to build everything until I figure out how to get a better/ +#dependency-respecting build system going + +rm -r dist +wasm-pack build --target=no-modules --out-dir=extension/dist .. +npx webpack \ No newline at end of file diff --git a/web/extension/js/bootstrap.js b/web/extension/js/bootstrap.js deleted file mode 100644 index 4266f6bac..000000000 --- a/web/extension/js/bootstrap.js +++ /dev/null @@ -1,7 +0,0 @@ -__webpack_public_path__ = window.__webpack_public_path__; - -// A dependency graph that contains any wasm must all be imported -// asynchronously. This `bootstrap.js` file does the single async import, so -// that no one else needs to worry about it again. -import("./index.js") - .catch(e => console.error("Error importing `index.js`:", e)); diff --git a/web/extension/js/lv0.js b/web/extension/js/lv0.js index 5a64eeb4c..545be8a3e 100644 --- a/web/extension/js/lv0.js +++ b/web/extension/js/lv0.js @@ -1,28 +1,28 @@ -/** - * This IIFE is *not touched by Webpack* and exists primarily to ensure Webpack - * is loaded without extension privileges. +(/** + * Pierce the extension sandbox by copying our code into window space. * - * Inside the IIFE, we do two things: + * The isolation extension content scripts get is neat, but it causes problems + * based on what browser you use: * - * 1. Use our fancy extension powers to generate an unprivileged script to set - * the webpack public path. - * 2. Generate another unprivileged script with a link to the Ruffle extension - * resource. + * 1. On Chrome, you are explicitly banned from registering custom elements + * 2. On Firefox, you can register custom elements but they can't expose any + * useful API surface, and can't even see their own methods. * - * This gives webpack the environment it expects, at the expense of breaking - * literally every site that uses it's own webpack. + * This code exists to pierce the extension sandbox, while maintaining: + * + * 1. The isolation of not interfering with the page's execution environment + * unintentionally. + * 2. The ability to load extension resources such as .wasm files */ -(function () { - // Browser extensions are loaded from a dynamically-generated URL, we have to - // tell webpack about that. - - var webpack_path_script = document.createElement('script'); - webpack_path_script.appendChild(document.createTextNode("__webpack_public_path__ = \"" + browser.runtime.getURL("dist/0.ruffle.js").replace("0.ruffle.js", "") + "\"")); - webpack_path_script.type = "text/javascript"; - document.body.appendChild(webpack_path_script); - - var script = document.createElement('script'); - script.src = browser.runtime.getURL("dist/ruffle.js"); - script.type = "text/javascript"; - document.body.appendChild(script); +async function() { + let ext_path = browser.runtime.getURL("dist/ruffle.js").replace("dist/ruffle.js", ""); + let ruffle_src_resp = await fetch(ext_path + "dist/ruffle.js"); + if (ruffle_src_resp.ok) { + let ruffle_src = "(function () { var runtime_path = \"" + ext_path + "\";\n" + await ruffle_src_resp.text() + "}())"; + let scriptelem = document.createElement("script"); + scriptelem.appendChild(document.createTextNode(ruffle_src)); + document.head.appendChild(scriptelem); + } else { + console.error("Critical error loading Ruffle into page") + } }()); \ No newline at end of file diff --git a/web/extension/webpack.config.js b/web/extension/webpack.config.js index 4eaec6fd7..eff3a94ca 100644 --- a/web/extension/webpack.config.js +++ b/web/extension/webpack.config.js @@ -1,5 +1,4 @@ const { CleanWebpackPlugin } = require('clean-webpack-plugin'); -const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin"); const webpack = require('webpack'); const path = require('path'); @@ -12,19 +11,12 @@ module.exports = (env, argv) => { console.log(`Building ${mode}...`); return { - entry: path.resolve(__dirname, "js/bootstrap.js"), + entry: path.resolve(__dirname, "js/index.js"), output: { path: path.resolve(__dirname, "dist"), filename: "ruffle.js", }, mode: mode, - plugins: [ - new CleanWebpackPlugin(), - new WasmPackPlugin({ - crateDirectory: path.resolve(__dirname, ".."), - extraArgs: "--out-name=ruffle", - forceMode: mode, - }) - ] + plugins: [] } }; diff --git a/web/js-src/load-ruffle.js b/web/js-src/load-ruffle.js new file mode 100644 index 000000000..09a7a79cd --- /dev/null +++ b/web/js-src/load-ruffle.js @@ -0,0 +1,60 @@ +/** + * Conditional ruffle loader + */ + +/** + * Load ruffle from an automatically-detected location. + * + * This function returns a new instance of Ruffle and downloads it every time. + * You should not use it directly; this module will memoize the resource + * download. + */ +async function fetch_ruffle() { + let ruffle_bindings_url = "ruffle_web.js"; + let ruffle_wasm_url = "ruffle_web_bg.wasm"; + + //Detect if we are executiong in a webextension context. + //If so, download Ruffle from the URL the browser gives us. + if (runtime_path) { + ruffle_bindings_url = runtime_path + "dist/ruffle_web.js"; + ruffle_wasm_url = runtime_path + "dist/ruffle_web_bg.wasm"; + } + + //We load the wasm package early so that both requests are parallelized. + //This won't be awaited by us at all. + let ruffle_wasm_request = fetch(ruffle_wasm_url); + + //One point of admin: `wasm-pack`, in no-modules mode, stores it's bindings + //straight in `window`, which we don't want. We grab whatever was in Window + //before loading in the module so we can replace what was there. + let ruffle_bindings_request = await fetch(ruffle_bindings_url); + let ruffle_bindings_src = await ruffle_bindings_request.text(); + let noconflict_wasm_bindgen = window.wasm_bindgen; + (new Function(ruffle_bindings_src))(); + let ruffle_wasm_bindgen = window.wasm_bindgen; + window.wasm_bindgen = noconflict_wasm_bindgen; + + //Next step: Actually initialize our bindings. + let ruffle_wasm_response = await ruffle_wasm_request; + let ruffle_wasm_data = await ruffle_wasm_response.arrayBuffer(); + let ruffle_bindings = await ruffle_wasm_bindgen(ruffle_wasm_data).catch(function (e) { + console.error(e); + }); + + return ruffle_wasm_bindgen.Ruffle; +} + +let last_loaded_ruffle = null; + +/** + * Obtain an instance of `Ruffle`. + * + * This function returns a promise which yields `Ruffle` asynchronously. + */ +export default function load_ruffle() { + if (last_loaded_ruffle == null) { + last_loaded_ruffle = fetch_ruffle(); + } + + return last_loaded_ruffle; +} \ No newline at end of file diff --git a/web/js-src/ruffle-embed.js b/web/js-src/ruffle-embed.js index 53f72620e..817eedda2 100644 --- a/web/js-src/ruffle-embed.js +++ b/web/js-src/ruffle-embed.js @@ -8,7 +8,7 @@ export default class RuffleEmbed extends RufflePlayer { } connectedCallback() { - this.stream_swf_url(this.attributes.src.value); + super.stream_swf_url(this.attributes.src.value); } get src() { @@ -24,8 +24,10 @@ export default class RuffleEmbed extends RufflePlayer { } attributeChangedCallback(name, oldValue, newValue) { + //TODO: We get a double play if we just naively load this twice. + //Check if the element is connected before doing anything! if (name === "src") { - this.stream_swf_url(this.attributes.src.value); + //this.stream_swf_url(this.attributes.src.value); } } diff --git a/web/js-src/ruffle-object.js b/web/js-src/ruffle-object.js index 93fb85af0..5ba4fa9dd 100644 --- a/web/js-src/ruffle-object.js +++ b/web/js-src/ruffle-object.js @@ -10,7 +10,7 @@ export default class RuffleObject extends RufflePlayer { //Kick off the SWF download. if (this.params.movie) { - this.stream_swf_url(this.params.movie); + super.stream_swf_url(this.params.movie); } } diff --git a/web/js-src/ruffle-player.js b/web/js-src/ruffle-player.js index 838dd29cd..989c54e2f 100644 --- a/web/js-src/ruffle-player.js +++ b/web/js-src/ruffle-player.js @@ -1,4 +1,4 @@ -import { Ruffle } from "../pkg/ruffle"; +import load_ruffle from "./load-ruffle"; import ruffle_shadow_template from "./shadow-template"; export default class RufflePlayer extends HTMLElement { @@ -9,29 +9,47 @@ export default class RufflePlayer extends HTMLElement { self.shadow.appendChild(ruffle_shadow_template.content.cloneNode(true)); self.canvas = self.shadow.getElementById("player"); - self.ruffle = null; + self.instance = null; + + self.Ruffle = load_ruffle(); return self; } - stream_swf_url(url) { + async stream_swf_url(url) { //TODO: Actually stream files... - console.log("Loading SWF file " + url); - return fetch(url).then(response => { + try { + let abs_url = new URL(url, window.location.href).toString(); + console.log("Loading SWF file " + url); + + let response = await fetch(abs_url); + if (response.ok) { - response.arrayBuffer().then(data => this.play_swf_data(data)) + let data = await response.arrayBuffer(); + await this.play_swf_data(data); + console.log("Playing " + url); } else { console.error("SWF load failed: " + response.status + " " + response.statusText + " for " + url); } - }); + } catch (err) { + console.error("Serious error occured loading SWF file: " + err); + throw err; + } } - play_swf_data(data) { - if (this.ruffle) { - this.ruffle.destroy(); - this.ruffle = null; + async play_swf_data(data) { + console.log("Got SWF data"); + + if (this.instance) { + this.instance.destroy(); + this.instance = null; } - this.ruffle = Ruffle.new(this.canvas, new Uint8Array(data)); + let Ruffle = await this.Ruffle.catch(function (e) { + console.error("Serious error loading Ruffle: " + e); + throw e; + }); + + this.instance = Ruffle.new(this.canvas, new Uint8Array(data)); } } \ No newline at end of file