Skip to main content

script/dom/cookiestore/
cookiestoremanager.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::rc::Rc;
6
7use dom_struct::dom_struct;
8use js::context::JSContext;
9use js::jsval::UndefinedValue;
10use script_bindings::cell::DomRefCell;
11use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};
12use script_bindings::root::{Dom, DomRoot};
13use servo_url::ServoUrl;
14
15use crate::dom::bindings::codegen::Bindings::CookieStoreBinding::CookieStoreGetOptions;
16use crate::dom::bindings::codegen::Bindings::CookieStoreManagerBinding::CookieStoreManagerMethods;
17use crate::dom::bindings::error::Error;
18use crate::dom::bindings::reflector::DomGlobal;
19use crate::dom::bindings::str::USVString;
20use crate::dom::cookiestore::cookiestore::CookieStore;
21use crate::dom::globalscope::GlobalScope;
22use crate::dom::promise::Promise;
23use crate::dom::serviceworker::serviceworkerregistration::{
24    ServiceWorkerRegistration, longest_prefix_match,
25};
26
27/// <https://cookiestore.spec.whatwg.org/#cookie-store-manager-interface>
28#[dom_struct]
29pub(crate) struct CookieStoreManager {
30    reflector_: Reflector,
31    // A CookieStoreManager has an associated registration which is a service worker registration.
32    serviceworker_registration: Dom<ServiceWorkerRegistration>,
33    // Let subscription list be registration's associated cookie change
34    // subscription list.
35    #[ignore_malloc_size_of = "generated WebIDL dictionary"]
36    subscriptions: DomRefCell<Vec<CookieStoreGetOptions>>,
37}
38
39impl CookieStoreManager {
40    fn new_inherited(registration: &ServiceWorkerRegistration) -> CookieStoreManager {
41        // Each ServiceWorkerRegistration has an associated CookieStoreManager
42        // object. The CookieStoreManager's registration is equal to the
43        // ServiceWorkerRegistration's service worker registration.
44        CookieStoreManager {
45            reflector_: Reflector::new(),
46            serviceworker_registration: Dom::from_ref(registration),
47            subscriptions: DomRefCell::new(Vec::new()),
48        }
49    }
50
51    pub(crate) fn new(
52        cx: &mut JSContext,
53        global: &GlobalScope,
54        registration: &ServiceWorkerRegistration,
55    ) -> DomRoot<CookieStoreManager> {
56        // The cookies getter steps are to return this's associated
57        // CookieStoreManager object.
58        reflect_dom_object_with_cx(Box::new(Self::new_inherited(registration)), global, cx)
59    }
60
61    /// <https://cookiestore.spec.whatwg.org/#dom-cookiestoremanager-subscribe>
62    fn normalize_subscription(
63        &self,
64        subscription: CookieStoreGetOptions,
65    ) -> Result<CookieStoreGetOptions, Error> {
66        // Step 4.2.1. Let name be null.
67        let name = subscription
68            .name
69            .as_ref()
70            // Step 4.2.2. If entry["name"] exists:
71            // Step 4.2.2.1. Set name to entry["name"].
72            // Step 4.2.2.2. Normalize name.
73            .map(|name| USVString(CookieStore::normalize(name)));
74
75        // Step 4.2.3. Let url be registration's scope URL.
76        let mut url = self.serviceworker_registration.scope_url().clone();
77        // Step 4.2.4. If entry["url"] exists, then set url to the result of
78        // parsing entry["url"] with settings' API base URL.
79        if let Some(entry_url) = subscription.url {
80            url = ServoUrl::parse_with_base(Some(&self.global().get_url()), &entry_url.0)
81                .map_err(|_| Error::Type(c"Invalid cookie subscription URL".to_owned()))?;
82        }
83
84        // Step 4.2.5. If url is failure or url does not start with
85        // registration's scope URL, reject p with a TypeError and abort these
86        // steps.
87        if !longest_prefix_match(self.serviceworker_registration.scope_url(), &url) {
88            return Err(Error::Type(
89                c"Cookie subscription URL is outside the scope".to_owned(),
90            ));
91        }
92
93        // Step 4.2.6. Let subscription be the cookie change subscription
94        // (name, url).
95        Ok(CookieStoreGetOptions {
96            name,
97            url: Some(USVString(url.as_str().to_owned())),
98        })
99    }
100}
101
102impl CookieStoreManagerMethods<crate::DomTypeHolder> for CookieStoreManager {
103    /// <https://cookiestore.spec.whatwg.org/#dom-cookiestoremanager-subscribe>
104    fn Subscribe(
105        &self,
106        cx: &mut JSContext,
107        subscriptions: Vec<CookieStoreGetOptions>,
108    ) -> Rc<Promise> {
109        // Step 1. Let settings be this's relevant settings object.
110        // Step 2. Let registration be this's registration.
111        // Step 3. Let p be a new promise.
112        let promise = Promise::new(cx, &self.global());
113        // Step 4.1. Let subscription list be registration's associated cookie
114        // change subscription list.
115
116        // Step 4.2. For each entry in subscriptions, run these steps.
117        for subscription in subscriptions {
118            let subscription = match self.normalize_subscription(subscription) {
119                Ok(subscription) => subscription,
120                Err(error) => {
121                    // Step 4.2.5. Reject p with a TypeError and abort these
122                    // steps.
123                    promise.reject_error(cx, error);
124                    return promise;
125                },
126            };
127
128            let mut current = self.subscriptions.safe_borrow_mut(cx);
129            // Step 4.2.7. If subscription list does not already contain
130            // subscription, then append subscription to subscription list.
131            if !current.iter().any(|existing| {
132                existing.name == subscription.name && existing.url == subscription.url
133            }) {
134                current.push(subscription);
135            }
136        }
137
138        // Step 4.3. Resolve p with undefined.
139        promise.resolve_native(cx, &UndefinedValue());
140        // Step 5. Return p.
141        promise
142    }
143
144    /// <https://cookiestore.spec.whatwg.org/#dom-cookiestoremanager-getsubscriptions>
145    fn GetSubscriptions(&self, cx: &mut JSContext) -> Rc<Promise> {
146        // Step 1. Let registration be this's registration.
147        // Step 2. Let p be a new promise.
148        let promise = Promise::new(cx, &self.global());
149        // Step 3.1. Let subscriptions be registration's associated cookie
150        // change subscription list.
151        let subscriptions = self.subscriptions.borrow();
152        // Step 3.2. Let result be « ».
153        // Step 3.3. For each subscription, append its name and URL to result.
154        // Step 3.4. Resolve p with result.
155        promise.resolve_native(cx, &*subscriptions);
156        // Step 4. Return p.
157        promise
158    }
159
160    /// <https://cookiestore.spec.whatwg.org/#dom-cookiestoremanager-unsubscribe>
161    fn Unsubscribe(
162        &self,
163        cx: &mut JSContext,
164        subscriptions: Vec<CookieStoreGetOptions>,
165    ) -> Rc<Promise> {
166        // Step 1. Let settings be this's relevant settings object.
167        // Step 2. Let registration be this's registration.
168        // Step 3. Let p be a new promise.
169        let promise = Promise::new(cx, &self.global());
170        // Step 4.1. Let subscription list be registration's associated cookie
171        // change subscription list.
172
173        // Step 4.2. For each entry in subscriptions, run these steps.
174        for subscription in subscriptions {
175            let subscription = match self.normalize_subscription(subscription) {
176                Ok(subscription) => subscription,
177                Err(error) => {
178                    // Step 4.2.5. Reject p with a TypeError and abort these
179                    // steps.
180                    promise.reject_error(cx, error);
181                    return promise;
182                },
183            };
184
185            let mut current = self.subscriptions.safe_borrow_mut(cx);
186            // Step 4.2.7. Remove any item from subscription list equal to
187            // subscription.
188            current.retain(|existing| {
189                existing.name != subscription.name || existing.url != subscription.url
190            });
191        }
192
193        // Step 4.3. Resolve p with undefined.
194        promise.resolve_native(cx, &UndefinedValue());
195        // Step 5. Return p.
196        promise
197    }
198}