// Copyright (c) 2011 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 "chrome/browser/plugin_data_remover.h"

#include "base/command_line.h"
#include "base/message_loop_proxy.h"
#include "base/metrics/histogram.h"
#include "base/synchronization/waitable_event.h"
#include "base/version.h"
#include "chrome/common/chrome_switches.h"
#include "content/browser/browser_thread.h"
#include "content/browser/plugin_service.h"
#include "content/common/plugin_messages.h"
#include "webkit/plugins/npapi/plugin_group.h"
#include "webkit/plugins/npapi/plugin_list.h"

#if defined(OS_POSIX)
#include "ipc/ipc_channel_posix.h"
#endif

namespace {

const char* kFlashMimeType = "application/x-shockwave-flash";
// The minimum Flash Player version that implements NPP_ClearSiteData.
const char* kMinFlashVersion = "10.3";
const int64 kRemovalTimeoutMs = 10000;
const uint64 kClearAllData = 0;

}  // namespace

PluginDataRemover::PluginDataRemover()
    : mime_type_(kFlashMimeType),
      is_removing_(false),
      event_(new base::WaitableEvent(true, false)),
      channel_(NULL) {
}

PluginDataRemover::~PluginDataRemover() {
  DCHECK(!is_removing_);
  if (channel_)
    BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE, channel_);
}

base::WaitableEvent* PluginDataRemover::StartRemoving(base::Time begin_time) {
  DCHECK(!is_removing_);
  remove_start_time_ = base::Time::Now();
  begin_time_ = begin_time;

  is_removing_ = true;

  // Balanced in OnChannelOpened or OnError. Exactly one them will eventually be
  // called, so we need to keep this object around until then.
  AddRef();
  PluginService::GetInstance()->OpenChannelToNpapiPlugin(
      0, 0, GURL(), mime_type_, this);

  BrowserThread::PostDelayedTask(
      BrowserThread::IO,
      FROM_HERE,
      NewRunnableMethod(this, &PluginDataRemover::OnTimeout),
      kRemovalTimeoutMs);

  return event_.get();
}

void PluginDataRemover::Wait() {
  base::Time start_time(base::Time::Now());
  bool result = true;
  if (is_removing_)
    result = event_->Wait();
  UMA_HISTOGRAM_TIMES("ClearPluginData.wait_at_shutdown",
                      base::Time::Now() - start_time);
  UMA_HISTOGRAM_TIMES("ClearPluginData.time_at_shutdown",
                      base::Time::Now() - remove_start_time_);
  DCHECK(result) << "Error waiting for plugin process";
}

int PluginDataRemover::ID() {
  // Generate a unique identifier for this PluginProcessHostClient.
  return ChildProcessInfo::GenerateChildProcessUniqueId();
}

bool PluginDataRemover::OffTheRecord() {
  return false;
}

void PluginDataRemover::SetPluginInfo(
    const webkit::npapi::WebPluginInfo& info) {
}

void PluginDataRemover::OnChannelOpened(const IPC::ChannelHandle& handle) {
  ConnectToChannel(handle);
  // Balancing the AddRef call in StartRemoving.
  Release();
}

void PluginDataRemover::ConnectToChannel(const IPC::ChannelHandle& handle) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  // If we timed out, don't bother connecting.
  if (!is_removing_)
    return;

  DCHECK(!channel_);
  channel_ = new IPC::Channel(handle, IPC::Channel::MODE_CLIENT, this);
  if (!channel_->Connect()) {
    NOTREACHED() << "Couldn't connect to plugin";
    SignalDone();
    return;
  }

  if (!channel_->Send(new PluginMsg_ClearSiteData(std::string(),
                                                  kClearAllData,
                                                  begin_time_))) {
    NOTREACHED() << "Couldn't send ClearSiteData message";
    SignalDone();
    return;
  }
}

void PluginDataRemover::OnError() {
  LOG(DFATAL) << "Couldn't open plugin channel";
  SignalDone();
  // Balancing the AddRef call in StartRemoving.
  Release();
}

void PluginDataRemover::OnClearSiteDataResult(bool success) {
  LOG_IF(DFATAL, !success) << "ClearSiteData returned error";
  UMA_HISTOGRAM_TIMES("ClearPluginData.time",
                      base::Time::Now() - remove_start_time_);
  SignalDone();
}

void PluginDataRemover::OnTimeout() {
  LOG_IF(DFATAL, is_removing_) << "Timed out";
  SignalDone();
}

bool PluginDataRemover::OnMessageReceived(const IPC::Message& msg) {
  IPC_BEGIN_MESSAGE_MAP(PluginDataRemover, msg)
    IPC_MESSAGE_HANDLER(PluginHostMsg_ClearSiteDataResult,
                        OnClearSiteDataResult)
    IPC_MESSAGE_UNHANDLED_ERROR()
  IPC_END_MESSAGE_MAP()

  return true;
}

void PluginDataRemover::OnChannelError() {
  if (is_removing_) {
    NOTREACHED() << "Channel error";
    SignalDone();
  }
}

void PluginDataRemover::SignalDone() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!is_removing_)
    return;
  is_removing_ = false;
  event_->Signal();
}

// static
bool PluginDataRemover::IsSupported() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
  bool allow_wildcard = false;
  webkit::npapi::WebPluginInfo plugin;
  std::string mime_type;
  if (!webkit::npapi::PluginList::Singleton()->GetPluginInfo(
          GURL(), kFlashMimeType, allow_wildcard, &plugin, &mime_type)) {
    return false;
  }
  scoped_ptr<Version> version(
      webkit::npapi::PluginGroup::CreateVersionFromString(plugin.version));
  scoped_ptr<Version> min_version(Version::GetVersionFromString(
      CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
          switches::kMinClearSiteDataFlashVersion)));
  if (!min_version.get())
    min_version.reset(Version::GetVersionFromString(kMinFlashVersion));
  return webkit::npapi::IsPluginEnabled(plugin) &&
         version.get() &&
         min_version->CompareTo(*version) == -1;
}