Kill `bootstrap.js`, load the WASM ourselves.

Instead of forcing a chunk load on Webpack just so we can have Webpack prepare the WASM for us, we're now using the `no-modules` mode of `wasm-pack` manually and loading the resulting files ourselves.

I still have to force Ruffle to load outside of the extension sandbox though...
This commit is contained in:
David Wendt 2019-08-24 19:56:46 -04:00
parent 7c6ad57443
commit 8060802465
8 changed files with 128 additions and 55 deletions

8
web/extension/build.sh Normal file
View File

@ -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

View File

@ -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));

View File

@ -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")
}
}());

View File

@ -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: []
}
};

60
web/js-src/load-ruffle.js Normal file
View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}