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}