Skip to main content

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