1// Copyright (c) 2012 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 "content/browser/plugin_loader_posix.h"
6
7#include "base/bind.h"
8#include "base/message_loop/message_loop.h"
9#include "base/message_loop/message_loop_proxy.h"
10#include "base/metrics/histogram.h"
11#include "content/browser/utility_process_host_impl.h"
12#include "content/common/child_process_host_impl.h"
13#include "content/common/plugin_list.h"
14#include "content/common/utility_messages.h"
15#include "content/public/browser/browser_thread.h"
16#include "content/public/browser/plugin_service.h"
17#include "content/public/browser/user_metrics.h"
18
19namespace content {
20
21PluginLoaderPosix::PluginLoaderPosix()
22    : next_load_index_(0) {
23}
24
25void PluginLoaderPosix::GetPlugins(
26    const PluginService::GetPluginsCallback& callback) {
27  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
28
29  std::vector<WebPluginInfo> cached_plugins;
30  if (PluginList::Singleton()->GetPluginsNoRefresh(&cached_plugins)) {
31    // Can't assume the caller is reentrant.
32    base::MessageLoop::current()->PostTask(FROM_HERE,
33        base::Bind(callback, cached_plugins));
34    return;
35  }
36
37  if (callbacks_.empty()) {
38    callbacks_.push_back(callback);
39
40    PluginList::Singleton()->PrepareForPluginLoading();
41
42    BrowserThread::PostTask(BrowserThread::FILE,
43                            FROM_HERE,
44                            base::Bind(&PluginLoaderPosix::GetPluginsToLoad,
45                                       make_scoped_refptr(this)));
46  } else {
47    // If we are currently loading plugins, the plugin list might have been
48    // invalidated in the mean time, or might get invalidated before we finish.
49    // We'll wait until we have finished the current run, then try to get them
50    // again from the plugin list. If it has indeed been invalidated, it will
51    // restart plugin loading, otherwise it will immediately run the callback.
52    callbacks_.push_back(base::Bind(&PluginLoaderPosix::GetPluginsWrapper,
53                                    make_scoped_refptr(this), callback));
54  }
55}
56
57bool PluginLoaderPosix::OnMessageReceived(const IPC::Message& message) {
58  bool handled = true;
59  IPC_BEGIN_MESSAGE_MAP(PluginLoaderPosix, message)
60    IPC_MESSAGE_HANDLER(UtilityHostMsg_LoadedPlugin, OnPluginLoaded)
61    IPC_MESSAGE_HANDLER(UtilityHostMsg_LoadPluginFailed, OnPluginLoadFailed)
62    IPC_MESSAGE_UNHANDLED(handled = false)
63  IPC_END_MESSAGE_MAP()
64  return handled;
65}
66
67void PluginLoaderPosix::OnProcessCrashed(int exit_code) {
68  RecordAction(
69      base::UserMetricsAction("PluginLoaderPosix.UtilityProcessCrashed"));
70
71  if (next_load_index_ == canonical_list_.size()) {
72    // How this case occurs is unknown. See crbug.com/111935.
73    canonical_list_.clear();
74  } else {
75    canonical_list_.erase(canonical_list_.begin(),
76                          canonical_list_.begin() + next_load_index_ + 1);
77  }
78
79  next_load_index_ = 0;
80
81  LoadPluginsInternal();
82}
83
84bool PluginLoaderPosix::Send(IPC::Message* message) {
85  if (process_host_.get())
86    return process_host_->Send(message);
87  return false;
88}
89
90PluginLoaderPosix::~PluginLoaderPosix() {
91}
92
93void PluginLoaderPosix::GetPluginsToLoad() {
94  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
95
96  base::TimeTicks start_time(base::TimeTicks::Now());
97
98  loaded_plugins_.clear();
99  next_load_index_ = 0;
100
101  canonical_list_.clear();
102  PluginList::Singleton()->GetPluginPathsToLoad(
103      &canonical_list_,
104      PluginService::GetInstance()->NPAPIPluginsSupported());
105
106  internal_plugins_.clear();
107  PluginList::Singleton()->GetInternalPlugins(&internal_plugins_);
108
109  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
110      base::Bind(&PluginLoaderPosix::LoadPluginsInternal,
111                 make_scoped_refptr(this)));
112
113  LOCAL_HISTOGRAM_TIMES("PluginLoaderPosix.GetPluginList",
114                        (base::TimeTicks::Now() - start_time) *
115                            base::Time::kMicrosecondsPerMillisecond);
116}
117
118void PluginLoaderPosix::LoadPluginsInternal() {
119  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
120
121  // Check if the list is empty or all plugins have already been loaded before
122  // forking.
123  if (MaybeRunPendingCallbacks())
124    return;
125
126  RecordAction(
127      base::UserMetricsAction("PluginLoaderPosix.LaunchUtilityProcess"));
128
129  if (load_start_time_.is_null())
130    load_start_time_ = base::TimeTicks::Now();
131
132  UtilityProcessHostImpl* host = new UtilityProcessHostImpl(
133      this,
134      BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO).get());
135  process_host_ = host->AsWeakPtr();
136  process_host_->DisableSandbox();
137#if defined(OS_MACOSX)
138  host->set_child_flags(ChildProcessHost::CHILD_ALLOW_HEAP_EXECUTION);
139#endif
140
141  process_host_->Send(new UtilityMsg_LoadPlugins(canonical_list_));
142}
143
144void PluginLoaderPosix::GetPluginsWrapper(
145    const PluginService::GetPluginsCallback& callback,
146    const std::vector<WebPluginInfo>& plugins_unused) {
147  // We are being called after plugin loading has finished, but we don't know
148  // whether the plugin list has been invalidated in the mean time
149  // (and therefore |plugins| might already be stale). So we simply ignore it
150  // and call regular GetPlugins() instead.
151  GetPlugins(callback);
152}
153
154void PluginLoaderPosix::OnPluginLoaded(uint32 index,
155                                       const WebPluginInfo& plugin) {
156  if (index != next_load_index_) {
157    LOG(ERROR) << "Received unexpected plugin load message for "
158               << plugin.path.value() << "; index=" << index;
159    return;
160  }
161
162  if (!MaybeAddInternalPlugin(plugin.path))
163    loaded_plugins_.push_back(plugin);
164
165  ++next_load_index_;
166
167  MaybeRunPendingCallbacks();
168}
169
170void PluginLoaderPosix::OnPluginLoadFailed(uint32 index,
171                                           const base::FilePath& plugin_path) {
172  if (index != next_load_index_) {
173    LOG(ERROR) << "Received unexpected plugin load failure message for "
174               << plugin_path.value() << "; index=" << index;
175    return;
176  }
177
178  ++next_load_index_;
179
180  MaybeAddInternalPlugin(plugin_path);
181  MaybeRunPendingCallbacks();
182}
183
184bool PluginLoaderPosix::MaybeAddInternalPlugin(
185    const base::FilePath& plugin_path) {
186  for (std::vector<WebPluginInfo>::iterator it = internal_plugins_.begin();
187       it != internal_plugins_.end();
188       ++it) {
189    if (it->path == plugin_path) {
190      loaded_plugins_.push_back(*it);
191      internal_plugins_.erase(it);
192      return true;
193    }
194  }
195  return false;
196}
197
198bool PluginLoaderPosix::MaybeRunPendingCallbacks() {
199  if (next_load_index_ < canonical_list_.size())
200    return false;
201
202  PluginList::Singleton()->SetPlugins(loaded_plugins_);
203
204  for (std::vector<PluginService::GetPluginsCallback>::iterator it =
205           callbacks_.begin();
206       it != callbacks_.end(); ++it) {
207    base::MessageLoop::current()->PostTask(FROM_HERE,
208                                           base::Bind(*it, loaded_plugins_));
209  }
210  callbacks_.clear();
211
212  LOCAL_HISTOGRAM_TIMES("PluginLoaderPosix.LoadDone",
213                        (base::TimeTicks::Now() - load_start_time_) *
214                            base::Time::kMicrosecondsPerMillisecond);
215  load_start_time_ = base::TimeTicks();
216
217  return true;
218}
219
220}  // namespace content
221