summaryrefslogtreecommitdiffstats
path: root/dom/workers/ServiceWorkerUpdateJob.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/workers/ServiceWorkerUpdateJob.cpp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/workers/ServiceWorkerUpdateJob.cpp')
-rw-r--r--dom/workers/ServiceWorkerUpdateJob.cpp552
1 files changed, 552 insertions, 0 deletions
diff --git a/dom/workers/ServiceWorkerUpdateJob.cpp b/dom/workers/ServiceWorkerUpdateJob.cpp
new file mode 100644
index 000000000..614fe4de5
--- /dev/null
+++ b/dom/workers/ServiceWorkerUpdateJob.cpp
@@ -0,0 +1,552 @@
+/* -*- 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 "ServiceWorkerUpdateJob.h"
+
+#include "nsIScriptError.h"
+#include "nsIURL.h"
+#include "ServiceWorkerScriptCache.h"
+#include "Workers.h"
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+namespace {
+
+/**
+ * The spec mandates slightly different behaviors for computing the scope
+ * prefix string in case a Service-Worker-Allowed header is specified versus
+ * when it's not available.
+ *
+ * With the header:
+ * "Set maxScopeString to "/" concatenated with the strings in maxScope's
+ * path (including empty strings), separated from each other by "/"."
+ * Without the header:
+ * "Set maxScopeString to "/" concatenated with the strings, except the last
+ * string that denotes the script's file name, in registration's registering
+ * script url's path (including empty strings), separated from each other by
+ * "/"."
+ *
+ * In simpler terms, if the header is not present, we should only use the
+ * "directory" part of the pathname, and otherwise the entire pathname should be
+ * used. ScopeStringPrefixMode allows the caller to specify the desired
+ * behavior.
+ */
+enum ScopeStringPrefixMode {
+ eUseDirectory,
+ eUsePath
+};
+
+nsresult
+GetRequiredScopeStringPrefix(nsIURI* aScriptURI, nsACString& aPrefix,
+ ScopeStringPrefixMode aPrefixMode)
+{
+ nsresult rv = aScriptURI->GetPrePath(aPrefix);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (aPrefixMode == eUseDirectory) {
+ nsCOMPtr<nsIURL> scriptURL(do_QueryInterface(aScriptURI));
+ if (NS_WARN_IF(!scriptURL)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString dir;
+ rv = scriptURL->GetDirectory(dir);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ aPrefix.Append(dir);
+ } else if (aPrefixMode == eUsePath) {
+ nsAutoCString path;
+ rv = aScriptURI->GetPath(path);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ aPrefix.Append(path);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Invalid value for aPrefixMode");
+ }
+ return NS_OK;
+}
+
+} // anonymous namespace
+
+class ServiceWorkerUpdateJob::CompareCallback final : public serviceWorkerScriptCache::CompareCallback
+{
+ RefPtr<ServiceWorkerUpdateJob> mJob;
+
+ ~CompareCallback()
+ {
+ }
+
+public:
+ explicit CompareCallback(ServiceWorkerUpdateJob* aJob)
+ : mJob(aJob)
+ {
+ MOZ_ASSERT(mJob);
+ }
+
+ virtual void
+ ComparisonResult(nsresult aStatus,
+ bool aInCacheAndEqual,
+ const nsAString& aNewCacheName,
+ const nsACString& aMaxScope) override
+ {
+ mJob->ComparisonResult(aStatus, aInCacheAndEqual, aNewCacheName, aMaxScope);
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(ServiceWorkerUpdateJob::CompareCallback, override)
+};
+
+class ServiceWorkerUpdateJob::ContinueUpdateRunnable final : public LifeCycleEventCallback
+{
+ nsMainThreadPtrHandle<ServiceWorkerUpdateJob> mJob;
+ bool mSuccess;
+
+public:
+ explicit ContinueUpdateRunnable(const nsMainThreadPtrHandle<ServiceWorkerUpdateJob>& aJob)
+ : mJob(aJob)
+ , mSuccess(false)
+ {
+ AssertIsOnMainThread();
+ }
+
+ void
+ SetResult(bool aResult) override
+ {
+ mSuccess = aResult;
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+ mJob->ContinueUpdateAfterScriptEval(mSuccess);
+ mJob = nullptr;
+ return NS_OK;
+ }
+};
+
+class ServiceWorkerUpdateJob::ContinueInstallRunnable final : public LifeCycleEventCallback
+{
+ nsMainThreadPtrHandle<ServiceWorkerUpdateJob> mJob;
+ bool mSuccess;
+
+public:
+ explicit ContinueInstallRunnable(const nsMainThreadPtrHandle<ServiceWorkerUpdateJob>& aJob)
+ : mJob(aJob)
+ , mSuccess(false)
+ {
+ AssertIsOnMainThread();
+ }
+
+ void
+ SetResult(bool aResult) override
+ {
+ mSuccess = aResult;
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+ mJob->ContinueAfterInstallEvent(mSuccess);
+ mJob = nullptr;
+ return NS_OK;
+ }
+};
+
+ServiceWorkerUpdateJob::ServiceWorkerUpdateJob(nsIPrincipal* aPrincipal,
+ const nsACString& aScope,
+ const nsACString& aScriptSpec,
+ nsILoadGroup* aLoadGroup)
+ : ServiceWorkerJob(Type::Update, aPrincipal, aScope, aScriptSpec)
+ , mLoadGroup(aLoadGroup)
+{
+}
+
+already_AddRefed<ServiceWorkerRegistrationInfo>
+ServiceWorkerUpdateJob::GetRegistration() const
+{
+ AssertIsOnMainThread();
+ RefPtr<ServiceWorkerRegistrationInfo> ref = mRegistration;
+ return ref.forget();
+}
+
+ServiceWorkerUpdateJob::ServiceWorkerUpdateJob(Type aType,
+ nsIPrincipal* aPrincipal,
+ const nsACString& aScope,
+ const nsACString& aScriptSpec,
+ nsILoadGroup* aLoadGroup)
+ : ServiceWorkerJob(aType, aPrincipal, aScope, aScriptSpec)
+ , mLoadGroup(aLoadGroup)
+{
+}
+
+ServiceWorkerUpdateJob::~ServiceWorkerUpdateJob()
+{
+}
+
+void
+ServiceWorkerUpdateJob::FailUpdateJob(ErrorResult& aRv)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aRv.Failed());
+
+ // Cleanup after a failed installation. This essentially implements
+ // step 12 of the Install algorithm.
+ //
+ // https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#installation-algorithm
+ //
+ // The spec currently only runs this after an install event fails,
+ // but we must handle many more internal errors. So we check for
+ // cleanup on every non-successful exit.
+ if (mRegistration) {
+ mRegistration->ClearEvaluating();
+ mRegistration->ClearInstalling();
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ swm->MaybeRemoveRegistration(mRegistration);
+ }
+ }
+
+ mRegistration = nullptr;
+
+ Finish(aRv);
+}
+
+void
+ServiceWorkerUpdateJob::FailUpdateJob(nsresult aRv)
+{
+ ErrorResult rv(aRv);
+ FailUpdateJob(rv);
+}
+
+void
+ServiceWorkerUpdateJob::AsyncExecute()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(GetType() == Type::Update);
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (Canceled() || !swm) {
+ FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
+ return;
+ }
+
+ // Begin step 1 of the Update algorithm.
+ //
+ // https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#update-algorithm
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ swm->GetRegistration(mPrincipal, mScope);
+
+ if (!registration || registration->mPendingUninstall) {
+ ErrorResult rv;
+ rv.ThrowTypeError<MSG_SW_UPDATE_BAD_REGISTRATION>(NS_ConvertUTF8toUTF16(mScope),
+ NS_LITERAL_STRING("uninstalled"));
+ FailUpdateJob(rv);
+ return;
+ }
+
+ // If a Register job with a new script executed ahead of us in the job queue,
+ // then our update for the old script no longer makes sense. Simply abort
+ // in this case.
+ RefPtr<ServiceWorkerInfo> newest = registration->Newest();
+ if (newest && !mScriptSpec.Equals(newest->ScriptSpec())) {
+ ErrorResult rv;
+ rv.ThrowTypeError<MSG_SW_UPDATE_BAD_REGISTRATION>(NS_ConvertUTF8toUTF16(mScope),
+ NS_LITERAL_STRING("changed"));
+ FailUpdateJob(rv);
+ return;
+ }
+
+ SetRegistration(registration);
+ Update();
+}
+
+void
+ServiceWorkerUpdateJob::SetRegistration(ServiceWorkerRegistrationInfo* aRegistration)
+{
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(!mRegistration);
+ MOZ_ASSERT(aRegistration);
+ mRegistration = aRegistration;
+}
+
+void
+ServiceWorkerUpdateJob::Update()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!Canceled());
+
+ // SetRegistration() must be called before Update().
+ MOZ_ASSERT(mRegistration);
+ MOZ_ASSERT(!mRegistration->GetInstalling());
+
+ // Begin the script download and comparison steps starting at step 5
+ // of the Update algorithm.
+
+ RefPtr<ServiceWorkerInfo> workerInfo = mRegistration->Newest();
+ nsAutoString cacheName;
+
+ // If the script has not changed, we need to perform a byte-for-byte
+ // comparison.
+ if (workerInfo && workerInfo->ScriptSpec().Equals(mScriptSpec)) {
+ cacheName = workerInfo->CacheName();
+ }
+
+ RefPtr<CompareCallback> callback = new CompareCallback(this);
+
+ nsresult rv =
+ serviceWorkerScriptCache::Compare(mRegistration, mPrincipal, cacheName,
+ NS_ConvertUTF8toUTF16(mScriptSpec),
+ callback, mLoadGroup);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailUpdateJob(rv);
+ return;
+ }
+}
+
+void
+ServiceWorkerUpdateJob::ComparisonResult(nsresult aStatus,
+ bool aInCacheAndEqual,
+ const nsAString& aNewCacheName,
+ const nsACString& aMaxScope)
+{
+ AssertIsOnMainThread();
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (NS_WARN_IF(Canceled() || !swm)) {
+ FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
+ return;
+ }
+
+ // Handle failure of the download or comparison. This is part of Update
+ // step 5 as "If the algorithm asynchronously completes with null, then:".
+ if (NS_WARN_IF(NS_FAILED(aStatus))) {
+ FailUpdateJob(aStatus);
+ return;
+ }
+
+ // The spec validates the response before performing the byte-for-byte check.
+ // Here we perform the comparison in another module and then validate the
+ // script URL and scope. Make sure to do this validation before accepting
+ // an byte-for-byte match since the service-worker-allowed header might have
+ // changed since the last time it was installed.
+
+ // This is step 2 the "validate response" section of Update algorithm step 5.
+ // Step 1 is performed in the serviceWorkerScriptCache code.
+
+ nsCOMPtr<nsIURI> scriptURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(scriptURI), mScriptSpec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsIURI> maxScopeURI;
+ if (!aMaxScope.IsEmpty()) {
+ rv = NS_NewURI(getter_AddRefs(maxScopeURI), aMaxScope,
+ nullptr, scriptURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+ }
+
+ nsAutoCString defaultAllowedPrefix;
+ rv = GetRequiredScopeStringPrefix(scriptURI, defaultAllowedPrefix,
+ eUseDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsAutoCString maxPrefix(defaultAllowedPrefix);
+ if (maxScopeURI) {
+ rv = GetRequiredScopeStringPrefix(maxScopeURI, maxPrefix, eUsePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+ }
+
+ if (!StringBeginsWith(mRegistration->mScope, maxPrefix)) {
+ nsXPIDLString message;
+ NS_ConvertUTF8toUTF16 reportScope(mRegistration->mScope);
+ NS_ConvertUTF8toUTF16 reportMaxPrefix(maxPrefix);
+ const char16_t* params[] = { reportScope.get(), reportMaxPrefix.get() };
+
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "ServiceWorkerScopePathMismatch",
+ params, message);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to format localized string");
+ swm->ReportToAllClients(mScope,
+ message,
+ EmptyString(),
+ EmptyString(), 0, 0,
+ nsIScriptError::errorFlag);
+ FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ // The response has been validated, so now we can consider if its a
+ // byte-for-byte match. This is step 6 of the Update algorithm.
+ if (aInCacheAndEqual) {
+ Finish(NS_OK);
+ return;
+ }
+
+ Telemetry::Accumulate(Telemetry::SERVICE_WORKER_UPDATED, 1);
+
+ // Begin step 7 of the Update algorithm to evaluate the new script.
+
+ RefPtr<ServiceWorkerInfo> sw =
+ new ServiceWorkerInfo(mRegistration->mPrincipal,
+ mRegistration->mScope,
+ mScriptSpec, aNewCacheName);
+
+ mRegistration->SetEvaluating(sw);
+
+ nsMainThreadPtrHandle<ServiceWorkerUpdateJob> handle(
+ new nsMainThreadPtrHolder<ServiceWorkerUpdateJob>(this));
+ RefPtr<LifeCycleEventCallback> callback = new ContinueUpdateRunnable(handle);
+
+ ServiceWorkerPrivate* workerPrivate = sw->WorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ rv = workerPrivate->CheckScriptEvaluation(callback);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
+ return;
+ }
+}
+
+void
+ServiceWorkerUpdateJob::ContinueUpdateAfterScriptEval(bool aScriptEvaluationResult)
+{
+ AssertIsOnMainThread();
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (Canceled() || !swm) {
+ FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
+ return;
+ }
+
+ // Step 7.5 of the Update algorithm verifying that the script evaluated
+ // successfully.
+
+ if (NS_WARN_IF(!aScriptEvaluationResult)) {
+ ErrorResult error;
+
+ NS_ConvertUTF8toUTF16 scriptSpec(mScriptSpec);
+ NS_ConvertUTF8toUTF16 scope(mRegistration->mScope);
+ error.ThrowTypeError<MSG_SW_SCRIPT_THREW>(scriptSpec, scope);
+ FailUpdateJob(error);
+ return;
+ }
+
+ Install(swm);
+}
+
+void
+ServiceWorkerUpdateJob::Install(ServiceWorkerManager* aSWM)
+{
+ AssertIsOnMainThread();
+ MOZ_DIAGNOSTIC_ASSERT(!Canceled());
+ MOZ_DIAGNOSTIC_ASSERT(aSWM);
+
+ MOZ_ASSERT(!mRegistration->GetInstalling());
+
+ // Begin step 2 of the Install algorithm.
+ //
+ // https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#installation-algorithm
+
+ mRegistration->TransitionEvaluatingToInstalling();
+
+ // Step 6 of the Install algorithm resolving the job promise.
+ InvokeResultCallbacks(NS_OK);
+
+ // The job promise cannot be rejected after this point, but the job can
+ // still fail; e.g. if the install event handler throws, etc.
+
+ // fire the updatefound event
+ nsCOMPtr<nsIRunnable> upr =
+ NewRunnableMethod<RefPtr<ServiceWorkerRegistrationInfo>>(
+ aSWM,
+ &ServiceWorkerManager::FireUpdateFoundOnServiceWorkerRegistrations,
+ mRegistration);
+ NS_DispatchToMainThread(upr);
+
+ // Call ContinueAfterInstallEvent(false) on main thread if the SW
+ // script fails to load.
+ nsCOMPtr<nsIRunnable> failRunnable = NewRunnableMethod<bool>
+ (this, &ServiceWorkerUpdateJob::ContinueAfterInstallEvent, false);
+
+ nsMainThreadPtrHandle<ServiceWorkerUpdateJob> handle(
+ new nsMainThreadPtrHolder<ServiceWorkerUpdateJob>(this));
+ RefPtr<LifeCycleEventCallback> callback = new ContinueInstallRunnable(handle);
+
+ // Send the install event to the worker thread
+ ServiceWorkerPrivate* workerPrivate =
+ mRegistration->GetInstalling()->WorkerPrivate();
+ nsresult rv = workerPrivate->SendLifeCycleEvent(NS_LITERAL_STRING("install"),
+ callback, failRunnable);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ContinueAfterInstallEvent(false /* aSuccess */);
+ }
+}
+
+void
+ServiceWorkerUpdateJob::ContinueAfterInstallEvent(bool aInstallEventSuccess)
+{
+ if (Canceled()) {
+ return FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
+ }
+
+ // If we haven't been canceled we should have a registration. There appears
+ // to be a path where it gets cleared before we call into here. Assert
+ // to try to catch this condition, but don't crash in release.
+ MOZ_DIAGNOSTIC_ASSERT(mRegistration);
+ if (!mRegistration) {
+ return FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
+ }
+
+ // Continue executing the Install algorithm at step 12.
+
+ // "If installFailed is true"
+ if (NS_WARN_IF(!aInstallEventSuccess)) {
+ // The installing worker is cleaned up by FailUpdateJob().
+ FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mRegistration->GetInstalling());
+ mRegistration->TransitionInstallingToWaiting();
+
+ Finish(NS_OK);
+
+ // Step 20 calls for explicitly waiting for queued event tasks to fire. Instead,
+ // we simply queue a runnable to execute Activate. This ensures the events are
+ // flushed from the queue before proceeding.
+
+ // Step 22 of the Install algorithm. Activate is executed after the completion
+ // of this job. The controlling client and skipWaiting checks are performed
+ // in TryToActivate().
+ mRegistration->TryToActivateAsync();
+}
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla