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