text: // Copyright 2015 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "content/browser/service_worker/service_worker_context_watcher.h"
  
  #include <utility>
  
  #include "base/bind.h"
  #include "base/containers/scoped_ptr_hash_map.h"
  #include "content/browser/service_worker/service_worker_context_observer.h"
  #include "content/browser/service_worker/service_worker_context_wrapper.h"
  #include "content/browser/service_worker/service_worker_version.h"
  #include "content/common/service_worker/service_worker_types.h"
  #include "content/public/browser/browser_thread.h"
  #include "content/public/common/console_message_level.h"
  #include "url/gurl.h"
  
  namespace content {
  namespace {
  
  bool IsStoppedAndRedundant(const ServiceWorkerVersionInfo& version_info) {
    return version_info.running_status ==
               content::ServiceWorkerVersion::STOPPED &&
           version_info.status == content::ServiceWorkerVersion::REDUNDANT;
  }
  
  }  // namespace
  
  ServiceWorkerContextWatcher::ServiceWorkerContextWatcher(
      scoped_refptr<ServiceWorkerContextWrapper> context,
      const WorkerRegistrationUpdatedCallback& registration_callback,
      const WorkerVersionUpdatedCallback& version_callback,
      const WorkerErrorReportedCallback& error_callback)
      : context_(context),
        registration_callback_(registration_callback),
        version_callback_(version_callback),
        error_callback_(error_callback) {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
  }
  
  void ServiceWorkerContextWatcher::Start() {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::Bind(&ServiceWorkerContextWatcher::GetStoredRegistrationsOnIOThread,
                   this));
  }
  
  void ServiceWorkerContextWatcher::Stop() {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::Bind(&ServiceWorkerContextWatcher::StopOnIOThread, this));
  }
  
  void ServiceWorkerContextWatcher::GetStoredRegistrationsOnIOThread() {
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    context_->GetAllRegistrations(base::Bind(
        &ServiceWorkerContextWatcher::OnStoredRegistrationsOnIOThread, this));
  }
  
  void ServiceWorkerContextWatcher::OnStoredRegistrationsOnIOThread(
      const std::vector<ServiceWorkerRegistrationInfo>& stored_registrations) {
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    context_->AddObserver(this);
  
    base::ScopedPtrHashMap<int64_t, scoped_ptr<ServiceWorkerRegistrationInfo>>
        registration_info_map;
    for (const auto& registration : stored_registrations)
      StoreRegistrationInfo(registration, &registration_info_map);
    for (const auto& registration : context_->GetAllLiveRegistrationInfo())
      StoreRegistrationInfo(registration, &registration_info_map);
    for (const auto& version : context_->GetAllLiveVersionInfo())
      StoreVersionInfo(version);
  
    std::vector<ServiceWorkerRegistrationInfo> registrations;
    registrations.reserve(registration_info_map.size());
    for (const auto& registration_id_info_pair : registration_info_map)
      registrations.push_back(*registration_id_info_pair.second);
  
    std::vector<ServiceWorkerVersionInfo> versions;
    versions.reserve(version_info_map_.size());
  
    for (auto version_it = version_info_map_.begin();
         version_it != version_info_map_.end();) {
      versions.push_back(*version_it->second);
      if (IsStoppedAndRedundant(*version_it->second))
        version_info_map_.erase(version_it++);
      else
        ++version_it;
    }
  
    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
                            base::Bind(registration_callback_, registrations));
    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
                            base::Bind(version_callback_, versions));
  }
  
  void ServiceWorkerContextWatcher::StopOnIOThread() {
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    context_->RemoveObserver(this);
  }
  
  ServiceWorkerContextWatcher::~ServiceWorkerContextWatcher() {
  }
  
  void ServiceWorkerContextWatcher::StoreRegistrationInfo(
      const ServiceWorkerRegistrationInfo& registration_info,
      base::ScopedPtrHashMap<int64_t, scoped_ptr<ServiceWorkerRegistrationInfo>>*
          info_map) {
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    if (registration_info.registration_id == kInvalidServiceWorkerRegistrationId)
      return;
    info_map->set(registration_info.registration_id,
                  scoped_ptr<ServiceWorkerRegistrationInfo>(
                      new ServiceWorkerRegistrationInfo(registration_info)));
    StoreVersionInfo(registration_info.active_version);
    StoreVersionInfo(registration_info.waiting_version);
    StoreVersionInfo(registration_info.installing_version);
  }
  
  void ServiceWorkerContextWatcher::StoreVersionInfo(
      const ServiceWorkerVersionInfo& version_info) {
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    if (version_info.version_id == kInvalidServiceWorkerVersionId)
      return;
    version_info_map_.set(version_info.version_id,
                          scoped_ptr<ServiceWorkerVersionInfo>(
                              new ServiceWorkerVersionInfo(version_info)));
  }
  
  void ServiceWorkerContextWatcher::SendRegistrationInfo(
      int64_t registration_id,
      const GURL& pattern,
      ServiceWorkerRegistrationInfo::DeleteFlag delete_flag) {
    std::vector<ServiceWorkerRegistrationInfo> registrations;
    ServiceWorkerRegistration* registration =
        context_->GetLiveRegistration(registration_id);
    if (registration) {
      registrations.push_back(registration->GetInfo());
    } else {
      registrations.push_back(
          ServiceWorkerRegistrationInfo(pattern, registration_id, delete_flag));
    }
    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
                            base::Bind(registration_callback_, registrations));
  }
  
  void ServiceWorkerContextWatcher::SendVersionInfo(
      const ServiceWorkerVersionInfo& version_info) {
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    std::vector<ServiceWorkerVersionInfo> versions;
    versions.push_back(version_info);
    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
                            base::Bind(version_callback_, versions));
  }
  
  void ServiceWorkerContextWatcher::OnNewLiveRegistration(int64_t registration_id,
                                                          const GURL& pattern) {
    SendRegistrationInfo(registration_id, pattern,
                         ServiceWorkerRegistrationInfo::IS_NOT_DELETED);
  }
  
  void ServiceWorkerContextWatcher::OnNewLiveVersion(int64_t version_id,
                                                     int64_t registration_id,
                                                     const GURL& script_url) {
    if (ServiceWorkerVersionInfo* version = version_info_map_.get(version_id)) {
      DCHECK_EQ(version->registration_id, registration_id);
      DCHECK_EQ(version->script_url, script_url);
      return;
    }
  
    scoped_ptr<ServiceWorkerVersionInfo> version(new ServiceWorkerVersionInfo());
    version->version_id = version_id;
    version->registration_id = registration_id;
    version->script_url = script_url;
    SendVersionInfo(*version);
    if (!IsStoppedAndRedundant(*version))
      version_info_map_.set(version_id, std::move(version));
  }
  
  void ServiceWorkerContextWatcher::OnRunningStateChanged(
      int64_t version_id,
      content::ServiceWorkerVersion::RunningStatus running_status) {
    ServiceWorkerVersionInfo* version = version_info_map_.get(version_id);
    DCHECK(version);
    if (version->running_status == running_status)
      return;
    version->running_status = running_status;
    SendVersionInfo(*version);
    if (IsStoppedAndRedundant(*version))
      version_info_map_.erase(version_id);
  }
  
  void ServiceWorkerContextWatcher::OnVersionStateChanged(
      int64_t version_id,
      content::ServiceWorkerVersion::Status status) {
    ServiceWorkerVersionInfo* version = version_info_map_.get(version_id);
    DCHECK(version);
    if (version->status == status)
      return;
    version->status = status;
    SendVersionInfo(*version);
    if (IsStoppedAndRedundant(*version))
      version_info_map_.erase(version_id);
  }
  
  void ServiceWorkerContextWatcher::OnMainScriptHttpResponseInfoSet(
      int64_t version_id,
      base::Time script_response_time,
      base::Time script_last_modified) {
    ServiceWorkerVersionInfo* version = version_info_map_.get(version_id);
    DCHECK(version);
    version->script_response_time = script_response_time;
    version->script_last_modified = script_last_modified;
    SendVersionInfo(*version);
  }
  
  void ServiceWorkerContextWatcher::OnErrorReported(int64_t version_id,
                                                    int process_id,
                                                    int thread_id,
                                                    const ErrorInfo& info) {
    int64_t registration_id = kInvalidServiceWorkerRegistrationId;
    if (ServiceWorkerVersionInfo* version = version_info_map_.get(version_id))
      registration_id = version->registration_id;
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        base::Bind(error_callback_, registration_id, version_id, info));
  }
  
  void ServiceWorkerContextWatcher::OnReportConsoleMessage(
      int64_t version_id,
      int process_id,
      int thread_id,
      const ConsoleMessage& message) {
    if (message.message_level != CONSOLE_MESSAGE_LEVEL_ERROR)
      return;
    int64_t registration_id = kInvalidServiceWorkerRegistrationId;
    if (ServiceWorkerVersionInfo* version = version_info_map_.get(version_id))
      registration_id = version->registration_id;
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        base::Bind(error_callback_, registration_id, version_id,
                   ErrorInfo(message.message, message.line_number, -1,
                             message.source_url)));
  }
  
  void ServiceWorkerContextWatcher::OnControlleeAdded(
      int64_t version_id,
      const std::string& uuid,
      int process_id,
      int route_id,
      ServiceWorkerProviderType type) {
    ServiceWorkerVersionInfo* version = version_info_map_.get(version_id);
    DCHECK(version);
    version->clients[uuid] =
        ServiceWorkerVersionInfo::ClientInfo(process_id, route_id, type);
    SendVersionInfo(*version);
  }
  
  void ServiceWorkerContextWatcher::OnControlleeRemoved(int64_t version_id,
                                                        const std::string& uuid) {
    ServiceWorkerVersionInfo* version = version_info_map_.get(version_id);
    if (!version)
      return;
    version->clients.erase(uuid);
    SendVersionInfo(*version);
  }
  
  void ServiceWorkerContextWatcher::OnRegistrationStored(int64_t registration_id,
                                                         const GURL& pattern) {
    SendRegistrationInfo(registration_id, pattern,
                         ServiceWorkerRegistrationInfo::IS_NOT_DELETED);
  }
  
  void ServiceWorkerContextWatcher::OnRegistrationDeleted(int64_t registration_id,
                                                          const GURL& pattern) {
    SendRegistrationInfo(registration_id, pattern,
                         ServiceWorkerRegistrationInfo::IS_DELETED);
  }
  
  void ServiceWorkerContextWatcher::OnForceUpdateOnPageLoadChanged(
      int64_t registration_id,
      bool force_update_on_page_load) {
    ServiceWorkerRegistration* registration =
        context_->GetLiveRegistration(registration_id);
    if (!registration)
      return;
    std::vector<ServiceWorkerRegistrationInfo> registrations;
    registrations.push_back(registration->GetInfo());
    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
                            base::Bind(registration_callback_, registrations));
  }
  
  }  // namespace content
  