1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/plugin_data_remover.h"
6
7#include "base/command_line.h"
8#include "base/message_loop_proxy.h"
9#include "base/metrics/histogram.h"
10#include "base/synchronization/waitable_event.h"
11#include "base/version.h"
12#include "chrome/common/chrome_switches.h"
13#include "content/browser/browser_thread.h"
14#include "content/browser/plugin_service.h"
15#include "content/common/plugin_messages.h"
16#include "webkit/plugins/npapi/plugin_group.h"
17#include "webkit/plugins/npapi/plugin_list.h"
18
19#if defined(OS_POSIX)
20#include "ipc/ipc_channel_posix.h"
21#endif
22
23namespace {
24
25const char* kFlashMimeType = "application/x-shockwave-flash";
26// The minimum Flash Player version that implements NPP_ClearSiteData.
27const char* kMinFlashVersion = "10.3";
28const int64 kRemovalTimeoutMs = 10000;
29const uint64 kClearAllData = 0;
30
31}  // namespace
32
33PluginDataRemover::PluginDataRemover()
34    : mime_type_(kFlashMimeType),
35      is_removing_(false),
36      event_(new base::WaitableEvent(true, false)),
37      channel_(NULL) {
38}
39
40PluginDataRemover::~PluginDataRemover() {
41  DCHECK(!is_removing_);
42  if (channel_)
43    BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE, channel_);
44}
45
46base::WaitableEvent* PluginDataRemover::StartRemoving(base::Time begin_time) {
47  DCHECK(!is_removing_);
48  remove_start_time_ = base::Time::Now();
49  begin_time_ = begin_time;
50
51  is_removing_ = true;
52
53  // Balanced in OnChannelOpened or OnError. Exactly one them will eventually be
54  // called, so we need to keep this object around until then.
55  AddRef();
56  PluginService::GetInstance()->OpenChannelToNpapiPlugin(
57      0, 0, GURL(), mime_type_, this);
58
59  BrowserThread::PostDelayedTask(
60      BrowserThread::IO,
61      FROM_HERE,
62      NewRunnableMethod(this, &PluginDataRemover::OnTimeout),
63      kRemovalTimeoutMs);
64
65  return event_.get();
66}
67
68void PluginDataRemover::Wait() {
69  base::Time start_time(base::Time::Now());
70  bool result = true;
71  if (is_removing_)
72    result = event_->Wait();
73  UMA_HISTOGRAM_TIMES("ClearPluginData.wait_at_shutdown",
74                      base::Time::Now() - start_time);
75  UMA_HISTOGRAM_TIMES("ClearPluginData.time_at_shutdown",
76                      base::Time::Now() - remove_start_time_);
77  DCHECK(result) << "Error waiting for plugin process";
78}
79
80int PluginDataRemover::ID() {
81  // Generate a unique identifier for this PluginProcessHostClient.
82  return ChildProcessInfo::GenerateChildProcessUniqueId();
83}
84
85bool PluginDataRemover::OffTheRecord() {
86  return false;
87}
88
89void PluginDataRemover::SetPluginInfo(
90    const webkit::npapi::WebPluginInfo& info) {
91}
92
93void PluginDataRemover::OnChannelOpened(const IPC::ChannelHandle& handle) {
94  ConnectToChannel(handle);
95  // Balancing the AddRef call in StartRemoving.
96  Release();
97}
98
99void PluginDataRemover::ConnectToChannel(const IPC::ChannelHandle& handle) {
100  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
101
102  // If we timed out, don't bother connecting.
103  if (!is_removing_)
104    return;
105
106  DCHECK(!channel_);
107  channel_ = new IPC::Channel(handle, IPC::Channel::MODE_CLIENT, this);
108  if (!channel_->Connect()) {
109    NOTREACHED() << "Couldn't connect to plugin";
110    SignalDone();
111    return;
112  }
113
114  if (!channel_->Send(new PluginMsg_ClearSiteData(std::string(),
115                                                  kClearAllData,
116                                                  begin_time_))) {
117    NOTREACHED() << "Couldn't send ClearSiteData message";
118    SignalDone();
119    return;
120  }
121}
122
123void PluginDataRemover::OnError() {
124  LOG(DFATAL) << "Couldn't open plugin channel";
125  SignalDone();
126  // Balancing the AddRef call in StartRemoving.
127  Release();
128}
129
130void PluginDataRemover::OnClearSiteDataResult(bool success) {
131  LOG_IF(DFATAL, !success) << "ClearSiteData returned error";
132  UMA_HISTOGRAM_TIMES("ClearPluginData.time",
133                      base::Time::Now() - remove_start_time_);
134  SignalDone();
135}
136
137void PluginDataRemover::OnTimeout() {
138  LOG_IF(DFATAL, is_removing_) << "Timed out";
139  SignalDone();
140}
141
142bool PluginDataRemover::OnMessageReceived(const IPC::Message& msg) {
143  IPC_BEGIN_MESSAGE_MAP(PluginDataRemover, msg)
144    IPC_MESSAGE_HANDLER(PluginHostMsg_ClearSiteDataResult,
145                        OnClearSiteDataResult)
146    IPC_MESSAGE_UNHANDLED_ERROR()
147  IPC_END_MESSAGE_MAP()
148
149  return true;
150}
151
152void PluginDataRemover::OnChannelError() {
153  if (is_removing_) {
154    NOTREACHED() << "Channel error";
155    SignalDone();
156  }
157}
158
159void PluginDataRemover::SignalDone() {
160  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
161  if (!is_removing_)
162    return;
163  is_removing_ = false;
164  event_->Signal();
165}
166
167// static
168bool PluginDataRemover::IsSupported() {
169  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
170  bool allow_wildcard = false;
171  webkit::npapi::WebPluginInfo plugin;
172  std::string mime_type;
173  if (!webkit::npapi::PluginList::Singleton()->GetPluginInfo(
174          GURL(), kFlashMimeType, allow_wildcard, &plugin, &mime_type)) {
175    return false;
176  }
177  scoped_ptr<Version> version(
178      webkit::npapi::PluginGroup::CreateVersionFromString(plugin.version));
179  scoped_ptr<Version> min_version(Version::GetVersionFromString(
180      CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
181          switches::kMinClearSiteDataFlashVersion)));
182  if (!min_version.get())
183    min_version.reset(Version::GetVersionFromString(kMinFlashVersion));
184  return webkit::npapi::IsPluginEnabled(plugin) &&
185         version.get() &&
186         min_version->CompareTo(*version) == -1;
187}
188