cabin/html/elements/
script.rs

1use std::borrow::Cow;
2use std::fmt::Write;
3
4use cabin_macros::Attribute;
5
6use super::anchor::ReferrerPolicy;
7use super::common::Common;
8use super::global::Global;
9use super::link::{Blocking, CrossOrigin, FetchPriority, Type};
10use crate::html::attributes::{Attributes, WithAttribute};
11use crate::html::{Aria, Html};
12use crate::render::{Escape, Renderer};
13use crate::view::RenderFuture;
14use crate::View;
15
16/// A `script` element allows to include dynamic script and data blocks in their documents.
17pub fn script(content: impl Into<Cow<'static, str>>) -> Html<marker::Script, (), impl View> {
18    Html::new("script", (), ScriptEscape(content.into()))
19}
20
21pub mod marker {
22    pub struct Script;
23}
24
25impl<A: Attributes, V: 'static> Script for Html<marker::Script, A, V> {}
26impl<A: Attributes, V: 'static> Common for Html<marker::Script, A, V> {}
27impl<A: Attributes, V: 'static> Global for Html<marker::Script, A, V> {}
28impl<A: Attributes, V: 'static> Aria for Html<marker::Script, A, V> {}
29
30/// A `script` element allows to include dynamic script and data blocks in their documents.
31pub trait Script: WithAttribute {
32    /// Address of the resource.
33    fn src(self, src: impl Into<Cow<'static, str>>) -> Self::Output<Src> {
34        self.with_attribute(Src(src.into()))
35    }
36
37    /// The type of the script.
38    fn r#type(self, r#type: impl Into<Cow<'static, str>>) -> Self::Output<Type> {
39        self.with_attribute(Type(r#type.into()))
40    }
41
42    /// Whether to prevent execution in user agents that support module scripts.
43    fn no_module(self) -> Self::Output<NoModule> {
44        self.with_no_module(true)
45    }
46
47    /// Whether to prevent execution in user agents that support module scripts.
48    fn with_no_module(self, no_module: bool) -> Self::Output<NoModule> {
49        self.with_attribute(NoModule(no_module))
50    }
51
52    /// Execute script when available, without blocking while fetching.
53    fn r#async(self) -> Self::Output<Async> {
54        self.with_async(true)
55    }
56
57    /// Execute script when available, without blocking while fetching.
58    fn with_async(self, r#async: bool) -> Self::Output<Async> {
59        self.with_attribute(Async(r#async))
60    }
61
62    /// Defer script execution.
63    fn defer(self) -> Self::Output<Defer> {
64        self.with_defer(true)
65    }
66
67    /// Defer script execution.
68    fn with_defer(self, defer: bool) -> Self::Output<Defer> {
69        self.with_attribute(Defer(defer))
70    }
71
72    /// Handling of crossorigin requests.
73    fn cross_origin(self, cross_origin: CrossOrigin) -> Self::Output<CrossOrigin> {
74        self.with_attribute(cross_origin)
75    }
76
77    /// Integrity metadata used in _Subresource Integrity_ checks.
78    fn integrity(self, integrity: impl Into<Cow<'static, str>>) -> Self::Output<Integrity> {
79        self.with_attribute(Integrity(integrity.into()))
80    }
81
82    /// How much referrer information to send.
83    fn referrer_policy(self, referrer_policy: ReferrerPolicy) -> Self::Output<ReferrerPolicy> {
84        self.with_attribute(referrer_policy)
85    }
86
87    fn blocking(self) -> Self::Output<Blocking> {
88        self.with_blocking(true)
89    }
90
91    fn with_blocking(self, blocking: bool) -> Self::Output<Blocking> {
92        self.with_attribute(Blocking(blocking))
93    }
94
95    /// Sets the priority for fetches initiated by the element.
96    fn fetch_priority(self, fetch_priority: FetchPriority) -> Self::Output<FetchPriority> {
97        self.with_attribute(fetch_priority)
98    }
99}
100
101/// Address of the resource.
102#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
103pub struct Src(pub Cow<'static, str>);
104
105/// Whether to prevent execution in user agents that support module scripts.
106#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
107pub struct NoModule(pub bool);
108
109/// Execute script when available, without blocking while fetching.
110#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
111pub struct Async(pub bool);
112
113/// Defer script execution.
114#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
115pub struct Defer(pub bool);
116
117/// Integrity metadata used in _Subresource Integrity_ checks.
118#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
119pub struct Integrity(pub Cow<'static, str>);
120
121pub struct ScriptEscape(pub Cow<'static, str>);
122
123impl View for ScriptEscape {
124    fn render(self, r: Renderer, _include_hash: bool) -> RenderFuture {
125        let mut txt = r.text();
126        RenderFuture::ready(
127            Escape::script(&mut txt)
128                .write_str(&self.0)
129                .map_err(crate::error::InternalError::from)
130                .map_err(crate::error::Error::from)
131                .and_then(|_| txt.end()),
132        )
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[tokio::test]
141    async fn test_script_escape() {
142        assert_eq!(
143            script("asd</script>")
144                .render(Renderer::new(), false)
145                .await
146                .unwrap()
147                .end()
148                .html,
149            r"<script>asd<\/script></script>"
150        );
151        assert_eq!(
152            script("asd<!--")
153                .render(Renderer::new(), false)
154                .await
155                .unwrap()
156                .end()
157                .html,
158            r"<script>asd<\!--</script>"
159        );
160        assert_eq!(
161            script(r#"if (1<2) alert("</script>")"#)
162                .render(Renderer::new(), false)
163                .await
164                .unwrap()
165                .end()
166                .html,
167            r#"<script>if (1<2) alert("<\/script>")</script>"#
168        );
169    }
170}