Skip to main content

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::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::{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, cx: &mut JSContext) {
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(cx);
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_string_value(&local_name!("content"))
119            .filter(|value| !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));
128        }
129    }
130
131    /// <https://drafts.csswg.org/css-viewport/#parsing-algorithm>
132    fn parse_and_send_viewport_if_necessary(&self, cx: &mut JSContext) {
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_string_value(&local_name!("content")) else {
143            return;
144        };
145
146        if let Ok(viewport) = ViewportDescription::from_str(&content) {
147            let initial_scale = viewport.initial_scale.get();
148            let window = self.owner_window();
149            window.paint_api().viewport(window.webview_id(), viewport);
150            window
151                .get_or_init_visual_viewport(CanGc::from_cx(cx))
152                .update_scale(initial_scale);
153        }
154    }
155
156    /// <https://html.spec.whatwg.org/multipage/#attr-meta-http-equiv-content-security-policy>
157    fn apply_csp_list(&self) {
158        // Step 1. If the meta element is not a child of a head element, return.
159        if self
160            .upcast::<Node>()
161            .GetParentElement()
162            .is_none_or(|parent| !parent.is::<HTMLHeadElement>())
163        {
164            return;
165        };
166        // Step 2. If the meta element has no content attribute, or if that attribute's value is the empty string, then return.
167        let Some(content) = self
168            .upcast::<Element>()
169            .get_attribute_string_value(&local_name!("content"))
170        else {
171            return;
172        };
173        if content.is_empty() {
174            return;
175        }
176        // Step 3. Let policy be the result of executing Content Security Policy's
177        // parse a serialized Content Security Policy algorithm
178        // on the meta element's content attribute's value,
179        // with a source of "meta", and a disposition of "enforce".
180        let mut policy = Policy::parse(&content, PolicySource::Meta, PolicyDisposition::Enforce);
181        // Step 4. Remove all occurrences of the report-uri, frame-ancestors,
182        // and sandbox directives from policy.
183        policy.directive_set.retain(|directive| {
184            !matches!(
185                directive.name.as_str(),
186                "report-uri" | "frame-ancestors" | "sandbox"
187            )
188        });
189        // Step 5. Enforce the policy policy.
190        self.owner_document().enforce_csp_policy(policy);
191    }
192
193    /// <https://html.spec.whatwg.org/multipage/#shared-declarative-refresh-steps>
194    fn declarative_refresh(&self) {
195        if !self.upcast::<Node>().is_in_a_document_tree() {
196            return;
197        }
198
199        // Step 2. Let input be the value of the element's content attribute.
200        let content = self.Content();
201        // Step 1. If the meta element has no content attribute, or if that attribute's value is the empty string, then return.
202        if !content.is_empty() {
203            // Step 3. Run the shared declarative refresh steps with the meta element's node document, input, and the meta element.
204            self.owner_document().shared_declarative_refresh_steps(
205                &content.as_bytes(),
206                /* from_meta_element */ true,
207            );
208        }
209    }
210}
211
212impl HTMLMetaElementMethods<crate::DomTypeHolder> for HTMLMetaElement {
213    // https://html.spec.whatwg.org/multipage/#dom-meta-name
214    make_getter!(Name, "name");
215
216    // https://html.spec.whatwg.org/multipage/#dom-meta-name
217    make_atomic_setter!(SetName, "name");
218
219    // https://html.spec.whatwg.org/multipage/#dom-meta-content
220    make_getter!(Content, "content");
221
222    // https://html.spec.whatwg.org/multipage/#dom-meta-content
223    make_setter!(SetContent, "content");
224
225    // https://html.spec.whatwg.org/multipage/#dom-meta-httpequiv
226    make_getter!(HttpEquiv, "http-equiv");
227    // https://html.spec.whatwg.org/multipage/#dom-meta-httpequiv
228    make_atomic_setter!(SetHttpEquiv, "http-equiv");
229
230    // https://html.spec.whatwg.org/multipage/#dom-meta-scheme
231    make_getter!(Scheme, "scheme");
232    // https://html.spec.whatwg.org/multipage/#dom-meta-scheme
233    make_setter!(SetScheme, "scheme");
234}
235
236impl VirtualMethods for HTMLMetaElement {
237    fn super_type(&self) -> Option<&dyn VirtualMethods> {
238        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
239    }
240
241    fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
242        if let Some(s) = self.super_type() {
243            s.bind_to_tree(cx, context);
244        }
245
246        if context.tree_connected {
247            self.process_attributes(cx);
248        }
249    }
250
251    fn attribute_mutated(
252        &self,
253        cx: &mut js::context::JSContext,
254        attr: AttrRef<'_>,
255        mutation: AttributeMutation,
256    ) {
257        if let Some(s) = self.super_type() {
258            s.attribute_mutated(cx, attr, mutation);
259        }
260
261        self.process_referrer_attribute();
262    }
263
264    fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
265        if let Some(s) = self.super_type() {
266            s.unbind_from_tree(cx, context);
267        }
268
269        if context.tree_connected {
270            self.process_referrer_attribute();
271        }
272    }
273}