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