// Copyright (c) 2012 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/worker_host/worker_process_host.h"

#include <set>
#include <string>
#include <vector>

#include "base/base_switches.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "content/browser/appcache/appcache_dispatcher_host.h"
#include "content/browser/appcache/chrome_appcache_service.h"
#include "content/browser/browser_child_process_host_impl.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/devtools/worker_devtools_manager.h"
#include "content/browser/devtools/worker_devtools_message_filter.h"
#include "content/browser/fileapi/fileapi_message_filter.h"
#include "content/browser/frame_host/render_frame_host_delegate.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/indexed_db/indexed_db_dispatcher_host.h"
#include "content/browser/loader/resource_message_filter.h"
#include "content/browser/message_port_message_filter.h"
#include "content/browser/message_port_service.h"
#include "content/browser/mime_registry_message_filter.h"
#include "content/browser/quota_dispatcher_host.h"
#include "content/browser/renderer_host/database_message_filter.h"
#include "content/browser/renderer_host/file_utilities_message_filter.h"
#include "content/browser/renderer_host/render_view_host_delegate.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/socket_stream_dispatcher_host.h"
#include "content/browser/resource_context_impl.h"
#include "content/browser/worker_host/worker_message_filter.h"
#include "content/browser/worker_host/worker_service_impl.h"
#include "content/common/child_process_host_impl.h"
#include "content/common/view_messages.h"
#include "content/common/worker_messages.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/user_metrics.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/result_codes.h"
#include "ipc/ipc_switches.h"
#include "net/base/mime_util.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/url_request/url_request_context_getter.h"
#include "ui/base/ui_base_switches.h"
#include "webkit/browser/fileapi/file_system_context.h"
#include "webkit/browser/fileapi/sandbox_file_system_backend.h"
#include "webkit/common/resource_type.h"

#if defined(OS_WIN)
#include "content/common/sandbox_win.h"
#include "content/public/common/sandboxed_process_launcher_delegate.h"
#endif

namespace content {
namespace {

#if defined(OS_WIN)
// NOTE: changes to this class need to be reviewed by the security team.
class WorkerSandboxedProcessLauncherDelegate
    : public content::SandboxedProcessLauncherDelegate {
 public:
  WorkerSandboxedProcessLauncherDelegate() {}
  virtual ~WorkerSandboxedProcessLauncherDelegate() {}

  virtual void PreSpawnTarget(sandbox::TargetPolicy* policy,
                              bool* success) {
    AddBaseHandleClosePolicy(policy);
  }
};
#endif  // OS_WIN

// Notifies RenderViewHost that one or more worker objects crashed.
void WorkerCrashCallback(int render_process_unique_id, int render_frame_id) {
  RenderFrameHostImpl* host =
      RenderFrameHostImpl::FromID(render_process_unique_id, render_frame_id);
  if (host)
    host->delegate()->WorkerCrashed(host);
}

void WorkerCreatedCallback(int render_process_id,
                           int render_frame_id,
                           int worker_process_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  RenderFrameHost* render_frame_host =
      RenderFrameHost::FromID(render_process_id, render_frame_id);
  if (!render_frame_host)
    return;
  SiteInstance* site_instance = render_frame_host->GetSiteInstance();
  GetContentClient()->browser()->WorkerProcessCreated(site_instance,
                                                      worker_process_id);
}

void WorkerTerminatedCallback(int render_process_id,
                              int render_frame_id,
                              int worker_process_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  RenderFrameHost* render_frame_host =
      RenderFrameHost::FromID(render_process_id, render_frame_id);
  if (!render_frame_host)
    return;
  SiteInstance* site_instance = render_frame_host->GetSiteInstance();
  GetContentClient()->browser()->WorkerProcessTerminated(site_instance,
                                                         worker_process_id);
}

}  // namespace

WorkerProcessHost::WorkerProcessHost(
    ResourceContext* resource_context,
    const WorkerStoragePartition& partition)
    : resource_context_(resource_context),
      partition_(partition),
      process_launched_(false) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  DCHECK(resource_context_);
  process_.reset(
      new BrowserChildProcessHostImpl(PROCESS_TYPE_WORKER, this));
}

WorkerProcessHost::~WorkerProcessHost() {
  // If we crashed, tell the RenderViewHosts.
  for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) {
    if (!i->load_failed()) {
      const WorkerDocumentSet::DocumentInfoSet& parents =
          i->worker_document_set()->documents();
      for (WorkerDocumentSet::DocumentInfoSet::const_iterator parent_iter =
               parents.begin(); parent_iter != parents.end(); ++parent_iter) {
        BrowserThread::PostTask(
            BrowserThread::UI, FROM_HERE,
            base::Bind(&WorkerCrashCallback, parent_iter->render_process_id(),
                       parent_iter->render_frame_id()));
      }
    }
    WorkerServiceImpl::GetInstance()->NotifyWorkerDestroyed(
        this, i->worker_route_id());
  }

  ChildProcessSecurityPolicyImpl::GetInstance()->Remove(
      process_->GetData().id);
}

bool WorkerProcessHost::Send(IPC::Message* message) {
  return process_->Send(message);
}

bool WorkerProcessHost::Init(int render_process_id, int render_frame_id) {
  std::string channel_id = process_->GetHost()->CreateChannel();
  if (channel_id.empty())
    return false;

#if defined(OS_LINUX)
  int flags = ChildProcessHost::CHILD_ALLOW_SELF;
#else
  int flags = ChildProcessHost::CHILD_NORMAL;
#endif

  base::FilePath exe_path = ChildProcessHost::GetChildPath(flags);
  if (exe_path.empty())
    return false;

  CommandLine* cmd_line = new CommandLine(exe_path);
  cmd_line->AppendSwitchASCII(switches::kProcessType, switches::kWorkerProcess);
  cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id);
  std::string locale = GetContentClient()->browser()->GetApplicationLocale();
  cmd_line->AppendSwitchASCII(switches::kLang, locale);

  static const char* const kSwitchNames[] = {
    switches::kDisableApplicationCache,
    switches::kDisableDatabases,
#if defined(OS_WIN)
    switches::kDisableDesktopNotifications,
#endif
    switches::kDisableFileSystem,
    switches::kDisableSeccompFilterSandbox,
    switches::kEnableExperimentalWebPlatformFeatures,
    switches::kEnableServiceWorker,
#if defined(OS_MACOSX)
    switches::kEnableSandboxLogging,
#endif
    switches::kJavaScriptFlags,
    switches::kNoSandbox
  };
  cmd_line->CopySwitchesFrom(*CommandLine::ForCurrentProcess(), kSwitchNames,
                             arraysize(kSwitchNames));

#if defined(OS_POSIX)
  bool use_zygote = true;

  if (CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kWaitForDebuggerChildren)) {
    // Look to pass-on the kWaitForDebugger flag.
    std::string value = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
        switches::kWaitForDebuggerChildren);
    if (value.empty() || value == switches::kWorkerProcess) {
      cmd_line->AppendSwitch(switches::kWaitForDebugger);
      use_zygote = false;
    }
  }

  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDebugChildren)) {
    // Look to pass-on the kDebugOnStart flag.
    std::string value = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
        switches::kDebugChildren);
    if (value.empty() || value == switches::kWorkerProcess) {
      // launches a new xterm, and runs the worker process in gdb, reading
      // optional commands from gdb_chrome file in the working directory.
      cmd_line->PrependWrapper("xterm -e gdb -x gdb_chrome --args");
      use_zygote = false;
    }
  }
#endif

  process_->Launch(
#if defined(OS_WIN)
      new WorkerSandboxedProcessLauncherDelegate,
#elif defined(OS_POSIX)
      use_zygote,
      base::EnvironmentMap(),
#endif
      cmd_line);

  ChildProcessSecurityPolicyImpl::GetInstance()->AddWorker(
      process_->GetData().id, render_process_id);
  CreateMessageFilters(render_process_id);

  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&WorkerCreatedCallback,
                 render_process_id,
                 render_frame_id,
                 process_->GetData().id));
  return true;
}

void WorkerProcessHost::CreateMessageFilters(int render_process_id) {
  ChromeBlobStorageContext* blob_storage_context =
      GetChromeBlobStorageContextForResourceContext(resource_context_);
  StreamContext* stream_context =
      GetStreamContextForResourceContext(resource_context_);

  net::URLRequestContextGetter* url_request_context =
      partition_.url_request_context();

  ResourceMessageFilter::GetContextsCallback get_contexts_callback(
      base::Bind(&WorkerProcessHost::GetContexts,
      base::Unretained(this)));

  ResourceMessageFilter* resource_message_filter = new ResourceMessageFilter(
      process_->GetData().id, PROCESS_TYPE_WORKER,
      partition_.appcache_service(),
      blob_storage_context,
      partition_.filesystem_context(),
      get_contexts_callback);
  process_->AddFilter(resource_message_filter);

  MessagePortMessageFilter* message_port_message_filter =
      new MessagePortMessageFilter(
          base::Bind(&WorkerServiceImpl::next_worker_route_id,
                     base::Unretained(WorkerServiceImpl::GetInstance())));
  process_->AddFilter(message_port_message_filter);
  worker_message_filter_ = new WorkerMessageFilter(render_process_id,
                                                   resource_context_,
                                                   partition_,
                                                   message_port_message_filter);
  process_->AddFilter(worker_message_filter_.get());
  process_->AddFilter(new AppCacheDispatcherHost(
      partition_.appcache_service(), process_->GetData().id));
  process_->AddFilter(new FileAPIMessageFilter(
      process_->GetData().id,
      url_request_context,
      partition_.filesystem_context(),
      blob_storage_context,
      stream_context));
  process_->AddFilter(new FileUtilitiesMessageFilter(
      process_->GetData().id));
  process_->AddFilter(new MimeRegistryMessageFilter());
  process_->AddFilter(new DatabaseMessageFilter(partition_.database_tracker()));
  process_->AddFilter(new QuotaDispatcherHost(
      process_->GetData().id,
      partition_.quota_manager(),
      GetContentClient()->browser()->CreateQuotaPermissionContext()));

  SocketStreamDispatcherHost::GetRequestContextCallback
      request_context_callback(
          base::Bind(&WorkerProcessHost::GetRequestContext,
          base::Unretained(this)));

  SocketStreamDispatcherHost* socket_stream_dispatcher_host =
      new SocketStreamDispatcherHost(
          render_process_id,
          request_context_callback,
          resource_context_);
  socket_stream_dispatcher_host_ = socket_stream_dispatcher_host;
  process_->AddFilter(socket_stream_dispatcher_host);
  process_->AddFilter(new WorkerDevToolsMessageFilter(process_->GetData().id));
  process_->AddFilter(
      new IndexedDBDispatcherHost(partition_.indexed_db_context()));
}

void WorkerProcessHost::CreateWorker(const WorkerInstance& instance) {
  ChildProcessSecurityPolicyImpl::GetInstance()->GrantRequestURL(
      process_->GetData().id, instance.url());

  instances_.push_back(instance);

  WorkerProcessMsg_CreateWorker_Params params;
  params.url = instance.url();
  params.name = instance.name();
  params.content_security_policy = instance.content_security_policy();
  params.security_policy_type = instance.security_policy_type();
  params.route_id = instance.worker_route_id();
  Send(new WorkerProcessMsg_CreateWorker(params));

  UpdateTitle();

  // Walk all pending filters and let them know the worker has been created
  // (could be more than one in the case where we had to queue up worker
  // creation because the worker process limit was reached).
  for (WorkerInstance::FilterList::const_iterator i =
           instance.filters().begin();
       i != instance.filters().end(); ++i) {
    i->filter()->Send(new ViewMsg_WorkerCreated(i->route_id()));
  }
}

bool WorkerProcessHost::FilterMessage(const IPC::Message& message,
                                      WorkerMessageFilter* filter) {
  for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) {
    if (!i->closed() && i->HasFilter(filter, message.routing_id())) {
      RelayMessage(message, filter, &(*i));
      return true;
    }
  }

  return false;
}

void WorkerProcessHost::OnProcessLaunched() {
  process_launched_ = true;

  WorkerServiceImpl::GetInstance()->NotifyWorkerProcessCreated();
}

bool WorkerProcessHost::OnMessageReceived(const IPC::Message& message) {
  bool msg_is_ok = true;
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP_EX(WorkerProcessHost, message, msg_is_ok)
    IPC_MESSAGE_HANDLER(WorkerHostMsg_WorkerContextClosed,
                        OnWorkerContextClosed)
    IPC_MESSAGE_HANDLER(WorkerHostMsg_WorkerScriptLoaded,
                        OnWorkerScriptLoaded)
    IPC_MESSAGE_HANDLER(WorkerHostMsg_WorkerScriptLoadFailed,
                        OnWorkerScriptLoadFailed)
    IPC_MESSAGE_HANDLER(WorkerHostMsg_WorkerConnected,
                        OnWorkerConnected)
    IPC_MESSAGE_HANDLER(WorkerProcessHostMsg_AllowDatabase, OnAllowDatabase)
    IPC_MESSAGE_HANDLER(WorkerProcessHostMsg_AllowFileSystem, OnAllowFileSystem)
    IPC_MESSAGE_HANDLER(WorkerProcessHostMsg_AllowIndexedDB, OnAllowIndexedDB)
    IPC_MESSAGE_HANDLER(WorkerProcessHostMsg_ForceKillWorker,
                        OnForceKillWorkerProcess)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP_EX()

  if (!msg_is_ok) {
    NOTREACHED();
    RecordAction(base::UserMetricsAction("BadMessageTerminate_WPH"));
    base::KillProcess(
        process_->GetData().handle, RESULT_CODE_KILLED_BAD_MESSAGE, false);
  }

  if (handled)
    return true;

  if (message.type() == WorkerHostMsg_WorkerContextDestroyed::ID) {
    WorkerServiceImpl::GetInstance()->NotifyWorkerDestroyed(
        this, message.routing_id());
  }

  for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) {
    if (i->worker_route_id() == message.routing_id()) {
      if (message.type() == WorkerHostMsg_WorkerContextDestroyed::ID) {
        instances_.erase(i);
        UpdateTitle();
      }
      return true;
    }
  }
  return false;
}

// Sent to notify the browser process when a worker context invokes close(), so
// no new connections are sent to shared workers.
void WorkerProcessHost::OnWorkerContextClosed(int worker_route_id) {
  for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) {
    if (i->worker_route_id() == worker_route_id) {
      // Set the closed flag - this will stop any further messages from
      // being sent to the worker (messages can still be sent from the worker,
      // for exception reporting, etc).
      i->set_closed(true);
      break;
    }
  }
}

void WorkerProcessHost::OnWorkerScriptLoaded(int worker_route_id) {
  WorkerDevToolsManager::GetInstance()->WorkerContextStarted(this,
                                                             worker_route_id);
}

void WorkerProcessHost::OnWorkerScriptLoadFailed(int worker_route_id) {
  bool shutdown = true;
  for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) {
    if (i->worker_route_id() != worker_route_id) {
      shutdown = false;
      continue;
    }
    i->set_load_failed(true);
    for (WorkerInstance::FilterList::const_iterator j = i->filters().begin();
          j != i->filters().end(); ++j) {
      j->filter()->Send(new ViewMsg_WorkerScriptLoadFailed(j->route_id()));
    }
  }
  if (shutdown) {
    base::KillProcess(
          process_->GetData().handle, RESULT_CODE_NORMAL_EXIT, false);
  }
}

void WorkerProcessHost::OnWorkerConnected(int message_port_id,
                                          int worker_route_id) {
  for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) {
    if (i->worker_route_id() != worker_route_id)
      continue;
    for (WorkerInstance::FilterList::const_iterator j = i->filters().begin();
          j != i->filters().end(); ++j) {
      if (j->message_port_id() != message_port_id)
        continue;
      j->filter()->Send(new ViewMsg_WorkerConnected(j->route_id()));
      return;
    }
  }
}

void WorkerProcessHost::OnAllowDatabase(int worker_route_id,
                                        const GURL& url,
                                        const base::string16& name,
                                        const base::string16& display_name,
                                        unsigned long estimated_size,
                                        bool* result) {
  *result = GetContentClient()->browser()->AllowWorkerDatabase(
      url, name, display_name, estimated_size, resource_context_,
      GetRenderFrameIDsForWorker(worker_route_id));
}

void WorkerProcessHost::OnAllowFileSystem(int worker_route_id,
                                          const GURL& url,
                                          bool* result) {
  *result = GetContentClient()->browser()->AllowWorkerFileSystem(
      url, resource_context_, GetRenderFrameIDsForWorker(worker_route_id));
}

void WorkerProcessHost::OnAllowIndexedDB(int worker_route_id,
                                         const GURL& url,
                                         const base::string16& name,
                                         bool* result) {
  *result = GetContentClient()->browser()->AllowWorkerIndexedDB(
      url, name, resource_context_,
      GetRenderFrameIDsForWorker(worker_route_id));
}

void WorkerProcessHost::OnForceKillWorkerProcess() {
  if (process_ && process_launched_)
    base::KillProcess(
          process_->GetData().handle, RESULT_CODE_NORMAL_EXIT, false);
  else
    RecordAction(base::UserMetricsAction("WorkerProcess_BadProcessToKill"));
}

void WorkerProcessHost::RelayMessage(
    const IPC::Message& message,
    WorkerMessageFilter* incoming_filter,
    WorkerInstance* instance) {
  if (message.type() == WorkerMsg_Connect::ID) {
    // Crack the SharedWorker Connect message to setup routing for the port.
    int sent_message_port_id;
    int new_routing_id;
    if (!WorkerMsg_Connect::Read(
            &message, &sent_message_port_id, &new_routing_id)) {
      return;
    }
    new_routing_id = worker_message_filter_->GetNextRoutingID();
    MessagePortService::GetInstance()->UpdateMessagePort(
        sent_message_port_id,
        worker_message_filter_->message_port_message_filter(),
        new_routing_id);

    instance->SetMessagePortID(incoming_filter,
                               message.routing_id(),
                               sent_message_port_id);
    // Resend the message with the new routing id.
    worker_message_filter_->Send(new WorkerMsg_Connect(
        instance->worker_route_id(), sent_message_port_id, new_routing_id));

    // Send any queued messages for the sent port.
    MessagePortService::GetInstance()->SendQueuedMessagesIfPossible(
        sent_message_port_id);
  } else {
    IPC::Message* new_message = new IPC::Message(message);
    new_message->set_routing_id(instance->worker_route_id());
    worker_message_filter_->Send(new_message);
    return;
  }
}

void WorkerProcessHost::ShutdownSocketStreamDispatcherHostIfNecessary() {
  if (!instances_.size() && socket_stream_dispatcher_host_.get()) {
    // We can assume that this object is going to delete, because
    // currently a WorkerInstance will never be added to a WorkerProcessHost
    // once it is initialized.

    // SocketStreamDispatcherHost should be notified now that the worker
    // process will shutdown soon.
    socket_stream_dispatcher_host_->Shutdown();
    socket_stream_dispatcher_host_ = NULL;
  }
}

void WorkerProcessHost::FilterShutdown(WorkerMessageFilter* filter) {
  for (Instances::iterator i = instances_.begin(); i != instances_.end();) {
    bool shutdown = false;
    i->RemoveFilters(filter);

    int render_frame_id = 0;
    const WorkerDocumentSet::DocumentInfoSet& documents =
        i->worker_document_set()->documents();
    for (WorkerDocumentSet::DocumentInfoSet::const_iterator doc =
         documents.begin(); doc != documents.end(); ++doc) {
      if (doc->filter() == filter) {
        render_frame_id = doc->render_frame_id();
        break;
      }
    }
    i->worker_document_set()->RemoveAll(filter);
    if (i->worker_document_set()->IsEmpty()) {
      shutdown = true;
    }
    if (shutdown) {
      BrowserThread::PostTask(
          BrowserThread::UI, FROM_HERE,
          base::Bind(&WorkerTerminatedCallback,
                     filter->render_process_id(),
                     render_frame_id,
                     process_->GetData().id));
      Send(new WorkerMsg_TerminateWorkerContext(i->worker_route_id()));
      i = instances_.erase(i);
    } else {
      ++i;
    }
  }
  ShutdownSocketStreamDispatcherHostIfNecessary();
}

bool WorkerProcessHost::CanShutdown() {
  return instances_.empty();
}

void WorkerProcessHost::UpdateTitle() {
  std::set<std::string> titles;
  for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) {
    // Allow the embedder first crack at special casing the title.
    std::string title = GetContentClient()->browser()->
        GetWorkerProcessTitle(i->url(), resource_context_);

    if (title.empty()) {
      title = net::registry_controlled_domains::GetDomainAndRegistry(
          i->url(),
          net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
    }

    // Use the host name if the domain is empty, i.e. localhost or IP address.
    if (title.empty())
      title = i->url().host();

    // If the host name is empty, i.e. file url, use the path.
    if (title.empty())
      title = i->url().path();
    titles.insert(title);
  }

  std::string display_title;
  for (std::set<std::string>::iterator i = titles.begin();
       i != titles.end(); ++i) {
    if (!display_title.empty())
      display_title += ", ";
    display_title += *i;
  }

  process_->SetName(base::UTF8ToUTF16(display_title));
}

void WorkerProcessHost::DocumentDetached(WorkerMessageFilter* filter,
                                         unsigned long long document_id) {
  // Walk all instances and remove the document from their document set.
  for (Instances::iterator i = instances_.begin(); i != instances_.end();) {
    int render_frame_id = 0;
    const WorkerDocumentSet::DocumentInfoSet& documents =
        i->worker_document_set()->documents();
    for (WorkerDocumentSet::DocumentInfoSet::const_iterator doc =
         documents.begin(); doc != documents.end(); ++doc) {
      if (doc->filter() == filter && doc->document_id() == document_id) {
        render_frame_id = doc->render_frame_id();
        break;
      }
    }
    i->worker_document_set()->Remove(filter, document_id);
    if (i->worker_document_set()->IsEmpty()) {
      BrowserThread::PostTask(
          BrowserThread::UI, FROM_HERE,
          base::Bind(&WorkerTerminatedCallback,
                     filter->render_process_id(),
                     render_frame_id,
                     process_->GetData().id));
      // This worker has no more associated documents - shut it down.
      Send(new WorkerMsg_TerminateWorkerContext(i->worker_route_id()));
      i = instances_.erase(i);
    } else {
      ++i;
    }
  }
  ShutdownSocketStreamDispatcherHostIfNecessary();
}

void WorkerProcessHost::TerminateWorker(int worker_route_id) {
  Send(new WorkerMsg_TerminateWorkerContext(worker_route_id));
}

void WorkerProcessHost::SetBackgrounded(bool backgrounded) {
  process_->SetBackgrounded(backgrounded);
}

const ChildProcessData& WorkerProcessHost::GetData() {
  return process_->GetData();
}

std::vector<std::pair<int, int> > WorkerProcessHost::GetRenderFrameIDsForWorker(
    int worker_route_id) {
  std::vector<std::pair<int, int> > result;
  WorkerProcessHost::Instances::const_iterator i;
  for (i = instances_.begin(); i != instances_.end(); ++i) {
    if (i->worker_route_id() != worker_route_id)
      continue;
    const WorkerDocumentSet::DocumentInfoSet& documents =
        i->worker_document_set()->documents();
    for (WorkerDocumentSet::DocumentInfoSet::const_iterator doc =
         documents.begin(); doc != documents.end(); ++doc) {
      result.push_back(
          std::make_pair(doc->render_process_id(), doc->render_frame_id()));
    }
    break;
  }
  return result;
}

void WorkerProcessHost::GetContexts(const ResourceHostMsg_Request& request,
                                    ResourceContext** resource_context,
                                    net::URLRequestContext** request_context) {
  *resource_context = resource_context_;
  *request_context = partition_.url_request_context()->GetURLRequestContext();
}

net::URLRequestContext* WorkerProcessHost::GetRequestContext(
    ResourceType::Type resource_type) {
  return partition_.url_request_context()->GetURLRequestContext();
}

WorkerProcessHost::WorkerInstance::WorkerInstance(
    const GURL& url,
    const base::string16& name,
    const base::string16& content_security_policy,
    blink::WebContentSecurityPolicyType security_policy_type,
    int worker_route_id,
    int render_frame_id,
    ResourceContext* resource_context,
    const WorkerStoragePartition& partition)
    : url_(url),
      closed_(false),
      name_(name),
      content_security_policy_(content_security_policy),
      security_policy_type_(security_policy_type),
      worker_route_id_(worker_route_id),
      render_frame_id_(render_frame_id),
      worker_document_set_(new WorkerDocumentSet()),
      resource_context_(resource_context),
      partition_(partition),
      load_failed_(false) {
  DCHECK(resource_context_);
}

WorkerProcessHost::WorkerInstance::~WorkerInstance() {
}

void WorkerProcessHost::WorkerInstance::SetMessagePortID(
    WorkerMessageFilter* filter,
    int route_id,
    int message_port_id) {
  for (FilterList::iterator i = filters_.begin(); i != filters_.end(); ++i) {
    if (i->filter() == filter && i->route_id() == route_id) {
      i->set_message_port_id(message_port_id);
      return;
    }
  }
}

// Compares an instance based on the algorithm in the WebWorkers spec - an
// instance matches if the origins of the URLs match, and:
// a) the names are non-empty and equal
// -or-
// b) the names are both empty, and the urls are equal
bool WorkerProcessHost::WorkerInstance::Matches(
    const GURL& match_url,
    const base::string16& match_name,
    const WorkerStoragePartition& partition,
    ResourceContext* resource_context) const {
  // Only match open shared workers.
  if (closed_)
    return false;

  // ResourceContext equivalence is being used as a proxy to ensure we only
  // matched shared workers within the same BrowserContext.
  if (resource_context_ != resource_context)
    return false;

  // We must be in the same storage partition otherwise sharing will violate
  // isolation.
  if (!partition_.Equals(partition))
    return false;

  if (url_.GetOrigin() != match_url.GetOrigin())
    return false;

  if (name_.empty() && match_name.empty())
    return url_ == match_url;

  return name_ == match_name;
}

void WorkerProcessHost::WorkerInstance::AddFilter(WorkerMessageFilter* filter,
                                                  int route_id) {
  CHECK(filter);
  if (!HasFilter(filter, route_id)) {
    FilterInfo info(filter, route_id);
    filters_.push_back(info);
  }
}

void WorkerProcessHost::WorkerInstance::RemoveFilter(
    WorkerMessageFilter* filter, int route_id) {
  for (FilterList::iterator i = filters_.begin(); i != filters_.end();) {
    if (i->filter() == filter && i->route_id() == route_id)
      i = filters_.erase(i);
    else
      ++i;
  }
  // Should not be duplicate copies in the filter set.
  DCHECK(!HasFilter(filter, route_id));
}

void WorkerProcessHost::WorkerInstance::RemoveFilters(
    WorkerMessageFilter* filter) {
  for (FilterList::iterator i = filters_.begin(); i != filters_.end();) {
    if (i->filter() == filter)
      i = filters_.erase(i);
    else
      ++i;
  }
}

bool WorkerProcessHost::WorkerInstance::HasFilter(
    WorkerMessageFilter* filter, int route_id) const {
  for (FilterList::const_iterator i = filters_.begin(); i != filters_.end();
       ++i) {
    if (i->filter() == filter && i->route_id() == route_id)
      return true;
  }
  return false;
}

bool WorkerProcessHost::WorkerInstance::FrameIsParent(
    int render_process_id, int render_frame_id) const {
  const WorkerDocumentSet::DocumentInfoSet& parents =
      worker_document_set()->documents();
  for (WorkerDocumentSet::DocumentInfoSet::const_iterator parent_iter =
           parents.begin();
       parent_iter != parents.end(); ++parent_iter) {
    if (parent_iter->render_process_id() == render_process_id &&
        parent_iter->render_frame_id() == render_frame_id) {
      return true;
    }
  }
  return false;
}

WorkerProcessHost::WorkerInstance::FilterInfo
WorkerProcessHost::WorkerInstance::GetFilter() const {
  DCHECK(NumFilters() == 1);
  return *filters_.begin();
}

}  // namespace content