Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/storage/StorageDBThread.cpp
Line
Count
Source (jump to first uncovered line)
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 "StorageDBThread.h"
8
#include "StorageDBUpdater.h"
9
#include "StorageUtils.h"
10
#include "LocalStorageCache.h"
11
#include "LocalStorageManager.h"
12
13
#include "nsIEffectiveTLDService.h"
14
#include "nsDirectoryServiceUtils.h"
15
#include "nsAppDirectoryServiceDefs.h"
16
#include "nsThreadUtils.h"
17
#include "nsProxyRelease.h"
18
#include "mozStorageCID.h"
19
#include "mozStorageHelper.h"
20
#include "mozIStorageService.h"
21
#include "mozIStorageBindingParamsArray.h"
22
#include "mozIStorageBindingParams.h"
23
#include "mozIStorageValueArray.h"
24
#include "mozIStorageFunction.h"
25
#include "mozilla/BasePrincipal.h"
26
#include "mozilla/ipc/BackgroundParent.h"
27
#include "nsIObserverService.h"
28
#include "nsVariant.h"
29
#include "mozilla/IOInterposer.h"
30
#include "mozilla/Services.h"
31
#include "mozilla/Tokenizer.h"
32
#include "GeckoProfiler.h"
33
34
// How long we collect write oprerations
35
// before they are flushed to the database
36
// In milliseconds.
37
0
#define FLUSHING_INTERVAL_MS 5000
38
39
// Write Ahead Log's maximum size is 512KB
40
0
#define MAX_WAL_SIZE_BYTES 512 * 1024
41
42
// Current version of the database schema
43
#define CURRENT_SCHEMA_VERSION 2
44
45
namespace mozilla {
46
namespace dom {
47
48
using namespace StorageUtils;
49
50
namespace { // anon
51
52
StorageDBThread* sStorageThread = nullptr;
53
54
// False until we shut the storage thread down.
55
bool sStorageThreadDown = false;
56
57
// This is only a compatibility code for schema version 0.  Returns the 'scope'
58
// key in the schema version 0 format for the scope column.
59
nsCString
60
Scheme0Scope(LocalStorageCacheBridge* aCache)
61
0
{
62
0
  nsCString result;
63
0
64
0
  nsCString suffix = aCache->OriginSuffix();
65
0
66
0
  OriginAttributes oa;
67
0
  if (!suffix.IsEmpty()) {
68
0
    DebugOnly<bool> success = oa.PopulateFromSuffix(suffix);
69
0
    MOZ_ASSERT(success);
70
0
  }
71
0
72
0
  if (oa.mAppId != nsIScriptSecurityManager::NO_APP_ID ||
73
0
      oa.mInIsolatedMozBrowser) {
74
0
    result.AppendInt(oa.mAppId);
75
0
    result.Append(':');
76
0
    result.Append(oa.mInIsolatedMozBrowser ? 't' : 'f');
77
0
    result.Append(':');
78
0
  }
79
0
80
0
  // If there is more than just appid and/or inbrowser stored in origin
81
0
  // attributes, put it to the schema 0 scope as well.  We must do that
82
0
  // to keep the scope column unique (same resolution as schema 1 has
83
0
  // with originAttributes and originKey columns) so that switch between
84
0
  // schema 1 and 0 always works in both ways.
85
0
  nsAutoCString remaining;
86
0
  oa.mAppId = 0;
87
0
  oa.mInIsolatedMozBrowser = false;
88
0
  oa.CreateSuffix(remaining);
89
0
  if (!remaining.IsEmpty()) {
90
0
    MOZ_ASSERT(!suffix.IsEmpty());
91
0
92
0
    if (result.IsEmpty()) {
93
0
      // Must contain the old prefix, otherwise we won't search for the whole
94
0
      // origin attributes suffix.
95
0
      result.AppendLiteral("0:f:");
96
0
    }
97
0
98
0
    // Append the whole origin attributes suffix despite we have already stored
99
0
    // appid and inbrowser.  We are only looking for it when the scope string
100
0
    // starts with "$appid:$inbrowser:" (with whatever valid values).
101
0
    //
102
0
    // The OriginAttributes suffix is a string in a form like:
103
0
    // "^addonId=101&userContextId=5" and it's ensured it always starts with '^'
104
0
    // and never contains ':'.  See OriginAttributes::CreateSuffix.
105
0
    result.Append(suffix);
106
0
    result.Append(':');
107
0
  }
108
0
109
0
  result.Append(aCache->OriginNoSuffix());
110
0
111
0
  return result;
112
0
}
113
114
} // anon
115
116
// XXX Fix me!
117
#if 0
118
StorageDBBridge::StorageDBBridge()
119
{
120
}
121
#endif
122
123
class StorageDBThread::InitHelper final
124
  : public Runnable
125
{
126
  nsCOMPtr<nsIEventTarget> mOwningThread;
127
  mozilla::Mutex mMutex;
128
  mozilla::CondVar mCondVar;
129
  nsString mProfilePath;
130
  nsresult mMainThreadResultCode;
131
  bool mWaiting;
132
133
public:
134
  InitHelper()
135
    : Runnable("dom::StorageDBThread::InitHelper")
136
    , mOwningThread(GetCurrentThreadEventTarget())
137
    , mMutex("InitHelper::mMutex")
138
    , mCondVar(mMutex, "InitHelper::mCondVar")
139
    , mMainThreadResultCode(NS_OK)
140
    , mWaiting(true)
141
0
  { }
142
143
  // Because of the `sync Preload` IPC, we need to be able to synchronously
144
  // initialize, which includes consulting and initializing
145
  // some main-thread-only APIs. Bug 1386441 discusses improving this situation.
146
  nsresult
147
  SyncDispatchAndReturnProfilePath(nsAString& aProfilePath);
148
149
private:
150
0
  ~InitHelper() override = default;
151
152
  nsresult
153
  RunOnMainThread();
154
155
  NS_DECL_NSIRUNNABLE
156
};
157
158
class StorageDBThread::NoteBackgroundThreadRunnable final
159
  : public Runnable
160
{
161
  nsCOMPtr<nsIEventTarget> mOwningThread;
162
163
public:
164
  NoteBackgroundThreadRunnable()
165
    : Runnable("dom::StorageDBThread::NoteBackgroundThreadRunnable")
166
    , mOwningThread(GetCurrentThreadEventTarget())
167
0
  { }
168
169
private:
170
0
  ~NoteBackgroundThreadRunnable() override = default;
171
172
  NS_DECL_NSIRUNNABLE
173
};
174
175
StorageDBThread::StorageDBThread()
176
  : mThread(nullptr)
177
  , mThreadObserver(new ThreadObserver())
178
  , mStopIOThread(false)
179
  , mWALModeEnabled(false)
180
  , mDBReady(false)
181
  , mStatus(NS_OK)
182
  , mWorkerStatements(mWorkerConnection)
183
  , mReaderStatements(mReaderConnection)
184
  , mFlushImmediately(false)
185
  , mPriorityCounter(0)
186
0
{
187
0
}
188
189
// static
190
StorageDBThread*
191
StorageDBThread::Get()
192
0
{
193
0
  AssertIsOnBackgroundThread();
194
0
195
0
  return sStorageThread;
196
0
}
197
198
// static
199
StorageDBThread*
200
StorageDBThread::GetOrCreate(const nsString& aProfilePath)
201
0
{
202
0
  AssertIsOnBackgroundThread();
203
0
204
0
  if (sStorageThread || sStorageThreadDown) {
205
0
    // When sStorageThreadDown is at true, sStorageThread is null.
206
0
    // Checking sStorageThreadDown flag here prevents reinitialization of
207
0
    // the storage thread after shutdown.
208
0
    return sStorageThread;
209
0
  }
210
0
211
0
  nsAutoPtr<StorageDBThread> storageThread(new StorageDBThread());
212
0
213
0
  nsresult rv = storageThread->Init(aProfilePath);
214
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
215
0
    return nullptr;
216
0
  }
217
0
218
0
  sStorageThread = storageThread.forget();
219
0
220
0
  return sStorageThread;
221
0
}
222
223
// static
224
nsresult
225
StorageDBThread::GetProfilePath(nsString& aProfilePath)
226
0
{
227
0
  MOZ_ASSERT(XRE_IsParentProcess());
228
0
  MOZ_ASSERT(NS_IsMainThread());
229
0
230
0
  // Need to determine location on the main thread, since
231
0
  // NS_GetSpecialDirectory accesses the atom table that can
232
0
  // only be accessed on the main thread.
233
0
  nsCOMPtr<nsIFile> profileDir;
234
0
  nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
235
0
                                       getter_AddRefs(profileDir));
236
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
237
0
    return rv;
238
0
  }
239
0
240
0
  rv = profileDir->GetPath(aProfilePath);
241
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
242
0
    return rv;
243
0
  }
244
0
245
0
  // This service has to be started on the main thread currently.
246
0
  nsCOMPtr<mozIStorageService> ss =
247
0
    do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
248
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
249
0
    return rv;
250
0
  }
251
0
252
0
  return NS_OK;
253
0
}
254
255
nsresult
256
StorageDBThread::Init(const nsString& aProfilePath)
257
0
{
258
0
  AssertIsOnBackgroundThread();
259
0
260
0
  nsresult rv;
261
0
262
0
  nsString profilePath;
263
0
  if (aProfilePath.IsEmpty()) {
264
0
    RefPtr<InitHelper> helper = new InitHelper();
265
0
266
0
    rv = helper->SyncDispatchAndReturnProfilePath(profilePath);
267
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
268
0
      return rv;
269
0
    }
270
0
  } else {
271
0
    profilePath = aProfilePath;
272
0
  }
273
0
274
0
  mDatabaseFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
275
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
276
0
    return rv;
277
0
  }
278
0
279
0
  rv = mDatabaseFile->InitWithPath(profilePath);
280
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
281
0
    return rv;
282
0
  }
283
0
284
0
  rv = mDatabaseFile->Append(NS_LITERAL_STRING("webappsstore.sqlite"));
285
0
  NS_ENSURE_SUCCESS(rv, rv);
286
0
287
0
  // Need to keep the lock to avoid setting mThread later then
288
0
  // the thread body executes.
289
0
  MonitorAutoLock monitor(mThreadObserver->GetMonitor());
290
0
291
0
  mThread = PR_CreateThread(PR_USER_THREAD, &StorageDBThread::ThreadFunc, this,
292
0
                            PR_PRIORITY_LOW, PR_GLOBAL_THREAD,
293
0
                            PR_JOINABLE_THREAD, 262144);
294
0
  if (!mThread) {
295
0
    return NS_ERROR_OUT_OF_MEMORY;
296
0
  }
297
0
298
0
  RefPtr<NoteBackgroundThreadRunnable> runnable =
299
0
    new NoteBackgroundThreadRunnable();
300
0
  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
301
0
302
0
  return NS_OK;
303
0
}
304
305
nsresult
306
StorageDBThread::Shutdown()
307
0
{
308
0
  AssertIsOnBackgroundThread();
309
0
310
0
  sStorageThreadDown = true;
311
0
312
0
  if (!mThread) {
313
0
    return NS_ERROR_NOT_INITIALIZED;
314
0
  }
315
0
316
0
  Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_SHUTDOWN_DATABASE_MS> timer;
317
0
318
0
  {
319
0
    MonitorAutoLock monitor(mThreadObserver->GetMonitor());
320
0
321
0
    // After we stop, no other operations can be accepted
322
0
    mFlushImmediately = true;
323
0
    mStopIOThread = true;
324
0
    monitor.Notify();
325
0
  }
326
0
327
0
  PR_JoinThread(mThread);
328
0
  mThread = nullptr;
329
0
330
0
  return mStatus;
331
0
}
332
333
void
334
StorageDBThread::SyncPreload(LocalStorageCacheBridge* aCache, bool aForceSync)
335
0
{
336
0
  AUTO_PROFILER_LABEL("StorageDBThread::SyncPreload", OTHER);
337
0
  if (!aForceSync && aCache->LoadedCount()) {
338
0
    // Preload already started for this cache, just wait for it to finish.
339
0
    // LoadWait will exit after LoadDone on the cache has been called.
340
0
    SetHigherPriority();
341
0
    aCache->LoadWait();
342
0
    SetDefaultPriority();
343
0
    return;
344
0
  }
345
0
346
0
  // Bypass sync load when an update is pending in the queue to write, we would
347
0
  // get incosistent data in the cache.  Also don't allow sync main-thread
348
0
  // preload when DB open and init is still pending on the background thread.
349
0
  if (mDBReady && mWALModeEnabled) {
350
0
    bool pendingTasks;
351
0
    {
352
0
      MonitorAutoLock monitor(mThreadObserver->GetMonitor());
353
0
      pendingTasks = mPendingTasks.IsOriginUpdatePending(aCache->OriginSuffix(), aCache->OriginNoSuffix()) ||
354
0
                     mPendingTasks.IsOriginClearPending(aCache->OriginSuffix(), aCache->OriginNoSuffix());
355
0
    }
356
0
357
0
    if (!pendingTasks) {
358
0
      // WAL is enabled, thus do the load synchronously on the main thread.
359
0
      DBOperation preload(DBOperation::opPreload, aCache);
360
0
      preload.PerformAndFinalize(this);
361
0
      return;
362
0
    }
363
0
  }
364
0
365
0
  // Need to go asynchronously since WAL is not allowed or scheduled updates
366
0
  // need to be flushed first.
367
0
  // Schedule preload for this cache as the first operation.
368
0
  nsresult rv = InsertDBOp(new DBOperation(DBOperation::opPreloadUrgent, aCache));
369
0
370
0
  // LoadWait exits after LoadDone of the cache has been called.
371
0
  if (NS_SUCCEEDED(rv)) {
372
0
    aCache->LoadWait();
373
0
  }
374
0
}
375
376
void
377
StorageDBThread::AsyncFlush()
378
0
{
379
0
  MonitorAutoLock monitor(mThreadObserver->GetMonitor());
380
0
  mFlushImmediately = true;
381
0
  monitor.Notify();
382
0
}
383
384
bool
385
StorageDBThread::ShouldPreloadOrigin(const nsACString& aOrigin)
386
0
{
387
0
  MonitorAutoLock monitor(mThreadObserver->GetMonitor());
388
0
  return mOriginsHavingData.Contains(aOrigin);
389
0
}
390
391
void
392
StorageDBThread::GetOriginsHavingData(InfallibleTArray<nsCString>* aOrigins)
393
0
{
394
0
  MonitorAutoLock monitor(mThreadObserver->GetMonitor());
395
0
  for (auto iter = mOriginsHavingData.Iter(); !iter.Done(); iter.Next()) {
396
0
    aOrigins->AppendElement(iter.Get()->GetKey());
397
0
  }
398
0
}
399
400
nsresult
401
StorageDBThread::InsertDBOp(StorageDBThread::DBOperation* aOperation)
402
0
{
403
0
  MonitorAutoLock monitor(mThreadObserver->GetMonitor());
404
0
405
0
  // Sentinel to don't forget to delete the operation when we exit early.
406
0
  nsAutoPtr<StorageDBThread::DBOperation> opScope(aOperation);
407
0
408
0
  if (NS_FAILED(mStatus)) {
409
0
    MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
410
0
    aOperation->Finalize(mStatus);
411
0
    return mStatus;
412
0
  }
413
0
414
0
  if (mStopIOThread) {
415
0
    // Thread use after shutdown demanded.
416
0
    MOZ_ASSERT(false);
417
0
    return NS_ERROR_NOT_INITIALIZED;
418
0
  }
419
0
420
0
  switch (aOperation->Type()) {
421
0
  case DBOperation::opPreload:
422
0
  case DBOperation::opPreloadUrgent:
423
0
    if (mPendingTasks.IsOriginUpdatePending(aOperation->OriginSuffix(), aOperation->OriginNoSuffix())) {
424
0
      // If there is a pending update operation for the scope first do the flush
425
0
      // before we preload the cache.  This may happen in an extremely rare case
426
0
      // when a child process throws away its cache before flush on the parent
427
0
      // has finished.  If we would preloaded the cache as a priority operation
428
0
      // before the pending flush, we would have got an inconsistent cache
429
0
      // content.
430
0
      mFlushImmediately = true;
431
0
    } else if (mPendingTasks.IsOriginClearPending(aOperation->OriginSuffix(), aOperation->OriginNoSuffix())) {
432
0
      // The scope is scheduled to be cleared, so just quickly load as empty.
433
0
      // We need to do this to prevent load of the DB data before the scope has
434
0
      // actually been cleared from the database.  Preloads are processed
435
0
      // immediately before update and clear operations on the database that are
436
0
      // flushed periodically in batches.
437
0
      MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
438
0
      aOperation->Finalize(NS_OK);
439
0
      return NS_OK;
440
0
    }
441
0
    MOZ_FALLTHROUGH;
442
0
443
0
  case DBOperation::opGetUsage:
444
0
    if (aOperation->Type() == DBOperation::opPreloadUrgent) {
445
0
      SetHigherPriority(); // Dropped back after urgent preload execution
446
0
      mPreloads.InsertElementAt(0, aOperation);
447
0
    } else {
448
0
      mPreloads.AppendElement(aOperation);
449
0
    }
450
0
451
0
    // DB operation adopted, don't delete it.
452
0
    opScope.forget();
453
0
454
0
    // Immediately start executing this.
455
0
    monitor.Notify();
456
0
    break;
457
0
458
0
  default:
459
0
    // Update operations are first collected, coalesced and then flushed
460
0
    // after a short time.
461
0
    mPendingTasks.Add(aOperation);
462
0
463
0
    // DB operation adopted, don't delete it.
464
0
    opScope.forget();
465
0
466
0
    ScheduleFlush();
467
0
    break;
468
0
  }
469
0
470
0
  return NS_OK;
471
0
}
472
473
void
474
StorageDBThread::SetHigherPriority()
475
0
{
476
0
  ++mPriorityCounter;
477
0
  PR_SetThreadPriority(mThread, PR_PRIORITY_URGENT);
478
0
}
479
480
void
481
StorageDBThread::SetDefaultPriority()
482
0
{
483
0
  if (--mPriorityCounter <= 0) {
484
0
    PR_SetThreadPriority(mThread, PR_PRIORITY_LOW);
485
0
  }
486
0
}
487
488
void
489
StorageDBThread::ThreadFunc(void* aArg)
490
0
{
491
0
  AUTO_PROFILER_REGISTER_THREAD("localStorage DB");
492
0
  NS_SetCurrentThreadName("localStorage DB");
493
0
  mozilla::IOInterposer::RegisterCurrentThread();
494
0
495
0
  StorageDBThread* thread = static_cast<StorageDBThread*>(aArg);
496
0
  thread->ThreadFunc();
497
0
  mozilla::IOInterposer::UnregisterCurrentThread();
498
0
}
499
500
void
501
StorageDBThread::ThreadFunc()
502
0
{
503
0
  nsresult rv = InitDatabase();
504
0
505
0
  MonitorAutoLock lockMonitor(mThreadObserver->GetMonitor());
506
0
507
0
  if (NS_FAILED(rv)) {
508
0
    mStatus = rv;
509
0
    mStopIOThread = true;
510
0
    return;
511
0
  }
512
0
513
0
  // Create an nsIThread for the current PRThread, so we can observe runnables
514
0
  // dispatched to it.
515
0
  nsCOMPtr<nsIThread> thread = NS_GetCurrentThread();
516
0
  nsCOMPtr<nsIThreadInternal> threadInternal = do_QueryInterface(thread);
517
0
  MOZ_ASSERT(threadInternal); // Should always succeed.
518
0
  threadInternal->SetObserver(mThreadObserver);
519
0
520
0
  while (MOZ_LIKELY(!mStopIOThread || mPreloads.Length() ||
521
0
                    mPendingTasks.HasTasks() ||
522
0
                    mThreadObserver->HasPendingEvents())) {
523
0
    // Process xpcom events first.
524
0
    while (MOZ_UNLIKELY(mThreadObserver->HasPendingEvents())) {
525
0
      mThreadObserver->ClearPendingEvents();
526
0
      MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
527
0
      bool processedEvent;
528
0
      do {
529
0
        rv = thread->ProcessNextEvent(false, &processedEvent);
530
0
      } while (NS_SUCCEEDED(rv) && processedEvent);
531
0
    }
532
0
533
0
    TimeDuration timeUntilFlush = TimeUntilFlush();
534
0
    if (MOZ_UNLIKELY(timeUntilFlush.IsZero())) {
535
0
      // Flush time is up or flush has been forced, do it now.
536
0
      UnscheduleFlush();
537
0
      if (mPendingTasks.Prepare()) {
538
0
        {
539
0
          MonitorAutoUnlock unlockMonitor(mThreadObserver->GetMonitor());
540
0
          rv = mPendingTasks.Execute(this);
541
0
        }
542
0
543
0
        if (!mPendingTasks.Finalize(rv)) {
544
0
          mStatus = rv;
545
0
          NS_WARNING("localStorage DB access broken");
546
0
        }
547
0
      }
548
0
      NotifyFlushCompletion();
549
0
    } else if (MOZ_LIKELY(mPreloads.Length())) {
550
0
      nsAutoPtr<DBOperation> op(mPreloads[0]);
551
0
      mPreloads.RemoveElementAt(0);
552
0
      {
553
0
        MonitorAutoUnlock unlockMonitor(mThreadObserver->GetMonitor());
554
0
        op->PerformAndFinalize(this);
555
0
      }
556
0
557
0
      if (op->Type() == DBOperation::opPreloadUrgent) {
558
0
        SetDefaultPriority(); // urgent preload unscheduled
559
0
      }
560
0
    } else if (MOZ_UNLIKELY(!mStopIOThread)) {
561
0
      AUTO_PROFILER_LABEL("StorageDBThread::ThreadFunc::Wait", IDLE);
562
0
      lockMonitor.Wait(timeUntilFlush);
563
0
    }
564
0
  } // thread loop
565
0
566
0
  mStatus = ShutdownDatabase();
567
0
568
0
  if (threadInternal) {
569
0
    threadInternal->SetObserver(nullptr);
570
0
  }
571
0
}
572
573
574
NS_IMPL_ISUPPORTS(StorageDBThread::ThreadObserver, nsIThreadObserver)
575
576
NS_IMETHODIMP
577
StorageDBThread::ThreadObserver::OnDispatchedEvent()
578
0
{
579
0
  MonitorAutoLock lock(mMonitor);
580
0
  mHasPendingEvents = true;
581
0
  lock.Notify();
582
0
  return NS_OK;
583
0
}
584
585
NS_IMETHODIMP
586
StorageDBThread::ThreadObserver::OnProcessNextEvent(nsIThreadInternal* aThread,
587
                                                    bool mayWait)
588
0
{
589
0
  return NS_OK;
590
0
}
591
592
NS_IMETHODIMP
593
StorageDBThread::ThreadObserver::AfterProcessNextEvent(nsIThreadInternal* aThread,
594
                                                       bool eventWasProcessed)
595
0
{
596
0
  return NS_OK;
597
0
}
598
599
nsresult
600
StorageDBThread::OpenDatabaseConnection()
601
0
{
602
0
  nsresult rv;
603
0
604
0
  MOZ_ASSERT(!NS_IsMainThread());
605
0
606
0
  nsCOMPtr<mozIStorageService> service
607
0
      = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
608
0
  NS_ENSURE_SUCCESS(rv, rv);
609
0
610
0
  rv = service->OpenUnsharedDatabase(mDatabaseFile, getter_AddRefs(mWorkerConnection));
611
0
  if (rv == NS_ERROR_FILE_CORRUPTED) {
612
0
    // delete the db and try opening again
613
0
    rv = mDatabaseFile->Remove(false);
614
0
    NS_ENSURE_SUCCESS(rv, rv);
615
0
    rv = service->OpenUnsharedDatabase(mDatabaseFile, getter_AddRefs(mWorkerConnection));
616
0
  }
617
0
  NS_ENSURE_SUCCESS(rv, rv);
618
0
619
0
  return NS_OK;
620
0
}
621
622
nsresult
623
StorageDBThread::OpenAndUpdateDatabase()
624
0
{
625
0
  nsresult rv;
626
0
627
0
  // Here we are on the worker thread. This opens the worker connection.
628
0
  MOZ_ASSERT(!NS_IsMainThread());
629
0
630
0
  rv = OpenDatabaseConnection();
631
0
  NS_ENSURE_SUCCESS(rv, rv);
632
0
633
0
  rv = TryJournalMode();
634
0
  NS_ENSURE_SUCCESS(rv, rv);
635
0
636
0
  return NS_OK;
637
0
}
638
639
nsresult
640
StorageDBThread::InitDatabase()
641
0
{
642
0
  nsresult rv;
643
0
644
0
  // Here we are on the worker thread. This opens the worker connection.
645
0
  MOZ_ASSERT(!NS_IsMainThread());
646
0
647
0
  rv = OpenAndUpdateDatabase();
648
0
  NS_ENSURE_SUCCESS(rv, rv);
649
0
650
0
  rv = StorageDBUpdater::Update(mWorkerConnection);
651
0
  if (NS_FAILED(rv)) {
652
0
    // Update has failed, rather throw the database away and try
653
0
    // opening and setting it up again.
654
0
    rv = mWorkerConnection->Close();
655
0
    mWorkerConnection = nullptr;
656
0
    NS_ENSURE_SUCCESS(rv, rv);
657
0
658
0
    rv = mDatabaseFile->Remove(false);
659
0
    NS_ENSURE_SUCCESS(rv, rv);
660
0
661
0
    rv = OpenAndUpdateDatabase();
662
0
    NS_ENSURE_SUCCESS(rv, rv);
663
0
  }
664
0
665
0
  // Create a read-only clone
666
0
  (void)mWorkerConnection->Clone(true, getter_AddRefs(mReaderConnection));
667
0
  NS_ENSURE_TRUE(mReaderConnection, NS_ERROR_FAILURE);
668
0
669
0
  // Database open and all initiation operation are done.  Switching this flag
670
0
  // to true allow main thread to read directly from the database.  If we would
671
0
  // allow this sooner, we would have opened a window where main thread read
672
0
  // might operate on a totally broken and incosistent database.
673
0
  mDBReady = true;
674
0
675
0
  // List scopes having any stored data
676
0
  nsCOMPtr<mozIStorageStatement> stmt;
677
0
  // Note: result of this select must match StorageManager::CreateOrigin()
678
0
  rv = mWorkerConnection->CreateStatement(NS_LITERAL_CSTRING(
679
0
        "SELECT DISTINCT originAttributes || ':' || originKey FROM webappsstore2"),
680
0
        getter_AddRefs(stmt));
681
0
  NS_ENSURE_SUCCESS(rv, rv);
682
0
  mozStorageStatementScoper scope(stmt);
683
0
684
0
  bool exists;
685
0
  while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
686
0
    nsAutoCString foundOrigin;
687
0
    rv = stmt->GetUTF8String(0, foundOrigin);
688
0
    NS_ENSURE_SUCCESS(rv, rv);
689
0
690
0
    MonitorAutoLock monitor(mThreadObserver->GetMonitor());
691
0
    mOriginsHavingData.PutEntry(foundOrigin);
692
0
  }
693
0
694
0
  return NS_OK;
695
0
}
696
697
nsresult
698
StorageDBThread::SetJournalMode(bool aIsWal)
699
0
{
700
0
  nsresult rv;
701
0
702
0
  nsAutoCString stmtString(
703
0
    MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = ");
704
0
  if (aIsWal) {
705
0
    stmtString.AppendLiteral("wal");
706
0
  } else {
707
0
    stmtString.AppendLiteral("truncate");
708
0
  }
709
0
710
0
  nsCOMPtr<mozIStorageStatement> stmt;
711
0
  rv = mWorkerConnection->CreateStatement(stmtString, getter_AddRefs(stmt));
712
0
  NS_ENSURE_SUCCESS(rv, rv);
713
0
  mozStorageStatementScoper scope(stmt);
714
0
715
0
  bool hasResult = false;
716
0
  rv = stmt->ExecuteStep(&hasResult);
717
0
  NS_ENSURE_SUCCESS(rv, rv);
718
0
  if (!hasResult) {
719
0
    return NS_ERROR_FAILURE;
720
0
  }
721
0
722
0
  nsAutoCString journalMode;
723
0
  rv = stmt->GetUTF8String(0, journalMode);
724
0
  NS_ENSURE_SUCCESS(rv, rv);
725
0
  if ((aIsWal && !journalMode.EqualsLiteral("wal")) ||
726
0
      (!aIsWal && !journalMode.EqualsLiteral("truncate"))) {
727
0
    return NS_ERROR_FAILURE;
728
0
  }
729
0
730
0
  return NS_OK;
731
0
}
732
733
nsresult
734
StorageDBThread::TryJournalMode()
735
0
{
736
0
  nsresult rv;
737
0
738
0
  rv = SetJournalMode(true);
739
0
  if (NS_FAILED(rv)) {
740
0
    mWALModeEnabled = false;
741
0
742
0
    rv = SetJournalMode(false);
743
0
    NS_ENSURE_SUCCESS(rv, rv);
744
0
  } else {
745
0
    mWALModeEnabled = true;
746
0
747
0
    rv = ConfigureWALBehavior();
748
0
    NS_ENSURE_SUCCESS(rv, rv);
749
0
  }
750
0
751
0
  return NS_OK;
752
0
}
753
754
nsresult
755
StorageDBThread::ConfigureWALBehavior()
756
0
{
757
0
  // Get the DB's page size
758
0
  nsCOMPtr<mozIStorageStatement> stmt;
759
0
  nsresult rv = mWorkerConnection->CreateStatement(NS_LITERAL_CSTRING(
760
0
    MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size"
761
0
  ), getter_AddRefs(stmt));
762
0
  NS_ENSURE_SUCCESS(rv, rv);
763
0
764
0
  bool hasResult = false;
765
0
  rv = stmt->ExecuteStep(&hasResult);
766
0
  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FAILURE);
767
0
768
0
  int32_t pageSize = 0;
769
0
  rv = stmt->GetInt32(0, &pageSize);
770
0
  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && pageSize > 0, NS_ERROR_UNEXPECTED);
771
0
772
0
  // Set the threshold for auto-checkpointing the WAL.
773
0
  // We don't want giant logs slowing down reads & shutdown.
774
0
  int32_t thresholdInPages = static_cast<int32_t>(MAX_WAL_SIZE_BYTES / pageSize);
775
0
  nsAutoCString thresholdPragma("PRAGMA wal_autocheckpoint = ");
776
0
  thresholdPragma.AppendInt(thresholdInPages);
777
0
  rv = mWorkerConnection->ExecuteSimpleSQL(thresholdPragma);
778
0
  NS_ENSURE_SUCCESS(rv, rv);
779
0
780
0
  // Set the maximum WAL log size to reduce footprint on mobile (large empty
781
0
  // WAL files will be truncated)
782
0
  nsAutoCString journalSizePragma("PRAGMA journal_size_limit = ");
783
0
  // bug 600307: mak recommends setting this to 3 times the auto-checkpoint
784
0
  // threshold
785
0
  journalSizePragma.AppendInt(MAX_WAL_SIZE_BYTES * 3);
786
0
  rv = mWorkerConnection->ExecuteSimpleSQL(journalSizePragma);
787
0
  NS_ENSURE_SUCCESS(rv, rv);
788
0
789
0
  return NS_OK;
790
0
}
791
792
nsresult
793
StorageDBThread::ShutdownDatabase()
794
0
{
795
0
  // Has to be called on the worker thread.
796
0
  MOZ_ASSERT(!NS_IsMainThread());
797
0
798
0
  nsresult rv = mStatus;
799
0
800
0
  mDBReady = false;
801
0
802
0
  // Finalize the cached statements.
803
0
  mReaderStatements.FinalizeStatements();
804
0
  mWorkerStatements.FinalizeStatements();
805
0
806
0
  if (mReaderConnection) {
807
0
    // No need to sync access to mReaderConnection since the main thread
808
0
    // is right now joining this thread, unable to execute any events.
809
0
    mReaderConnection->Close();
810
0
    mReaderConnection = nullptr;
811
0
  }
812
0
813
0
  if (mWorkerConnection) {
814
0
    rv = mWorkerConnection->Close();
815
0
    mWorkerConnection = nullptr;
816
0
  }
817
0
818
0
  return rv;
819
0
}
820
821
void
822
StorageDBThread::ScheduleFlush()
823
0
{
824
0
  if (mDirtyEpoch) {
825
0
    return; // Already scheduled
826
0
  }
827
0
828
0
  // Must be non-zero to indicate we are scheduled
829
0
  mDirtyEpoch = TimeStamp::Now();
830
0
831
0
  // Wake the monitor from indefinite sleep...
832
0
  (mThreadObserver->GetMonitor()).Notify();
833
0
}
834
835
void
836
StorageDBThread::UnscheduleFlush()
837
0
{
838
0
  // We are just about to do the flush, drop flags
839
0
  mFlushImmediately = false;
840
0
  mDirtyEpoch = TimeStamp();
841
0
}
842
843
TimeDuration
844
StorageDBThread::TimeUntilFlush()
845
0
{
846
0
  if (mFlushImmediately) {
847
0
    return 0; // Do it now regardless the timeout.
848
0
  }
849
0
850
0
  if (!mDirtyEpoch) {
851
0
    return TimeDuration::Forever(); // No pending task...
852
0
  }
853
0
854
0
  TimeStamp now = TimeStamp::Now();
855
0
  TimeDuration age = now - mDirtyEpoch;
856
0
  static const TimeDuration kMaxAge = TimeDuration::FromMilliseconds(FLUSHING_INTERVAL_MS);
857
0
  if (age > kMaxAge) {
858
0
    return 0; // It is time.
859
0
  }
860
0
861
0
  return kMaxAge - age; // Time left. This is used to sleep the monitor.
862
0
}
863
864
void
865
StorageDBThread::NotifyFlushCompletion()
866
0
{
867
0
#ifdef DOM_STORAGE_TESTS
868
0
  if (!NS_IsMainThread()) {
869
0
    RefPtr<nsRunnableMethod<StorageDBThread, void, false>> event =
870
0
      NewNonOwningRunnableMethod("dom::StorageDBThread::NotifyFlushCompletion",
871
0
                                 this,
872
0
                                 &StorageDBThread::NotifyFlushCompletion);
873
0
    NS_DispatchToMainThread(event);
874
0
    return;
875
0
  }
876
0
877
0
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
878
0
  if (obs) {
879
0
    obs->NotifyObservers(nullptr, "domstorage-test-flushed", nullptr);
880
0
  }
881
0
#endif
882
0
}
883
884
// Helper SQL function classes
885
886
namespace {
887
888
class OriginAttrsPatternMatchSQLFunction final : public mozIStorageFunction
889
{
890
  NS_DECL_ISUPPORTS
891
  NS_DECL_MOZISTORAGEFUNCTION
892
893
  explicit OriginAttrsPatternMatchSQLFunction(OriginAttributesPattern const& aPattern)
894
0
    : mPattern(aPattern) {}
895
896
private:
897
  OriginAttrsPatternMatchSQLFunction() = delete;
898
0
  ~OriginAttrsPatternMatchSQLFunction() {}
899
900
  OriginAttributesPattern mPattern;
901
};
902
903
NS_IMPL_ISUPPORTS(OriginAttrsPatternMatchSQLFunction, mozIStorageFunction)
904
905
NS_IMETHODIMP
906
OriginAttrsPatternMatchSQLFunction::OnFunctionCall(
907
    mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
908
0
{
909
0
  nsresult rv;
910
0
911
0
  nsAutoCString suffix;
912
0
  rv = aFunctionArguments->GetUTF8String(0, suffix);
913
0
  NS_ENSURE_SUCCESS(rv, rv);
914
0
915
0
  OriginAttributes oa;
916
0
  bool success = oa.PopulateFromSuffix(suffix);
917
0
  NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
918
0
  bool result = mPattern.Matches(oa);
919
0
920
0
  RefPtr<nsVariant> outVar(new nsVariant());
921
0
  rv = outVar->SetAsBool(result);
922
0
  NS_ENSURE_SUCCESS(rv, rv);
923
0
924
0
  outVar.forget(aResult);
925
0
  return NS_OK;
926
0
}
927
928
} // namespace
929
930
// StorageDBThread::DBOperation
931
932
StorageDBThread::DBOperation::DBOperation(const OperationType aType,
933
                                          LocalStorageCacheBridge* aCache,
934
                                          const nsAString& aKey,
935
                                          const nsAString& aValue)
936
: mType(aType)
937
, mCache(aCache)
938
, mKey(aKey)
939
, mValue(aValue)
940
0
{
941
0
  MOZ_ASSERT(mType == opPreload ||
942
0
             mType == opPreloadUrgent ||
943
0
             mType == opAddItem ||
944
0
             mType == opUpdateItem ||
945
0
             mType == opRemoveItem ||
946
0
             mType == opClear ||
947
0
             mType == opClearAll);
948
0
  MOZ_COUNT_CTOR(StorageDBThread::DBOperation);
949
0
}
950
951
StorageDBThread::DBOperation::DBOperation(const OperationType aType,
952
                                          StorageUsageBridge* aUsage)
953
: mType(aType)
954
, mUsage(aUsage)
955
0
{
956
0
  MOZ_ASSERT(mType == opGetUsage);
957
0
  MOZ_COUNT_CTOR(StorageDBThread::DBOperation);
958
0
}
959
960
StorageDBThread::DBOperation::DBOperation(const OperationType aType,
961
                                          const nsACString& aOriginNoSuffix)
962
: mType(aType)
963
, mCache(nullptr)
964
, mOrigin(aOriginNoSuffix)
965
0
{
966
0
  MOZ_ASSERT(mType == opClearMatchingOrigin);
967
0
  MOZ_COUNT_CTOR(StorageDBThread::DBOperation);
968
0
}
969
970
StorageDBThread::DBOperation::DBOperation(const OperationType aType,
971
                                          const OriginAttributesPattern& aOriginNoSuffix)
972
: mType(aType)
973
, mCache(nullptr)
974
, mOriginPattern(aOriginNoSuffix)
975
0
{
976
0
  MOZ_ASSERT(mType == opClearMatchingOriginAttributes);
977
0
  MOZ_COUNT_CTOR(StorageDBThread::DBOperation);
978
0
}
979
980
StorageDBThread::DBOperation::~DBOperation()
981
0
{
982
0
  MOZ_COUNT_DTOR(StorageDBThread::DBOperation);
983
0
}
984
985
const nsCString
986
StorageDBThread::DBOperation::OriginNoSuffix() const
987
0
{
988
0
  if (mCache) {
989
0
    return mCache->OriginNoSuffix();
990
0
  }
991
0
992
0
  return EmptyCString();
993
0
}
994
995
const nsCString
996
StorageDBThread::DBOperation::OriginSuffix() const
997
0
{
998
0
  if (mCache) {
999
0
    return mCache->OriginSuffix();
1000
0
  }
1001
0
1002
0
  return EmptyCString();
1003
0
}
1004
1005
const nsCString
1006
StorageDBThread::DBOperation::Origin() const
1007
0
{
1008
0
  if (mCache) {
1009
0
    return mCache->Origin();
1010
0
  }
1011
0
1012
0
  return mOrigin;
1013
0
}
1014
1015
const nsCString
1016
StorageDBThread::DBOperation::Target() const
1017
0
{
1018
0
  switch (mType) {
1019
0
    case opAddItem:
1020
0
    case opUpdateItem:
1021
0
    case opRemoveItem:
1022
0
      return Origin() + NS_LITERAL_CSTRING("|") + NS_ConvertUTF16toUTF8(mKey);
1023
0
1024
0
    default:
1025
0
      return Origin();
1026
0
  }
1027
0
}
1028
1029
void
1030
StorageDBThread::DBOperation::PerformAndFinalize(StorageDBThread* aThread)
1031
0
{
1032
0
  Finalize(Perform(aThread));
1033
0
}
1034
1035
nsresult
1036
StorageDBThread::DBOperation::Perform(StorageDBThread* aThread)
1037
0
{
1038
0
  nsresult rv;
1039
0
1040
0
  switch (mType) {
1041
0
  case opPreload:
1042
0
  case opPreloadUrgent:
1043
0
  {
1044
0
    // Already loaded?
1045
0
    if (mCache->Loaded()) {
1046
0
      break;
1047
0
    }
1048
0
1049
0
    StatementCache* statements;
1050
0
    if (MOZ_UNLIKELY(IsOnBackgroundThread())) {
1051
0
      statements = &aThread->mReaderStatements;
1052
0
    } else {
1053
0
      statements = &aThread->mWorkerStatements;
1054
0
    }
1055
0
1056
0
    // OFFSET is an optimization when we have to do a sync load
1057
0
    // and cache has already loaded some parts asynchronously.
1058
0
    // It skips keys we have already loaded.
1059
0
    nsCOMPtr<mozIStorageStatement> stmt = statements->GetCachedStatement(
1060
0
        "SELECT key, value FROM webappsstore2 "
1061
0
        "WHERE originAttributes = :originAttributes AND originKey = :originKey "
1062
0
        "ORDER BY key LIMIT -1 OFFSET :offset");
1063
0
    NS_ENSURE_STATE(stmt);
1064
0
    mozStorageStatementScoper scope(stmt);
1065
0
1066
0
    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
1067
0
                                    mCache->OriginSuffix());
1068
0
    NS_ENSURE_SUCCESS(rv, rv);
1069
0
1070
0
    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
1071
0
                                    mCache->OriginNoSuffix());
1072
0
    NS_ENSURE_SUCCESS(rv, rv);
1073
0
1074
0
    rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("offset"),
1075
0
                               static_cast<int32_t>(mCache->LoadedCount()));
1076
0
    NS_ENSURE_SUCCESS(rv, rv);
1077
0
1078
0
    bool exists;
1079
0
    while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
1080
0
      nsAutoString key;
1081
0
      rv = stmt->GetString(0, key);
1082
0
      NS_ENSURE_SUCCESS(rv, rv);
1083
0
1084
0
      nsAutoString value;
1085
0
      rv = stmt->GetString(1, value);
1086
0
      NS_ENSURE_SUCCESS(rv, rv);
1087
0
1088
0
      if (!mCache->LoadItem(key, value)) {
1089
0
        break;
1090
0
      }
1091
0
    }
1092
0
    // The loop condition's call to ExecuteStep() may have terminated because
1093
0
    // !NS_SUCCEEDED(), we need an early return to cover that case.  This also
1094
0
    // covers success cases as well, but that's inductively safe.
1095
0
    NS_ENSURE_SUCCESS(rv, rv);
1096
0
    break;
1097
0
  }
1098
0
1099
0
  case opGetUsage:
1100
0
  {
1101
0
    nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
1102
0
      "SELECT SUM(LENGTH(key) + LENGTH(value)) FROM webappsstore2 "
1103
0
      "WHERE (originAttributes || ':' || originKey) LIKE :usageOrigin"
1104
0
    );
1105
0
    NS_ENSURE_STATE(stmt);
1106
0
1107
0
    mozStorageStatementScoper scope(stmt);
1108
0
1109
0
    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("usageOrigin"),
1110
0
                                    mUsage->OriginScope());
1111
0
    NS_ENSURE_SUCCESS(rv, rv);
1112
0
1113
0
    bool exists;
1114
0
    rv = stmt->ExecuteStep(&exists);
1115
0
    NS_ENSURE_SUCCESS(rv, rv);
1116
0
1117
0
    int64_t usage = 0;
1118
0
    if (exists) {
1119
0
      rv = stmt->GetInt64(0, &usage);
1120
0
      NS_ENSURE_SUCCESS(rv, rv);
1121
0
    }
1122
0
1123
0
    mUsage->LoadUsage(usage);
1124
0
    break;
1125
0
  }
1126
0
1127
0
  case opAddItem:
1128
0
  case opUpdateItem:
1129
0
  {
1130
0
    MOZ_ASSERT(!NS_IsMainThread());
1131
0
1132
0
    nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
1133
0
      "INSERT OR REPLACE INTO webappsstore2 (originAttributes, originKey, scope, key, value) "
1134
0
      "VALUES (:originAttributes, :originKey, :scope, :key, :value) "
1135
0
    );
1136
0
    NS_ENSURE_STATE(stmt);
1137
0
1138
0
    mozStorageStatementScoper scope(stmt);
1139
0
1140
0
    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
1141
0
                                    mCache->OriginSuffix());
1142
0
    NS_ENSURE_SUCCESS(rv, rv);
1143
0
    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
1144
0
                                    mCache->OriginNoSuffix());
1145
0
    NS_ENSURE_SUCCESS(rv, rv);
1146
0
    // Filling the 'scope' column just for downgrade compatibility reasons
1147
0
    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
1148
0
                                    Scheme0Scope(mCache));
1149
0
    NS_ENSURE_SUCCESS(rv, rv);
1150
0
    rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"),
1151
0
                                mKey);
1152
0
    NS_ENSURE_SUCCESS(rv, rv);
1153
0
    rv = stmt->BindStringByName(NS_LITERAL_CSTRING("value"),
1154
0
                                mValue);
1155
0
    NS_ENSURE_SUCCESS(rv, rv);
1156
0
1157
0
    rv = stmt->Execute();
1158
0
    NS_ENSURE_SUCCESS(rv, rv);
1159
0
1160
0
    MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor());
1161
0
    aThread->mOriginsHavingData.PutEntry(Origin());
1162
0
    break;
1163
0
  }
1164
0
1165
0
  case opRemoveItem:
1166
0
  {
1167
0
    MOZ_ASSERT(!NS_IsMainThread());
1168
0
1169
0
    nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
1170
0
      "DELETE FROM webappsstore2 "
1171
0
      "WHERE originAttributes = :originAttributes AND originKey = :originKey "
1172
0
        "AND key = :key "
1173
0
    );
1174
0
    NS_ENSURE_STATE(stmt);
1175
0
    mozStorageStatementScoper scope(stmt);
1176
0
1177
0
    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
1178
0
                                    mCache->OriginSuffix());
1179
0
    NS_ENSURE_SUCCESS(rv, rv);
1180
0
    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
1181
0
                                    mCache->OriginNoSuffix());
1182
0
    NS_ENSURE_SUCCESS(rv, rv);
1183
0
    rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"),
1184
0
                                mKey);
1185
0
    NS_ENSURE_SUCCESS(rv, rv);
1186
0
1187
0
    rv = stmt->Execute();
1188
0
    NS_ENSURE_SUCCESS(rv, rv);
1189
0
1190
0
    break;
1191
0
  }
1192
0
1193
0
  case opClear:
1194
0
  {
1195
0
    MOZ_ASSERT(!NS_IsMainThread());
1196
0
1197
0
    nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
1198
0
      "DELETE FROM webappsstore2 "
1199
0
      "WHERE originAttributes = :originAttributes AND originKey = :originKey"
1200
0
    );
1201
0
    NS_ENSURE_STATE(stmt);
1202
0
    mozStorageStatementScoper scope(stmt);
1203
0
1204
0
    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
1205
0
                                    mCache->OriginSuffix());
1206
0
    NS_ENSURE_SUCCESS(rv, rv);
1207
0
    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
1208
0
                                    mCache->OriginNoSuffix());
1209
0
    NS_ENSURE_SUCCESS(rv, rv);
1210
0
1211
0
    rv = stmt->Execute();
1212
0
    NS_ENSURE_SUCCESS(rv, rv);
1213
0
1214
0
    MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor());
1215
0
    aThread->mOriginsHavingData.RemoveEntry(Origin());
1216
0
    break;
1217
0
  }
1218
0
1219
0
  case opClearAll:
1220
0
  {
1221
0
    MOZ_ASSERT(!NS_IsMainThread());
1222
0
1223
0
    nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
1224
0
      "DELETE FROM webappsstore2"
1225
0
    );
1226
0
    NS_ENSURE_STATE(stmt);
1227
0
    mozStorageStatementScoper scope(stmt);
1228
0
1229
0
    rv = stmt->Execute();
1230
0
    NS_ENSURE_SUCCESS(rv, rv);
1231
0
1232
0
    MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor());
1233
0
    aThread->mOriginsHavingData.Clear();
1234
0
    break;
1235
0
  }
1236
0
1237
0
  case opClearMatchingOrigin:
1238
0
  {
1239
0
    MOZ_ASSERT(!NS_IsMainThread());
1240
0
1241
0
    nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
1242
0
      "DELETE FROM webappsstore2"
1243
0
      " WHERE originKey GLOB :scope"
1244
0
    );
1245
0
    NS_ENSURE_STATE(stmt);
1246
0
    mozStorageStatementScoper scope(stmt);
1247
0
1248
0
    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
1249
0
                                    mOrigin + NS_LITERAL_CSTRING("*"));
1250
0
    NS_ENSURE_SUCCESS(rv, rv);
1251
0
1252
0
    rv = stmt->Execute();
1253
0
    NS_ENSURE_SUCCESS(rv, rv);
1254
0
1255
0
    // No need to selectively clear mOriginsHavingData here.  That hashtable
1256
0
    // only prevents preload for scopes with no data.  Leaving a false record in
1257
0
    // it has a negligible effect on performance.
1258
0
    break;
1259
0
  }
1260
0
1261
0
  case opClearMatchingOriginAttributes:
1262
0
  {
1263
0
    MOZ_ASSERT(!NS_IsMainThread());
1264
0
1265
0
    // Register the ORIGIN_ATTRS_PATTERN_MATCH function, initialized with the
1266
0
    // pattern
1267
0
    nsCOMPtr<mozIStorageFunction> patternMatchFunction(
1268
0
      new OriginAttrsPatternMatchSQLFunction(mOriginPattern));
1269
0
1270
0
    rv = aThread->mWorkerConnection->CreateFunction(
1271
0
      NS_LITERAL_CSTRING("ORIGIN_ATTRS_PATTERN_MATCH"), 1, patternMatchFunction);
1272
0
    NS_ENSURE_SUCCESS(rv, rv);
1273
0
1274
0
    nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
1275
0
      "DELETE FROM webappsstore2"
1276
0
      " WHERE ORIGIN_ATTRS_PATTERN_MATCH(originAttributes)"
1277
0
    );
1278
0
1279
0
    if (stmt) {
1280
0
      mozStorageStatementScoper scope(stmt);
1281
0
      rv = stmt->Execute();
1282
0
    } else {
1283
0
      rv = NS_ERROR_UNEXPECTED;
1284
0
    }
1285
0
1286
0
    // Always remove the function
1287
0
    aThread->mWorkerConnection->RemoveFunction(
1288
0
      NS_LITERAL_CSTRING("ORIGIN_ATTRS_PATTERN_MATCH"));
1289
0
1290
0
    NS_ENSURE_SUCCESS(rv, rv);
1291
0
1292
0
    // No need to selectively clear mOriginsHavingData here.  That hashtable
1293
0
    // only prevents preload for scopes with no data.  Leaving a false record in
1294
0
    // it has a negligible effect on performance.
1295
0
    break;
1296
0
  }
1297
0
1298
0
  default:
1299
0
    NS_ERROR("Unknown task type");
1300
0
    break;
1301
0
  }
1302
0
1303
0
  return NS_OK;
1304
0
}
1305
1306
void
1307
StorageDBThread::DBOperation::Finalize(nsresult aRv)
1308
0
{
1309
0
  switch (mType) {
1310
0
  case opPreloadUrgent:
1311
0
  case opPreload:
1312
0
    if (NS_FAILED(aRv)) {
1313
0
      // When we are here, something failed when loading from the database.
1314
0
      // Notify that the storage is loaded to prevent deadlock of the main
1315
0
      // thread, even though it is actually empty or incomplete.
1316
0
      NS_WARNING("Failed to preload localStorage");
1317
0
    }
1318
0
1319
0
    mCache->LoadDone(aRv);
1320
0
    break;
1321
0
1322
0
  case opGetUsage:
1323
0
    if (NS_FAILED(aRv)) {
1324
0
      mUsage->LoadUsage(0);
1325
0
    }
1326
0
1327
0
    break;
1328
0
1329
0
  default:
1330
0
    if (NS_FAILED(aRv)) {
1331
0
      NS_WARNING("localStorage update/clear operation failed,"
1332
0
                 " data may not persist or clean up");
1333
0
    }
1334
0
1335
0
    break;
1336
0
  }
1337
0
}
1338
1339
// StorageDBThread::PendingOperations
1340
1341
StorageDBThread::PendingOperations::PendingOperations()
1342
: mFlushFailureCount(0)
1343
0
{
1344
0
}
1345
1346
bool
1347
StorageDBThread::PendingOperations::HasTasks() const
1348
0
{
1349
0
  return !!mUpdates.Count() || !!mClears.Count();
1350
0
}
1351
1352
namespace {
1353
1354
bool OriginPatternMatches(const nsACString& aOriginSuffix,
1355
                          const OriginAttributesPattern& aPattern)
1356
0
{
1357
0
  OriginAttributes oa;
1358
0
  DebugOnly<bool> rv = oa.PopulateFromSuffix(aOriginSuffix);
1359
0
  MOZ_ASSERT(rv);
1360
0
  return aPattern.Matches(oa);
1361
0
}
1362
1363
} // namespace
1364
1365
bool
1366
StorageDBThread::PendingOperations::CheckForCoalesceOpportunity(DBOperation* aNewOp,
1367
                                                                DBOperation::OperationType aPendingType,
1368
                                                                DBOperation::OperationType aNewType)
1369
0
{
1370
0
  if (aNewOp->Type() != aNewType) {
1371
0
    return false;
1372
0
  }
1373
0
1374
0
  StorageDBThread::DBOperation* pendingTask;
1375
0
  if (!mUpdates.Get(aNewOp->Target(), &pendingTask)) {
1376
0
    return false;
1377
0
  }
1378
0
1379
0
  if (pendingTask->Type() != aPendingType) {
1380
0
    return false;
1381
0
  }
1382
0
1383
0
  return true;
1384
0
}
1385
1386
void
1387
StorageDBThread::PendingOperations::Add(StorageDBThread::DBOperation* aOperation)
1388
0
{
1389
0
  // Optimize: when a key to remove has never been written to disk
1390
0
  // just bypass this operation.  A key is new when an operation scheduled
1391
0
  // to write it to the database is of type opAddItem.
1392
0
  if (CheckForCoalesceOpportunity(aOperation, DBOperation::opAddItem,
1393
0
                                  DBOperation::opRemoveItem)) {
1394
0
    mUpdates.Remove(aOperation->Target());
1395
0
    delete aOperation;
1396
0
    return;
1397
0
  }
1398
0
1399
0
  // Optimize: when changing a key that is new and has never been
1400
0
  // written to disk, keep type of the operation to store it at opAddItem.
1401
0
  // This allows optimization to just forget adding a new key when
1402
0
  // it is removed from the storage before flush.
1403
0
  if (CheckForCoalesceOpportunity(aOperation, DBOperation::opAddItem,
1404
0
                                  DBOperation::opUpdateItem)) {
1405
0
    aOperation->mType = DBOperation::opAddItem;
1406
0
  }
1407
0
1408
0
  // Optimize: to prevent lose of remove operation on a key when doing
1409
0
  // remove/set/remove on a previously existing key we have to change
1410
0
  // opAddItem to opUpdateItem on the new operation when there is opRemoveItem
1411
0
  // pending for the key.
1412
0
  if (CheckForCoalesceOpportunity(aOperation, DBOperation::opRemoveItem,
1413
0
                                  DBOperation::opAddItem)) {
1414
0
    aOperation->mType = DBOperation::opUpdateItem;
1415
0
  }
1416
0
1417
0
  switch (aOperation->Type())
1418
0
  {
1419
0
  // Operations on single keys
1420
0
1421
0
  case DBOperation::opAddItem:
1422
0
  case DBOperation::opUpdateItem:
1423
0
  case DBOperation::opRemoveItem:
1424
0
    // Override any existing operation for the target (=scope+key).
1425
0
    mUpdates.Put(aOperation->Target(), aOperation);
1426
0
    break;
1427
0
1428
0
  // Clear operations
1429
0
1430
0
  case DBOperation::opClear:
1431
0
  case DBOperation::opClearMatchingOrigin:
1432
0
  case DBOperation::opClearMatchingOriginAttributes:
1433
0
    // Drop all update (insert/remove) operations for equivavelent or matching
1434
0
    // scope.  We do this as an optimization as well as a must based on the
1435
0
    // logic, if we would not delete the update tasks, changes would have been
1436
0
    // stored to the database after clear operations have been executed.
1437
0
    for (auto iter = mUpdates.Iter(); !iter.Done(); iter.Next()) {
1438
0
      nsAutoPtr<DBOperation>& pendingTask = iter.Data();
1439
0
1440
0
      if (aOperation->Type() == DBOperation::opClear &&
1441
0
          (pendingTask->OriginNoSuffix() != aOperation->OriginNoSuffix() ||
1442
0
           pendingTask->OriginSuffix() != aOperation->OriginSuffix())) {
1443
0
        continue;
1444
0
      }
1445
0
1446
0
      if (aOperation->Type() == DBOperation::opClearMatchingOrigin &&
1447
0
          !StringBeginsWith(pendingTask->OriginNoSuffix(), aOperation->Origin())) {
1448
0
        continue;
1449
0
      }
1450
0
1451
0
      if (aOperation->Type() == DBOperation::opClearMatchingOriginAttributes &&
1452
0
          !OriginPatternMatches(pendingTask->OriginSuffix(), aOperation->OriginPattern())) {
1453
0
        continue;
1454
0
      }
1455
0
1456
0
      iter.Remove();
1457
0
    }
1458
0
1459
0
    mClears.Put(aOperation->Target(), aOperation);
1460
0
    break;
1461
0
1462
0
  case DBOperation::opClearAll:
1463
0
    // Drop simply everything, this is a super-operation.
1464
0
    mUpdates.Clear();
1465
0
    mClears.Clear();
1466
0
    mClears.Put(aOperation->Target(), aOperation);
1467
0
    break;
1468
0
1469
0
  default:
1470
0
    MOZ_ASSERT(false);
1471
0
    break;
1472
0
  }
1473
0
}
1474
1475
bool
1476
StorageDBThread::PendingOperations::Prepare()
1477
0
{
1478
0
  // Called under the lock
1479
0
1480
0
  // First collect clear operations and then updates, we can
1481
0
  // do this since whenever a clear operation for a scope is
1482
0
  // scheduled, we drop all updates matching that scope. So,
1483
0
  // all scope-related update operations we have here now were
1484
0
  // scheduled after the clear operations.
1485
0
  for (auto iter = mClears.Iter(); !iter.Done(); iter.Next()) {
1486
0
    mExecList.AppendElement(iter.Data().forget());
1487
0
  }
1488
0
  mClears.Clear();
1489
0
1490
0
  for (auto iter = mUpdates.Iter(); !iter.Done(); iter.Next()) {
1491
0
    mExecList.AppendElement(iter.Data().forget());
1492
0
  }
1493
0
  mUpdates.Clear();
1494
0
1495
0
  return !!mExecList.Length();
1496
0
}
1497
1498
nsresult
1499
StorageDBThread::PendingOperations::Execute(StorageDBThread* aThread)
1500
0
{
1501
0
  // Called outside the lock
1502
0
1503
0
  mozStorageTransaction transaction(aThread->mWorkerConnection, false);
1504
0
1505
0
  nsresult rv;
1506
0
1507
0
  for (uint32_t i = 0; i < mExecList.Length(); ++i) {
1508
0
    StorageDBThread::DBOperation* task = mExecList[i];
1509
0
    rv = task->Perform(aThread);
1510
0
    if (NS_FAILED(rv)) {
1511
0
      return rv;
1512
0
    }
1513
0
  }
1514
0
1515
0
  rv = transaction.Commit();
1516
0
  if (NS_FAILED(rv)) {
1517
0
    return rv;
1518
0
  }
1519
0
1520
0
  return NS_OK;
1521
0
}
1522
1523
bool
1524
StorageDBThread::PendingOperations::Finalize(nsresult aRv)
1525
0
{
1526
0
  // Called under the lock
1527
0
1528
0
  // The list is kept on a failure to retry it
1529
0
  if (NS_FAILED(aRv)) {
1530
0
    // XXX Followup: we may try to reopen the database and flush these
1531
0
    // pending tasks, however testing showed that even though I/O is actually
1532
0
    // broken some amount of operations is left in sqlite+system buffers and
1533
0
    // seems like successfully flushed to disk.
1534
0
    // Tested by removing a flash card and disconnecting from network while
1535
0
    // using a network drive on Windows system.
1536
0
    NS_WARNING("Flush operation on localStorage database failed");
1537
0
1538
0
    ++mFlushFailureCount;
1539
0
1540
0
    return mFlushFailureCount >= 5;
1541
0
  }
1542
0
1543
0
  mFlushFailureCount = 0;
1544
0
  mExecList.Clear();
1545
0
  return true;
1546
0
}
1547
1548
namespace {
1549
1550
bool
1551
FindPendingClearForOrigin(const nsACString& aOriginSuffix,
1552
                          const nsACString& aOriginNoSuffix,
1553
                          StorageDBThread::DBOperation* aPendingOperation)
1554
0
{
1555
0
  if (aPendingOperation->Type() == StorageDBThread::DBOperation::opClearAll) {
1556
0
    return true;
1557
0
  }
1558
0
1559
0
  if (aPendingOperation->Type() == StorageDBThread::DBOperation::opClear &&
1560
0
      aOriginNoSuffix == aPendingOperation->OriginNoSuffix() &&
1561
0
      aOriginSuffix == aPendingOperation->OriginSuffix()) {
1562
0
    return true;
1563
0
  }
1564
0
1565
0
  if (aPendingOperation->Type() == StorageDBThread::DBOperation::opClearMatchingOrigin &&
1566
0
      StringBeginsWith(aOriginNoSuffix, aPendingOperation->Origin())) {
1567
0
    return true;
1568
0
  }
1569
0
1570
0
  if (aPendingOperation->Type() == StorageDBThread::DBOperation::opClearMatchingOriginAttributes &&
1571
0
      OriginPatternMatches(aOriginSuffix, aPendingOperation->OriginPattern())) {
1572
0
    return true;
1573
0
  }
1574
0
1575
0
  return false;
1576
0
}
1577
1578
} // namespace
1579
1580
bool
1581
StorageDBThread::PendingOperations::IsOriginClearPending(const nsACString& aOriginSuffix,
1582
                                                         const nsACString& aOriginNoSuffix) const
1583
0
{
1584
0
  // Called under the lock
1585
0
1586
0
  for (auto iter = mClears.ConstIter(); !iter.Done(); iter.Next()) {
1587
0
    if (FindPendingClearForOrigin(aOriginSuffix, aOriginNoSuffix, iter.UserData())) {
1588
0
      return true;
1589
0
    }
1590
0
  }
1591
0
1592
0
  for (uint32_t i = 0; i < mExecList.Length(); ++i) {
1593
0
    if (FindPendingClearForOrigin(aOriginSuffix, aOriginNoSuffix, mExecList[i])) {
1594
0
      return true;
1595
0
    }
1596
0
  }
1597
0
1598
0
  return false;
1599
0
}
1600
1601
namespace {
1602
1603
bool
1604
FindPendingUpdateForOrigin(const nsACString& aOriginSuffix,
1605
                           const nsACString& aOriginNoSuffix,
1606
                           StorageDBThread::DBOperation* aPendingOperation)
1607
0
{
1608
0
  if ((aPendingOperation->Type() == StorageDBThread::DBOperation::opAddItem ||
1609
0
       aPendingOperation->Type() == StorageDBThread::DBOperation::opUpdateItem ||
1610
0
       aPendingOperation->Type() == StorageDBThread::DBOperation::opRemoveItem) &&
1611
0
       aOriginNoSuffix == aPendingOperation->OriginNoSuffix() &&
1612
0
       aOriginSuffix == aPendingOperation->OriginSuffix()) {
1613
0
    return true;
1614
0
  }
1615
0
1616
0
  return false;
1617
0
}
1618
1619
} // namespace
1620
1621
bool
1622
StorageDBThread::PendingOperations::IsOriginUpdatePending(const nsACString& aOriginSuffix,
1623
                                                          const nsACString& aOriginNoSuffix) const
1624
0
{
1625
0
  // Called under the lock
1626
0
1627
0
  for (auto iter = mUpdates.ConstIter(); !iter.Done(); iter.Next()) {
1628
0
    if (FindPendingUpdateForOrigin(aOriginSuffix, aOriginNoSuffix, iter.UserData())) {
1629
0
      return true;
1630
0
    }
1631
0
  }
1632
0
1633
0
  for (uint32_t i = 0; i < mExecList.Length(); ++i) {
1634
0
    if (FindPendingUpdateForOrigin(aOriginSuffix, aOriginNoSuffix, mExecList[i])) {
1635
0
      return true;
1636
0
    }
1637
0
  }
1638
0
1639
0
  return false;
1640
0
}
1641
1642
nsresult
1643
StorageDBThread::
1644
InitHelper::SyncDispatchAndReturnProfilePath(nsAString& aProfilePath)
1645
0
{
1646
0
  AssertIsOnBackgroundThread();
1647
0
1648
0
  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
1649
0
1650
0
  mozilla::MutexAutoLock autolock(mMutex);
1651
0
  while (mWaiting) {
1652
0
    mCondVar.Wait();
1653
0
  }
1654
0
1655
0
  if (NS_WARN_IF(NS_FAILED(mMainThreadResultCode))) {
1656
0
    return mMainThreadResultCode;
1657
0
  }
1658
0
1659
0
  aProfilePath = mProfilePath;
1660
0
  return NS_OK;
1661
0
}
1662
1663
NS_IMETHODIMP
1664
StorageDBThread::
1665
InitHelper::Run()
1666
0
{
1667
0
  MOZ_ASSERT(NS_IsMainThread());
1668
0
1669
0
  nsresult rv = GetProfilePath(mProfilePath);
1670
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1671
0
    mMainThreadResultCode = rv;
1672
0
  }
1673
0
1674
0
  mozilla::MutexAutoLock lock(mMutex);
1675
0
  MOZ_ASSERT(mWaiting);
1676
0
1677
0
  mWaiting = false;
1678
0
  mCondVar.Notify();
1679
0
1680
0
  return NS_OK;
1681
0
}
1682
1683
NS_IMETHODIMP
1684
StorageDBThread::
1685
NoteBackgroundThreadRunnable::Run()
1686
0
{
1687
0
  MOZ_ASSERT(NS_IsMainThread());
1688
0
1689
0
  StorageObserver* observer = StorageObserver::Self();
1690
0
  MOZ_ASSERT(observer);
1691
0
1692
0
  observer->NoteBackgroundThread(mOwningThread);
1693
0
1694
0
  return NS_OK;
1695
0
}
1696
1697
NS_IMETHODIMP
1698
StorageDBThread::
1699
ShutdownRunnable::Run()
1700
0
{
1701
0
  if (NS_IsMainThread()) {
1702
0
    mDone = true;
1703
0
1704
0
    return NS_OK;
1705
0
  }
1706
0
1707
0
  AssertIsOnBackgroundThread();
1708
0
1709
0
  if (sStorageThread) {
1710
0
    sStorageThread->Shutdown();
1711
0
1712
0
    delete sStorageThread;
1713
0
    sStorageThread = nullptr;
1714
0
  }
1715
0
1716
0
  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
1717
0
1718
0
  return NS_OK;
1719
0
}
1720
1721
} // namespace dom
1722
} // namespace mozilla