LSSnapshot.cpp (28644B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=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 file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/LSSnapshot.h" 8 9 // Local includes 10 #include "ActorsChild.h" 11 #include "LSDatabase.h" 12 #include "LSWriteOptimizer.h" 13 #include "LSWriteOptimizerImpl.h" 14 #include "LocalStorageCommon.h" 15 16 // Global includes 17 #include <cstdint> 18 #include <new> 19 #include <utility> 20 21 #include "ErrorList.h" 22 #include "mozilla/DebugOnly.h" 23 #include "mozilla/GeckoTrace.h" 24 #include "mozilla/Maybe.h" 25 #include "mozilla/Preferences.h" 26 #include "mozilla/RefPtr.h" 27 #include "mozilla/ScopeExit.h" 28 #include "mozilla/StaticPrefs_dom.h" 29 #include "mozilla/UniquePtr.h" 30 #include "mozilla/dom/BindingDeclarations.h" 31 #include "mozilla/dom/LSValue.h" 32 #include "mozilla/dom/PBackgroundLSDatabase.h" 33 #include "mozilla/dom/PBackgroundLSSharedTypes.h" 34 #include "mozilla/dom/PBackgroundLSSnapshot.h" 35 #include "mozilla/dom/quota/QuotaCommon.h" 36 #include "mozilla/dom/quota/ResultExtensions.h" 37 #include "mozilla/dom/quota/ScopedLogExtraInfo.h" 38 #include "nsBaseHashtable.h" 39 #include "nsCOMPtr.h" 40 #include "nsContentUtils.h" 41 #include "nsDebug.h" 42 #include "nsError.h" 43 #include "nsITimer.h" 44 #include "nsString.h" 45 #include "nsStringFlags.h" 46 #include "nsStringFwd.h" 47 #include "nsTArray.h" 48 #include "nsTStringRepr.h" 49 #include "nscore.h" 50 51 namespace mozilla::dom { 52 53 /** 54 * Coalescing manipulation queue used by `LSSnapshot`. Used by `LSSnapshot` to 55 * buffer and coalesce manipulations before they are sent to the parent process, 56 * when a Snapshot Checkpoints. (This can only be done when there are no 57 * observers for other content processes.) 58 */ 59 class SnapshotWriteOptimizer final : public LSWriteOptimizer<LSValue> { 60 public: 61 void Enumerate(nsTArray<LSWriteInfo>& aWriteInfos); 62 }; 63 64 void SnapshotWriteOptimizer::Enumerate(nsTArray<LSWriteInfo>& aWriteInfos) { 65 AssertIsOnOwningThread(); 66 67 // The mWriteInfos hash table contains all write infos, but it keeps them in 68 // an arbitrary order, which means write infos need to be sorted before being 69 // processed. 70 71 nsTArray<NotNull<WriteInfo*>> writeInfos; 72 GetSortedWriteInfos(writeInfos); 73 74 for (WriteInfo* writeInfo : writeInfos) { 75 switch (writeInfo->GetType()) { 76 case WriteInfo::InsertItem: { 77 auto insertItemInfo = static_cast<InsertItemInfo*>(writeInfo); 78 79 LSSetItemInfo setItemInfo; 80 setItemInfo.key() = insertItemInfo->GetKey(); 81 setItemInfo.value() = insertItemInfo->GetValue(); 82 83 aWriteInfos.AppendElement(std::move(setItemInfo)); 84 85 break; 86 } 87 88 case WriteInfo::UpdateItem: { 89 auto updateItemInfo = static_cast<UpdateItemInfo*>(writeInfo); 90 91 if (updateItemInfo->UpdateWithMove()) { 92 // See the comment in LSWriteOptimizer::InsertItem for more details 93 // about the UpdateWithMove flag. 94 95 LSRemoveItemInfo removeItemInfo; 96 removeItemInfo.key() = updateItemInfo->GetKey(); 97 98 aWriteInfos.AppendElement(std::move(removeItemInfo)); 99 } 100 101 LSSetItemInfo setItemInfo; 102 setItemInfo.key() = updateItemInfo->GetKey(); 103 setItemInfo.value() = updateItemInfo->GetValue(); 104 105 aWriteInfos.AppendElement(std::move(setItemInfo)); 106 107 break; 108 } 109 110 case WriteInfo::DeleteItem: { 111 auto deleteItemInfo = static_cast<DeleteItemInfo*>(writeInfo); 112 113 LSRemoveItemInfo removeItemInfo; 114 removeItemInfo.key() = deleteItemInfo->GetKey(); 115 116 aWriteInfos.AppendElement(std::move(removeItemInfo)); 117 118 break; 119 } 120 121 case WriteInfo::Truncate: { 122 LSClearInfo clearInfo; 123 124 aWriteInfos.AppendElement(std::move(clearInfo)); 125 126 break; 127 } 128 129 default: 130 MOZ_CRASH("Bad type!"); 131 } 132 } 133 } 134 135 LSSnapshot::LSSnapshot(LSDatabase* aDatabase) 136 : mDatabase(aDatabase), 137 mActor(nullptr), 138 mInitLength(0), 139 mLength(0), 140 mUsage(0), 141 mPeakUsage(0), 142 mLoadState(LoadState::Initial), 143 mHasOtherProcessDatabases(false), 144 mHasOtherProcessObservers(false), 145 mExplicit(false), 146 mHasPendingStableStateCallback(false), 147 mHasPendingIdleTimerCallback(false), 148 mDirty(false) 149 #ifdef DEBUG 150 , 151 mInitialized(false), 152 mSentFinish(false) 153 #endif 154 { 155 AssertIsOnOwningThread(); 156 } 157 158 LSSnapshot::~LSSnapshot() { 159 AssertIsOnOwningThread(); 160 MOZ_ASSERT(mDatabase); 161 MOZ_ASSERT(!mHasPendingStableStateCallback); 162 MOZ_ASSERT(!mHasPendingIdleTimerCallback); 163 MOZ_ASSERT_IF(mInitialized, mSentFinish); 164 165 if (mActor) { 166 mActor->SendDeleteMeInternal(); 167 MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!"); 168 } 169 } 170 171 void LSSnapshot::SetActor(LSSnapshotChild* aActor) { 172 AssertIsOnOwningThread(); 173 MOZ_ASSERT(aActor); 174 MOZ_ASSERT(!mActor); 175 176 mActor = aActor; 177 } 178 179 nsresult LSSnapshot::Init(const nsAString& aKey, 180 const LSSnapshotInitInfo& aInitInfo, bool aExplicit) { 181 AssertIsOnOwningThread(); 182 MOZ_ASSERT(!mSelfRef); 183 MOZ_ASSERT(mActor); 184 MOZ_ASSERT(mLoadState == LoadState::Initial); 185 MOZ_ASSERT(!mInitialized); 186 MOZ_ASSERT(!mSentFinish); 187 188 mSelfRef = this; 189 190 LoadState loadState = aInitInfo.loadState(); 191 192 const nsTArray<LSItemInfo>& itemInfos = aInitInfo.itemInfos(); 193 for (uint32_t i = 0; i < itemInfos.Length(); i++) { 194 const LSItemInfo& itemInfo = itemInfos[i]; 195 196 const LSValue& value = itemInfo.value(); 197 198 if (loadState != LoadState::AllOrderedItems && !value.IsVoid()) { 199 mLoadedItems.Insert(itemInfo.key()); 200 } 201 202 mValues.InsertOrUpdate(itemInfo.key(), value.AsString()); 203 } 204 205 if (loadState == LoadState::Partial) { 206 if (aInitInfo.addKeyToUnknownItems()) { 207 mUnknownItems.Insert(aKey); 208 } 209 mInitLength = aInitInfo.totalLength(); 210 mLength = mInitLength; 211 } else if (loadState == LoadState::AllOrderedKeys) { 212 mInitLength = aInitInfo.totalLength(); 213 } else { 214 MOZ_ASSERT(loadState == LoadState::AllOrderedItems); 215 } 216 217 mUsage = aInitInfo.usage(); 218 mPeakUsage = aInitInfo.peakUsage(); 219 220 mLoadState = aInitInfo.loadState(); 221 222 mHasOtherProcessDatabases = aInitInfo.hasOtherProcessDatabases(); 223 mHasOtherProcessObservers = aInitInfo.hasOtherProcessObservers(); 224 225 mExplicit = aExplicit; 226 227 #ifdef DEBUG 228 mInitialized = true; 229 #endif 230 231 if (mHasOtherProcessObservers) { 232 mWriteAndNotifyInfos = MakeUnique<nsTArray<LSWriteAndNotifyInfo>>(); 233 } else { 234 mWriteOptimizer = MakeUnique<SnapshotWriteOptimizer>(); 235 } 236 237 if (!mExplicit) { 238 mIdleTimer = NS_NewTimer(); 239 MOZ_ASSERT(mIdleTimer); 240 241 ScheduleStableStateCallback(); 242 } 243 244 return NS_OK; 245 } 246 247 nsresult LSSnapshot::GetLength(uint32_t* aResult) { 248 AssertIsOnOwningThread(); 249 MOZ_ASSERT(mActor); 250 MOZ_ASSERT(mInitialized); 251 MOZ_ASSERT(!mSentFinish); 252 253 MaybeScheduleStableStateCallback(); 254 255 if (mLoadState == LoadState::Partial) { 256 *aResult = mLength; 257 } else { 258 *aResult = mValues.Count(); 259 } 260 261 return NS_OK; 262 } 263 264 nsresult LSSnapshot::GetKey(uint32_t aIndex, nsAString& aResult) { 265 AssertIsOnOwningThread(); 266 MOZ_ASSERT(mActor); 267 MOZ_ASSERT(mInitialized); 268 MOZ_ASSERT(!mSentFinish); 269 270 MaybeScheduleStableStateCallback(); 271 272 nsresult rv = EnsureAllKeys(); 273 if (NS_WARN_IF(NS_FAILED(rv))) { 274 return rv; 275 } 276 277 aResult.SetIsVoid(true); 278 for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) { 279 if (aIndex == 0) { 280 aResult = iter.Key(); 281 return NS_OK; 282 } 283 aIndex--; 284 } 285 286 return NS_OK; 287 } 288 289 nsresult LSSnapshot::GetItem(const nsAString& aKey, nsAString& aResult) { 290 AssertIsOnOwningThread(); 291 MOZ_ASSERT(mActor); 292 MOZ_ASSERT(mInitialized); 293 MOZ_ASSERT(!mSentFinish); 294 295 MaybeScheduleStableStateCallback(); 296 297 nsString result; 298 nsresult rv = GetItemInternal(aKey, Optional<nsString>(), result); 299 if (NS_WARN_IF(NS_FAILED(rv))) { 300 return rv; 301 } 302 303 aResult = result; 304 return NS_OK; 305 } 306 307 nsresult LSSnapshot::GetKeys(nsTArray<nsString>& aKeys) { 308 AssertIsOnOwningThread(); 309 MOZ_ASSERT(mActor); 310 MOZ_ASSERT(mInitialized); 311 MOZ_ASSERT(!mSentFinish); 312 313 MaybeScheduleStableStateCallback(); 314 315 nsresult rv = EnsureAllKeys(); 316 if (NS_WARN_IF(NS_FAILED(rv))) { 317 return rv; 318 } 319 320 AppendToArray(aKeys, mValues.Keys()); 321 322 return NS_OK; 323 } 324 325 nsresult LSSnapshot::SetItem(const nsAString& aKey, const nsAString& aValue, 326 LSNotifyInfo& aNotifyInfo) { 327 GECKO_TRACE_SCOPE("dom::localstorage", "LSSnapshot::SetItem"); 328 329 AssertIsOnOwningThread(); 330 MOZ_ASSERT(mActor); 331 MOZ_ASSERT(mInitialized); 332 MOZ_ASSERT(!mSentFinish); 333 334 MaybeScheduleStableStateCallback(); 335 336 nsString oldValue; 337 nsresult rv = 338 GetItemInternal(aKey, Optional<nsString>(nsString(aValue)), oldValue); 339 if (NS_WARN_IF(NS_FAILED(rv))) { 340 return rv; 341 } 342 343 bool changed; 344 if (oldValue == aValue && oldValue.IsVoid() == aValue.IsVoid()) { 345 changed = false; 346 } else { 347 changed = true; 348 349 auto autoRevertValue = MakeScopeExit([&] { 350 if (oldValue.IsVoid()) { 351 mValues.Remove(aKey); 352 } else { 353 mValues.InsertOrUpdate(aKey, oldValue); 354 } 355 }); 356 357 // Anything that can fail must be done early before we start modifying the 358 // state. 359 360 Maybe<LSValue> oldValueFromString; 361 if (mHasOtherProcessObservers) { 362 oldValueFromString.emplace(); 363 if (NS_WARN_IF(!oldValueFromString->InitFromString(oldValue))) { 364 return NS_ERROR_FAILURE; 365 } 366 } 367 368 LSValue valueFromString; 369 if (NS_WARN_IF(!valueFromString.InitFromString(aValue))) { 370 return NS_ERROR_FAILURE; 371 } 372 373 int64_t delta = static_cast<int64_t>(aValue.Length()) - 374 static_cast<int64_t>(oldValue.Length()); 375 376 if (oldValue.IsVoid()) { 377 delta += static_cast<int64_t>(aKey.Length()); 378 } 379 380 { 381 quota::ScopedLogExtraInfo scope{ 382 quota::ScopedLogExtraInfo::kTagContextTainted, 383 "dom::localstorage::LSSnapshot::SetItem::UpdateUsage"_ns}; 384 GECKO_TRACE_SCOPE("dom::localstorage", 385 "LSSnapshot::SetItem::UpdateUsage"); 386 QM_TRY(MOZ_TO_RESULT(UpdateUsage(delta)), QM_PROPAGATE, QM_NO_CLEANUP, 387 ([]() { 388 static uint32_t counter = 0u; 389 // To not flood the product maintenance diagnostics, 390 // this predicate qualifies an error by returning true 391 // only when counter and 1 + counter bits are like 392 // 0111... and 1000... , i.e. when counter is one less 393 // than a power of two. 394 const bool result = 0u == (counter & (1u + counter)); 395 ++counter; 396 return result; 397 })); 398 } 399 400 if (oldValue.IsVoid() && mLoadState == LoadState::Partial) { 401 mLength++; 402 } 403 404 if (mHasOtherProcessObservers) { 405 MOZ_ASSERT(mWriteAndNotifyInfos); 406 MOZ_ASSERT(oldValueFromString.isSome()); 407 408 LSSetItemAndNotifyInfo setItemAndNotifyInfo; 409 setItemAndNotifyInfo.key() = aKey; 410 setItemAndNotifyInfo.oldValue() = oldValueFromString.value(); 411 setItemAndNotifyInfo.value() = valueFromString; 412 413 mWriteAndNotifyInfos->AppendElement(std::move(setItemAndNotifyInfo)); 414 } else { 415 MOZ_ASSERT(mWriteOptimizer); 416 417 if (oldValue.IsVoid()) { 418 mWriteOptimizer->InsertItem(aKey, valueFromString); 419 } else { 420 mWriteOptimizer->UpdateItem(aKey, valueFromString); 421 } 422 } 423 424 autoRevertValue.release(); 425 } 426 427 aNotifyInfo.changed() = changed; 428 aNotifyInfo.oldValue() = oldValue; 429 430 return NS_OK; 431 } 432 433 nsresult LSSnapshot::RemoveItem(const nsAString& aKey, 434 LSNotifyInfo& aNotifyInfo) { 435 AssertIsOnOwningThread(); 436 MOZ_ASSERT(mActor); 437 MOZ_ASSERT(mInitialized); 438 MOZ_ASSERT(!mSentFinish); 439 440 MaybeScheduleStableStateCallback(); 441 442 nsString oldValue; 443 nsresult rv = 444 GetItemInternal(aKey, Optional<nsString>(VoidString()), oldValue); 445 if (NS_WARN_IF(NS_FAILED(rv))) { 446 return rv; 447 } 448 449 bool changed; 450 if (oldValue.IsVoid()) { 451 changed = false; 452 } else { 453 changed = true; 454 455 auto autoRevertValue = MakeScopeExit([&] { 456 MOZ_ASSERT(!oldValue.IsVoid()); 457 mValues.InsertOrUpdate(aKey, oldValue); 458 }); 459 460 // Anything that can fail must be done early before we start modifying the 461 // state. 462 463 Maybe<LSValue> oldValueFromString; 464 if (mHasOtherProcessObservers) { 465 oldValueFromString.emplace(); 466 if (NS_WARN_IF(!oldValueFromString->InitFromString(oldValue))) { 467 return NS_ERROR_FAILURE; 468 } 469 } 470 471 int64_t delta = -(static_cast<int64_t>(aKey.Length()) + 472 static_cast<int64_t>(oldValue.Length())); 473 474 DebugOnly<nsresult> rv = UpdateUsage(delta); 475 MOZ_ASSERT(NS_SUCCEEDED(rv)); 476 477 if (mLoadState == LoadState::Partial) { 478 mLength--; 479 } 480 481 if (mHasOtherProcessObservers) { 482 MOZ_ASSERT(mWriteAndNotifyInfos); 483 MOZ_ASSERT(oldValueFromString.isSome()); 484 485 LSRemoveItemAndNotifyInfo removeItemAndNotifyInfo; 486 removeItemAndNotifyInfo.key() = aKey; 487 removeItemAndNotifyInfo.oldValue() = oldValueFromString.value(); 488 489 mWriteAndNotifyInfos->AppendElement(std::move(removeItemAndNotifyInfo)); 490 } else { 491 MOZ_ASSERT(mWriteOptimizer); 492 493 mWriteOptimizer->DeleteItem(aKey); 494 } 495 496 autoRevertValue.release(); 497 } 498 499 aNotifyInfo.changed() = changed; 500 aNotifyInfo.oldValue() = oldValue; 501 502 return NS_OK; 503 } 504 505 nsresult LSSnapshot::Clear(LSNotifyInfo& aNotifyInfo) { 506 AssertIsOnOwningThread(); 507 MOZ_ASSERT(mActor); 508 MOZ_ASSERT(mInitialized); 509 MOZ_ASSERT(!mSentFinish); 510 511 MaybeScheduleStableStateCallback(); 512 513 uint32_t length; 514 if (mLoadState == LoadState::Partial) { 515 length = mLength; 516 MOZ_ASSERT(length); 517 518 MOZ_ALWAYS_TRUE(mActor->SendLoaded()); 519 520 mLoadedItems.Clear(); 521 mUnknownItems.Clear(); 522 mLength = 0; 523 mLoadState = LoadState::AllOrderedItems; 524 } else { 525 length = mValues.Count(); 526 } 527 528 bool changed; 529 if (!length) { 530 changed = false; 531 } else { 532 changed = true; 533 534 int64_t delta = 0; 535 for (const auto& entry : mValues) { 536 const nsAString& key = entry.GetKey(); 537 const nsString& value = entry.GetData(); 538 539 delta += -static_cast<int64_t>(key.Length()) - 540 static_cast<int64_t>(value.Length()); 541 } 542 543 DebugOnly<nsresult> rv = UpdateUsage(delta); 544 MOZ_ASSERT(NS_SUCCEEDED(rv)); 545 546 mValues.Clear(); 547 548 if (mHasOtherProcessObservers) { 549 MOZ_ASSERT(mWriteAndNotifyInfos); 550 551 LSClearInfo clearInfo; 552 553 mWriteAndNotifyInfos->AppendElement(std::move(clearInfo)); 554 } else { 555 MOZ_ASSERT(mWriteOptimizer); 556 557 mWriteOptimizer->Truncate(); 558 } 559 } 560 561 aNotifyInfo.changed() = changed; 562 563 return NS_OK; 564 } 565 566 void LSSnapshot::MarkDirty() { 567 AssertIsOnOwningThread(); 568 MOZ_ASSERT(mActor); 569 MOZ_ASSERT(mInitialized); 570 MOZ_ASSERT(!mSentFinish); 571 572 if (mDirty) { 573 return; 574 } 575 576 mDirty = true; 577 578 if (!mExplicit && !mHasPendingStableStateCallback) { 579 CancelIdleTimer(); 580 581 MOZ_ALWAYS_SUCCEEDS(Checkpoint()); 582 583 MOZ_ALWAYS_SUCCEEDS(Finish()); 584 } else { 585 MOZ_ASSERT(!mHasPendingIdleTimerCallback); 586 } 587 } 588 589 nsresult LSSnapshot::ExplicitCheckpoint() { 590 AssertIsOnOwningThread(); 591 MOZ_ASSERT(mActor); 592 MOZ_ASSERT(mExplicit); 593 MOZ_ASSERT(!mHasPendingStableStateCallback); 594 MOZ_ASSERT(!mHasPendingIdleTimerCallback); 595 MOZ_ASSERT(mInitialized); 596 MOZ_ASSERT(!mSentFinish); 597 598 nsresult rv = Checkpoint(/* aSync */ true); 599 if (NS_WARN_IF(NS_FAILED(rv))) { 600 return rv; 601 } 602 603 return NS_OK; 604 } 605 606 nsresult LSSnapshot::ExplicitEnd() { 607 AssertIsOnOwningThread(); 608 MOZ_ASSERT(mActor); 609 MOZ_ASSERT(mExplicit); 610 MOZ_ASSERT(!mHasPendingStableStateCallback); 611 MOZ_ASSERT(!mHasPendingIdleTimerCallback); 612 MOZ_ASSERT(mInitialized); 613 MOZ_ASSERT(!mSentFinish); 614 615 nsresult rv = Checkpoint(); 616 if (NS_WARN_IF(NS_FAILED(rv))) { 617 return rv; 618 } 619 620 RefPtr<LSSnapshot> kungFuDeathGrip = this; 621 622 rv = Finish(/* aSync */ true); 623 if (NS_WARN_IF(NS_FAILED(rv))) { 624 return rv; 625 } 626 627 return NS_OK; 628 } 629 630 int64_t LSSnapshot::GetUsage() const { 631 AssertIsOnOwningThread(); 632 MOZ_ASSERT(mActor); 633 MOZ_ASSERT(mInitialized); 634 MOZ_ASSERT(!mSentFinish); 635 636 return mUsage; 637 } 638 639 void LSSnapshot::ScheduleStableStateCallback() { 640 AssertIsOnOwningThread(); 641 MOZ_ASSERT(mIdleTimer); 642 MOZ_ASSERT(!mExplicit); 643 MOZ_ASSERT(!mHasPendingStableStateCallback); 644 645 CancelIdleTimer(); 646 647 nsCOMPtr<nsIRunnable> runnable = this; 648 nsContentUtils::RunInStableState(runnable.forget()); 649 650 mHasPendingStableStateCallback = true; 651 } 652 653 void LSSnapshot::MaybeScheduleStableStateCallback() { 654 AssertIsOnOwningThread(); 655 656 if (!mExplicit && !mHasPendingStableStateCallback) { 657 ScheduleStableStateCallback(); 658 } else { 659 MOZ_ASSERT(!mHasPendingIdleTimerCallback); 660 } 661 } 662 663 nsresult LSSnapshot::GetItemInternal(const nsAString& aKey, 664 const Optional<nsString>& aValue, 665 nsAString& aResult) { 666 AssertIsOnOwningThread(); 667 MOZ_ASSERT(mActor); 668 MOZ_ASSERT(mInitialized); 669 MOZ_ASSERT(!mSentFinish); 670 671 nsString result; 672 673 switch (mLoadState) { 674 case LoadState::Partial: { 675 if (mValues.Get(aKey, &result)) { 676 MOZ_ASSERT(!result.IsVoid()); 677 } else if (mLoadedItems.Contains(aKey) || mUnknownItems.Contains(aKey)) { 678 result.SetIsVoid(true); 679 } else { 680 LSValue value; 681 nsTArray<LSItemInfo> itemInfos; 682 if (NS_WARN_IF(!mActor->SendLoadValueAndMoreItems( 683 nsString(aKey), &value, &itemInfos))) { 684 return NS_ERROR_FAILURE; 685 } 686 687 result = value.AsString(); 688 689 if (result.IsVoid()) { 690 mUnknownItems.Insert(aKey); 691 } else { 692 mLoadedItems.Insert(aKey); 693 mValues.InsertOrUpdate(aKey, result); 694 695 // mLoadedItems.Count()==mInitLength is checked below. 696 } 697 698 for (uint32_t i = 0; i < itemInfos.Length(); i++) { 699 const LSItemInfo& itemInfo = itemInfos[i]; 700 701 mLoadedItems.Insert(itemInfo.key()); 702 mValues.InsertOrUpdate(itemInfo.key(), itemInfo.value().AsString()); 703 } 704 705 if (mLoadedItems.Count() == mInitLength) { 706 mLoadedItems.Clear(); 707 mUnknownItems.Clear(); 708 mLength = 0; 709 mLoadState = LoadState::AllUnorderedItems; 710 } 711 } 712 713 if (aValue.WasPassed()) { 714 const nsString& value = aValue.Value(); 715 if (!value.IsVoid()) { 716 mValues.InsertOrUpdate(aKey, value); 717 } else if (!result.IsVoid()) { 718 mValues.Remove(aKey); 719 } 720 } 721 722 break; 723 } 724 725 case LoadState::AllOrderedKeys: { 726 if (mValues.Get(aKey, &result)) { 727 if (result.IsVoid()) { 728 LSValue value; 729 nsTArray<LSItemInfo> itemInfos; 730 if (NS_WARN_IF(!mActor->SendLoadValueAndMoreItems( 731 nsString(aKey), &value, &itemInfos))) { 732 return NS_ERROR_FAILURE; 733 } 734 735 result = value.AsString(); 736 737 MOZ_ASSERT(!result.IsVoid()); 738 739 mLoadedItems.Insert(aKey); 740 mValues.InsertOrUpdate(aKey, result); 741 742 // mLoadedItems.Count()==mInitLength is checked below. 743 744 for (uint32_t i = 0; i < itemInfos.Length(); i++) { 745 const LSItemInfo& itemInfo = itemInfos[i]; 746 747 mLoadedItems.Insert(itemInfo.key()); 748 mValues.InsertOrUpdate(itemInfo.key(), itemInfo.value().AsString()); 749 } 750 751 if (mLoadedItems.Count() == mInitLength) { 752 mLoadedItems.Clear(); 753 MOZ_ASSERT(mLength == 0); 754 mLoadState = LoadState::AllOrderedItems; 755 } 756 } 757 } else { 758 result.SetIsVoid(true); 759 } 760 761 if (aValue.WasPassed()) { 762 const nsString& value = aValue.Value(); 763 if (!value.IsVoid()) { 764 mValues.InsertOrUpdate(aKey, value); 765 } else if (!result.IsVoid()) { 766 mValues.Remove(aKey); 767 } 768 } 769 770 break; 771 } 772 773 case LoadState::AllUnorderedItems: 774 case LoadState::AllOrderedItems: { 775 if (aValue.WasPassed()) { 776 const nsString& value = aValue.Value(); 777 if (!value.IsVoid()) { 778 mValues.WithEntryHandle(aKey, [&](auto&& entry) { 779 if (entry) { 780 result = std::exchange(entry.Data(), value); 781 } else { 782 result.SetIsVoid(true); 783 entry.Insert(value); 784 } 785 }); 786 } else { 787 if (auto entry = mValues.Lookup(aKey)) { 788 result = entry.Data(); 789 MOZ_ASSERT(!result.IsVoid()); 790 entry.Remove(); 791 } else { 792 result.SetIsVoid(true); 793 } 794 } 795 } else { 796 if (mValues.Get(aKey, &result)) { 797 MOZ_ASSERT(!result.IsVoid()); 798 } else { 799 result.SetIsVoid(true); 800 } 801 } 802 803 break; 804 } 805 806 default: 807 MOZ_CRASH("Bad state!"); 808 } 809 810 aResult = result; 811 return NS_OK; 812 } 813 814 nsresult LSSnapshot::EnsureAllKeys() { 815 AssertIsOnOwningThread(); 816 MOZ_ASSERT(mActor); 817 MOZ_ASSERT(mInitialized); 818 MOZ_ASSERT(!mSentFinish); 819 MOZ_ASSERT(mLoadState != LoadState::Initial); 820 821 if (mLoadState == LoadState::AllOrderedKeys || 822 mLoadState == LoadState::AllOrderedItems) { 823 return NS_OK; 824 } 825 826 nsTArray<nsString> keys; 827 if (NS_WARN_IF(!mActor->SendLoadKeys(&keys))) { 828 return NS_ERROR_FAILURE; 829 } 830 831 nsTHashMap<nsStringHashKey, nsString> newValues; 832 833 for (auto key : keys) { 834 newValues.InsertOrUpdate(key, VoidString()); 835 } 836 837 if (mHasOtherProcessObservers) { 838 MOZ_ASSERT(mWriteAndNotifyInfos); 839 840 if (!mWriteAndNotifyInfos->IsEmpty()) { 841 for (uint32_t index = 0; index < mWriteAndNotifyInfos->Length(); 842 index++) { 843 const LSWriteAndNotifyInfo& writeAndNotifyInfo = 844 mWriteAndNotifyInfos->ElementAt(index); 845 846 switch (writeAndNotifyInfo.type()) { 847 case LSWriteAndNotifyInfo::TLSSetItemAndNotifyInfo: { 848 newValues.InsertOrUpdate( 849 writeAndNotifyInfo.get_LSSetItemAndNotifyInfo().key(), 850 VoidString()); 851 break; 852 } 853 case LSWriteAndNotifyInfo::TLSRemoveItemAndNotifyInfo: { 854 newValues.Remove( 855 writeAndNotifyInfo.get_LSRemoveItemAndNotifyInfo().key()); 856 break; 857 } 858 case LSWriteAndNotifyInfo::TLSClearInfo: { 859 newValues.Clear(); 860 break; 861 } 862 863 default: 864 MOZ_CRASH("Should never get here!"); 865 } 866 } 867 } 868 } else { 869 MOZ_ASSERT(mWriteOptimizer); 870 871 if (mWriteOptimizer->HasWrites()) { 872 nsTArray<LSWriteInfo> writeInfos; 873 mWriteOptimizer->Enumerate(writeInfos); 874 875 MOZ_ASSERT(!writeInfos.IsEmpty()); 876 877 for (uint32_t index = 0; index < writeInfos.Length(); index++) { 878 const LSWriteInfo& writeInfo = writeInfos[index]; 879 880 switch (writeInfo.type()) { 881 case LSWriteInfo::TLSSetItemInfo: { 882 newValues.InsertOrUpdate(writeInfo.get_LSSetItemInfo().key(), 883 VoidString()); 884 break; 885 } 886 case LSWriteInfo::TLSRemoveItemInfo: { 887 newValues.Remove(writeInfo.get_LSRemoveItemInfo().key()); 888 break; 889 } 890 case LSWriteInfo::TLSClearInfo: { 891 newValues.Clear(); 892 break; 893 } 894 895 default: 896 MOZ_CRASH("Should never get here!"); 897 } 898 } 899 } 900 } 901 902 MOZ_ASSERT_IF(mLoadState == LoadState::AllUnorderedItems, 903 newValues.Count() == mValues.Count()); 904 905 for (auto iter = newValues.Iter(); !iter.Done(); iter.Next()) { 906 nsString value; 907 if (mValues.Get(iter.Key(), &value)) { 908 iter.Data() = value; 909 } 910 } 911 912 mValues.SwapElements(newValues); 913 914 if (mLoadState == LoadState::Partial) { 915 mUnknownItems.Clear(); 916 mLength = 0; 917 mLoadState = LoadState::AllOrderedKeys; 918 } else { 919 MOZ_ASSERT(mLoadState == LoadState::AllUnorderedItems); 920 921 MOZ_ASSERT(mUnknownItems.Count() == 0); 922 MOZ_ASSERT(mLength == 0); 923 mLoadState = LoadState::AllOrderedItems; 924 } 925 926 return NS_OK; 927 } 928 929 nsresult LSSnapshot::UpdateUsage(int64_t aDelta) { 930 AssertIsOnOwningThread(); 931 MOZ_ASSERT(mDatabase); 932 MOZ_ASSERT(mActor); 933 MOZ_ASSERT(mPeakUsage >= mUsage); 934 MOZ_ASSERT(mInitialized); 935 MOZ_ASSERT(!mSentFinish); 936 937 int64_t newUsage = mUsage + aDelta; 938 if (newUsage > mPeakUsage) { 939 const int64_t minSize = newUsage - mPeakUsage; 940 941 int64_t size; 942 if (NS_WARN_IF(!mActor->SendIncreasePeakUsage(minSize, &size))) { 943 return NS_ERROR_FAILURE; 944 } 945 946 MOZ_ASSERT(size >= 0); 947 948 if (size == 0) { 949 return NS_ERROR_FILE_NO_DEVICE_SPACE; 950 } 951 952 mPeakUsage += size; 953 } 954 955 mUsage = newUsage; 956 return NS_OK; 957 } 958 959 nsresult LSSnapshot::Checkpoint(bool aSync) { 960 AssertIsOnOwningThread(); 961 MOZ_ASSERT(mActor); 962 MOZ_ASSERT(mInitialized); 963 MOZ_ASSERT(!mSentFinish); 964 965 if (mHasOtherProcessObservers) { 966 MOZ_ASSERT(mWriteAndNotifyInfos); 967 968 if (!mWriteAndNotifyInfos->IsEmpty()) { 969 if (aSync) { 970 MOZ_ALWAYS_TRUE( 971 mActor->SendSyncCheckpointAndNotify(*mWriteAndNotifyInfos)); 972 } else { 973 MOZ_ALWAYS_TRUE( 974 mActor->SendAsyncCheckpointAndNotify(*mWriteAndNotifyInfos)); 975 } 976 977 mWriteAndNotifyInfos->Clear(); 978 } 979 } else { 980 MOZ_ASSERT(mWriteOptimizer); 981 982 if (mWriteOptimizer->HasWrites()) { 983 nsTArray<LSWriteInfo> writeInfos; 984 mWriteOptimizer->Enumerate(writeInfos); 985 986 MOZ_ASSERT(!writeInfos.IsEmpty()); 987 988 if (aSync) { 989 MOZ_ALWAYS_TRUE(mActor->SendSyncCheckpoint(writeInfos)); 990 } else { 991 MOZ_ALWAYS_TRUE(mActor->SendAsyncCheckpoint(writeInfos)); 992 } 993 994 mWriteOptimizer->Reset(); 995 } 996 } 997 998 return NS_OK; 999 } 1000 1001 nsresult LSSnapshot::Finish(bool aSync) { 1002 AssertIsOnOwningThread(); 1003 MOZ_ASSERT(mDatabase); 1004 MOZ_ASSERT(mActor); 1005 MOZ_ASSERT(mInitialized); 1006 MOZ_ASSERT(!mSentFinish); 1007 1008 if (aSync) { 1009 MOZ_ALWAYS_TRUE(mActor->SendSyncFinish()); 1010 } else { 1011 MOZ_ALWAYS_TRUE(mActor->SendAsyncFinish()); 1012 } 1013 1014 mDatabase->NoteFinishedSnapshot(this); 1015 1016 #ifdef DEBUG 1017 mSentFinish = true; 1018 #endif 1019 1020 // Clear the self reference added in Init method. 1021 MOZ_ASSERT(mSelfRef); 1022 mSelfRef = nullptr; 1023 1024 return NS_OK; 1025 } 1026 1027 void LSSnapshot::CancelIdleTimer() { 1028 AssertIsOnOwningThread(); 1029 MOZ_ASSERT(mIdleTimer); 1030 1031 if (mHasPendingIdleTimerCallback) { 1032 MOZ_ALWAYS_SUCCEEDS(mIdleTimer->Cancel()); 1033 mHasPendingIdleTimerCallback = false; 1034 } 1035 } 1036 1037 // static 1038 void LSSnapshot::IdleTimerCallback(nsITimer* aTimer, void* aClosure) { 1039 MOZ_ASSERT(aTimer); 1040 1041 auto* self = static_cast<LSSnapshot*>(aClosure); 1042 MOZ_ASSERT(self); 1043 MOZ_ASSERT(self->mIdleTimer); 1044 MOZ_ASSERT(SameCOMIdentity(self->mIdleTimer, aTimer)); 1045 MOZ_ASSERT(!self->mHasPendingStableStateCallback); 1046 MOZ_ASSERT(self->mHasPendingIdleTimerCallback); 1047 1048 self->mHasPendingIdleTimerCallback = false; 1049 1050 MOZ_ALWAYS_SUCCEEDS(self->Finish()); 1051 } 1052 1053 NS_IMPL_ISUPPORTS(LSSnapshot, nsIRunnable) 1054 1055 NS_IMETHODIMP 1056 LSSnapshot::Run() { 1057 AssertIsOnOwningThread(); 1058 MOZ_ASSERT(!mExplicit); 1059 MOZ_ASSERT(mHasPendingStableStateCallback); 1060 MOZ_ASSERT(!mHasPendingIdleTimerCallback); 1061 1062 mHasPendingStableStateCallback = false; 1063 1064 MOZ_ALWAYS_SUCCEEDS(Checkpoint()); 1065 1066 // 1. The unused pre-incremented snapshot peak usage can't be undone when 1067 // there are other snapshots for the same database. We only add a pending 1068 // usage delta when a snapshot finishes and usage deltas are then applied 1069 // when the last database becomes inactive. 1070 // 2. If there's a snapshot with pre-incremented peak usage, the next 1071 // snapshot will use that as a base for its usage. 1072 // 3. When a task for given snapshot finishes, we try to reuse the snapshot 1073 // by only checkpointing the snapshot and delaying the finish by a timer. 1074 // 4. If two or more tabs for the same origin use localStorage periodically 1075 // at the same time the usage gradually grows until it hits the quota 1076 // limit. 1077 // 5. We prevent that from happening by finishing the snapshot immediatelly 1078 // if there are databases in other processess. 1079 1080 if (mDirty || mHasOtherProcessDatabases || 1081 !Preferences::GetBool("dom.storage.snapshot_reusing")) { 1082 MOZ_ALWAYS_SUCCEEDS(Finish()); 1083 } else { 1084 MOZ_ASSERT(mIdleTimer); 1085 1086 MOZ_ALWAYS_SUCCEEDS(mIdleTimer->InitWithNamedFuncCallback( 1087 IdleTimerCallback, this, 1088 StaticPrefs::dom_storage_snapshot_idle_timeout_ms(), 1089 nsITimer::TYPE_ONE_SHOT, "LSSnapshot::IdleTimerCallback"_ns)); 1090 1091 mHasPendingIdleTimerCallback = true; 1092 } 1093 1094 return NS_OK; 1095 } 1096 1097 } // namespace mozilla::dom