|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "DOMStorageObserver.h" |
|
7 |
|
8 #include "DOMStorageDBThread.h" |
|
9 #include "DOMStorageCache.h" |
|
10 |
|
11 #include "nsIObserverService.h" |
|
12 #include "nsIURI.h" |
|
13 #include "nsIURL.h" |
|
14 #include "nsIScriptSecurityManager.h" |
|
15 #include "nsIPermission.h" |
|
16 #include "nsIIDNService.h" |
|
17 #include "mozIApplicationClearPrivateDataParams.h" |
|
18 #include "nsICookiePermission.h" |
|
19 |
|
20 #include "nsPrintfCString.h" |
|
21 #include "nsXULAppAPI.h" |
|
22 #include "nsEscape.h" |
|
23 #include "nsNetCID.h" |
|
24 #include "mozilla/Services.h" |
|
25 #include "nsServiceManagerUtils.h" |
|
26 |
|
27 namespace mozilla { |
|
28 namespace dom { |
|
29 |
|
30 static const char kStartupTopic[] = "sessionstore-windows-restored"; |
|
31 static const uint32_t kStartupDelay = 0; |
|
32 |
|
33 NS_IMPL_ISUPPORTS(DOMStorageObserver, |
|
34 nsIObserver, |
|
35 nsISupportsWeakReference) |
|
36 |
|
37 DOMStorageObserver* DOMStorageObserver::sSelf = nullptr; |
|
38 |
|
39 extern nsresult |
|
40 CreateReversedDomain(const nsACString& aAsciiDomain, nsACString& aKey); |
|
41 |
|
42 // static |
|
43 nsresult |
|
44 DOMStorageObserver::Init() |
|
45 { |
|
46 if (sSelf) { |
|
47 return NS_OK; |
|
48 } |
|
49 |
|
50 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); |
|
51 if (!obs) { |
|
52 return NS_ERROR_UNEXPECTED; |
|
53 } |
|
54 |
|
55 sSelf = new DOMStorageObserver(); |
|
56 NS_ADDREF(sSelf); |
|
57 |
|
58 // Chrome clear operations. |
|
59 obs->AddObserver(sSelf, kStartupTopic, true); |
|
60 obs->AddObserver(sSelf, "cookie-changed", true); |
|
61 obs->AddObserver(sSelf, "perm-changed", true); |
|
62 obs->AddObserver(sSelf, "browser:purge-domain-data", true); |
|
63 obs->AddObserver(sSelf, "last-pb-context-exited", true); |
|
64 obs->AddObserver(sSelf, "webapps-clear-data", true); |
|
65 |
|
66 // Shutdown |
|
67 obs->AddObserver(sSelf, "profile-after-change", true); |
|
68 obs->AddObserver(sSelf, "profile-before-change", true); |
|
69 obs->AddObserver(sSelf, "xpcom-shutdown", true); |
|
70 |
|
71 // Observe low device storage notifications. |
|
72 obs->AddObserver(sSelf, "disk-space-watcher", true); |
|
73 |
|
74 #ifdef DOM_STORAGE_TESTS |
|
75 // Testing |
|
76 obs->AddObserver(sSelf, "domstorage-test-flush-force", true); |
|
77 if (XRE_GetProcessType() == GeckoProcessType_Default) { |
|
78 // Only to forward to child process. |
|
79 obs->AddObserver(sSelf, "domstorage-test-flushed", true); |
|
80 } |
|
81 |
|
82 obs->AddObserver(sSelf, "domstorage-test-reload", true); |
|
83 #endif |
|
84 |
|
85 return NS_OK; |
|
86 } |
|
87 |
|
88 // static |
|
89 nsresult |
|
90 DOMStorageObserver::Shutdown() |
|
91 { |
|
92 if (!sSelf) { |
|
93 return NS_ERROR_NOT_INITIALIZED; |
|
94 } |
|
95 |
|
96 NS_RELEASE(sSelf); |
|
97 return NS_OK; |
|
98 } |
|
99 |
|
100 void |
|
101 DOMStorageObserver::AddSink(DOMStorageObserverSink* aObs) |
|
102 { |
|
103 mSinks.AppendElement(aObs); |
|
104 } |
|
105 |
|
106 void |
|
107 DOMStorageObserver::RemoveSink(DOMStorageObserverSink* aObs) |
|
108 { |
|
109 mSinks.RemoveElement(aObs); |
|
110 } |
|
111 |
|
112 void |
|
113 DOMStorageObserver::Notify(const char* aTopic, const nsACString& aData) |
|
114 { |
|
115 for (uint32_t i = 0; i < mSinks.Length(); ++i) { |
|
116 DOMStorageObserverSink* sink = mSinks[i]; |
|
117 sink->Observe(aTopic, aData); |
|
118 } |
|
119 } |
|
120 |
|
121 NS_IMETHODIMP |
|
122 DOMStorageObserver::Observe(nsISupports* aSubject, |
|
123 const char* aTopic, |
|
124 const char16_t* aData) |
|
125 { |
|
126 nsresult rv; |
|
127 |
|
128 // Start the thread that opens the database. |
|
129 if (!strcmp(aTopic, kStartupTopic)) { |
|
130 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); |
|
131 obs->RemoveObserver(this, kStartupTopic); |
|
132 |
|
133 mDBThreadStartDelayTimer = do_CreateInstance(NS_TIMER_CONTRACTID); |
|
134 if (!mDBThreadStartDelayTimer) { |
|
135 return NS_ERROR_UNEXPECTED; |
|
136 } |
|
137 |
|
138 mDBThreadStartDelayTimer->Init(this, nsITimer::TYPE_ONE_SHOT, kStartupDelay); |
|
139 |
|
140 return NS_OK; |
|
141 } |
|
142 |
|
143 // Timer callback used to start the database a short timer after startup |
|
144 if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) { |
|
145 nsCOMPtr<nsITimer> timer = do_QueryInterface(aSubject); |
|
146 if (!timer) { |
|
147 return NS_ERROR_UNEXPECTED; |
|
148 } |
|
149 |
|
150 if (timer == mDBThreadStartDelayTimer) { |
|
151 mDBThreadStartDelayTimer = nullptr; |
|
152 |
|
153 DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); |
|
154 NS_ENSURE_TRUE(db, NS_ERROR_FAILURE); |
|
155 } |
|
156 |
|
157 return NS_OK; |
|
158 } |
|
159 |
|
160 // Clear everything, caches + database |
|
161 if (!strcmp(aTopic, "cookie-changed")) { |
|
162 if (!NS_LITERAL_STRING("cleared").Equals(aData)) { |
|
163 return NS_OK; |
|
164 } |
|
165 |
|
166 DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); |
|
167 NS_ENSURE_TRUE(db, NS_ERROR_FAILURE); |
|
168 |
|
169 db->AsyncClearAll(); |
|
170 |
|
171 Notify("cookie-cleared"); |
|
172 |
|
173 return NS_OK; |
|
174 } |
|
175 |
|
176 // Clear from caches everything that has been stored |
|
177 // while in session-only mode |
|
178 if (!strcmp(aTopic, "perm-changed")) { |
|
179 // Check for cookie permission change |
|
180 nsCOMPtr<nsIPermission> perm(do_QueryInterface(aSubject)); |
|
181 if (!perm) { |
|
182 return NS_OK; |
|
183 } |
|
184 |
|
185 nsAutoCString type; |
|
186 perm->GetType(type); |
|
187 if (type != NS_LITERAL_CSTRING("cookie")) { |
|
188 return NS_OK; |
|
189 } |
|
190 |
|
191 uint32_t cap = 0; |
|
192 perm->GetCapability(&cap); |
|
193 if (!(cap & nsICookiePermission::ACCESS_SESSION) || |
|
194 !NS_LITERAL_STRING("deleted").Equals(nsDependentString(aData))) { |
|
195 return NS_OK; |
|
196 } |
|
197 |
|
198 nsAutoCString host; |
|
199 perm->GetHost(host); |
|
200 if (host.IsEmpty()) { |
|
201 return NS_OK; |
|
202 } |
|
203 |
|
204 nsAutoCString scope; |
|
205 rv = CreateReversedDomain(host, scope); |
|
206 NS_ENSURE_SUCCESS(rv, rv); |
|
207 |
|
208 Notify("session-only-cleared", scope); |
|
209 |
|
210 return NS_OK; |
|
211 } |
|
212 |
|
213 // Clear everything (including so and pb data) from caches and database |
|
214 // for the gived domain and subdomains. |
|
215 if (!strcmp(aTopic, "browser:purge-domain-data")) { |
|
216 // Convert the domain name to the ACE format |
|
217 nsAutoCString aceDomain; |
|
218 nsCOMPtr<nsIIDNService> converter = do_GetService(NS_IDNSERVICE_CONTRACTID); |
|
219 if (converter) { |
|
220 rv = converter->ConvertUTF8toACE(NS_ConvertUTF16toUTF8(aData), aceDomain); |
|
221 NS_ENSURE_SUCCESS(rv, rv); |
|
222 } else { |
|
223 // In case the IDN service is not available, this is the best we can come up with! |
|
224 NS_EscapeURL(NS_ConvertUTF16toUTF8(aData), |
|
225 esc_OnlyNonASCII | esc_AlwaysCopy, |
|
226 aceDomain); |
|
227 } |
|
228 |
|
229 nsAutoCString scopePrefix; |
|
230 rv = CreateReversedDomain(aceDomain, scopePrefix); |
|
231 NS_ENSURE_SUCCESS(rv, rv); |
|
232 |
|
233 DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); |
|
234 NS_ENSURE_TRUE(db, NS_ERROR_FAILURE); |
|
235 |
|
236 db->AsyncClearMatchingScope(scopePrefix); |
|
237 |
|
238 Notify("domain-data-cleared", scopePrefix); |
|
239 |
|
240 return NS_OK; |
|
241 } |
|
242 |
|
243 // Clear all private-browsing caches |
|
244 if (!strcmp(aTopic, "last-pb-context-exited")) { |
|
245 Notify("private-browsing-data-cleared"); |
|
246 |
|
247 return NS_OK; |
|
248 } |
|
249 |
|
250 // Clear data beloging to an app. |
|
251 if (!strcmp(aTopic, "webapps-clear-data")) { |
|
252 nsCOMPtr<mozIApplicationClearPrivateDataParams> params = |
|
253 do_QueryInterface(aSubject); |
|
254 if (!params) { |
|
255 NS_ERROR("'webapps-clear-data' notification's subject should be a mozIApplicationClearPrivateDataParams"); |
|
256 return NS_ERROR_UNEXPECTED; |
|
257 } |
|
258 |
|
259 uint32_t appId; |
|
260 bool browserOnly; |
|
261 |
|
262 rv = params->GetAppId(&appId); |
|
263 NS_ENSURE_SUCCESS(rv, rv); |
|
264 |
|
265 rv = params->GetBrowserOnly(&browserOnly); |
|
266 NS_ENSURE_SUCCESS(rv, rv); |
|
267 |
|
268 MOZ_ASSERT(appId != nsIScriptSecurityManager::UNKNOWN_APP_ID); |
|
269 |
|
270 DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); |
|
271 NS_ENSURE_TRUE(db, NS_ERROR_FAILURE); |
|
272 |
|
273 nsAutoCString scope; |
|
274 scope.AppendInt(appId); |
|
275 scope.Append(NS_LITERAL_CSTRING(":t:")); |
|
276 db->AsyncClearMatchingScope(scope); |
|
277 Notify("app-data-cleared", scope); |
|
278 |
|
279 if (!browserOnly) { |
|
280 scope.Truncate(); |
|
281 scope.AppendInt(appId); |
|
282 scope.Append(NS_LITERAL_CSTRING(":f:")); |
|
283 db->AsyncClearMatchingScope(scope); |
|
284 Notify("app-data-cleared", scope); |
|
285 } |
|
286 |
|
287 return NS_OK; |
|
288 } |
|
289 |
|
290 if (!strcmp(aTopic, "profile-after-change")) { |
|
291 Notify("profile-change"); |
|
292 |
|
293 return NS_OK; |
|
294 } |
|
295 |
|
296 if (!strcmp(aTopic, "profile-before-change") || |
|
297 !strcmp(aTopic, "xpcom-shutdown")) { |
|
298 rv = DOMStorageCache::StopDatabase(); |
|
299 if (NS_FAILED(rv)) { |
|
300 NS_WARNING("Error while stopping DOMStorage DB background thread"); |
|
301 } |
|
302 |
|
303 return NS_OK; |
|
304 } |
|
305 |
|
306 if (!strcmp(aTopic, "disk-space-watcher")) { |
|
307 if (NS_LITERAL_STRING("full").Equals(aData)) { |
|
308 Notify("low-disk-space"); |
|
309 } else if (NS_LITERAL_STRING("free").Equals(aData)) { |
|
310 Notify("no-low-disk-space"); |
|
311 } |
|
312 |
|
313 return NS_OK; |
|
314 } |
|
315 |
|
316 #ifdef DOM_STORAGE_TESTS |
|
317 if (!strcmp(aTopic, "domstorage-test-flush-force")) { |
|
318 DOMStorageDBBridge* db = DOMStorageCache::GetDatabase(); |
|
319 if (db) { |
|
320 db->AsyncFlush(); |
|
321 } |
|
322 |
|
323 return NS_OK; |
|
324 } |
|
325 |
|
326 if (!strcmp(aTopic, "domstorage-test-flushed")) { |
|
327 // Only used to propagate to IPC children |
|
328 Notify("test-flushed"); |
|
329 |
|
330 return NS_OK; |
|
331 } |
|
332 |
|
333 if (!strcmp(aTopic, "domstorage-test-reload")) { |
|
334 Notify("test-reload"); |
|
335 |
|
336 return NS_OK; |
|
337 } |
|
338 #endif |
|
339 |
|
340 NS_ERROR("Unexpected topic"); |
|
341 return NS_ERROR_UNEXPECTED; |
|
342 } |
|
343 |
|
344 } // ::dom |
|
345 } // ::mozilla |