Commit 76b70f91 authored by jam@chromium.org's avatar jam@chromium.org

Fix race in PluginDataRemoverImpl going away while it's still being used on...

Fix race in PluginDataRemoverImpl going away while it's still being used on the IO thread, which was introduced in 110530. This also fix the incorrect usage of PluginService::OpenChannelToNpapiPlugin on the UI thread which existed before.

BUG=104553,cros:23179
Review URL: http://codereview.chromium.org/8603012

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@110979 0039d316-1c4b-4281-b951-d872f2087c98
parent d3bc7eeb
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "base/metrics/histogram.h" #include "base/metrics/histogram.h"
#include "base/synchronization/waitable_event.h" #include "base/synchronization/waitable_event.h"
#include "base/version.h" #include "base/version.h"
#include "content/browser/plugin_process_host.h"
#include "content/browser/plugin_service.h" #include "content/browser/plugin_service.h"
#include "content/common/plugin_messages.h" #include "content/common/plugin_messages.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
...@@ -54,137 +55,185 @@ bool PluginDataRemover::IsSupported(webkit::WebPluginInfo* plugin) { ...@@ -54,137 +55,185 @@ bool PluginDataRemover::IsSupported(webkit::WebPluginInfo* plugin) {
} }
PluginDataRemoverImpl::PluginDataRemoverImpl( class PluginDataRemoverImpl::Context
const content::ResourceContext& resource_context) : public PluginProcessHost::Client,
: mime_type_(kFlashMimeType), public IPC::Channel::Listener,
is_starting_process_(false), public base::RefCountedThreadSafe<Context> {
is_removing_(false), public:
context_(resource_context), Context(const std::string& mime_type,
event_(new base::WaitableEvent(true, false)), base::Time begin_time,
channel_(NULL), const content::ResourceContext& resource_context)
ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { : event_(new base::WaitableEvent(true, false)),
} begin_time_(begin_time),
is_removing_(false),
resource_context_(resource_context),
channel_(NULL) {
// Balanced in OnChannelOpened or OnError. Exactly one them will eventually
// be called, so we need to keep this object around until then.
AddRef();
remove_start_time_ = base::Time::Now();
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&Context::Init, this, mime_type));
BrowserThread::PostDelayedTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&Context::OnTimeout, this),
kRemovalTimeoutMs);
}
PluginDataRemoverImpl::~PluginDataRemoverImpl() { virtual ~Context() {
if (is_starting_process_) if (channel_)
PluginService::GetInstance()->CancelOpenChannelToNpapiPlugin(this); BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE, channel_);
DCHECK(!is_removing_); }
if (channel_)
BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE, channel_);
}
base::WaitableEvent* PluginDataRemoverImpl::StartRemoving( // PluginProcessHost::Client methods.
base::Time begin_time) { virtual int ID() OVERRIDE {
DCHECK(!is_removing_); // Generate a unique identifier for this PluginProcessHostClient.
remove_start_time_ = base::Time::Now(); return ChildProcessInfo::GenerateChildProcessUniqueId();
begin_time_ = begin_time; }
is_starting_process_ = true;
is_removing_ = true;
PluginService::GetInstance()->OpenChannelToNpapiPlugin(
0, 0, GURL(), GURL(), mime_type_, this);
BrowserThread::PostDelayedTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&PluginDataRemoverImpl::OnTimeout, weak_factory_.GetWeakPtr()),
kRemovalTimeoutMs);
return event_.get();
}
int PluginDataRemoverImpl::ID() { virtual bool OffTheRecord() OVERRIDE {
// Generate a unique identifier for this PluginProcessHostClient. return false;
return ChildProcessInfo::GenerateChildProcessUniqueId(); }
}
bool PluginDataRemoverImpl::OffTheRecord() { virtual const content::ResourceContext& GetResourceContext() OVERRIDE {
return false; return resource_context_;
} }
const content::ResourceContext& PluginDataRemoverImpl::GetResourceContext() { virtual void SetPluginInfo(const webkit::WebPluginInfo& info) OVERRIDE {
return context_; }
}
void PluginDataRemoverImpl::SetPluginInfo( virtual void OnFoundPluginProcessHost(PluginProcessHost* host) OVERRIDE {
const webkit::WebPluginInfo& info) { }
}
void PluginDataRemoverImpl::OnFoundPluginProcessHost( virtual void OnSentPluginChannelRequest() OVERRIDE {
PluginProcessHost* host) { }
}
void PluginDataRemoverImpl::OnSentPluginChannelRequest() { virtual void OnChannelOpened(const IPC::ChannelHandle& handle) OVERRIDE {
} ConnectToChannel(handle);
// Balancing the AddRef call.
Release();
}
void PluginDataRemoverImpl::OnChannelOpened(const IPC::ChannelHandle& handle) { virtual void OnError() OVERRIDE {
is_starting_process_ = false; LOG(DFATAL) << "Couldn't open plugin channel";
ConnectToChannel(handle); SignalDone();
} // Balancing the AddRef call.
Release();
}
// IPC::Channel::Listener methods.
virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
IPC_BEGIN_MESSAGE_MAP(Context, message)
IPC_MESSAGE_HANDLER(PluginHostMsg_ClearSiteDataResult,
OnClearSiteDataResult)
IPC_MESSAGE_UNHANDLED_ERROR()
IPC_END_MESSAGE_MAP()
return true;
}
virtual void OnChannelError() OVERRIDE {
if (is_removing_) {
NOTREACHED() << "Channel error";
SignalDone();
}
}
void PluginDataRemoverImpl::ConnectToChannel(const IPC::ChannelHandle& handle) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// If we timed out, don't bother connecting. base::WaitableEvent* event() { return event_.get(); }
if (!is_removing_)
return;
DCHECK(!channel_); private:
channel_ = new IPC::Channel(handle, IPC::Channel::MODE_CLIENT, this); // Initialize on the IO thread.
if (!channel_->Connect()) { void Init(const std::string& mime_type) {
NOTREACHED() << "Couldn't connect to plugin"; DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
is_removing_ = true;
PluginService::GetInstance()->OpenChannelToNpapiPlugin(
0, 0, GURL(), GURL(), mime_type, this);
}
// Connects the client side of a newly opened plug-in channel.
void 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;
}
}
// Handles the PluginHostMsg_ClearSiteDataResult message.
void OnClearSiteDataResult(bool success) {
LOG_IF(ERROR, !success) << "ClearSiteData returned error";
UMA_HISTOGRAM_TIMES("ClearPluginData.time",
base::Time::Now() - remove_start_time_);
SignalDone(); SignalDone();
return;
} }
if (!channel_->Send(new PluginMsg_ClearSiteData(std::string(), // Called when a timeout happens in order not to block the client
kClearAllData, // indefinitely.
begin_time_))) { void OnTimeout() {
NOTREACHED() << "Couldn't send ClearSiteData message"; LOG_IF(ERROR, is_removing_) << "Timed out";
SignalDone(); SignalDone();
return;
} }
}
void PluginDataRemoverImpl::OnError() { // Signals that we are finished with removing data (successful or not). This
LOG(DFATAL) << "Couldn't open plugin channel"; // method is safe to call multiple times.
SignalDone(); void SignalDone() {
} DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!is_removing_)
return;
is_removing_ = false;
event_->Signal();
}
void PluginDataRemoverImpl::OnClearSiteDataResult(bool success) { scoped_ptr<base::WaitableEvent> event_;
LOG_IF(ERROR, !success) << "ClearSiteData returned error"; // The point in time when we start removing data.
UMA_HISTOGRAM_TIMES("ClearPluginData.time", base::Time remove_start_time_;
base::Time::Now() - remove_start_time_); // The point in time from which on we remove data.
SignalDone(); base::Time begin_time_;
} bool is_removing_;
void PluginDataRemoverImpl::OnTimeout() { // The resource context for the profile.
LOG_IF(ERROR, is_removing_) << "Timed out"; const content::ResourceContext& resource_context_;
SignalDone();
}
bool PluginDataRemoverImpl::OnMessageReceived(const IPC::Message& msg) { // We own the channel, but it's used on the IO thread, so it needs to be
IPC_BEGIN_MESSAGE_MAP(PluginDataRemoverImpl, msg) // deleted there. It's NULL until we have opened a connection to the plug-in
IPC_MESSAGE_HANDLER(PluginHostMsg_ClearSiteDataResult, // process.
OnClearSiteDataResult) IPC::Channel* channel_;
IPC_MESSAGE_UNHANDLED_ERROR() };
IPC_END_MESSAGE_MAP()
return true;
PluginDataRemoverImpl::PluginDataRemoverImpl(
const content::ResourceContext& resource_context)
: mime_type_(kFlashMimeType),
resource_context_(resource_context) {
} }
void PluginDataRemoverImpl::OnChannelError() { PluginDataRemoverImpl::~PluginDataRemoverImpl() {
is_starting_process_ = false;
if (is_removing_) {
NOTREACHED() << "Channel error";
SignalDone();
}
} }
void PluginDataRemoverImpl::SignalDone() { base::WaitableEvent* PluginDataRemoverImpl::StartRemoving(
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); base::Time begin_time) {
if (!is_removing_) DCHECK(!context_.get());
return; context_ = new Context(mime_type_, begin_time, resource_context_);
is_removing_ = false; return context_->event();
event_->Signal();
} }
...@@ -9,14 +9,10 @@ ...@@ -9,14 +9,10 @@
#include <string> #include <string>
#include "base/compiler_specific.h" #include "base/compiler_specific.h"
#include "base/memory/weak_ptr.h" #include "base/memory/ref_counted.h"
#include "content/browser/plugin_process_host.h"
#include "content/public/browser/plugin_data_remover.h" #include "content/public/browser/plugin_data_remover.h"
class CONTENT_EXPORT PluginDataRemoverImpl class CONTENT_EXPORT PluginDataRemoverImpl : public content::PluginDataRemover {
: public content::PluginDataRemover,
public NON_EXPORTED_BASE(PluginProcessHost::Client),
public IPC::Channel::Listener {
public: public:
explicit PluginDataRemoverImpl( explicit PluginDataRemoverImpl(
const content::ResourceContext& resource_context); const content::ResourceContext& resource_context);
...@@ -30,48 +26,16 @@ class CONTENT_EXPORT PluginDataRemoverImpl ...@@ -30,48 +26,16 @@ class CONTENT_EXPORT PluginDataRemoverImpl
// different plug-in (for example in tests). // different plug-in (for example in tests).
void set_mime_type(const std::string& mime_type) { mime_type_ = mime_type; } void set_mime_type(const std::string& mime_type) { mime_type_ = mime_type; }
// PluginProcessHost::Client methods.
virtual int ID() OVERRIDE;
virtual bool OffTheRecord() OVERRIDE;
virtual const content::ResourceContext& GetResourceContext() OVERRIDE;
virtual void SetPluginInfo(const webkit::WebPluginInfo& info) OVERRIDE;
virtual void OnFoundPluginProcessHost(PluginProcessHost* host) OVERRIDE;
virtual void OnSentPluginChannelRequest() OVERRIDE;
virtual void OnChannelOpened(const IPC::ChannelHandle& handle) OVERRIDE;
virtual void OnError() OVERRIDE;
// IPC::Channel::Listener methods.
virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
virtual void OnChannelError() OVERRIDE;
private: private:
// Signals that we are finished with removing data (successful or not). This class Context;
// method is safe to call multiple times.
void SignalDone();
// Connects the client side of a newly opened plug-in channel.
void ConnectToChannel(const IPC::ChannelHandle& handle);
// Handles the PluginHostMsg_ClearSiteDataResult message.
void OnClearSiteDataResult(bool success);
// Called when a timeout happens in order not to block the client
// indefinitely.
void OnTimeout();
std::string mime_type_; std::string mime_type_;
bool is_starting_process_;
bool is_removing_;
// The point in time when we start removing data.
base::Time remove_start_time_;
// The point in time from which on we remove data.
base::Time begin_time_;
// The resource context for the profile. // The resource context for the profile.
const content::ResourceContext& context_; const content::ResourceContext& resource_context_;
scoped_ptr<base::WaitableEvent> event_;
// We own the channel, but it's used on the IO thread, so it needs to be
// deleted there. It's NULL until we have opened a connection to the plug-in
// process.
IPC::Channel* channel_;
base::WeakPtrFactory<PluginDataRemoverImpl> weak_factory_; // This allows this object to be deleted on the UI thread while it's still
// being used on the IO thread.
scoped_refptr<Context> context_;
DISALLOW_COPY_AND_ASSIGN(PluginDataRemoverImpl); DISALLOW_COPY_AND_ASSIGN(PluginDataRemoverImpl);
}; };
......
...@@ -332,6 +332,7 @@ void PluginService::OpenChannelToNpapiPlugin( ...@@ -332,6 +332,7 @@ void PluginService::OpenChannelToNpapiPlugin(
const GURL& page_url, const GURL& page_url,
const std::string& mime_type, const std::string& mime_type,
PluginProcessHost::Client* client) { PluginProcessHost::Client* client) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(!ContainsKey(pending_plugin_clients_, client)); DCHECK(!ContainsKey(pending_plugin_clients_, client));
pending_plugin_clients_.insert(client); pending_plugin_clients_.insert(client);
......
...@@ -23,6 +23,13 @@ namespace { ...@@ -23,6 +23,13 @@ namespace {
const char kNPAPITestPluginMimeType[] = "application/vnd.npapi-test"; const char kNPAPITestPluginMimeType[] = "application/vnd.npapi-test";
void OpenChannel(PluginProcessHost::Client* client) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// Start opening the channel
PluginService::GetInstance()->OpenChannelToNpapiPlugin(
0, 0, GURL(), GURL(), kNPAPITestPluginMimeType, client);
}
// Mock up of the Client and the Listener classes that would supply the // Mock up of the Client and the Listener classes that would supply the
// communication channel with the plugin. // communication channel with the plugin.
class MockPluginProcessHostClient : public PluginProcessHost::Client, class MockPluginProcessHostClient : public PluginProcessHost::Client,
...@@ -100,8 +107,9 @@ class PluginServiceTest : public InProcessBrowserTest { ...@@ -100,8 +107,9 @@ class PluginServiceTest : public InProcessBrowserTest {
IN_PROC_BROWSER_TEST_F(PluginServiceTest, OpenChannelToPlugin) { IN_PROC_BROWSER_TEST_F(PluginServiceTest, OpenChannelToPlugin) {
::testing::StrictMock<MockPluginProcessHostClient> mock_client( ::testing::StrictMock<MockPluginProcessHostClient> mock_client(
browser()->profile()->GetResourceContext()); browser()->profile()->GetResourceContext());
PluginService::GetInstance()->OpenChannelToNpapiPlugin( BrowserThread::PostTask(
0, 0, GURL(), GURL(), kNPAPITestPluginMimeType, &mock_client); BrowserThread::IO, FROM_HERE,
base::Bind(&OpenChannel, &mock_client));
ui_test_utils::RunMessageLoop(); ui_test_utils::RunMessageLoop();
} }
...@@ -234,13 +242,6 @@ class MockCanceledBeforeSentPluginProcessHostClient ...@@ -234,13 +242,6 @@ class MockCanceledBeforeSentPluginProcessHostClient
DISALLOW_COPY_AND_ASSIGN(MockCanceledBeforeSentPluginProcessHostClient); DISALLOW_COPY_AND_ASSIGN(MockCanceledBeforeSentPluginProcessHostClient);
}; };
void OpenChannel(PluginProcessHost::Client* client) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// Start opening the channel
PluginService::GetInstance()->OpenChannelToNpapiPlugin(
0, 0, GURL(), GURL(), kNPAPITestPluginMimeType, client);
}
IN_PROC_BROWSER_TEST_F( IN_PROC_BROWSER_TEST_F(
PluginServiceTest, CancelBeforeSentOpenChannelToPluginProcessHost) { PluginServiceTest, CancelBeforeSentOpenChannelToPluginProcessHost) {
::testing::StrictMock<MockCanceledBeforeSentPluginProcessHostClient> ::testing::StrictMock<MockCanceledBeforeSentPluginProcessHostClient>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment