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