script/dom/html/
htmlmetaelement.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::str::FromStr;
6
7use content_security_policy::{Policy, PolicyDisposition, PolicySource};
8use dom_struct::dom_struct;
9use html5ever::{LocalName, Prefix, local_name};
10use js::context::JSContext;
11use js::rust::HandleObject;
12use net_traits::ReferrerPolicy;
13use paint_api::viewport_description::ViewportDescription;
14use servo_config::pref;
15use style::str::HTML_SPACE_CHARACTERS;
16
17use crate::dom::attr::Attr;
18use crate::dom::bindings::codegen::Bindings::HTMLMetaElementBinding::HTMLMetaElementMethods;
19use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
20use crate::dom::bindings::inheritance::Castable;
21use crate::dom::bindings::root::DomRoot;
22use crate::dom::bindings::str::DOMString;
23use crate::dom::document::Document;
24use crate::dom::element::{AttributeMutation, Element};
25use crate::dom::html::htmlelement::HTMLElement;
26use crate::dom::html::htmlheadelement::HTMLHeadElement;
27use crate::dom::node::{BindContext, Node, NodeTraits, UnbindContext};
28use crate::dom::virtualmethods::VirtualMethods;
29use crate::script_runtime::CanGc;
30
31#[dom_struct]
32pub(crate) struct HTMLMetaElement {
33    htmlelement: HTMLElement,
34}
35
36impl HTMLMetaElement {
37    fn new_inherited(
38        local_name: LocalName,
39        prefix: Option<Prefix>,
40        document: &Document,
41    ) -> HTMLMetaElement {
42        HTMLMetaElement {
43            htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
44        }
45    }
46
47    pub(crate) fn new(
48        cx: &mut js::context::JSContext,
49        local_name: LocalName,
50        prefix: Option<Prefix>,
51        document: &Document,
52        proto: Option<HandleObject>,
53    ) -> DomRoot<HTMLMetaElement> {
54        Node::reflect_node_with_proto(
55            cx,
56            Box::new(HTMLMetaElement::new_inherited(local_name, prefix, document)),
57            document,
58            proto,
59        )
60    }
61
62    fn process_attributes(&self) {
63        let element = self.upcast::<Element>();
64        if let Some(ref name) = element.get_name() {
65            let name = name.to_ascii_lowercase();
66            let name = name.trim_matches(HTML_SPACE_CHARACTERS);
67            if name == "referrer" {
68                self.apply_referrer();
69            }
70            if name == "viewport" {
71                self.parse_and_send_viewport_if_necessary();
72            }
73        // https://html.spec.whatwg.org/multipage/#attr-meta-http-equiv
74        } else if !self.HttpEquiv().is_empty() {
75            // TODO: Implement additional http-equiv candidates
76            match self.HttpEquiv().to_ascii_lowercase().as_str() {
77                "refresh" => self.declarative_refresh(),
78                "content-security-policy" => self.apply_csp_list(),
79                _ => {},
80            }
81        }
82    }
83
84    fn process_referrer_attribute(&self) {
85        let element = self.upcast::<Element>();
86        if let Some(ref name) = element.get_name() {
87            let name = name.to_ascii_lowercase();
88            let name = name.trim_matches(HTML_SPACE_CHARACTERS);
89
90            if name == "referrer" {
91                self.apply_referrer();
92            }
93        }
94    }
95
96    /// <https://html.spec.whatwg.org/multipage/#meta-referrer>
97    fn apply_referrer(&self) {
98        let doc = self.owner_document();
99        // From spec: For historical reasons, unlike other standard metadata names, the processing model for referrer
100        // is not responsive to element removals, and does not use tree order. Only the most-recently-inserted or
101        // most-recently-modified meta element in this state has an effect.
102        // Step 1. If element is not in a document tree, then return.
103        let meta_node = self.upcast::<Node>();
104        if !meta_node.is_in_a_document_tree() {
105            return;
106        }
107
108        // Step 2. If element does not have a name attribute whose value is an ASCII
109        // case-insensitive match for "referrer", then return.
110        if self.upcast::<Element>().get_name() != Some(atom!("referrer")) {
111            return;
112        }
113
114        // Step 3. If element does not have a content attribute, or that attribute's value is the
115        // empty string, then return.
116        if let Some(content) = self
117            .upcast::<Element>()
118            .get_attribute(&local_name!("content"))
119            .filter(|attr| !attr.value().is_empty())
120        {
121            // Step 4. Let value be the value of element's content attribute, converted to ASCII
122            // lowercase.
123            // Step 5. If value is one of the values given in the first column of the following
124            // table, then set value to the value given in the second column:
125            // Step 6. If value is a referrer policy, then set element's node document's policy
126            // container's referrer policy to policy.
127            doc.set_referrer_policy(ReferrerPolicy::from_with_legacy(&content.value()));
128        }
129    }
130
131    /// <https://drafts.csswg.org/css-viewport/#parsing-algorithm>
132    fn parse_and_send_viewport_if_necessary(&self) {
133        if !pref!(viewport_meta_enabled) {
134            return;
135        }
136
137        // Skip processing if this isn't the top level frame
138        if !self.owner_window().is_top_level() {
139            return;
140        }
141        let element = self.upcast::<Element>();
142        let Some(content) = element.get_attribute(&local_name!("content")) else {
143            return;
144        };
145
146        if let Ok(viewport) = ViewportDescription::from_str(&content.value()) {
147            self.owner_window()
148                .paint_api()
149                .viewport(self.owner_window().webview_id(), viewport);
150        }
151    }
152
153    /// <https://html.spec.whatwg.org/multipage/#attr-meta-http-equiv-content-security-policy>
154    fn apply_csp_list(&self) {
155        // Step 1. If the meta element is not a child of a head element, return.
156        if self
157            .upcast::<Node>()
158            .GetParentElement()
159            .is_none_or(|parent| !parent.is::<HTMLHeadElement>())
160        {
161            return;
162        };
163        // Step 2. If the meta element has no content attribute, or if that attribute's value is the empty string, then return.
164        let Some(content) = self
165            .upcast::<Element>()
166            .get_attribute(&local_name!("content"))
167        else {
168            return;
169        };
170        let content = content.value();
171        if content.is_empty() {
172            return;
173        }
174        // Step 3. Let policy be the result of executing Content Security Policy's
175        // parse a serialized Content Security Policy algorithm
176        // on the meta element's content attribute's value,
177        // with a source of "meta", and a disposition of "enforce".
178        let mut policy = Policy::parse(&content, PolicySource::Meta, PolicyDisposition::Enforce);
179        // Step 4. Remove all occurrences of the report-uri, frame-ancestors,
180        // and sandbox directives from policy.
181        policy.directive_set.retain(|directive| {
182            !matches!(
183                directive.name.as_str(),
184                "report-uri" | "frame-ancestors" | "sandbox"
185            )
186        });
187        // Step 5. Enforce the policy policy.
188        self.owner_document().enforce_csp_policy(policy);
189    }
190
191    /// <https://html.spec.whatwg.org/multipage/#shared-declarative-refresh-steps>
192    fn declarative_refresh(&self) {
193        if !self.upcast::<Node>().is_in_a_document_tree() {
194            return;
195        }
196
197        // Step 2. Let input be the value of the element's content attribute.
198        let content = self.Content();
199        // Step 1. If the meta element has no content attribute, or if that attribute's value is the empty string, then return.
200        if !content.is_empty() {
201            // Step 3. Run the shared declarative refresh steps with the meta element's node document, input, and the meta element.
202            self.owner_document()
203                .shared_declarative_refresh_steps(&content.as_bytes());
204        }
205    }
206}
207
208impl HTMLMetaElementMethods<crate::DomTypeHolder> for HTMLMetaElement {
209    // https://html.spec.whatwg.org/multipage/#dom-meta-name
210    make_getter!(Name, "name");
211
212    // https://html.spec.whatwg.org/multipage/#dom-meta-name
213    make_atomic_setter!(SetName, "name");
214
215    // https://html.spec.whatwg.org/multipage/#dom-meta-content
216    make_getter!(Content, "content");
217
218    // https://html.spec.whatwg.org/multipage/#dom-meta-content
219    make_setter!(SetContent, "content");
220
221    // https://html.spec.whatwg.org/multipage/#dom-meta-httpequiv
222    make_getter!(HttpEquiv, "http-equiv");
223    // https://html.spec.whatwg.org/multipage/#dom-meta-httpequiv
224    make_atomic_setter!(SetHttpEquiv, "http-equiv");
225
226    // https://html.spec.whatwg.org/multipage/#dom-meta-scheme
227    make_getter!(Scheme, "scheme");
228    // https://html.spec.whatwg.org/multipage/#dom-meta-scheme
229    make_setter!(SetScheme, "scheme");
230}
231
232impl VirtualMethods for HTMLMetaElement {
233    fn super_type(&self) -> Option<&dyn VirtualMethods> {
234        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
235    }
236
237    fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
238        if let Some(s) = self.super_type() {
239            s.bind_to_tree(cx, context);
240        }
241
242        if context.tree_connected {
243            self.process_attributes();
244        }
245    }
246
247    fn attribute_mutated(
248        &self,
249        cx: &mut js::context::JSContext,
250        attr: &Attr,
251        mutation: AttributeMutation,
252    ) {
253        if let Some(s) = self.super_type() {
254            s.attribute_mutated(cx, attr, mutation);
255        }
256
257        self.process_referrer_attribute();
258    }
259
260    fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
261        if let Some(s) = self.super_type() {
262            s.unbind_from_tree(context, can_gc);
263        }
264
265        if context.tree_connected {
266            self.process_referrer_attribute();
267        }
268    }
269}