blob: 5e8231e0eeb8ab83c3cd21f5a7c869992b65d364 [file] [log] [blame]
// Copyright 2022 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/worker/service_worker_registration_map.h"
#include <list>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/synchronization/lock.h"
#include "base/trace_event/trace_event.h"
#include "cobalt/script/exception_message.h"
#include "cobalt/script/promise.h"
#include "cobalt/script/script_value.h"
#include "cobalt/web/context.h"
#include "cobalt/web/environment_settings.h"
#include "cobalt/worker/service_worker_jobs.h"
#include "cobalt/worker/service_worker_registration_object.h"
#include "cobalt/worker/service_worker_update_via_cache.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace cobalt {
namespace worker {
namespace {
// Returns the serialized URL excluding the fragment.
std::string SerializeExcludingFragment(const GURL& url) {
url::Replacements<char> replacements;
replacements.ClearRef();
GURL no_fragment_url = url.ReplaceComponents(replacements);
DCHECK(!no_fragment_url.has_ref() || no_fragment_url.ref().empty());
DCHECK(!no_fragment_url.is_empty());
return no_fragment_url.spec();
}
} // namespace
scoped_refptr<ServiceWorkerRegistrationObject>
ServiceWorkerRegistrationMap::MatchServiceWorkerRegistration(
const url::Origin& storage_key, const GURL& client_url) {
TRACE_EVENT0(
"cobalt::worker",
"ServiceWorkerRegistrationMap::MatchServiceWorkerRegistration()");
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Algorithm for Match Service Worker Registration:
// https://w3c.github.io/ServiceWorker/#scope-match-algorithm
GURL matching_scope;
// 1. Run the following steps atomically.
{
base::AutoLock lock(mutex_);
// 2. Let clientURLString be serialized clientURL.
std::string client_url_string(client_url.spec());
// 3. Let matchingScopeString be the empty string.
std::string matching_scope_string;
// 4. Let scopeStringSet be an empty list.
std::list<std::string> scope_string_set;
// 5. For each (entry storage key, entry scope) → registration of
// registration map:
for (const auto& entry : registration_map_) {
// 5.1. If storage key equals entry storage key, then append entry scope
// to the end of scopeStringSet.
if (entry.first.first == storage_key) {
scope_string_set.push_back(entry.first.second);
}
}
// 6. Set matchingScopeString to the longest value in scopeStringSet which
// the value of clientURLString starts with, if it exists.
for (const auto& scope_string : scope_string_set) {
// TODO(b/234659851): Verify whether this is the expected behavior, where
// a substring of the scope string is compared with the client url string.
bool starts_with =
client_url_string.substr(0, scope_string.length()) == scope_string;
if (starts_with &&
(scope_string.length() > matching_scope_string.length())) {
matching_scope_string = scope_string;
}
}
// 7. Let matchingScope be null.
// 8. If matchingScopeString is not the empty string, then:
if (!matching_scope_string.empty()) {
// 8.1. Set matchingScope to the result of parsing matchingScopeString.
matching_scope = GURL(matching_scope_string);
// 8.2. Assert: matchingScope’s origin and clientURL’s origin are same
// origin.
DCHECK_EQ(url::Origin::Create(matching_scope),
url::Origin::Create(client_url));
}
}
// 9. Return the result of running Get Registration given storage key and
// matchingScope.
return GetRegistration(storage_key, matching_scope);
}
scoped_refptr<ServiceWorkerRegistrationObject>
ServiceWorkerRegistrationMap::GetRegistration(const url::Origin& storage_key,
const GURL& scope) {
TRACE_EVENT0("cobalt::worker",
"ServiceWorkerRegistrationMap::GetRegistration()");
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Algorithm for Get Registration:
// https://w3c.github.io/ServiceWorker/#get-registration-algorithm
// 1. Run the following steps atomically.
base::AutoLock lock(mutex_);
// 2. Let scopeString be the empty string.
std::string scope_string;
// 3. If scope is not null, set scopeString to serialized scope with the
// exclude fragment flag set.
if (!scope.is_empty()) {
scope_string = SerializeExcludingFragment(scope);
}
Key registration_key(storage_key, scope_string);
// 4. For each (entry storage key, entry scope) → registration of registration
// map:
for (const auto& entry : registration_map_) {
// 4.1. If storage key equals entry storage key and scopeString matches
// entry scope, then return registration.
if (entry.first == registration_key) {
return entry.second.get();
}
}
// 5. Return null.
return nullptr;
}
scoped_refptr<ServiceWorkerRegistrationObject>
ServiceWorkerRegistrationMap::SetRegistration(
const url::Origin& storage_key, const GURL& scope,
const ServiceWorkerUpdateViaCache& update_via_cache) {
TRACE_EVENT0("cobalt::worker",
"ServiceWorkerRegistrationMap::SetRegistration()");
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Algorithm for Set Registration:
// https://w3c.github.io/ServiceWorker/#set-registration-algorithm
// 1. Run the following steps atomically.
base::AutoLock lock(mutex_);
// 2. Let scopeString be serialized scope with the exclude fragment flag set.
std::string scope_string = SerializeExcludingFragment(scope);
// 3. Let registration be a new service worker registration whose storage key
// is set to storage key, scope url is set to scope, and update via cache mode
// is set to updateViaCache.
scoped_refptr<ServiceWorkerRegistrationObject> registration(
new ServiceWorkerRegistrationObject(storage_key, scope,
update_via_cache));
// 4. Set registration map[(storage key, scopeString)] to registration.
Key registration_key(storage_key, scope_string);
registration_map_.insert(std::make_pair(
registration_key,
scoped_refptr<ServiceWorkerRegistrationObject>(registration)));
// 5. Return registration.
return registration;
}
void ServiceWorkerRegistrationMap::RemoveRegistration(
const url::Origin& storage_key, const GURL& scope) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
std::string scope_string = SerializeExcludingFragment(scope);
Key registration_key(storage_key, scope_string);
auto entry = registration_map_.find(registration_key);
DCHECK(entry != registration_map_.end());
if (entry != registration_map_.end()) {
registration_map_.erase(entry);
}
}
bool ServiceWorkerRegistrationMap::IsUnregistered(
ServiceWorkerRegistrationObject* registration) {
// A service worker registration is said to be unregistered if registration
// map[this service worker registration's (storage key, serialized scope url)]
// is not this service worker registration.
// https://w3c.github.io/ServiceWorker/#dfn-service-worker-registration-unregistered
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
Key registration_key(registration->storage_key(),
registration->scope_url().spec());
auto entry = registration_map_.find(registration_key);
if (entry == registration_map_.end()) return true;
return entry->second.get() != registration;
}
void ServiceWorkerRegistrationMap::HandleUserAgentShutdown(
ServiceWorkerJobs* jobs) {
TRACE_EVENT0("cobalt::worker",
"ServiceWorkerRegistrationMap::HandleUserAgentShutdown()");
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Algorithm for Handle User Agent Shutdown:
// https://w3c.github.io/ServiceWorker/#on-user-agent-shutdown-algorithm
// 1. For each (storage key, scope) -> registration of registration map:
for (auto& entry : registration_map_) {
const scoped_refptr<ServiceWorkerRegistrationObject>& registration =
entry.second;
// 1.1. If registration’s installing worker installingWorker is not null,
// then:
if (registration->installing_worker()) {
// 1.1.1. If registration’s waiting worker is null and registration’s
// active worker is null, invoke Clear Registration with registration and
// continue to the next iteration of the loop.
if (!registration->waiting_worker() && !registration->active_worker()) {
jobs->ClearRegistration(registration);
continue;
} else {
// 1.1.2. Else, set installingWorker to null.
registration->set_installing_worker(nullptr);
}
}
if (registration->waiting_worker()) {
// 1.2. If registration’s waiting worker is not null, run the following
// substep in parallel:
// 1.2.1. Invoke Activate with registration.
jobs->Activate(registration);
}
}
}
} // namespace worker
} // namespace cobalt