1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use html5ever::{local_name, namespace_url, ns};
use malloc_size_of::malloc_size_of_is_0;
use style::str::HTML_SPACE_CHARACTERS;

use crate::dom::types::Element;

bitflags::bitflags! {
    #[derive(Clone, Copy, Debug)]
    pub struct LinkRelations: u32 {
        const ALTERNATE = 1;
        const AUTHOR = 1 << 1;
        const BOOKMARK = 1 << 2;
        const CANONICAL = 1 << 3;
        const DNS_PREFETCH = 1 << 4;
        const EXPECT = 1 << 5;
        const EXTERNAL = 1 << 6;
        const HELP = 1 << 7;
        const ICON = 1 << 8;
        const LICENSE = 1 << 9;
        const NEXT = 1 << 10;
        const MANIFEST = 1 << 11;
        const MODULE_PRELOAD = 1 << 12;
        const NO_FOLLOW = 1 << 13;
        const NO_OPENER = 1 << 14;
        const NO_REFERRER = 1 << 15;
        const OPENER = 1 << 16;
        const PING_BACK = 1 << 17;
        const PRECONNECT = 1 << 18;
        const PREFETCH = 1 << 19;
        const PRELOAD = 1 << 20;
        const PREV = 1 << 21;
        const PRIVACY_POLICY = 1 << 22;
        const SEARCH = 1 << 23;
        const STYLESHEET = 1 << 24;
        const TAG = 1 << 25;
        const TermsOfService = 1 << 26;
    }
}

impl LinkRelations {
    pub fn for_element(element: &Element) -> Self {
        let rel = element.get_attribute(&ns!(), &local_name!("rel")).map(|e| {
            let value = e.value();
            (**value).to_owned()
        });

        // FIXME: for a, area and form elements we need to allow a different
        //        set of attributes
        let mut relations = rel
            .map(|attribute| {
                attribute
                    .split(HTML_SPACE_CHARACTERS)
                    .map(Self::from_single_keyword_for_link_element)
                    .collect()
            })
            .unwrap_or(Self::empty());

        // For historical reasons, "rev=made" is treated as if the "author" relation was specified
        let has_legacy_author_relation = element
            .get_attribute(&ns!(), &local_name!("rev"))
            .is_some_and(|rev| &**rev.value() == "made");
        if has_legacy_author_relation {
            relations |= Self::AUTHOR;
        }

        relations
    }

    /// Parse one of the relations allowed for the `<link>` element
    ///
    /// If the keyword is invalid then `Self::empty` is returned.
    fn from_single_keyword_for_link_element(keyword: &str) -> Self {
        if keyword.eq_ignore_ascii_case("alternate") {
            Self::ALTERNATE
        } else if keyword.eq_ignore_ascii_case("canonical") {
            Self::CANONICAL
        } else if keyword.eq_ignore_ascii_case("author") {
            Self::AUTHOR
        } else if keyword.eq_ignore_ascii_case("dns-prefetch") {
            Self::DNS_PREFETCH
        } else if keyword.eq_ignore_ascii_case("expect") {
            Self::EXPECT
        } else if keyword.eq_ignore_ascii_case("help") {
            Self::HELP
        } else if keyword.eq_ignore_ascii_case("icon") ||
            keyword.eq_ignore_ascii_case("shortcut icon") ||
            keyword.eq_ignore_ascii_case("apple-touch-icon")
        {
            // TODO: "apple-touch-icon" is not in the spec. Where did it come from? Do we need it?
            //       There is also "apple-touch-icon-precomposed" listed in
            //       https://github.com/servo/servo/blob/e43e4778421be8ea30db9d5c553780c042161522/components/script/dom/htmllinkelement.rs#L452-L467
            Self::ICON
        } else if keyword.eq_ignore_ascii_case("manifest") {
            Self::MANIFEST
        } else if keyword.eq_ignore_ascii_case("modulepreload") {
            Self::MODULE_PRELOAD
        } else if keyword.eq_ignore_ascii_case("license") ||
            keyword.eq_ignore_ascii_case("copyright")
        {
            Self::LICENSE
        } else if keyword.eq_ignore_ascii_case("next") {
            Self::NEXT
        } else if keyword.eq_ignore_ascii_case("pingback") {
            Self::PING_BACK
        } else if keyword.eq_ignore_ascii_case("preconnect") {
            Self::PRECONNECT
        } else if keyword.eq_ignore_ascii_case("prefetch") {
            Self::PREFETCH
        } else if keyword.eq_ignore_ascii_case("preload") {
            Self::PRELOAD
        } else if keyword.eq_ignore_ascii_case("prev") || keyword.eq_ignore_ascii_case("previous") {
            Self::PREV
        } else if keyword.eq_ignore_ascii_case("privacy-policy") {
            Self::PRIVACY_POLICY
        } else if keyword.eq_ignore_ascii_case("search") {
            Self::SEARCH
        } else if keyword.eq_ignore_ascii_case("stylesheet") {
            Self::STYLESHEET
        } else if keyword.eq_ignore_ascii_case("terms-of-service") {
            Self::TermsOfService
        } else {
            Self::empty()
        }
    }
}

malloc_size_of_is_0!(LinkRelations);