Files
palemoon27/dom/cache/Context.cpp
T
roytam1 74c60cb62d import changes from `dev' branch of rmottola/Arctic-Fox:
- Use CompositorWidgetProxy for dispatching vsync to the compositor. (bug 1269037 part 1, r=mchang) (c8b7a4240c)
- Remove unused null widget checks. (bug 1269037 part 2, r=mchang) (4f4cc9952b)
- Bug 1269422: Wrap Gonk widget in |CompositorWidgetProxyWrapper|. r=dvander (4513035cbf)
- Remove nsBaseWidget::NewCompositorBridgeParent. (bug 1272472 part 1, r=kats) (ca813c1f2b)
- Use IPDL to schedule composites on GTK. (bug 1272472 part 2, r=nical) (7402cf834e)
- Use IPC to schedule composites on Windows. (bug 1272472 part 3, r=jimm) (e5d7281dd7)
- Don't use nsIWidget to check APZ in LayerManagerComposite. (bug 1269653 part 1, r=kats) (79a1644111)
- Restrict SetDispAcquireFence's nsIWidget access to Gonk. (bug 1269653 part 2, r=kats) (72110b7bc7)
- Bug 1264764 - Move PTexture under PCompositorBridge r=nical,dvander (17e6ec7fc1)
- Move CompositorThreadHolder into its own file. (bug 1273017 part 1, r=mattwoodrow) (464ede8be1)
- Move CompositorBridgeParent::CompositorLoop to CompositorThreadHolder. (bug 1273017 part 2, r=mattwoodrow) (85708f3cde)
- Bug 1268313: Part 1 - Be explicit about which NewRunnableMethod callers want to be able to cancel. r=froydnj (faa07aa139)
- Bug 1268313: Part 2 - Replace some NewRunnableMethods with NS_NewNonOwningRunnableMethod. r=froydnj (010c43d000)
- Bug 1268313: Part 3 - Replace some NewCancelableRunnableMethod with NS_NewNonOwningCancelableRunnableMethod. r=froydnj (55018ef234)
- Bug 1268313: Part 4 - Replace NewCancelableRunnableMethod with NS_NewCancelableRunnableMethod. r=froydnj (c22711b35d)
- Bug 1268313: Part 5 - Make NS_NewRunnableMethod able to call const functions. r=froydnj (b0f60963a3)
- Bug 1268313: Part 6 - Replace NewRunnableMethod with NS_NewRunnableMethod. r=froydnj (18d40def2c)
- Bug 1260950 - Set mInitialSizeFound to true when the initial size id found. r=jesup, r=pehrsons a=kwierso (5d6abe57e0)
- Bug 1237176 - Notify synth start if we get a finished event without a blocking-changed event. r=roc (c1aebe903a)
- Bug 911546, use a runnable so that popups don't rollup during a grab, r=karlt (a06bd44e6c)
- Bug 1237617 - Call nsWindow::ForcePresent during going active r=bas.schouten (066cad8f89)
- Bug 1265420 - SetAndFetchFaviconForPage should return a cancelable object to allow aborting the fetch. r=Gijs (a3f7dc54b7)
- Bug 1268313: Part 7 - Move NS_NewRunnableMethod and friends to mozilla::NewRunnableMethod. r=froydnj (8b4bf34961)
- Bug 1266595: Followup to fix IPDL tests. r=billm (216f2dcff5)
- Bug 1268313: Fix up IPDL tests. r=billm (228348d642)
- Add WinCompositorWidgetProxy. (bug 1265975 part 1, r=jimm) (bfafe7a8e2)
- Implement WinCompositorWidgetProxy::GetClientSize. (bug 1265975 part 2, r=jimm) (a8710a3259)
- Move the WM_SETTEXT present lock to CompositorWidgetProxy. (bug 1265975 part 3, r=jimm) (297ce28c8a)
- Move transparency handling to WinCompositorWigetProxy. (bug 1265975 part 4, r=jimm) (46ba0c6d01)
- Remove Windows-specific compositor calls to nsIWidget. (bug 1265975 part 5, r=jimm) (3ef157c160)
- Remove plugin-related CompositorBridgeParent use of nsIWidget. (bug 1265975 part 6, r=jimm) (6d80cdd6fd)
- Hide top-level CompositorBridgeParents behind a new API. (bug 1272472 part 4, r=mattwoodrow,kats,gwagner) (228c0efdb7)
- Bug 1253424 - part 1 - add a already_AddRefed nsTransactionStack::Push overload; r=erahm (7059e20914)
- Bug 1253424 - part 2 - add nsTransactionStack::IsEmpty; r=erahm (7e9764a146)
- Bug 1254618 - modify nsTransactionStack to use nsDeque rather than std::deque; r=ehsan (5e47ea431e)
- Bug 1136857 - Make DOMStorageCache::mLoaded flag atomic to prevent potential races, r=nfroyd (39aaea1de3)
- Bug 1265408 - Add webidl for IIRFilterNode; r=smaug (040ce9aa43)
- Bug 1265408 - Implement IIRFilterNode; r=padenot (6bf569a412)
- Bug 1265408 - Import IIRFilter from blink; r=padenot (71b28c0ad2)
- Bug 1265408 - Use IIRFilter from blink; r=padenot (5d058d8568)
- Bug 1265408 - Add buffersAreZero to IIRFilter; r=karlt (45edba3e13)
- Bug 1265408 - Avoid subnormals in IIRFilter; r=karlt (0e1ae93f0b)
- Bug 1265408 - Add LogToDeveloperConsole to WebAudioUtils; r=padenot (88d5f0222a)
- Bug 1268984 - Store GMPStorage on GMPServiceParent so that it persists inside the same PB session. r=gerald (17d4d0abaf)
- Bug 1267905 - Replace uses of ScopedCERTCertList with UniqueCERTCertList. r=keeler (783bf11b2a)
- Bug 1270005 - Replace uses of ScopedPK11SlotInfo with UniquePK11SlotInfo in PSM. r=keeler (ea9a4011aa)
- Bug 1271501 - Remove unnecessary uses of reinterpret_cast in PSM. r=keeler (6be40f0a85)
- Bug 1271501 - Downgrade unnecessarily strong reinterpret_casts in PSM. r=keeler (95245f00ce)
- Bug 1082346 - 01. Convert PKCS12 password endian using copyAndSwapToBigEndian. r=keeler (9cc58fc550)
- Bug 1082346 - 02. Test case. r=keeler r=Cykesiopka (7fb0e8abc4)
- Bug 160122 - Stop using PR_smprintf in PSM. r=keeler (1e5b68819c)
- Bug 1271501 - Use mozilla::BitwiseCast instead of reinterpret_cast in PSM. r=keeler (894966a2ef)
- Bug 1273855: TraceLogger - Include PID in the log names in order to support browser with e10s, r=bbouvier (8cf2233db3)
- Bug 1274189. Part 1 - rename some functions to be consistent with other MediaDataDecoder sub-classes. r=jya. (4511b3d3f7)
- Bug 1274189. Part 2 - remove use of FlushableTaskQueue::Flush(). r=jya. (77e745fdd1)
- Bug 1274189. Part 3 - remove use of FlushableTaskQueue. r=jya (aac61dcd02)
- Bug 1269963. Part 1 - Add a SyncRunnable::DispatchToThread() overload for AbstractThread. r=bobbyholley. (839752aff4)
- Bug 1269672 - part1 : revert sampling rate changing of the bug1235612. (9015782e13)
- Bug 1270698 - check if we need to enter buffering periodically to ensure we start buffering when running out of decoded audio/video data. r=cpearce. (16734549b7)
- Bug 1271581 - use newCurrentTime, instead of GetMediaTime() to decide the nextState; r=jwwang (9c5075eada)
- Bug 1224973 - Part 1: Remove MediaDecoderOwner->IsHidden(). r=cpearce,jwwang (4fde3ede5a)
- Bug 1224973 - Part 2: Set MediaDecoder visibility via NotifyOwnerActivityChanged. r=cpearce,jwwang (be917202eb)
- Bug 1224973 - Part 3: Plumb element visibility into MDSM. r=jya,jwwang (9ec83fa243)
- Bug 1224973 - Part 4: Pref media.suspend-bkgnd-video.enabled. r=cpearce,jwwang (43413a025f)
- Bug 1269408: P1. Retry InternalSeek if previous attempt failed once more data is available. r=gerald (05db58dc7c)
- crude fix (0097068989)
- Bug 1269408: P2. Update mochitest. r=gerald (464b4c0724)
- Bug 1269408: P3. Ensure a new seek request will cancel the previous internal seek. r=gerald (6ed4b8dc95)
- Bug 1269408: P4. Ensure the decoders are flushed prior performing an internal seek. r=gerald (074234067b)
- Bug 1269408: P5. Only drop the seek target if it's exactly the seek target. r=gerald (88701eb05a)
- Bug 1269408: P6. Add debugging information, useful when a mochitest timeout. r=gerald (ef0270ab0d)
- Bug 1269408: P7. Start skip to next keyframe logic when resume point is behind current time. r=gerald (bd40ebf3bc)
- Bug 1269408: P8. Add debugging log. r=gerald (e6dbd1f0a6)
- Bug 1269408: P9. Move handling logic of skip to next keyframe to its own function. r=gerald (3c8039e417)
- Bug 1269408: P10. Reject promise early if in error state. r=me (8af54c574e)
- Bug 1224973 - Part 5: Implement suspend decoding for background video. r=cpearce,jwwang,jya (22081521e3)
- Bug 1242874 - part1 : create suspened types. r=baku (d3ac9548e5)
- Bug 1242874 - part2 : window's suspend attribute. r=baku, r=ehsan (1fd9dc2647)
- remove allowscirpted (39ab523036)
- Bug 1242874 - part3 : implement different suspended methods. r=baku, r=jwwang (25d1f27a03)
- Bug 1242874 - part4 : wrap the volume/mute/suspend for notifyStartedPlaying. r=baku (b8ba3238c2)
- bug 1242874 - part5 : add test. r=baku, r=ehsan (f840139b5a)
- Bug 1235612 - Part 1: Implement notify media-playback. r=baku (b5ec29da20)
- Bug 1235612 - Part 2: Notify audible state in NotifyStartedPlaying. r=baku (dc38583a62)
- Bug 1235612 - Part 3: Implement the logic of audible state notification for agent owners. r=baku (f65b3952fa)
- Bug 1235612 - Part 4: Modify check audible method. r=jwwang (73457e39eb)
- Bug 1269672 - part2 : move audible data checking from MDSM to DecodedAudioDataSink. (d2c3b6874c)
- Bug 1269936 - Introduce and call a runtime-wide servo initialization hook. r=heycam (d4d505d4c2)
- Bug 1263778 - Rename a bunch of low-level [[Prototype]] access methods to make their interactions with statically-known and dynamically-computed [[Prototype]]s clearer. r=efaust (66bbe8e7db)
- Bug 888969 - Permit a cyclic [[Prototype]] chain to be created through a Location object. r=bz, r=efaust (3e3b9cbb16)
- re-apply Bug 1054906 - Implement ES6 Symbol.hasInstance 2/2; r=jandem (8d5c7573ff)
- Bug 1054906 - Implement ES6 Symbol.hasInstance 1/2; r=evilpie,bz (a836904e5d)
- fix misspatch (54a5f2d708)
- Bug 1270349 part 1. Add IDL parser support for [LegacyUnenumerableNamedProperties]. r=peterv (8c836bc74a)
- Bug 1270349 part 2. Add [LegacyUnenumerableNamedProperties] to the interfaces that specify it in DOM and HTML. r=peterv (25d3cc1377)
- Bug 1270349 part 3. Add a way to ask an interface descriptor for a proxy whether its named props should be enumerable. r=peterv (0a9f804867)
- Bug 1270349 part 4. Use LegacyUnenumerableNamedProperties instead of NameIsEnumerable() calls to determine whether named props on DOM proxies should be enumerable. r=peterv (82f5158963)
- Bug 1270349 part 5. Use LegacyUnenumerableNamedProperties instead of passing flags to GetSupportedNames to determine whether named props on DOM proxies should be reflected in ownPropertyKeys. r=peterv (3984176834)
- Bug 1270349 followup to address a review comment. r=peterv (b49f4c5335)
2024-10-03 21:44:56 +08:00

1150 lines
31 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/cache/Context.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/dom/cache/Action.h"
#include "mozilla/dom/cache/FileUtils.h"
#include "mozilla/dom/cache/Manager.h"
#include "mozilla/dom/cache/ManagerId.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozIStorageConnection.h"
#include "nsIFile.h"
#include "nsIPrincipal.h"
#include "nsIRunnable.h"
#include "nsThreadUtils.h"
namespace {
using mozilla::dom::cache::Action;
using mozilla::dom::cache::QuotaInfo;
class NullAction final : public Action
{
public:
NullAction()
{
}
virtual void
RunOnTarget(Resolver* aResolver, const QuotaInfo&, Data*) override
{
// Resolve success immediately. This Action does no actual work.
MOZ_ASSERT(aResolver);
aResolver->Resolve(NS_OK);
}
};
} // namespace
namespace mozilla {
namespace dom {
namespace cache {
using mozilla::DebugOnly;
using mozilla::dom::quota::AssertIsOnIOThread;
using mozilla::dom::quota::OpenDirectoryListener;
using mozilla::dom::quota::QuotaManager;
using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
using mozilla::dom::quota::PersistenceType;
class Context::Data final : public Action::Data
{
public:
explicit Data(nsIThread* aTarget)
: mTarget(aTarget)
{
MOZ_ASSERT(mTarget);
}
virtual mozIStorageConnection*
GetConnection() const override
{
MOZ_ASSERT(mTarget == NS_GetCurrentThread());
return mConnection;
}
virtual void
SetConnection(mozIStorageConnection* aConn) override
{
MOZ_ASSERT(mTarget == NS_GetCurrentThread());
MOZ_ASSERT(!mConnection);
mConnection = aConn;
MOZ_ASSERT(mConnection);
}
private:
~Data()
{
// We could proxy release our data here, but instead just assert. The
// Context code should guarantee that we are destroyed on the target
// thread once the connection is initialized. If we're not, then
// QuotaManager might race and try to clear the origin out from under us.
MOZ_ASSERT_IF(mConnection, mTarget == NS_GetCurrentThread());
}
nsCOMPtr<nsIThread> mTarget;
nsCOMPtr<mozIStorageConnection> mConnection;
// Threadsafe counting because we're created on the PBackground thread
// and destroyed on the target IO thread.
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Context::Data)
};
// Executed to perform the complicated dance of steps necessary to initialize
// the QuotaManager. This must be performed for each origin before any disk
// IO occurrs.
class Context::QuotaInitRunnable final : public nsIRunnable
, public OpenDirectoryListener
{
public:
QuotaInitRunnable(Context* aContext,
Manager* aManager,
Data* aData,
nsIThread* aTarget,
Action* aInitAction)
: mContext(aContext)
, mThreadsafeHandle(aContext->CreateThreadsafeHandle())
, mManager(aManager)
, mData(aData)
, mTarget(aTarget)
, mInitAction(aInitAction)
, mInitiatingThread(NS_GetCurrentThread())
, mResult(NS_OK)
, mState(STATE_INIT)
, mCanceled(false)
{
MOZ_ASSERT(mContext);
MOZ_ASSERT(mManager);
MOZ_ASSERT(mData);
MOZ_ASSERT(mTarget);
MOZ_ASSERT(mInitiatingThread);
MOZ_ASSERT(mInitAction);
}
nsresult Dispatch()
{
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
MOZ_ASSERT(mState == STATE_INIT);
mState = STATE_GET_INFO;
nsresult rv = NS_DispatchToMainThread(this, nsIThread::DISPATCH_NORMAL);
if (NS_WARN_IF(NS_FAILED(rv))) {
mState = STATE_COMPLETE;
Clear();
}
return rv;
}
void Cancel()
{
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
MOZ_ASSERT(!mCanceled);
mCanceled = true;
mInitAction->CancelOnInitiatingThread();
}
void OpenDirectory();
// OpenDirectoryListener methods
virtual void
DirectoryLockAcquired(DirectoryLock* aLock) override;
virtual void
DirectoryLockFailed() override;
private:
class SyncResolver final : public Action::Resolver
{
public:
SyncResolver()
: mResolved(false)
, mResult(NS_OK)
{ }
virtual void
Resolve(nsresult aRv) override
{
MOZ_ASSERT(!mResolved);
mResolved = true;
mResult = aRv;
};
bool Resolved() const { return mResolved; }
nsresult Result() const { return mResult; }
private:
~SyncResolver() { }
bool mResolved;
nsresult mResult;
NS_INLINE_DECL_REFCOUNTING(Context::QuotaInitRunnable::SyncResolver, override)
};
~QuotaInitRunnable()
{
MOZ_ASSERT(mState == STATE_COMPLETE);
MOZ_ASSERT(!mContext);
MOZ_ASSERT(!mInitAction);
}
enum State
{
STATE_INIT,
STATE_GET_INFO,
STATE_CREATE_QUOTA_MANAGER,
STATE_OPEN_DIRECTORY,
STATE_WAIT_FOR_DIRECTORY_LOCK,
STATE_ENSURE_ORIGIN_INITIALIZED,
STATE_RUN_ON_TARGET,
STATE_RUNNING,
STATE_COMPLETING,
STATE_COMPLETE
};
void Complete(nsresult aResult)
{
MOZ_ASSERT(mState == STATE_RUNNING || NS_FAILED(aResult));
MOZ_ASSERT(NS_SUCCEEDED(mResult));
mResult = aResult;
mState = STATE_COMPLETING;
MOZ_ALWAYS_SUCCEEDS(
mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL));
}
void Clear()
{
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
MOZ_ASSERT(mContext);
mContext = nullptr;
mManager = nullptr;
mInitAction = nullptr;
}
RefPtr<Context> mContext;
RefPtr<ThreadsafeHandle> mThreadsafeHandle;
RefPtr<Manager> mManager;
RefPtr<Data> mData;
nsCOMPtr<nsIThread> mTarget;
RefPtr<Action> mInitAction;
nsCOMPtr<nsIThread> mInitiatingThread;
nsresult mResult;
QuotaInfo mQuotaInfo;
RefPtr<DirectoryLock> mDirectoryLock;
State mState;
Atomic<bool> mCanceled;
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIRUNNABLE
};
void
Context::QuotaInitRunnable::OpenDirectory()
{
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
MOZ_ASSERT(mState == STATE_CREATE_QUOTA_MANAGER ||
mState == STATE_OPEN_DIRECTORY);
MOZ_ASSERT(QuotaManager::Get());
// QuotaManager::OpenDirectory() will hold a reference to us as
// a listener. We will then get DirectoryLockAcquired() on the owning
// thread when it is safe to access our storage directory.
mState = STATE_WAIT_FOR_DIRECTORY_LOCK;
QuotaManager::Get()->OpenDirectory(PERSISTENCE_TYPE_DEFAULT,
mQuotaInfo.mGroup,
mQuotaInfo.mOrigin,
mQuotaInfo.mIsApp,
quota::Client::DOMCACHE,
/* aExclusive */ false,
this);
}
void
Context::QuotaInitRunnable::DirectoryLockAcquired(DirectoryLock* aLock)
{
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
MOZ_ASSERT(mState == STATE_WAIT_FOR_DIRECTORY_LOCK);
MOZ_ASSERT(!mDirectoryLock);
mDirectoryLock = aLock;
if (mCanceled) {
Complete(NS_ERROR_ABORT);
return;
}
QuotaManager* qm = QuotaManager::Get();
MOZ_ASSERT(qm);
mState = STATE_ENSURE_ORIGIN_INITIALIZED;
nsresult rv = qm->IOThread()->Dispatch(this, nsIThread::DISPATCH_NORMAL);
if (NS_WARN_IF(NS_FAILED(rv))) {
Complete(rv);
return;
}
}
void
Context::QuotaInitRunnable::DirectoryLockFailed()
{
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
MOZ_ASSERT(mState == STATE_WAIT_FOR_DIRECTORY_LOCK);
MOZ_ASSERT(!mDirectoryLock);
NS_WARNING("Failed to acquire a directory lock!");
Complete(NS_ERROR_FAILURE);
}
NS_IMPL_ISUPPORTS(mozilla::dom::cache::Context::QuotaInitRunnable, nsIRunnable);
// The QuotaManager init state machine is represented in the following diagram:
//
// +---------------+
// | Start | Resolve(error)
// | (Orig Thread) +---------------------+
// +-------+-------+ |
// | |
// +----------v-----------+ |
// | GetInfo | Resolve(error) |
// | (Main Thread) +-----------------+
// +----------+-----------+ |
// | |
// +----------v-----------+ |
// | CreateQuotaManager | Resolve(error) |
// | (Orig Thread) +-----------------+
// +----------+-----------+ |
// | |
// +----------v-----------+ |
// | OpenDirectory | Resolve(error) |
// | (Orig Thread) +-----------------+
// +----------+-----------+ |
// | |
// +----------v-----------+ |
// | WaitForDirectoryLock | Resolve(error) |
// | (Orig Thread) +-----------------+
// +----------+-----------+ |
// | |
// +----------v------------+ |
// |EnsureOriginInitialized| Resolve(error) |
// | (Quota IO Thread) +----------------+
// +----------+------------+ |
// | |
// +----------v------------+ |
// | RunOnTarget | Resolve(error) |
// | (Target Thread) +----------------+
// +----------+------------+ |
// | |
// +---------v---------+ +------v------+
// | Running | | Completing |
// | (Target Thread) +------------>(Orig Thread)|
// +-------------------+ +------+------+
// |
// +-----v----+
// | Complete |
// +----------+
//
// The initialization process proceeds through the main states. If an error
// occurs, then we transition to Completing state back on the original thread.
NS_IMETHODIMP
Context::QuotaInitRunnable::Run()
{
// May run on different threads depending on the state. See individual
// state cases for thread assertions.
RefPtr<SyncResolver> resolver = new SyncResolver();
switch(mState) {
// -----------------------------------
case STATE_GET_INFO:
{
MOZ_ASSERT(NS_IsMainThread());
if (mCanceled) {
resolver->Resolve(NS_ERROR_ABORT);
break;
}
RefPtr<ManagerId> managerId = mManager->GetManagerId();
nsCOMPtr<nsIPrincipal> principal = managerId->Principal();
nsresult rv = QuotaManager::GetInfoFromPrincipal(principal,
&mQuotaInfo.mGroup,
&mQuotaInfo.mOrigin,
&mQuotaInfo.mIsApp);
if (NS_WARN_IF(NS_FAILED(rv))) {
resolver->Resolve(rv);
break;
}
mState = STATE_CREATE_QUOTA_MANAGER;
MOZ_ALWAYS_SUCCEEDS(
mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL));
break;
}
// ----------------------------------
case STATE_CREATE_QUOTA_MANAGER:
{
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
if (mCanceled || QuotaManager::IsShuttingDown()) {
resolver->Resolve(NS_ERROR_ABORT);
break;
}
if (QuotaManager::Get()) {
OpenDirectory();
return NS_OK;
}
mState = STATE_OPEN_DIRECTORY;
QuotaManager::GetOrCreate(this);
break;
}
// ----------------------------------
case STATE_OPEN_DIRECTORY:
{
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
if (NS_WARN_IF(!QuotaManager::Get())) {
resolver->Resolve(NS_ERROR_FAILURE);
break;
}
OpenDirectory();
break;
}
// ----------------------------------
case STATE_ENSURE_ORIGIN_INITIALIZED:
{
AssertIsOnIOThread();
if (mCanceled) {
resolver->Resolve(NS_ERROR_ABORT);
break;
}
QuotaManager* qm = QuotaManager::Get();
MOZ_ASSERT(qm);
nsresult rv = qm->EnsureOriginIsInitialized(PERSISTENCE_TYPE_DEFAULT,
mQuotaInfo.mGroup,
mQuotaInfo.mOrigin,
mQuotaInfo.mIsApp,
getter_AddRefs(mQuotaInfo.mDir));
if (NS_FAILED(rv)) {
resolver->Resolve(rv);
break;
}
mState = STATE_RUN_ON_TARGET;
MOZ_ALWAYS_SUCCEEDS(
mTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
break;
}
// -------------------
case STATE_RUN_ON_TARGET:
{
MOZ_ASSERT(NS_GetCurrentThread() == mTarget);
mState = STATE_RUNNING;
// Execute the provided initialization Action. The Action must Resolve()
// before returning.
mInitAction->RunOnTarget(resolver, mQuotaInfo, mData);
MOZ_ASSERT(resolver->Resolved());
mData = nullptr;
// If the database was opened, then we should always succeed when creating
// the marker file. If it wasn't opened successfully, then no need to
// create a marker file anyway.
if (NS_SUCCEEDED(resolver->Result())) {
MOZ_ALWAYS_SUCCEEDS(CreateMarkerFile(mQuotaInfo));
}
break;
}
// -------------------
case STATE_COMPLETING:
{
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
mInitAction->CompleteOnInitiatingThread(mResult);
mContext->OnQuotaInit(mResult, mQuotaInfo, mDirectoryLock.forget());
mState = STATE_COMPLETE;
// Explicitly cleanup here as the destructor could fire on any of
// the threads we have bounced through.
Clear();
break;
}
// -----
case STATE_WAIT_FOR_DIRECTORY_LOCK:
default:
{
MOZ_CRASH("unexpected state in QuotaInitRunnable");
}
}
if (resolver->Resolved()) {
Complete(resolver->Result());
}
return NS_OK;
}
// Runnable wrapper around Action objects dispatched on the Context. This
// runnable executes the Action on the appropriate threads while the Context
// is initialized.
class Context::ActionRunnable final : public nsIRunnable
, public Action::Resolver
, public Context::Activity
{
public:
ActionRunnable(Context* aContext, Data* aData, nsIEventTarget* aTarget,
Action* aAction, const QuotaInfo& aQuotaInfo)
: mContext(aContext)
, mData(aData)
, mTarget(aTarget)
, mAction(aAction)
, mQuotaInfo(aQuotaInfo)
, mInitiatingThread(NS_GetCurrentThread())
, mState(STATE_INIT)
, mResult(NS_OK)
, mExecutingRunOnTarget(false)
{
MOZ_ASSERT(mContext);
// mData may be nullptr
MOZ_ASSERT(mTarget);
MOZ_ASSERT(mAction);
// mQuotaInfo.mDir may be nullptr if QuotaInitRunnable failed
MOZ_ASSERT(mInitiatingThread);
}
nsresult Dispatch()
{
NS_ASSERT_OWNINGTHREAD(ActionRunnable);
MOZ_ASSERT(mState == STATE_INIT);
mState = STATE_RUN_ON_TARGET;
nsresult rv = mTarget->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
if (NS_WARN_IF(NS_FAILED(rv))) {
mState = STATE_COMPLETE;
Clear();
}
return rv;
}
virtual bool
MatchesCacheId(CacheId aCacheId) const override
{
NS_ASSERT_OWNINGTHREAD(ActionRunnable);
return mAction->MatchesCacheId(aCacheId);
}
virtual void
Cancel() override
{
NS_ASSERT_OWNINGTHREAD(ActionRunnable);
mAction->CancelOnInitiatingThread();
}
virtual void Resolve(nsresult aRv) override
{
MOZ_ASSERT(mTarget == NS_GetCurrentThread());
MOZ_ASSERT(mState == STATE_RUNNING);
mResult = aRv;
// We ultimately must complete on the initiating thread, but bounce through
// the current thread again to ensure that we don't destroy objects and
// state out from under the currently running action's stack.
mState = STATE_RESOLVING;
// If we were resolved synchronously within Action::RunOnTarget() then we
// can avoid a thread bounce and just resolve once RunOnTarget() returns.
// The Run() method will handle this by looking at mState after
// RunOnTarget() returns.
if (mExecutingRunOnTarget) {
return;
}
// Otherwise we are in an asynchronous resolve. And must perform a thread
// bounce to run on the target thread again.
MOZ_ALWAYS_SUCCEEDS(
mTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
}
private:
~ActionRunnable()
{
MOZ_ASSERT(mState == STATE_COMPLETE);
MOZ_ASSERT(!mContext);
MOZ_ASSERT(!mAction);
}
void Clear()
{
NS_ASSERT_OWNINGTHREAD(ActionRunnable);
MOZ_ASSERT(mContext);
MOZ_ASSERT(mAction);
mContext->RemoveActivity(this);
mContext = nullptr;
mAction = nullptr;
}
enum State
{
STATE_INIT,
STATE_RUN_ON_TARGET,
STATE_RUNNING,
STATE_RESOLVING,
STATE_COMPLETING,
STATE_COMPLETE
};
RefPtr<Context> mContext;
RefPtr<Data> mData;
nsCOMPtr<nsIEventTarget> mTarget;
RefPtr<Action> mAction;
const QuotaInfo mQuotaInfo;
nsCOMPtr<nsIThread> mInitiatingThread;
State mState;
nsresult mResult;
// Only accessible on target thread;
bool mExecutingRunOnTarget;
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIRUNNABLE
};
NS_IMPL_ISUPPORTS(mozilla::dom::cache::Context::ActionRunnable, nsIRunnable);
// The ActionRunnable has a simpler state machine. It basically needs to run
// the action on the target thread and then complete on the original thread.
//
// +-------------+
// | Start |
// |(Orig Thread)|
// +-----+-------+
// |
// +-------v---------+
// | RunOnTarget |
// |Target IO Thread)+---+ Resolve()
// +-------+---------+ |
// | |
// +-------v----------+ |
// | Running | |
// |(Target IO Thread)| |
// +------------------+ |
// | Resolve() |
// +-------v----------+ |
// | Resolving <--+ +-------------+
// | | | Completing |
// |(Target IO Thread)+---------------------->(Orig Thread)|
// +------------------+ +-------+-----+
// |
// |
// +----v---+
// |Complete|
// +--------+
//
// Its important to note that synchronous actions will effectively Resolve()
// out of the Running state immediately. Asynchronous Actions may remain
// in the Running state for some time, but normally the ActionRunnable itself
// does not see any execution there. Its all handled internal to the Action.
NS_IMETHODIMP
Context::ActionRunnable::Run()
{
switch(mState) {
// ----------------------
case STATE_RUN_ON_TARGET:
{
MOZ_ASSERT(NS_GetCurrentThread() == mTarget);
MOZ_ASSERT(!mExecutingRunOnTarget);
// Note that we are calling RunOnTarget(). This lets us detect
// if Resolve() is called synchronously.
AutoRestore<bool> executingRunOnTarget(mExecutingRunOnTarget);
mExecutingRunOnTarget = true;
mState = STATE_RUNNING;
mAction->RunOnTarget(this, mQuotaInfo, mData);
mData = nullptr;
// Resolve was called synchronously from RunOnTarget(). We can
// immediately move to completing now since we are sure RunOnTarget()
// completed.
if (mState == STATE_RESOLVING) {
// Use recursion instead of switch case fall-through... Seems slightly
// easier to understand.
Run();
}
break;
}
// -----------------
case STATE_RESOLVING:
{
MOZ_ASSERT(NS_GetCurrentThread() == mTarget);
// The call to Action::RunOnTarget() must have returned now if we
// are running on the target thread again. We may now proceed
// with completion.
mState = STATE_COMPLETING;
// Shutdown must be delayed until all Contexts are destroyed. Crash
// for this invariant violation.
MOZ_ALWAYS_SUCCEEDS(
mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL));
break;
}
// -------------------
case STATE_COMPLETING:
{
NS_ASSERT_OWNINGTHREAD(ActionRunnable);
mAction->CompleteOnInitiatingThread(mResult);
mState = STATE_COMPLETE;
// Explicitly cleanup here as the destructor could fire on any of
// the threads we have bounced through.
Clear();
break;
}
// -----------------
default:
{
MOZ_CRASH("unexpected state in ActionRunnable");
break;
}
}
return NS_OK;
}
void
Context::ThreadsafeHandle::AllowToClose()
{
if (mOwningThread == NS_GetCurrentThread()) {
AllowToCloseOnOwningThread();
return;
}
// Dispatch is guaranteed to succeed here because we block shutdown until
// all Contexts have been destroyed.
nsCOMPtr<nsIRunnable> runnable =
NewRunnableMethod(this, &ThreadsafeHandle::AllowToCloseOnOwningThread);
MOZ_ALWAYS_SUCCEEDS(
mOwningThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL));
}
void
Context::ThreadsafeHandle::InvalidateAndAllowToClose()
{
if (mOwningThread == NS_GetCurrentThread()) {
InvalidateAndAllowToCloseOnOwningThread();
return;
}
// Dispatch is guaranteed to succeed here because we block shutdown until
// all Contexts have been destroyed.
nsCOMPtr<nsIRunnable> runnable =
NewRunnableMethod(this, &ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread);
MOZ_ALWAYS_SUCCEEDS(
mOwningThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL));
}
Context::ThreadsafeHandle::ThreadsafeHandle(Context* aContext)
: mStrongRef(aContext)
, mWeakRef(aContext)
, mOwningThread(NS_GetCurrentThread())
{
}
Context::ThreadsafeHandle::~ThreadsafeHandle()
{
// Normally we only touch mStrongRef on the owning thread. This is safe,
// however, because when we do use mStrongRef on the owning thread we are
// always holding a strong ref to the ThreadsafeHandle via the owning
// runnable. So we cannot run the ThreadsafeHandle destructor simultaneously.
if (!mStrongRef || mOwningThread == NS_GetCurrentThread()) {
return;
}
// Dispatch is guaranteed to succeed here because we block shutdown until
// all Contexts have been destroyed.
NS_ProxyRelease(mOwningThread, mStrongRef.forget());
}
void
Context::ThreadsafeHandle::AllowToCloseOnOwningThread()
{
MOZ_ASSERT(mOwningThread == NS_GetCurrentThread());
// A Context "closes" when its ref count drops to zero. Dropping this
// strong ref is necessary, but not sufficient for the close to occur.
// Any outstanding IO will continue and keep the Context alive. Once
// the Context is idle, it will be destroyed.
// First, tell the context to flush any target thread shared data. This
// data must be released on the target thread prior to running the Context
// destructor. This will schedule an Action which ensures that the
// ~Context() is not immediately executed when we drop the strong ref.
if (mStrongRef) {
mStrongRef->DoomTargetData();
}
// Now drop our strong ref and let Context finish running any outstanding
// Actions.
mStrongRef = nullptr;
}
void
Context::ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread()
{
MOZ_ASSERT(mOwningThread == NS_GetCurrentThread());
// Cancel the Context through the weak reference. This means we can
// allow the Context to close by dropping the strong ref, but then
// still cancel ongoing IO if necessary.
if (mWeakRef) {
mWeakRef->Invalidate();
}
// We should synchronously have AllowToCloseOnOwningThread called when
// the Context is canceled.
MOZ_ASSERT(!mStrongRef);
}
void
Context::ThreadsafeHandle::ContextDestroyed(Context* aContext)
{
MOZ_ASSERT(mOwningThread == NS_GetCurrentThread());
MOZ_ASSERT(!mStrongRef);
MOZ_ASSERT(mWeakRef);
MOZ_ASSERT(mWeakRef == aContext);
mWeakRef = nullptr;
}
// static
already_AddRefed<Context>
Context::Create(Manager* aManager, nsIThread* aTarget,
Action* aInitAction, Context* aOldContext)
{
RefPtr<Context> context = new Context(aManager, aTarget, aInitAction);
context->Init(aOldContext);
return context.forget();
}
Context::Context(Manager* aManager, nsIThread* aTarget, Action* aInitAction)
: mManager(aManager)
, mTarget(aTarget)
, mData(new Data(aTarget))
, mState(STATE_CONTEXT_PREINIT)
, mOrphanedData(false)
, mInitAction(aInitAction)
{
MOZ_ASSERT(mManager);
MOZ_ASSERT(mTarget);
}
void
Context::Dispatch(Action* aAction)
{
NS_ASSERT_OWNINGTHREAD(Context);
MOZ_ASSERT(aAction);
MOZ_ASSERT(mState != STATE_CONTEXT_CANCELED);
if (mState == STATE_CONTEXT_CANCELED) {
return;
} else if (mState == STATE_CONTEXT_INIT ||
mState == STATE_CONTEXT_PREINIT) {
PendingAction* pending = mPendingActions.AppendElement();
pending->mAction = aAction;
return;
}
MOZ_ASSERT(STATE_CONTEXT_READY);
DispatchAction(aAction);
}
void
Context::CancelAll()
{
NS_ASSERT_OWNINGTHREAD(Context);
// In PREINIT state we have not dispatch the init action yet. Just
// forget it.
if (mState == STATE_CONTEXT_PREINIT) {
MOZ_ASSERT(!mInitRunnable);
mInitAction = nullptr;
// In INIT state we have dispatched the runnable, but not received the
// async completion yet. Cancel the runnable, but don't forget about it
// until we get OnQuotaInit() callback.
} else if (mState == STATE_CONTEXT_INIT) {
mInitRunnable->Cancel();
}
mState = STATE_CONTEXT_CANCELED;
mPendingActions.Clear();
{
ActivityList::ForwardIterator iter(mActivityList);
while (iter.HasMore()) {
iter.GetNext()->Cancel();
}
}
AllowToClose();
}
bool
Context::IsCanceled() const
{
NS_ASSERT_OWNINGTHREAD(Context);
return mState == STATE_CONTEXT_CANCELED;
}
void
Context::Invalidate()
{
NS_ASSERT_OWNINGTHREAD(Context);
mManager->NoteClosing();
CancelAll();
}
void
Context::AllowToClose()
{
NS_ASSERT_OWNINGTHREAD(Context);
if (mThreadsafeHandle) {
mThreadsafeHandle->AllowToClose();
}
}
void
Context::CancelForCacheId(CacheId aCacheId)
{
NS_ASSERT_OWNINGTHREAD(Context);
// Remove matching pending actions
for (int32_t i = mPendingActions.Length() - 1; i >= 0; --i) {
if (mPendingActions[i].mAction->MatchesCacheId(aCacheId)) {
mPendingActions.RemoveElementAt(i);
}
}
// Cancel activities and let them remove themselves
ActivityList::ForwardIterator iter(mActivityList);
while (iter.HasMore()) {
Activity* activity = iter.GetNext();
if (activity->MatchesCacheId(aCacheId)) {
activity->Cancel();
}
}
}
Context::~Context()
{
NS_ASSERT_OWNINGTHREAD(Context);
MOZ_ASSERT(mManager);
MOZ_ASSERT(!mData);
if (mThreadsafeHandle) {
mThreadsafeHandle->ContextDestroyed(this);
}
// Note, this may set the mOrphanedData flag.
mManager->RemoveContext(this);
if (mQuotaInfo.mDir && !mOrphanedData) {
MOZ_ALWAYS_SUCCEEDS(DeleteMarkerFile(mQuotaInfo));
}
if (mNextContext) {
mNextContext->Start();
}
}
void
Context::Init(Context* aOldContext)
{
NS_ASSERT_OWNINGTHREAD(Context);
if (aOldContext) {
aOldContext->SetNextContext(this);
return;
}
Start();
}
void
Context::Start()
{
NS_ASSERT_OWNINGTHREAD(Context);
// Previous context closing delayed our start, but then we were canceled.
// In this case, just do nothing here.
if (mState == STATE_CONTEXT_CANCELED) {
MOZ_ASSERT(!mInitRunnable);
MOZ_ASSERT(!mInitAction);
return;
}
MOZ_ASSERT(mState == STATE_CONTEXT_PREINIT);
MOZ_ASSERT(!mInitRunnable);
mInitRunnable = new QuotaInitRunnable(this, mManager, mData, mTarget,
mInitAction);
mInitAction = nullptr;
mState = STATE_CONTEXT_INIT;
nsresult rv = mInitRunnable->Dispatch();
if (NS_FAILED(rv)) {
// Shutdown must be delayed until all Contexts are destroyed. Shutdown
// must also prevent any new Contexts from being constructed. Crash
// for this invariant violation.
MOZ_CRASH("Failed to dispatch QuotaInitRunnable.");
}
}
void
Context::DispatchAction(Action* aAction, bool aDoomData)
{
NS_ASSERT_OWNINGTHREAD(Context);
RefPtr<ActionRunnable> runnable =
new ActionRunnable(this, mData, mTarget, aAction, mQuotaInfo);
if (aDoomData) {
mData = nullptr;
}
nsresult rv = runnable->Dispatch();
if (NS_FAILED(rv)) {
// Shutdown must be delayed until all Contexts are destroyed. Crash
// for this invariant violation.
MOZ_CRASH("Failed to dispatch ActionRunnable to target thread.");
}
AddActivity(runnable);
}
void
Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo,
already_AddRefed<DirectoryLock> aDirectoryLock)
{
NS_ASSERT_OWNINGTHREAD(Context);
MOZ_ASSERT(mInitRunnable);
mInitRunnable = nullptr;
mQuotaInfo = aQuotaInfo;
// Always save the directory lock to ensure QuotaManager does not shutdown
// before the Context has gone away.
MOZ_ASSERT(!mDirectoryLock);
mDirectoryLock = aDirectoryLock;
// If we opening the context failed, but we were not explicitly canceled,
// still treat the entire context as canceled. We don't want to allow
// new actions to be dispatched. We also cannot leave the context in
// the INIT state after failing to open.
if (NS_FAILED(aRv)) {
mState = STATE_CONTEXT_CANCELED;
}
if (mState == STATE_CONTEXT_CANCELED) {
for (uint32_t i = 0; i < mPendingActions.Length(); ++i) {
mPendingActions[i].mAction->CompleteOnInitiatingThread(aRv);
}
mPendingActions.Clear();
mThreadsafeHandle->AllowToClose();
// Context will destruct after return here and last ref is released.
return;
}
MOZ_ASSERT(mState == STATE_CONTEXT_INIT);
mState = STATE_CONTEXT_READY;
for (uint32_t i = 0; i < mPendingActions.Length(); ++i) {
DispatchAction(mPendingActions[i].mAction);
}
mPendingActions.Clear();
}
void
Context::AddActivity(Activity* aActivity)
{
NS_ASSERT_OWNINGTHREAD(Context);
MOZ_ASSERT(aActivity);
MOZ_ASSERT(!mActivityList.Contains(aActivity));
mActivityList.AppendElement(aActivity);
}
void
Context::RemoveActivity(Activity* aActivity)
{
NS_ASSERT_OWNINGTHREAD(Context);
MOZ_ASSERT(aActivity);
MOZ_ALWAYS_TRUE(mActivityList.RemoveElement(aActivity));
MOZ_ASSERT(!mActivityList.Contains(aActivity));
}
void
Context::NoteOrphanedData()
{
NS_ASSERT_OWNINGTHREAD(Context);
// This may be called more than once
mOrphanedData = true;
}
already_AddRefed<Context::ThreadsafeHandle>
Context::CreateThreadsafeHandle()
{
NS_ASSERT_OWNINGTHREAD(Context);
if (!mThreadsafeHandle) {
mThreadsafeHandle = new ThreadsafeHandle(this);
}
RefPtr<ThreadsafeHandle> ref = mThreadsafeHandle;
return ref.forget();
}
void
Context::SetNextContext(Context* aNextContext)
{
NS_ASSERT_OWNINGTHREAD(Context);
MOZ_ASSERT(aNextContext);
MOZ_ASSERT(!mNextContext);
mNextContext = aNextContext;
}
void
Context::DoomTargetData()
{
NS_ASSERT_OWNINGTHREAD(Context);
MOZ_ASSERT(mData);
// We are about to drop our reference to the Data. We need to ensure that
// the ~Context() destructor does not run until contents of Data have been
// released on the Target thread.
// Dispatch a no-op Action. This will hold the Context alive through a
// roundtrip to the target thread and back to the owning thread. The
// ref to the Data object is cleared on the owning thread after creating
// the ActionRunnable, but before dispatching it.
RefPtr<Action> action = new NullAction();
DispatchAction(action, true /* doomed data */);
MOZ_ASSERT(!mData);
}
} // namespace cache
} // namespace dom
} // namespace mozilla