StorageObserver.cpp (15288B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "StorageObserver.h" 8 9 #include "LocalStorageCache.h" 10 #include "StorageCommon.h" 11 #include "StorageDBThread.h" 12 #include "StorageIPC.h" 13 #include "StorageUtils.h" 14 #include "mozilla/BasePrincipal.h" 15 #include "mozilla/Preferences.h" 16 #include "mozilla/Services.h" 17 #include "mozilla/SpinEventLoopUntil.h" 18 #include "mozilla/dom/LocalStorageCommon.h" 19 #include "nsCOMPtr.h" 20 #include "nsEscape.h" 21 #include "nsIClearBySiteEntry.h" 22 #include "nsICookieNotification.h" 23 #include "nsICookiePermission.h" 24 #include "nsIObserverService.h" 25 #include "nsIPermission.h" 26 #include "nsIURI.h" 27 #include "nsNetCID.h" 28 #include "nsNetUtil.h" 29 #include "nsPrintfCString.h" 30 #include "nsServiceManagerUtils.h" 31 #include "nsXULAppAPI.h" 32 33 namespace mozilla::dom { 34 35 using namespace StorageUtils; 36 37 static const char kStartupTopic[] = "sessionstore-windows-restored"; 38 static const uint32_t kStartupDelay = 0; 39 40 const char kTestingPref[] = "dom.storage.testing"; 41 42 constexpr auto kPrivateBrowsingPattern = u"{ \"privateBrowsingId\": 1 }"_ns; 43 44 NS_IMPL_ISUPPORTS(StorageObserver, nsIObserver, nsINamed, 45 nsISupportsWeakReference) 46 47 StorageObserver* StorageObserver::sSelf = nullptr; 48 49 // static 50 nsresult StorageObserver::Init() { 51 static_assert(kPrivateBrowsingIdCount * sizeof(mBackgroundThread[0]) == 52 sizeof(mBackgroundThread)); 53 if (sSelf) { 54 return NS_OK; 55 } 56 57 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 58 if (!obs) { 59 return NS_ERROR_UNEXPECTED; 60 } 61 62 sSelf = new StorageObserver(); 63 NS_ADDREF(sSelf); 64 65 // Chrome clear operations. 66 obs->AddObserver(sSelf, kStartupTopic, true); 67 obs->AddObserver(sSelf, "cookie-changed", true); 68 obs->AddObserver(sSelf, "perm-changed", true); 69 obs->AddObserver(sSelf, "last-pb-context-exited", true); 70 obs->AddObserver(sSelf, "clear-origin-attributes-data", true); 71 obs->AddObserver(sSelf, "dom-storage:clear-origin-attributes-data", true); 72 obs->AddObserver(sSelf, "extension:purge-localStorage", true); 73 obs->AddObserver(sSelf, "browser:purge-sessionStorage", true); 74 obs->AddObserver(sSelf, "extension:purge-sessionStorage", true); 75 76 // Shutdown 77 obs->AddObserver(sSelf, "profile-after-change", true); 78 if (XRE_IsParentProcess()) { 79 obs->AddObserver(sSelf, "profile-before-change", true); 80 } 81 82 // Testing 83 #ifdef DOM_STORAGE_TESTS 84 Preferences::RegisterCallbackAndCall(TestingPrefChanged, kTestingPref); 85 #endif 86 87 return NS_OK; 88 } 89 90 // static 91 nsresult StorageObserver::Shutdown() { 92 AssertIsOnMainThread(); 93 94 if (!sSelf) { 95 return NS_ERROR_NOT_INITIALIZED; // Is this always an error? 96 } 97 98 sSelf->mSinks.Clear(); 99 100 NS_RELEASE(sSelf); 101 return NS_OK; 102 } 103 104 // static 105 void StorageObserver::TestingPrefChanged(const char* aPrefName, 106 void* aClosure) { 107 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 108 if (!obs || !sSelf) { 109 return; 110 } 111 112 if (Preferences::GetBool(kTestingPref)) { 113 obs->AddObserver(sSelf, "domstorage-test-flush-force", true); 114 if (XRE_IsParentProcess()) { 115 // Only to forward to child process. 116 obs->AddObserver(sSelf, "domstorage-test-flushed", true); 117 } 118 obs->AddObserver(sSelf, "domstorage-test-reload", true); 119 } else { 120 obs->RemoveObserver(sSelf, "domstorage-test-flush-force"); 121 if (XRE_IsParentProcess()) { 122 // Only to forward to child process. 123 obs->RemoveObserver(sSelf, "domstorage-test-flushed"); 124 } 125 obs->RemoveObserver(sSelf, "domstorage-test-reload"); 126 } 127 } 128 129 void StorageObserver::AddSink(StorageObserverSink* aObs) { 130 AssertIsOnMainThread(); 131 132 MOZ_ASSERT(sSelf); 133 134 mSinks.AppendElement(aObs); 135 } 136 137 void StorageObserver::RemoveSink(StorageObserverSink* aObs) { 138 AssertIsOnMainThread(); 139 140 MOZ_ASSERT(sSelf); 141 142 mSinks.RemoveElement(aObs); 143 } 144 145 void StorageObserver::Notify(const char* aTopic, 146 const nsAString& aOriginAttributesPattern, 147 const nsACString& aOriginScope) { 148 AssertIsOnMainThread(); 149 150 MOZ_ASSERT(sSelf); 151 152 for (auto sink : mSinks.ForwardRange()) { 153 sink->Observe(aTopic, aOriginAttributesPattern, aOriginScope); 154 } 155 } 156 157 void StorageObserver::NoteBackgroundThread(const uint32_t aPrivateBrowsingId, 158 nsIEventTarget* aBackgroundThread) { 159 MOZ_RELEASE_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount); 160 161 mBackgroundThread[aPrivateBrowsingId] = aBackgroundThread; 162 } 163 164 nsresult StorageObserver::GetOriginScope(const char16_t* aData, 165 nsACString& aOriginScope) { 166 nsresult rv; 167 168 NS_ConvertUTF16toUTF8 domain(aData); 169 170 nsAutoCString convertedDomain; 171 rv = NS_DomainToASCIIAllowAnyGlyphfulASCII(domain, convertedDomain); 172 if (NS_WARN_IF(NS_FAILED(rv))) { 173 return rv; 174 } 175 176 nsCString originScope; 177 rv = CreateReversedDomain(convertedDomain, originScope); 178 if (NS_WARN_IF(NS_FAILED(rv))) { 179 return rv; 180 } 181 182 aOriginScope = originScope; 183 return NS_OK; 184 } 185 186 NS_IMETHODIMP 187 StorageObserver::Observe(nsISupports* aSubject, const char* aTopic, 188 const char16_t* aData) { 189 if (NS_WARN_IF(!sSelf)) { // Shutdown took place 190 return NS_OK; 191 } 192 193 nsresult rv; 194 195 // Start the thread that opens the database. 196 if (!strcmp(aTopic, kStartupTopic)) { 197 MOZ_ASSERT(XRE_IsParentProcess()); 198 199 if (NextGenLocalStorageEnabled()) { 200 return NS_OK; 201 } 202 203 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 204 obs->RemoveObserver(this, kStartupTopic); 205 206 return NS_NewTimerWithObserver(getter_AddRefs(mDBThreadStartDelayTimer), 207 this, nsITimer::TYPE_ONE_SHOT, 208 kStartupDelay); 209 } 210 211 // Timer callback used to start the database a short timer after startup 212 if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) { 213 MOZ_ASSERT(XRE_IsParentProcess()); 214 MOZ_ASSERT(!NextGenLocalStorageEnabled()); 215 216 nsCOMPtr<nsITimer> timer = do_QueryInterface(aSubject); 217 if (!timer) { 218 return NS_ERROR_UNEXPECTED; 219 } 220 221 if (timer == mDBThreadStartDelayTimer) { 222 mDBThreadStartDelayTimer = nullptr; 223 224 for (const uint32_t id : {0, 1}) { 225 StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id); 226 if (NS_WARN_IF(!storageChild)) { 227 return NS_ERROR_FAILURE; 228 } 229 230 storageChild->SendStartup(); 231 } 232 } 233 234 return NS_OK; 235 } 236 237 // Clear everything, caches + database 238 if (!strcmp(aTopic, "cookie-changed")) { 239 nsCOMPtr<nsICookieNotification> notification = do_QueryInterface(aSubject); 240 NS_ENSURE_TRUE(notification, NS_ERROR_FAILURE); 241 242 if (notification->GetAction() != 243 nsICookieNotification::ALL_COOKIES_CLEARED) { 244 return NS_OK; 245 } 246 247 if (!NextGenLocalStorageEnabled()) { 248 for (const uint32_t id : {0, 1}) { 249 StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id); 250 if (NS_WARN_IF(!storageChild)) { 251 return NS_ERROR_FAILURE; 252 } 253 254 storageChild->AsyncClearAll(); 255 256 if (XRE_IsParentProcess()) { 257 storageChild->SendClearAll(); 258 } 259 } 260 } 261 262 Notify("cookie-cleared"); 263 264 return NS_OK; 265 } 266 267 // Clear from caches everything that has been stored 268 // while in session-only mode 269 if (!strcmp(aTopic, "perm-changed")) { 270 // Check for cookie permission change 271 nsCOMPtr<nsIPermission> perm(do_QueryInterface(aSubject)); 272 if (!perm) { 273 return NS_OK; 274 } 275 276 nsAutoCString type; 277 perm->GetType(type); 278 if (type != "cookie"_ns) { 279 return NS_OK; 280 } 281 282 uint32_t cap = 0; 283 perm->GetCapability(&cap); 284 if (!(cap & nsICookiePermission::ACCESS_SESSION) || 285 !u"deleted"_ns.Equals(nsDependentString(aData))) { 286 return NS_OK; 287 } 288 289 nsCOMPtr<nsIPrincipal> principal; 290 perm->GetPrincipal(getter_AddRefs(principal)); 291 if (!principal) { 292 return NS_OK; 293 } 294 295 nsAutoCString originSuffix; 296 BasePrincipal::Cast(principal)->OriginAttributesRef().CreateSuffix( 297 originSuffix); 298 299 nsAutoCString host; 300 principal->GetHost(host); 301 if (host.IsEmpty()) { 302 return NS_OK; 303 } 304 305 nsAutoCString originScope; 306 rv = CreateReversedDomain(host, originScope); 307 NS_ENSURE_SUCCESS(rv, rv); 308 309 Notify("session-only-cleared", NS_ConvertUTF8toUTF16(originSuffix), 310 originScope); 311 312 return NS_OK; 313 } 314 315 if (!strcmp(aTopic, "extension:purge-localStorage")) { 316 if (NextGenLocalStorageEnabled()) { 317 return NS_OK; 318 } 319 320 const char topic[] = "extension:purge-localStorage-caches"; 321 322 if (aData) { 323 nsCString originScope; 324 325 rv = GetOriginScope(aData, originScope); 326 if (NS_WARN_IF(NS_FAILED(rv))) { 327 return rv; 328 } 329 330 if (XRE_IsParentProcess()) { 331 for (const uint32_t id : {0, 1}) { 332 StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id); 333 if (NS_WARN_IF(!storageChild)) { 334 return NS_ERROR_FAILURE; 335 } 336 337 storageChild->SendClearMatchingOrigin(originScope); 338 } 339 } 340 341 Notify(topic, u""_ns, originScope); 342 } else { 343 for (const uint32_t id : {0, 1}) { 344 StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id); 345 if (NS_WARN_IF(!storageChild)) { 346 return NS_ERROR_FAILURE; 347 } 348 349 storageChild->AsyncClearAll(); 350 351 if (XRE_IsParentProcess()) { 352 storageChild->SendClearAll(); 353 } 354 } 355 356 Notify(topic); 357 } 358 359 return NS_OK; 360 } 361 362 if (!strcmp(aTopic, "browser:purge-sessionStorage") || 363 !strcmp(aTopic, "extension:purge-sessionStorage")) { 364 // The caller passed an nsIClearBySiteEntry object which consists of both 365 // site and pattern. 366 // If both are passed, aSubject takes precedence over aData. 367 if (aSubject) { 368 nsCOMPtr<nsIClearBySiteEntry> entry = do_QueryInterface(aSubject); 369 370 NS_ENSURE_TRUE(entry, NS_ERROR_FAILURE); 371 372 nsAutoCString schemelessSite; 373 rv = entry->GetSchemelessSite(schemelessSite); 374 NS_ENSURE_SUCCESS(rv, rv); 375 376 nsAutoString patternJSON; 377 rv = entry->GetPatternJSON(patternJSON); 378 NS_ENSURE_SUCCESS(rv, rv); 379 380 nsCString originScope; 381 if (!schemelessSite.IsEmpty()) { 382 rv = GetOriginScope(NS_ConvertUTF8toUTF16(schemelessSite).get(), 383 originScope); 384 NS_ENSURE_SUCCESS(rv, rv); 385 } 386 387 Notify(aTopic, patternJSON, originScope); 388 } else if (aData) { 389 // The caller passed only a schemeless site, origin or host. 390 nsCString originScope; 391 rv = GetOriginScope(aData, originScope); 392 if (NS_WARN_IF(NS_FAILED(rv))) { 393 return rv; 394 } 395 396 Notify(aTopic, u""_ns, originScope); 397 } else { 398 // The caller passed nothing, clear everything. 399 Notify(aTopic, u""_ns, ""_ns); 400 } 401 402 return NS_OK; 403 } 404 405 // Clear all private-browsing caches 406 if (!strcmp(aTopic, "last-pb-context-exited")) { 407 if (NextGenLocalStorageEnabled()) { 408 return NS_OK; 409 } 410 411 // We get the notification in both processes (parent and content), but the 412 // clearing of the in-memory database should be triggered from the parent 413 // process only to avoid creation of redundant clearing operations. 414 // Also, if we create a new StorageDBChild instance late during content 415 // process shutdown, then it might be leaked in debug builds because it 416 // could happen that there is no chance to properly destroy it. 417 if (XRE_IsParentProcess()) { 418 // This doesn't use a loop with privateBrowsingId 0 and 1, since we only 419 // need to clear the in-memory database which is represented by 420 // privateBrowsingId 1. 421 static const uint32_t id = 1; 422 423 StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id); 424 if (NS_WARN_IF(!storageChild)) { 425 return NS_ERROR_FAILURE; 426 } 427 428 OriginAttributesPattern pattern; 429 if (!pattern.Init(kPrivateBrowsingPattern)) { 430 NS_ERROR("Cannot parse origin attributes pattern"); 431 return NS_ERROR_FAILURE; 432 } 433 434 storageChild->SendClearMatchingOriginAttributes(pattern); 435 } 436 437 Notify("private-browsing-data-cleared", kPrivateBrowsingPattern); 438 439 return NS_OK; 440 } 441 442 // Clear data of the origins whose prefixes will match the suffix. 443 if (!strcmp(aTopic, "clear-origin-attributes-data") || 444 !strcmp(aTopic, "dom-storage:clear-origin-attributes-data")) { 445 MOZ_ASSERT(XRE_IsParentProcess()); 446 447 OriginAttributesPattern pattern; 448 if (!pattern.Init(nsDependentString(aData))) { 449 NS_ERROR("Cannot parse origin attributes pattern"); 450 return NS_ERROR_FAILURE; 451 } 452 453 if (NextGenLocalStorageEnabled()) { 454 Notify("session-storage:clear-origin-attributes-data", 455 nsDependentString(aData)); 456 return NS_OK; 457 } 458 459 for (const uint32_t id : {0, 1}) { 460 StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id); 461 if (NS_WARN_IF(!storageChild)) { 462 return NS_ERROR_FAILURE; 463 } 464 465 storageChild->SendClearMatchingOriginAttributes(pattern); 466 } 467 468 Notify(aTopic, nsDependentString(aData)); 469 470 return NS_OK; 471 } 472 473 if (!strcmp(aTopic, "profile-after-change")) { 474 Notify("profile-change"); 475 476 return NS_OK; 477 } 478 479 if (!strcmp(aTopic, "profile-before-change")) { 480 MOZ_ASSERT(XRE_IsParentProcess()); 481 482 if (NextGenLocalStorageEnabled()) { 483 return NS_OK; 484 } 485 486 for (const uint32_t id : {0, 1}) { 487 if (mBackgroundThread[id]) { 488 bool done = false; 489 490 RefPtr<StorageDBThread::ShutdownRunnable> shutdownRunnable = 491 new StorageDBThread::ShutdownRunnable(id, done); 492 MOZ_ALWAYS_SUCCEEDS(mBackgroundThread[id]->Dispatch( 493 shutdownRunnable, NS_DISPATCH_NORMAL)); 494 495 MOZ_ALWAYS_TRUE(SpinEventLoopUntil( 496 "StorageObserver::Observe profile-before-change"_ns, 497 [&]() { return done; })); 498 499 mBackgroundThread[id] = nullptr; 500 } 501 } 502 503 return NS_OK; 504 } 505 506 #ifdef DOM_STORAGE_TESTS 507 if (!strcmp(aTopic, "domstorage-test-flush-force")) { 508 if (NextGenLocalStorageEnabled()) { 509 return NS_OK; 510 } 511 512 for (const uint32_t id : {0, 1}) { 513 StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id); 514 if (NS_WARN_IF(!storageChild)) { 515 return NS_ERROR_FAILURE; 516 } 517 518 storageChild->SendAsyncFlush(); 519 } 520 521 return NS_OK; 522 } 523 524 if (!strcmp(aTopic, "domstorage-test-flushed")) { 525 if (NextGenLocalStorageEnabled()) { 526 return NS_OK; 527 } 528 529 // Only used to propagate to IPC children 530 Notify("test-flushed"); 531 532 return NS_OK; 533 } 534 535 if (!strcmp(aTopic, "domstorage-test-reload")) { 536 if (NextGenLocalStorageEnabled()) { 537 return NS_OK; 538 } 539 540 Notify("test-reload"); 541 542 return NS_OK; 543 } 544 #endif 545 546 NS_ERROR("Unexpected topic"); 547 return NS_ERROR_UNEXPECTED; 548 } 549 550 NS_IMETHODIMP 551 StorageObserver::GetName(nsACString& aName) { 552 aName.AssignLiteral("StorageObserver"); 553 return NS_OK; 554 } 555 556 } // namespace mozilla::dom