Don't use inline scripts and function constructor (#4310)
* Don't use inline scripts and function constructor. This allows a stricter Content-Security-Policy which is required for web extensions. * Load module scripts asynchronously * Extract common code into method * Fix injecting loading script * Append to the file instead of reading and appending to the file string --------- Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com> Co-authored-by: Evan Almloff <evanalmloff@gmail.com>
This commit is contained in:
5 files changed
+114
-70
No files matched your search
Vendored
+4
-1
@@ -9,6 +9,9 @@
|
|||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.formatOnSave": false
|
"editor.formatOnSave": false
|
||||||
},
|
},
|
||||||
|
"[html]": {
|
||||||
|
"editor.formatOnSave": false
|
||||||
|
},
|
||||||
"dioxus.formatOnSave": "disabled",
|
"dioxus.formatOnSave": "disabled",
|
||||||
// "rust-analyzer.check.workspace": true,
|
// "rust-analyzer.check.workspace": true,
|
||||||
// "rust-analyzer.check.workspace": false,
|
// "rust-analyzer.check.workspace": false,
|
||||||
@@ -21,4 +24,4 @@
|
|||||||
"rust-analyzer.cargo.extraArgs": [
|
"rust-analyzer.cargo.extraArgs": [
|
||||||
"--tests"
|
"--tests"
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>{app_title}</title>
|
<title>{app_title}</title>
|
||||||
|
|||||||
@@ -3364,8 +3364,14 @@ impl BuildRequest {
|
|||||||
wasm_opt::optimize(&post_bindgen_wasm, &post_bindgen_wasm, &wasm_opt_options).await?;
|
wasm_opt::optimize(&post_bindgen_wasm, &post_bindgen_wasm, &wasm_opt_options).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In release mode, we make the wasm and bindgen files into assets so they get bundled with max
|
if self.should_bundle_to_asset() {
|
||||||
// optimizations.
|
// Make sure to register the main wasm file with the asset system
|
||||||
|
assets.register_asset(&post_bindgen_wasm, AssetOptions::Unknown)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that the wasm is registered as an asset, we can write the js glue shim
|
||||||
|
self.write_js_glue_shim(assets)?;
|
||||||
|
|
||||||
if self.should_bundle_to_asset() {
|
if self.should_bundle_to_asset() {
|
||||||
// Register the main.js with the asset system so it bundles in the snippets and optimizes
|
// Register the main.js with the asset system so it bundles in the snippets and optimizes
|
||||||
assets.register_asset(
|
assets.register_asset(
|
||||||
@@ -3374,36 +3380,63 @@ impl BuildRequest {
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.should_bundle_to_asset() {
|
|
||||||
// Make sure to register the main wasm file with the asset system
|
|
||||||
assets.register_asset(&post_bindgen_wasm, AssetOptions::Unknown)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the index.html file with the pre-configured contents we got from pre-rendering
|
// Write the index.html file with the pre-configured contents we got from pre-rendering
|
||||||
self.write_index_html(assets)?;
|
self.write_index_html(assets)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_js_glue_shim(&self, assets: &AssetManifest) -> Result<()> {
|
||||||
|
let wasm_path = self.bundled_wasm_path(assets);
|
||||||
|
|
||||||
|
// Load and initialize wasm without requiring a separate javascript file.
|
||||||
|
// This also allows using a strict Content-Security-Policy.
|
||||||
|
let mut js = std::fs::OpenOptions::new()
|
||||||
|
.append(true)
|
||||||
|
.open(self.wasm_bindgen_js_output_file())?;
|
||||||
|
let mut buf_writer = std::io::BufWriter::new(&mut js);
|
||||||
|
writeln!(
|
||||||
|
buf_writer,
|
||||||
|
r#"
|
||||||
|
window.__wasm_split_main_initSync = initSync;
|
||||||
|
|
||||||
|
// Actually perform the load
|
||||||
|
__wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{
|
||||||
|
// assign this module to be accessible globally
|
||||||
|
window.__dx_mainWasm = wasm;
|
||||||
|
window.__dx_mainInit = __wbg_init;
|
||||||
|
window.__dx_mainInitSync = initSync;
|
||||||
|
window.__dx___wbg_get_imports = __wbg_get_imports;
|
||||||
|
|
||||||
|
if (wasm.__wbindgen_start == undefined) {{
|
||||||
|
wasm.main();
|
||||||
|
}}
|
||||||
|
}});
|
||||||
|
"#,
|
||||||
|
self.base_path_or_default(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Write the index.html file to the output directory. This must be called after the wasm and js
|
/// Write the index.html file to the output directory. This must be called after the wasm and js
|
||||||
/// assets are registered with the asset system if this is a release build.
|
/// assets are registered with the asset system if this is a release build.
|
||||||
pub(crate) fn write_index_html(&self, assets: &AssetManifest) -> Result<()> {
|
pub(crate) fn write_index_html(&self, assets: &AssetManifest) -> Result<()> {
|
||||||
// Get the path to the wasm-bindgen output files. Either the direct file or the opitmized one depending on the build mode
|
let wasm_path = self.bundled_wasm_path(assets);
|
||||||
let wasm_bindgen_wasm_out = self.wasm_bindgen_wasm_output_file();
|
let js_path = self.bundled_js_path(assets);
|
||||||
let wasm_path = if self.should_bundle_to_asset() {
|
|
||||||
let name = assets
|
|
||||||
.get_first_asset_for_source(&wasm_bindgen_wasm_out)
|
|
||||||
.expect("The wasm source must exist before creating index.html");
|
|
||||||
format!("assets/{}", name.bundled_path())
|
|
||||||
} else {
|
|
||||||
format!(
|
|
||||||
"wasm/{}",
|
|
||||||
wasm_bindgen_wasm_out.file_name().unwrap().to_str().unwrap()
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// Write the index.html file with the pre-configured contents we got from pre-rendering
|
||||||
|
std::fs::write(
|
||||||
|
self.root_dir().join("index.html"),
|
||||||
|
self.prepare_html(assets, &wasm_path, &js_path).unwrap(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bundled_js_path(&self, assets: &AssetManifest) -> String {
|
||||||
let wasm_bindgen_js_out = self.wasm_bindgen_js_output_file();
|
let wasm_bindgen_js_out = self.wasm_bindgen_js_output_file();
|
||||||
let js_path = if self.should_bundle_to_asset() {
|
if self.should_bundle_to_asset() {
|
||||||
let name = assets
|
let name = assets
|
||||||
.get_first_asset_for_source(&wasm_bindgen_js_out)
|
.get_first_asset_for_source(&wasm_bindgen_js_out)
|
||||||
.expect("The js source must exist before creating index.html");
|
.expect("The js source must exist before creating index.html");
|
||||||
@@ -3413,14 +3446,23 @@ impl BuildRequest {
|
|||||||
"wasm/{}",
|
"wasm/{}",
|
||||||
wasm_bindgen_js_out.file_name().unwrap().to_str().unwrap()
|
wasm_bindgen_js_out.file_name().unwrap().to_str().unwrap()
|
||||||
)
|
)
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Write the index.html file with the pre-configured contents we got from pre-rendering
|
/// Get the path to the wasm-bindgen output files. Either the direct file or the opitmized one depending on the build mode
|
||||||
std::fs::write(
|
fn bundled_wasm_path(&self, assets: &AssetManifest) -> String {
|
||||||
self.root_dir().join("index.html"),
|
let wasm_bindgen_wasm_out = self.wasm_bindgen_wasm_output_file();
|
||||||
self.prepare_html(assets, &wasm_path, &js_path).unwrap(),
|
if self.should_bundle_to_asset() {
|
||||||
)?;
|
let name = assets
|
||||||
Ok(())
|
.get_first_asset_for_source(&wasm_bindgen_wasm_out)
|
||||||
|
.expect("The wasm source must exist before creating index.html");
|
||||||
|
format!("assets/{}", name.bundled_path())
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"wasm/{}",
|
||||||
|
wasm_bindgen_wasm_out.file_name().unwrap().to_str().unwrap()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn info_plist_contents(&self, platform: Platform) -> Result<String> {
|
fn info_plist_contents(&self, platform: Platform) -> Result<String> {
|
||||||
@@ -3908,7 +3950,7 @@ impl BuildRequest {
|
|||||||
self.inject_resources(assets, wasm_path, &mut html)?;
|
self.inject_resources(assets, wasm_path, &mut html)?;
|
||||||
|
|
||||||
// Inject loading scripts if they are not already present
|
// Inject loading scripts if they are not already present
|
||||||
self.inject_loading_scripts(&mut html);
|
self.inject_loading_scripts(assets, &mut html);
|
||||||
|
|
||||||
// Replace any special placeholders in the HTML with resolved values
|
// Replace any special placeholders in the HTML with resolved values
|
||||||
self.replace_template_placeholders(&mut html, wasm_path, js_path);
|
self.replace_template_placeholders(&mut html, wasm_path, js_path);
|
||||||
@@ -4000,7 +4042,7 @@ impl BuildRequest {
|
|||||||
|
|
||||||
// Manually inject the wasm file for preloading. WASM currently doesn't support preloading in the manganis asset system
|
// Manually inject the wasm file for preloading. WASM currently doesn't support preloading in the manganis asset system
|
||||||
head_resources.push_str(&format!(
|
head_resources.push_str(&format!(
|
||||||
"<link rel=\"preload\" as=\"fetch\" type=\"application/wasm\" href=\"/{{base_path}}/assets/{wasm_path}\" crossorigin>"
|
"<link rel=\"preload\" as=\"fetch\" type=\"application/wasm\" href=\"/{{base_path}}/{wasm_path}\" crossorigin>"
|
||||||
));
|
));
|
||||||
Self::replace_or_insert_before("{style_include}", "</head", &head_resources, html);
|
Self::replace_or_insert_before("{style_include}", "</head", &head_resources, html);
|
||||||
|
|
||||||
@@ -4008,38 +4050,21 @@ impl BuildRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Inject loading scripts if they are not already present
|
/// Inject loading scripts if they are not already present
|
||||||
fn inject_loading_scripts(&self, html: &mut String) {
|
fn inject_loading_scripts(&self, assets: &AssetManifest, html: &mut String) {
|
||||||
// If it looks like we are already loading wasm or the current build opted out of injecting loading scripts, don't inject anything
|
// If the current build opted out of injecting loading scripts, don't inject anything
|
||||||
if !self.inject_loading_scripts || html.contains("__wbindgen_start") {
|
if !self.inject_loading_scripts {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not, insert the script
|
// If not, insert the script
|
||||||
*html = html.replace(
|
*html = html.replace(
|
||||||
"</body",
|
"</body",
|
||||||
r#" <script>
|
&format!(
|
||||||
// We can't use a module script here because we need to start the script immediately when streaming
|
r#"<script type="module" async src="/{}/{}"></script>
|
||||||
import("/{base_path}/{js_path}").then(
|
|
||||||
({ default: init, initSync, __wbg_get_imports }) => {
|
|
||||||
// export initSync in case a split module needs to initialize
|
|
||||||
window.__wasm_split_main_initSync = initSync;
|
|
||||||
|
|
||||||
// Actually perform the load
|
|
||||||
init({module_or_path: "/{base_path}/{wasm_path}"}).then((wasm) => {
|
|
||||||
// assign this module to be accessible globally
|
|
||||||
window.__dx_mainWasm = wasm;
|
|
||||||
window.__dx_mainInit = init;
|
|
||||||
window.__dx_mainInitSync = initSync;
|
|
||||||
window.__dx___wbg_get_imports = __wbg_get_imports;
|
|
||||||
|
|
||||||
if (wasm.__wbindgen_start == undefined) {
|
|
||||||
wasm.main();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
</body"#,
|
</body"#,
|
||||||
|
self.base_path_or_default(),
|
||||||
|
self.bundled_js_path(assets)
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ features = [
|
|||||||
"History",
|
"History",
|
||||||
"HtmlElement",
|
"HtmlElement",
|
||||||
"HtmlFormElement",
|
"HtmlFormElement",
|
||||||
|
"HtmlHeadElement",
|
||||||
"HtmlInputElement",
|
"HtmlInputElement",
|
||||||
"HtmlSelectElement",
|
"HtmlSelectElement",
|
||||||
"HtmlTextAreaElement",
|
"HtmlTextAreaElement",
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use dioxus_core::prelude::queue_effect;
|
use dioxus_core::prelude::queue_effect;
|
||||||
use dioxus_core::ScopeId;
|
use dioxus_core::ScopeId;
|
||||||
use dioxus_document::{
|
use dioxus_document::{
|
||||||
create_element_in_head, Document, Eval, EvalError, Evaluator, LinkProps, MetaProps,
|
Document, Eval, EvalError, Evaluator, LinkProps, MetaProps, ScriptProps, StyleProps,
|
||||||
ScriptProps, StyleProps,
|
|
||||||
};
|
};
|
||||||
use dioxus_history::History;
|
use dioxus_history::History;
|
||||||
use futures_util::FutureExt;
|
use futures_util::FutureExt;
|
||||||
@@ -97,45 +96,60 @@ impl Document for WebDocument {
|
|||||||
|
|
||||||
/// Create a new meta tag in the head
|
/// Create a new meta tag in the head
|
||||||
fn create_meta(&self, props: MetaProps) {
|
fn create_meta(&self, props: MetaProps) {
|
||||||
let myself = self.clone();
|
|
||||||
queue_effect(move || {
|
queue_effect(move || {
|
||||||
myself.eval(create_element_in_head("meta", &props.attributes(), None));
|
append_element_to_head("meta", &props.attributes(), None);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new script tag in the head
|
/// Create a new script tag in the head
|
||||||
fn create_script(&self, props: ScriptProps) {
|
fn create_script(&self, props: ScriptProps) {
|
||||||
let myself = self.clone();
|
|
||||||
queue_effect(move || {
|
queue_effect(move || {
|
||||||
myself.eval(create_element_in_head(
|
append_element_to_head(
|
||||||
"script",
|
"script",
|
||||||
&props.attributes(),
|
&props.attributes(),
|
||||||
props.script_contents().ok(),
|
props.script_contents().ok().as_deref(),
|
||||||
));
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new style tag in the head
|
/// Create a new style tag in the head
|
||||||
fn create_style(&self, props: StyleProps) {
|
fn create_style(&self, props: StyleProps) {
|
||||||
let myself = self.clone();
|
|
||||||
queue_effect(move || {
|
queue_effect(move || {
|
||||||
myself.eval(create_element_in_head(
|
append_element_to_head(
|
||||||
"style",
|
"style",
|
||||||
&props.attributes(),
|
&props.attributes(),
|
||||||
props.style_contents().ok(),
|
props.style_contents().ok().as_deref(),
|
||||||
));
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new link tag in the head
|
/// Create a new link tag in the head
|
||||||
fn create_link(&self, props: LinkProps) {
|
fn create_link(&self, props: LinkProps) {
|
||||||
let myself = self.clone();
|
|
||||||
queue_effect(move || {
|
queue_effect(move || {
|
||||||
myself.eval(create_element_in_head("link", &props.attributes(), None));
|
append_element_to_head("link", &props.attributes(), None);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn append_element_to_head(
|
||||||
|
local_name: &str,
|
||||||
|
attributes: &Vec<(&'static str, String)>,
|
||||||
|
text_content: Option<&str>,
|
||||||
|
) {
|
||||||
|
let window = web_sys::window().expect("no global `window` exists");
|
||||||
|
let document = window.document().expect("should have a document on window");
|
||||||
|
let head = document.head().expect("document should have a head");
|
||||||
|
|
||||||
|
let element = document.create_element(local_name).unwrap();
|
||||||
|
for (name, value) in attributes {
|
||||||
|
element.set_attribute(name, value).unwrap();
|
||||||
|
}
|
||||||
|
if text_content.is_some() {
|
||||||
|
element.set_text_content(text_content);
|
||||||
|
}
|
||||||
|
head.append_child(&element).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
/// Required to avoid blocking the Rust WASM thread.
|
/// Required to avoid blocking the Rust WASM thread.
|
||||||
const PROMISE_WRAPPER: &str = r#"
|
const PROMISE_WRAPPER: &str = r#"
|
||||||
return (async function(){
|
return (async function(){
|
||||||
|
|||||||
Reference in new issue
Block a user