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(&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, 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(&local_name!("content")) else {
143            return;
144        };
145
146        if let Ok(viewport) = ViewportDescription::from_str(&content.value()) {
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(&local_name!("content"))
170        else {
171            return;
172        };
173        let content = content.value();
174        if content.is_empty() {
175            return;
176        }
177        // Step 3. Let policy be the result of executing Content Security Policy's
178        // parse a serialized Content Security Policy algorithm
179        // on the meta element's content attribute's value,
180        // with a source of "meta", and a disposition of "enforce".
181        let mut policy = Policy::parse(&content, PolicySource::Meta, PolicyDisposition::Enforce);
182        // Step 4. Remove all occurrences of the report-uri, frame-ancestors,
183        // and sandbox directives from policy.
184        policy.directive_set.retain(|directive| {
185            !matches!(
186                directive.name.as_str(),
187                "report-uri" | "frame-ancestors" | "sandbox"
188            )
189        });
190        // Step 5. Enforce the policy policy.
191        self.owner_document().enforce_csp_policy(policy);
192    }
193
194    /// <https://html.spec.whatwg.org/multipage/#shared-declarative-refresh-steps>
195    fn declarative_refresh(&self) {
196        if !self.upcast::<Node>().is_in_a_document_tree() {
197            return;
198        }
199
200        // Step 2. Let input be the value of the element's content attribute.
201        let content = self.Content();
202        // Step 1. If the meta element has no content attribute, or if that attribute's value is the empty string, then return.
203        if !content.is_empty() {
204            // Step 3. Run the shared declarative refresh steps with the meta element's node document, input, and the meta element.
205            self.owner_document()
206                .shared_declarative_refresh_steps(&content.as_bytes());
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}