/* -*- 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 "ServiceWorkerInfo.h" #include "ServiceWorkerScriptCache.h" BEGIN_WORKERS_NAMESPACE static_assert(nsIServiceWorkerInfo::STATE_PARSED == static_cast(ServiceWorkerState::Parsed), "ServiceWorkerState enumeration value should match state values from nsIServiceWorkerInfo."); static_assert(nsIServiceWorkerInfo::STATE_INSTALLING == static_cast(ServiceWorkerState::Installing), "ServiceWorkerState enumeration value should match state values from nsIServiceWorkerInfo."); static_assert(nsIServiceWorkerInfo::STATE_INSTALLED == static_cast(ServiceWorkerState::Installed), "ServiceWorkerState enumeration value should match state values from nsIServiceWorkerInfo."); static_assert(nsIServiceWorkerInfo::STATE_ACTIVATING == static_cast(ServiceWorkerState::Activating), "ServiceWorkerState enumeration value should match state values from nsIServiceWorkerInfo."); static_assert(nsIServiceWorkerInfo::STATE_ACTIVATED == static_cast(ServiceWorkerState::Activated), "ServiceWorkerState enumeration value should match state values from nsIServiceWorkerInfo."); static_assert(nsIServiceWorkerInfo::STATE_REDUNDANT == static_cast(ServiceWorkerState::Redundant), "ServiceWorkerState enumeration value should match state values from nsIServiceWorkerInfo."); static_assert(nsIServiceWorkerInfo::STATE_UNKNOWN == static_cast(ServiceWorkerState::EndGuard_), "ServiceWorkerState enumeration value should match state values from nsIServiceWorkerInfo."); NS_IMPL_ISUPPORTS(ServiceWorkerInfo, nsIServiceWorkerInfo) NS_IMETHODIMP ServiceWorkerInfo::GetScriptSpec(nsAString& aScriptSpec) { AssertIsOnMainThread(); CopyUTF8toUTF16(mScriptSpec, aScriptSpec); return NS_OK; } NS_IMETHODIMP ServiceWorkerInfo::GetCacheName(nsAString& aCacheName) { AssertIsOnMainThread(); aCacheName = mCacheName; return NS_OK; } NS_IMETHODIMP ServiceWorkerInfo::GetState(uint16_t* aState) { MOZ_ASSERT(aState); AssertIsOnMainThread(); *aState = static_cast(State()); return NS_OK; } NS_IMETHODIMP ServiceWorkerInfo::GetDebugger(nsIWorkerDebugger** aResult) { if (NS_WARN_IF(!aResult)) { return NS_ERROR_FAILURE; } return mServiceWorkerPrivate->GetDebugger(aResult); } NS_IMETHODIMP ServiceWorkerInfo::GetHandlesFetchEvents(bool* aValue) { MOZ_ASSERT(aValue); AssertIsOnMainThread(); *aValue = HandlesFetch(); return NS_OK; } NS_IMETHODIMP ServiceWorkerInfo::GetInstalledTime(PRTime* _retval) { AssertIsOnMainThread(); MOZ_ASSERT(_retval); *_retval = mInstalledTime; return NS_OK; } NS_IMETHODIMP ServiceWorkerInfo::GetActivatedTime(PRTime* _retval) { AssertIsOnMainThread(); MOZ_ASSERT(_retval); *_retval = mActivatedTime; return NS_OK; } NS_IMETHODIMP ServiceWorkerInfo::GetRedundantTime(PRTime* _retval) { AssertIsOnMainThread(); MOZ_ASSERT(_retval); *_retval = mRedundantTime; return NS_OK; } NS_IMETHODIMP ServiceWorkerInfo::AttachDebugger() { return mServiceWorkerPrivate->AttachDebugger(); } NS_IMETHODIMP ServiceWorkerInfo::DetachDebugger() { return mServiceWorkerPrivate->DetachDebugger(); } void ServiceWorkerInfo::AppendWorker(ServiceWorker* aWorker) { MOZ_ASSERT(aWorker); #ifdef DEBUG nsAutoString workerURL; aWorker->GetScriptURL(workerURL); MOZ_ASSERT(workerURL.Equals(NS_ConvertUTF8toUTF16(mScriptSpec))); #endif MOZ_ASSERT(!mInstances.Contains(aWorker)); mInstances.AppendElement(aWorker); aWorker->SetState(State()); } void ServiceWorkerInfo::RemoveWorker(ServiceWorker* aWorker) { MOZ_ASSERT(aWorker); #ifdef DEBUG nsAutoString workerURL; aWorker->GetScriptURL(workerURL); MOZ_ASSERT(workerURL.Equals(NS_ConvertUTF8toUTF16(mScriptSpec))); #endif MOZ_ASSERT(mInstances.Contains(aWorker)); mInstances.RemoveElement(aWorker); } namespace { class ChangeStateUpdater final : public Runnable { public: ChangeStateUpdater(const nsTArray& aInstances, ServiceWorkerState aState) : Runnable("dom::workers::ChangeStateUpdater") , mState(aState) { for (size_t i = 0; i < aInstances.Length(); ++i) { mInstances.AppendElement(aInstances[i]); } } NS_IMETHOD Run() override { // We need to update the state of all instances atomically before notifying // them to make sure that the observed state for all instances inside // statechange event handlers is correct. for (size_t i = 0; i < mInstances.Length(); ++i) { mInstances[i]->SetState(mState); } for (size_t i = 0; i < mInstances.Length(); ++i) { mInstances[i]->DispatchStateChange(mState); } return NS_OK; } private: AutoTArray, 1> mInstances; ServiceWorkerState mState; }; } void ServiceWorkerInfo::UpdateState(ServiceWorkerState aState) { AssertIsOnMainThread(); #ifdef DEBUG // Any state can directly transition to redundant, but everything else is // ordered. if (aState != ServiceWorkerState::Redundant) { MOZ_ASSERT_IF(State() == ServiceWorkerState::EndGuard_, aState == ServiceWorkerState::Installing); MOZ_ASSERT_IF(State() == ServiceWorkerState::Installing, aState == ServiceWorkerState::Installed); MOZ_ASSERT_IF(State() == ServiceWorkerState::Installed, aState == ServiceWorkerState::Activating); MOZ_ASSERT_IF(State() == ServiceWorkerState::Activating, aState == ServiceWorkerState::Activated); } // Activated can only go to redundant. MOZ_ASSERT_IF(State() == ServiceWorkerState::Activated, aState == ServiceWorkerState::Redundant); #endif // Flush any pending functional events to the worker when it transitions to the // activated state. // TODO: Do we care that these events will race with the propagation of the // state change? if (State() != aState) { mServiceWorkerPrivate->UpdateState(aState); } mDescriptor.SetState(aState); nsCOMPtr r = new ChangeStateUpdater(mInstances, State()); MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r.forget())); if (State() == ServiceWorkerState::Redundant) { serviceWorkerScriptCache::PurgeCache(mPrincipal, mCacheName); } } ServiceWorkerInfo::ServiceWorkerInfo(nsIPrincipal* aPrincipal, const nsACString& aScope, const nsACString& aScriptSpec, const nsAString& aCacheName, nsLoadFlags aImportsLoadFlags) : mPrincipal(aPrincipal) , mScriptSpec(aScriptSpec) , mCacheName(aCacheName) , mImportsLoadFlags(aImportsLoadFlags) , mCreationTime(PR_Now()) , mCreationTimeStamp(TimeStamp::Now()) , mInstalledTime(0) , mActivatedTime(0) , mRedundantTime(0) , mServiceWorkerPrivate(new ServiceWorkerPrivate(this)) , mSkipWaitingFlag(false) , mHandlesFetch(Unknown) { MOZ_ASSERT(mPrincipal); // cache origin attributes so we can use them off main thread mOriginAttributes = mPrincipal->OriginAttributesRef(); MOZ_ASSERT(!mScriptSpec.IsEmpty()); MOZ_ASSERT(!mCacheName.IsEmpty()); // Scripts of a service worker should always be loaded bypass service workers. // Otherwise, we might not be able to update a service worker correctly, if // there is a service worker generating the script. MOZ_DIAGNOSTIC_ASSERT(mImportsLoadFlags & nsIChannel::LOAD_BYPASS_SERVICE_WORKER); PrincipalInfo principalInfo; MOZ_ALWAYS_SUCCEEDS(PrincipalToPrincipalInfo(aPrincipal, &principalInfo)); mDescriptor = ServiceWorkerDescriptor(GetNextID(), principalInfo, aScope, ServiceWorkerState::Parsed); } ServiceWorkerInfo::~ServiceWorkerInfo() { MOZ_ASSERT(mServiceWorkerPrivate); mServiceWorkerPrivate->NoteDeadServiceWorkerInfo(); } static uint64_t gServiceWorkerInfoCurrentID = 0; uint64_t ServiceWorkerInfo::GetNextID() const { return ++gServiceWorkerInfoCurrentID; } already_AddRefed ServiceWorkerInfo::GetOrCreateInstance(nsPIDOMWindowInner* aWindow) { AssertIsOnMainThread(); MOZ_ASSERT(aWindow); RefPtr ref; for (uint32_t i = 0; i < mInstances.Length(); ++i) { MOZ_ASSERT(mInstances[i]); if (mInstances[i]->GetOwner() == aWindow) { ref = mInstances[i]; break; } } if (!ref) { ref = new ServiceWorker(aWindow, this); } return ref.forget(); } void ServiceWorkerInfo::UpdateInstalledTime() { MOZ_ASSERT(State() == ServiceWorkerState::Installed); MOZ_ASSERT(mInstalledTime == 0); mInstalledTime = mCreationTime + static_cast((TimeStamp::Now() - mCreationTimeStamp).ToMicroseconds()); } void ServiceWorkerInfo::UpdateActivatedTime() { MOZ_ASSERT(State() == ServiceWorkerState::Activated); MOZ_ASSERT(mActivatedTime == 0); mActivatedTime = mCreationTime + static_cast((TimeStamp::Now() - mCreationTimeStamp).ToMicroseconds()); } void ServiceWorkerInfo::UpdateRedundantTime() { MOZ_ASSERT(State() == ServiceWorkerState::Redundant); MOZ_ASSERT(mRedundantTime == 0); mRedundantTime = mCreationTime + static_cast((TimeStamp::Now() - mCreationTimeStamp).ToMicroseconds()); } END_WORKERS_NAMESPACE