summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorviettrungluu@chromium.org <viettrungluu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-12-21 00:45:43 +0000
committerviettrungluu@chromium.org <viettrungluu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-12-21 00:45:43 +0000
commit64264efa8b4a0bf37b19042f295f9990a7f388c9 (patch)
tree5e7cbf44e04f00388078da62d6e0eed878da3d6d
parentc85b0ba71b5a55647b01de9d345e46896979033d (diff)
downloadchromium_src-64264efa8b4a0bf37b19042f295f9990a7f388c9.zip
chromium_src-64264efa8b4a0bf37b19042f295f9990a7f388c9.tar.gz
chromium_src-64264efa8b4a0bf37b19042f295f9990a7f388c9.tar.bz2
Properly cancel PPAPI callbacks.
This sets up infrastructure to properly cancel PPAPI callbacks (which have particular semantics), and converts a couple things to use them. BUG= TEST=test_shell_tests and PPAPI tests Review URL: http://codereview.chromium.org/5562008 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@69773 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--ppapi/tests/test_directory_reader.cc116
-rw-r--r--ppapi/tests/test_file_ref.cc140
-rw-r--r--ppapi/tests/test_utils.cc4
-rw-r--r--ppapi/tests/test_utils.h4
-rw-r--r--webkit/glue/webkit_glue.gypi2
-rw-r--r--webkit/plugins/ppapi/callbacks.cc155
-rw-r--r--webkit/plugins/ppapi/callbacks.h204
-rw-r--r--webkit/plugins/ppapi/callbacks_unittest.cc249
-rw-r--r--webkit/plugins/ppapi/file_callbacks.cc32
-rw-r--r--webkit/plugins/ppapi/file_callbacks.h9
-rw-r--r--webkit/plugins/ppapi/plugin_module.cc11
-rw-r--r--webkit/plugins/ppapi/plugin_module.h9
-rw-r--r--webkit/plugins/ppapi/ppapi_unittest.cc8
-rw-r--r--webkit/plugins/ppapi/ppapi_unittest.h3
-rw-r--r--webkit/plugins/ppapi/ppb_directory_reader_impl.cc3
-rw-r--r--webkit/plugins/ppapi/ppb_file_ref_impl.cc12
-rw-r--r--webkit/plugins/ppapi/ppb_file_system_impl.cc2
-rw-r--r--webkit/plugins/ppapi/resource.cc3
-rw-r--r--webkit/plugins/ppapi/resource_tracker_unittest.cc3
-rw-r--r--webkit/tools/test_shell/test_shell.gypi1
20 files changed, 895 insertions, 75 deletions
diff --git a/ppapi/tests/test_directory_reader.cc b/ppapi/tests/test_directory_reader.cc
index d8bf737..3ae83f3 100644
--- a/ppapi/tests/test_directory_reader.cc
+++ b/ppapi/tests/test_directory_reader.cc
@@ -92,52 +92,84 @@ std::string TestDirectoryReader::TestGetNextFile() {
if (rv != PP_OK)
return ReportError("FileRef::MakeDirectory", rv);
- pp::DirectoryReader_Dev directory_reader(dir_ref);
- std::vector<pp::DirectoryEntry_Dev> entries;
- pp::DirectoryEntry_Dev entry;
- do {
- rv = directory_reader.GetNextEntry(&entry, callback);
- if (rv == PP_ERROR_WOULDBLOCK)
+ {
+ pp::DirectoryReader_Dev directory_reader(dir_ref);
+ std::vector<pp::DirectoryEntry_Dev> entries;
+ pp::DirectoryEntry_Dev entry;
+ do {
+ rv = directory_reader.GetNextEntry(&entry, callback);
+ if (rv == PP_ERROR_WOULDBLOCK)
+ rv = callback.WaitForResult();
+ if (rv != PP_OK)
+ return ReportError("DirectoryReader::GetNextEntry", rv);
+ if (!entry.is_null())
+ entries.push_back(entry);
+ } while (!entry.is_null());
+
+ if (entries.size() != 6)
+ return "Expected 6 entries, got " + IntegerToString(entries.size());
+
+ std::set<std::string> expected_file_names;
+ expected_file_names.insert("/file_1");
+ expected_file_names.insert("/file_2");
+ expected_file_names.insert("/file_3");
+
+ std::set<std::string> expected_dir_names;
+ expected_dir_names.insert("/dir_1");
+ expected_dir_names.insert("/dir_2");
+ expected_dir_names.insert("/dir_3");
+
+ for (std::vector<pp::DirectoryEntry_Dev>::const_iterator it =
+ entries.begin(); it != entries.end(); it++) {
+ pp::FileRef_Dev file_ref = it->file_ref();
+ std::string file_path = file_ref.GetPath().AsString();
+ std::set<std::string>::iterator found =
+ expected_file_names.find(file_path);
+ if (found != expected_file_names.end()) {
+ if (it->file_type() != PP_FILETYPE_REGULAR)
+ return file_path + " should have been a regular file.";
+ expected_file_names.erase(found);
+ } else {
+ found = expected_dir_names.find(file_path);
+ if (found == expected_dir_names.end())
+ return "Unexpected file path: " + file_path;
+ if (it->file_type() != PP_FILETYPE_DIRECTORY)
+ return file_path + " should have been a directory.";
+ expected_dir_names.erase(found);
+ }
+ }
+ if (!expected_file_names.empty() || !expected_dir_names.empty())
+ return "Expected more file paths.";
+ }
+
+ // Test cancellation of asynchronous |GetNextEntry()|.
+ {
+ // Declaring |entry| here prevents memory corruption for some failure modes
+ // and lets us to detect some other failures.
+ pp::DirectoryEntry_Dev entry;
+ callback.reset_run_count();
+ // Note that the directory reader will be deleted immediately.
+ rv = pp::DirectoryReader_Dev(dir_ref).GetNextEntry(&entry, callback);
+ if (callback.run_count() > 0)
+ return "DirectoryReader::GetNextEntry ran callback synchronously.";
+
+ // If |GetNextEntry()| is completing asynchronously, the callback should be
+ // aborted (i.e., called with |PP_ERROR_ABORTED| from the message loop)
+ // since the resource was destroyed.
+ if (rv == PP_ERROR_WOULDBLOCK) {
+ // |GetNextEntry()| *may* have written to |entry| (e.g., synchronously,
+ // before the resource was destroyed), but it must not write to it after
+ // destruction.
+ bool entry_is_null = entry.is_null();
rv = callback.WaitForResult();
- if (rv != PP_OK)
+ if (rv != PP_ERROR_ABORTED)
+ return "DirectoryReader::GetNextEntry not aborted.";
+ if (entry.is_null() != entry_is_null)
+ return "DirectoryReader::GetNextEntry wrote result after destruction.";
+ } else if (rv != PP_OK) {
return ReportError("DirectoryReader::GetNextEntry", rv);
- if (!entry.is_null())
- entries.push_back(entry);
- } while (!entry.is_null());
-
- if (entries.size() != 6)
- return "Expected 6 entries, got " + IntegerToString(entries.size());
-
- std::set<std::string> expected_file_names;
- expected_file_names.insert("/file_1");
- expected_file_names.insert("/file_2");
- expected_file_names.insert("/file_3");
-
- std::set<std::string> expected_dir_names;
- expected_dir_names.insert("/dir_1");
- expected_dir_names.insert("/dir_2");
- expected_dir_names.insert("/dir_3");
-
- for (std::vector<pp::DirectoryEntry_Dev>::const_iterator it = entries.begin();
- it != entries.end(); it++) {
- pp::FileRef_Dev file_ref = it->file_ref();
- std::string file_path = file_ref.GetPath().AsString();
- std::set<std::string>::iterator found = expected_file_names.find(file_path);
- if (found != expected_file_names.end()) {
- if (it->file_type() != PP_FILETYPE_REGULAR)
- return file_path + " should have been a regular file.";
- expected_file_names.erase(found);
- } else {
- found = expected_dir_names.find(file_path);
- if (found == expected_dir_names.end())
- return "Unexpected file path: " + file_path;
- if (it->file_type() != PP_FILETYPE_DIRECTORY)
- return file_path + " should have been a directory.";
- expected_dir_names.erase(found);
}
}
- if (!expected_file_names.empty() || !expected_dir_names.empty())
- return "Expected more file paths.";
return "";
}
diff --git a/ppapi/tests/test_file_ref.cc b/ppapi/tests/test_file_ref.cc
index b1062a7..a81b8ed2 100644
--- a/ppapi/tests/test_file_ref.cc
+++ b/ppapi/tests/test_file_ref.cc
@@ -246,6 +246,7 @@ std::string TestFileRef::TestGetParent() {
std::string TestFileRef::TestMakeDirectory() {
TestCompletionCallback callback;
+ // Open.
pp::FileSystem_Dev file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY);
int32_t rv = file_system.Open(1024, callback);
if (rv == PP_ERROR_WOULDBLOCK)
@@ -253,6 +254,21 @@ std::string TestFileRef::TestMakeDirectory() {
if (rv != PP_OK)
return ReportError("FileSystem::Open", rv);
+ // Open aborted (see the DirectoryReader test for comments).
+ callback.reset_run_count();
+ rv = pp::FileSystem_Dev(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY)
+ .Open(1024, callback);
+ if (callback.run_count() > 0)
+ return "FileSystem::Open ran callback synchronously.";
+ if (rv == PP_ERROR_WOULDBLOCK) {
+ rv = callback.WaitForResult();
+ if (rv != PP_ERROR_ABORTED)
+ return "FileSystem::Open not aborted.";
+ } else if (rv != PP_OK) {
+ return ReportError("FileSystem::Open", rv);
+ }
+
+ // MakeDirectory.
pp::FileRef_Dev dir_ref(file_system, "/test_dir_make_directory");
rv = dir_ref.MakeDirectory(callback);
if (rv == PP_ERROR_WOULDBLOCK)
@@ -260,6 +276,21 @@ std::string TestFileRef::TestMakeDirectory() {
if (rv != PP_OK)
return ReportError("FileSystem::MakeDirectory", rv);
+ // MakeDirectory aborted.
+ callback.reset_run_count();
+ rv = pp::FileRef_Dev(file_system, "/test_dir_make_abort")
+ .MakeDirectory(callback);
+ if (callback.run_count() > 0)
+ return "FileSystem::MakeDirectory ran callback synchronously.";
+ if (rv == PP_ERROR_WOULDBLOCK) {
+ rv = callback.WaitForResult();
+ if (rv != PP_ERROR_ABORTED)
+ return "FileSystem::MakeDirectory not aborted.";
+ } else if (rv != PP_OK) {
+ return ReportError("FileSystem::MakeDirectory", rv);
+ }
+
+ // MakeDirectoryIncludingAncestors.
dir_ref = pp::FileRef_Dev(file_system, "/dir_make_dir_1/dir_make_dir_2");
rv = dir_ref.MakeDirectoryIncludingAncestors(callback);
if (rv == PP_ERROR_WOULDBLOCK)
@@ -267,13 +298,31 @@ std::string TestFileRef::TestMakeDirectory() {
if (rv != PP_OK)
return ReportError("FileSystem::MakeDirectoryIncludingAncestors", rv);
+ // MakeDirectoryIncludingAncestors aborted.
+ callback.reset_run_count();
+ rv = pp::FileRef_Dev(file_system, "/dir_make_abort_1/dir_make_abort_2")
+ .MakeDirectoryIncludingAncestors(callback);
+ if (callback.run_count() > 0) {
+ return "FileSystem::MakeDirectoryIncludingAncestors "
+ "ran callback synchronously.";
+ }
+ if (rv == PP_ERROR_WOULDBLOCK) {
+ rv = callback.WaitForResult();
+ if (rv != PP_ERROR_ABORTED)
+ return "FileSystem::MakeDirectoryIncludingAncestors not aborted.";
+ } else if (rv != PP_OK) {
+ return ReportError("FileSystem::MakeDirectoryIncludingAncestors", rv);
+ }
+
+ // MakeDirectory with nested path.
dir_ref = pp::FileRef_Dev(file_system, "/dir_make_dir_3/dir_make_dir_4");
rv = dir_ref.MakeDirectory(callback);
if (rv == PP_ERROR_WOULDBLOCK)
rv = callback.WaitForResult();
- if (rv == PP_OK)
- return "Calling FileSystem::MakeDirectory() with a nested directory path " \
- "should have failed.";
+ if (rv == PP_OK) {
+ return "Calling FileSystem::MakeDirectory() with a nested directory path "
+ "should have failed.";
+ }
return "";
}
@@ -304,6 +353,7 @@ std::string TestFileRef::TestQueryAndTouchFile() {
if (rv != 4)
return ReportError("FileIO::Write", rv);
+ // Touch.
// last_access_time's granularity is 1 day
// last_modified_time's granularity is 2 seconds
const PP_Time last_access_time = 123 * 24 * 3600.0;
@@ -314,6 +364,21 @@ std::string TestFileRef::TestQueryAndTouchFile() {
if (rv != PP_OK)
return ReportError("FileSystem::Touch", rv);
+ // Touch aborted.
+ callback.reset_run_count();
+ rv = pp::FileRef_Dev(file_system, "/file_touch_abort")
+ .Touch(last_access_time, last_modified_time, callback);
+ if (callback.run_count() > 0)
+ return "FileSystem::Touch ran callback synchronously.";
+ if (rv == PP_ERROR_WOULDBLOCK) {
+ rv = callback.WaitForResult();
+ if (rv != PP_ERROR_ABORTED)
+ return "FileSystem::Touch not aborted.";
+ } else if (rv != PP_OK) {
+ return ReportError("FileSystem::Touch", rv);
+ }
+
+ // Query.
PP_FileInfo_Dev info;
rv = file_ref.Query(&info, callback);
if (rv == PP_ERROR_WOULDBLOCK)
@@ -328,6 +393,21 @@ std::string TestFileRef::TestQueryAndTouchFile() {
(info.last_modified_time != last_modified_time))
return "FileSystem::Query() has returned bad data.";
+ // Cancellation test.
+ // TODO(viettrungluu): this test causes a bunch of LOG(WARNING)s; investigate.
+ callback.reset_run_count();
+ // TODO(viettrungluu): check |info| for late writes.
+ rv = pp::FileRef_Dev(file_system, "/file_touch").Query(&info, callback);
+ if (callback.run_count() > 0)
+ return "FileSystem::Query ran callback synchronously.";
+ if (rv == PP_ERROR_WOULDBLOCK) {
+ rv = callback.WaitForResult();
+ if (rv != PP_ERROR_ABORTED)
+ return "FileSystem::Query not aborted.";
+ } else if (rv != PP_OK) {
+ return ReportError("FileSystem::Query", rv);
+ }
+
return "";
}
@@ -374,7 +454,9 @@ std::string TestFileRef::TestDeleteFileAndDirectory() {
if (rv != PP_OK)
return ReportError("FileSystem::MakeDirectoryIncludingAncestors", rv);
- rv = nested_dir_ref.GetParent().Delete(callback);
+ // Hang on to a ref to the parent; otherwise the callback will be aborted.
+ pp::FileRef_Dev parent_dir_ref = nested_dir_ref.GetParent();
+ rv = parent_dir_ref.Delete(callback);
if (rv == PP_ERROR_WOULDBLOCK)
rv = callback.WaitForResult();
if (rv != PP_ERROR_FAILED)
@@ -387,6 +469,29 @@ std::string TestFileRef::TestDeleteFileAndDirectory() {
if (rv != PP_ERROR_FILENOTFOUND)
return ReportError("FileSystem::Delete", rv);
+ // Delete aborted.
+ {
+ pp::FileRef_Dev file_ref_abort(file_system, "/file_delete_abort");
+ pp::FileIO_Dev file_io_abort;
+ rv = file_io_abort.Open(file_ref_abort, PP_FILEOPENFLAG_CREATE, callback);
+ if (rv == PP_ERROR_WOULDBLOCK)
+ rv = callback.WaitForResult();
+ if (rv != PP_OK)
+ return ReportError("FileIO::Open", rv);
+
+ callback.reset_run_count();
+ rv = file_ref_abort.Delete(callback);
+ }
+ if (callback.run_count() > 0)
+ return "FileSystem::Delete ran callback synchronously.";
+ if (rv == PP_ERROR_WOULDBLOCK) {
+ rv = callback.WaitForResult();
+ if (rv != PP_ERROR_ABORTED)
+ return "FileSystem::Delete not aborted.";
+ } else if (rv != PP_OK) {
+ return ReportError("FileSystem::Delete", rv);
+ }
+
return "";
}
@@ -442,5 +547,32 @@ std::string TestFileRef::TestRenameFileAndDirectory() {
if (rv != PP_ERROR_FAILED)
return ReportError("FileSystem::Rename", rv);
+ // Rename aborted.
+ // TODO(viettrungluu): Figure out what we want to do if the target file
+ // resource is destroyed before completion.
+ pp::FileRef_Dev target_file_ref_abort(file_system,
+ "/target_file_rename_abort");
+ {
+ pp::FileRef_Dev file_ref_abort(file_system, "/file_rename_abort");
+ pp::FileIO_Dev file_io_abort;
+ rv = file_io_abort.Open(file_ref_abort, PP_FILEOPENFLAG_CREATE, callback);
+ if (rv == PP_ERROR_WOULDBLOCK)
+ rv = callback.WaitForResult();
+ if (rv != PP_OK)
+ return ReportError("FileIO::Open", rv);
+
+ callback.reset_run_count();
+ rv = file_ref_abort.Rename(target_file_ref_abort, callback);
+ }
+ if (callback.run_count() > 0)
+ return "FileSystem::Rename ran callback synchronously.";
+ if (rv == PP_ERROR_WOULDBLOCK) {
+ rv = callback.WaitForResult();
+ if (rv != PP_ERROR_ABORTED)
+ return "FileSystem::Rename not aborted.";
+ } else if (rv != PP_OK) {
+ return ReportError("FileSystem::Rename", rv);
+ }
+
return "";
}
diff --git a/ppapi/tests/test_utils.cc b/ppapi/tests/test_utils.cc
index c9386ce..30246ab 100644
--- a/ppapi/tests/test_utils.cc
+++ b/ppapi/tests/test_utils.cc
@@ -28,7 +28,8 @@ std::string ReportError(const char* method, int32_t error) {
TestCompletionCallback::TestCompletionCallback()
: result_(PP_ERROR_WOULDBLOCK),
- post_quit_task_(false) {
+ post_quit_task_(false),
+ run_count_(0) {
}
int32_t TestCompletionCallback::WaitForResult() {
@@ -48,6 +49,7 @@ void TestCompletionCallback::Handler(void* user_data, int32_t result) {
TestCompletionCallback* callback =
static_cast<TestCompletionCallback*>(user_data);
callback->result_ = result;
+ callback->run_count_++;
if (callback->post_quit_task_) {
callback->post_quit_task_ = false;
GetTestingInterface()->QuitMessageLoop();
diff --git a/ppapi/tests/test_utils.h b/ppapi/tests/test_utils.h
index 7af515d..192b239 100644
--- a/ppapi/tests/test_utils.h
+++ b/ppapi/tests/test_utils.h
@@ -22,11 +22,15 @@ class TestCompletionCallback {
operator pp::CompletionCallback() const;
+ unsigned run_count() const { return run_count_; }
+ void reset_run_count() { run_count_ = 0; }
+
private:
static void Handler(void* user_data, int32_t result);
int32_t result_;
bool post_quit_task_;
+ unsigned run_count_;
};
#endif // PPAPI_TESTS_TEST_UTILS_H_
diff --git a/webkit/glue/webkit_glue.gypi b/webkit/glue/webkit_glue.gypi
index 13545b8..1499a6f 100644
--- a/webkit/glue/webkit_glue.gypi
+++ b/webkit/glue/webkit_glue.gypi
@@ -170,6 +170,8 @@
# names.
'../plugins/plugin_switches.cc',
'../plugins/plugin_switches.h',
+ '../plugins/ppapi/callbacks.cc',
+ '../plugins/ppapi/callbacks.h',
'../plugins/ppapi/common.h',
'../plugins/ppapi/dir_contents.h',
'../plugins/ppapi/error_util.cc',
diff --git a/webkit/plugins/ppapi/callbacks.cc b/webkit/plugins/ppapi/callbacks.cc
new file mode 100644
index 0000000..69cd2e8
--- /dev/null
+++ b/webkit/plugins/ppapi/callbacks.cc
@@ -0,0 +1,155 @@
+// Copyright (c) 2010 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 "webkit/plugins/ppapi/callbacks.h"
+
+#include <algorithm>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "ppapi/c/pp_completion_callback.h"
+#include "ppapi/c/pp_errors.h"
+
+namespace webkit {
+namespace ppapi {
+
+// CallbackTracker -------------------------------------------------------------
+
+CallbackTracker::CallbackTracker() {
+}
+
+void CallbackTracker::AbortAll() {
+ // Iterate over a copy since |Abort()| calls |Remove()| (indirectly).
+ // TODO(viettrungluu): This obviously isn't so efficient.
+ CallbackSetMap pending_callbacks_copy = pending_callbacks_;
+ for (CallbackSetMap::iterator it1 = pending_callbacks_copy.begin();
+ it1 != pending_callbacks_copy.end(); ++it1) {
+ for (CallbackSet::iterator it2 = it1->second.begin();
+ it2 != it1->second.end(); ++it2) {
+ (*it2)->Abort();
+ }
+ }
+}
+
+void CallbackTracker::PostAbortForResource(PP_Resource resource_id) {
+ CHECK(resource_id != 0);
+ CallbackSetMap::iterator it1 = pending_callbacks_.find(resource_id);
+ if (it1 == pending_callbacks_.end())
+ return;
+ for (CallbackSet::iterator it2 = it1->second.begin();
+ it2 != it1->second.end(); ++it2) {
+ // Post the abort.
+ (*it2)->PostAbort();
+ }
+}
+
+CallbackTracker::~CallbackTracker() {
+ // All callbacks must be aborted before destruction.
+ CHECK_EQ(0u, pending_callbacks_.size());
+}
+
+void CallbackTracker::Add(
+ const scoped_refptr<TrackedCallback>& tracked_callback) {
+ PP_Resource resource_id = tracked_callback->resource_id();
+ DCHECK(pending_callbacks_[resource_id].find(tracked_callback) ==
+ pending_callbacks_[resource_id].end());
+ pending_callbacks_[resource_id].insert(tracked_callback);
+}
+
+void CallbackTracker::Remove(
+ const scoped_refptr<TrackedCallback>& tracked_callback) {
+ CallbackSetMap::iterator map_it =
+ pending_callbacks_.find(tracked_callback->resource_id());
+ DCHECK(map_it != pending_callbacks_.end());
+ CallbackSet::iterator it = map_it->second.find(tracked_callback);
+ DCHECK(it != map_it->second.end());
+ map_it->second.erase(it);
+
+ // If there are no pending callbacks left for this ID, get rid of the entry.
+ if (map_it->second.empty())
+ pending_callbacks_.erase(map_it);
+}
+
+// TrackedCallback -------------------------------------------------------------
+
+TrackedCallback::TrackedCallback(const scoped_refptr<CallbackTracker>& tracker,
+ PP_Resource resource_id)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(abort_impl_factory_(this)),
+ tracker_(tracker),
+ resource_id_(resource_id),
+ completed_(false),
+ aborted_(false) {
+ tracker_->Add(make_scoped_refptr(this));
+}
+
+void TrackedCallback::Abort() {
+ if (!completed()) {
+ aborted_ = true;
+ AbortImpl();
+ }
+}
+
+void TrackedCallback::PostAbort() {
+ if (!completed()) {
+ aborted_ = true;
+ // Post a task for the abort (only if necessary).
+ if (abort_impl_factory_.empty()) {
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ abort_impl_factory_.NewRunnableMethod(&TrackedCallback::AbortImpl));
+ }
+ }
+}
+
+TrackedCallback::~TrackedCallback() {
+ // The tracker must ensure that the callback is completed (maybe abortively).
+ DCHECK(completed_);
+}
+
+void TrackedCallback::MarkAsCompleted() {
+ DCHECK(!completed());
+
+ // We will be removed; maintain a reference to ensure we won't be deleted
+ // until we're done.
+ scoped_refptr<TrackedCallback> thiz = this;
+ completed_ = true;
+ tracker_->Remove(thiz);
+ tracker_ = NULL;
+}
+
+// TrackedCompletionCallback ---------------------------------------------------
+
+TrackedCompletionCallback::TrackedCompletionCallback(
+ const scoped_refptr<CallbackTracker>& tracker,
+ PP_Resource resource_id,
+ const PP_CompletionCallback& callback)
+ : TrackedCallback(tracker, resource_id),
+ callback_(callback) {
+}
+
+void TrackedCompletionCallback::Run(int32_t result) {
+ if (!completed()) {
+ // Cancel any pending calls.
+ abort_impl_factory_.RevokeAll();
+
+ // Copy |callback_| and look at |aborted()| now, since |MarkAsCompleted()|
+ // may delete us.
+ PP_CompletionCallback callback = callback_;
+ if (aborted())
+ result = PP_ERROR_ABORTED;
+
+ // Do this before running the callback in case of reentrancy (which
+ // shouldn't happen, but avoid strange failures).
+ MarkAsCompleted();
+ PP_RunCompletionCallback(&callback, result);
+ }
+}
+
+void TrackedCompletionCallback::AbortImpl() {
+ Run(PP_ERROR_ABORTED);
+}
+
+} // namespace ppapi
+} // namespace webkit
diff --git a/webkit/plugins/ppapi/callbacks.h b/webkit/plugins/ppapi/callbacks.h
new file mode 100644
index 0000000..065fb01
--- /dev/null
+++ b/webkit/plugins/ppapi/callbacks.h
@@ -0,0 +1,204 @@
+// Copyright (c) 2010 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.
+
+#ifndef WEBKIT_GLUE_PLUGINS_PEPPER_CALLBACKS_H_
+#define WEBKIT_GLUE_PLUGINS_PEPPER_CALLBACKS_H_
+
+#include <map>
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "base/task.h"
+#include "ppapi/c/pp_completion_callback.h"
+#include "ppapi/c/pp_resource.h"
+
+namespace webkit {
+namespace ppapi {
+
+class TrackedCallback;
+
+// Pepper callbacks have the following semantics (unless otherwise specified;
+// in particular, the below apply to all completion callbacks):
+// - Callbacks are always run on the main thread.
+// - Callbacks are always called from the main message loop. In particular,
+// calling into Pepper will not result in the plugin being re-entered via a
+// synchronously-run callback.
+// - Each callback will be executed (a.k.a. completed) exactly once.
+// - Each callback may be *aborted*, which means that it will be executed with
+// result |PP_ERROR_ABORTED| (in the case of completion callbacks).
+// - Before |PPP_ShutdownModule()| is called, every pending callback (for that
+// module) will be aborted.
+// - Callbacks are usually associated to a resource, whose "deletion" provides
+// a "cancellation" (or abort) mechanism -- see below.
+// - When a plugin releases its last reference to resource, all callbacks
+// associated to that resource are aborted. Even if a non-abortive completion
+// of such a callback had previously been scheduled (i.e., posted), that
+// callback must now be aborted. The aborts should be scheduled immediately
+// (upon the last reference being given up) and should not rely on anything
+// else (e.g., a background task to complete or further action from the
+// plugin).
+// - Abortive completion gives no information about the status of the
+// asynchronous operation: The operation may have not yet begun, may be in
+// progress, or may be completed (successfully or not). In fact, the
+// operation may still proceed after the callback has been aborted.
+// - Any output data buffers provided to Pepper are associated with a resource.
+// Once that resource is released, no subsequent writes to those buffers. (If
+// background threads are set up to write into such buffers, the final
+// release operation should not return into the plugin until it can
+// guaranteed that those threads will no longer write into the buffers.)
+//
+// Thread-safety notes:
+// Currently, everything should happen on the main thread. The objects are
+// thread-safe ref-counted, so objects which live on different threads may keep
+// references. Releasing a reference to |TrackedCallback| on a different thread
+// (possibly causing destruction) is also okay. Otherwise, all methods should be
+// called only from the main thread.
+
+// |CallbackTracker| tracks pending Pepper callbacks for a single module. It
+// also tracks, for each resource ID, which callbacks are pending. When a
+// callback is (just about to be) completed, it is removed from the tracker. We
+// use |CallbackTracker| for two things: (1) to ensure that all callbacks are
+// properly aborted before module shutdown, and (2) to ensure that all callbacks
+// associated to a given resource are aborted when a plugin (module) releases
+// its last reference to that resource.
+class CallbackTracker : public base::RefCountedThreadSafe<CallbackTracker> {
+ public:
+ CallbackTracker();
+
+ // Abort all callbacks (synchronously).
+ void AbortAll();
+
+ // Abort all callbacks associated to the given resource ID (which must be
+ // valid, i.e., nonzero) by posting a task (or tasks).
+ void PostAbortForResource(PP_Resource resource_id);
+
+ private:
+ friend class base::RefCountedThreadSafe<CallbackTracker>;
+ ~CallbackTracker();
+
+ // |TrackedCallback| are expected to automatically add and
+ // remove themselves from their provided |CallbackTracker|.
+ friend class TrackedCallback;
+ void Add(const scoped_refptr<TrackedCallback>& tracked_callback);
+ void Remove(const scoped_refptr<TrackedCallback>& tracked_callback);
+
+ // For each resource ID with a pending callback, store a set with its pending
+ // callbacks. (Resource ID 0 is used for callbacks not associated to a valid
+ // resource.) If a resource ID is re-used for another resource, there may be
+ // aborted callbacks corresponding to the original resource in that set; these
+ // will be removed when they are completed (abortively).
+ typedef std::set<scoped_refptr<TrackedCallback> > CallbackSet;
+ typedef std::map<PP_Resource, CallbackSet> CallbackSetMap;
+ CallbackSetMap pending_callbacks_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallbackTracker);
+};
+
+// |TrackedCallback| represents a tracked Pepper callback (from the browser to
+// the plugin), typically still pending. Such callbacks have the standard Pepper
+// callback semantics. Execution (i.e., completion) of callbacks happens through
+// objects of subclasses of |TrackedCallback|. Two things are ensured: (1) that
+// the callback is executed at most once, and (2) once a callback is marked to
+// be aborted, any subsequent completion is abortive (even if a non-abortive
+// completion had previously been scheduled).
+//
+// The details of non-abortive completion depend on the type of callback (e.g.,
+// different parameters may be required), but basic abort functionality is core.
+// The ability to post aborts is needed in many situations to ensure that the
+// plugin is not re-entered into. (Note that posting a task to just run
+// |Abort()| is different and not correct; calling |PostAbort()| additionally
+// guarantees that all subsequent completions will be abortive.)
+//
+// This class is reference counted so that different things can hang on to it,
+// and not worry too much about ensuring Pepper callback semantics. Note that
+// the "owning" |CallbackTracker| will keep a reference until the callback is
+// completed.
+//
+// Subclasses must do several things:
+// - They must ensure that the callback is executed at most once (by looking at
+// |completed()| before running the callback).
+// - They must ensure that the callback is run abortively if it is marked as to
+// be aborted (by looking at |aborted()| before running the callback).
+// - They must call |MarkAsCompleted()| immediately before actually running the
+// callback; see the comment for |MarkAsCompleted()| for a caveat.
+class TrackedCallback : public base::RefCountedThreadSafe<TrackedCallback> {
+ public:
+ // The constructor will add the new object to the tracker. The resource ID is
+ // optional -- set it to 0 if no resource is associated to the callback.
+ TrackedCallback(const scoped_refptr<CallbackTracker>& tracker,
+ PP_Resource resource_id);
+
+ // These run the callback in an abortive manner, or post a task to do so (but
+ // immediately marking the callback as to be aborted).
+ void Abort();
+ void PostAbort();
+
+ // Returns the ID of the resource which "owns" the callback, or 0 if the
+ // callback is not associated with any resource.
+ PP_Resource resource_id() const { return resource_id_; }
+
+ // Returns true if the callback was completed (possibly aborted).
+ bool completed() const { return completed_; }
+
+ // Returns true if the callback was or should be aborted; this will be the
+ // case whenever |Abort()| or |PostAbort()| is called before a non-abortive
+ // completion.
+ bool aborted() const { return aborted_; }
+
+ protected:
+ // This class is ref counted.
+ friend class base::RefCountedThreadSafe<TrackedCallback>;
+ virtual ~TrackedCallback();
+
+ // To be implemented by subclasses: Actually run the callback abortively.
+ virtual void AbortImpl() = 0;
+
+ // Mark this object as complete and remove it from the tracker. This must only
+ // be called once. Note that running this may result in this object being
+ // deleted (so keep a reference if it'll still be needed).
+ void MarkAsCompleted();
+
+ // Factory used by |PostAbort()|. Note that it's safe to cancel any pending
+ // posted aborts on destruction -- before it's destroyed, the "owning"
+ // |CallbackTracker| must have gone through and done (synchronous) |Abort()|s.
+ ScopedRunnableMethodFactory<TrackedCallback> abort_impl_factory_;
+
+ private:
+ scoped_refptr<CallbackTracker> tracker_;
+ PP_Resource resource_id_;
+ bool completed_;
+ bool aborted_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrackedCallback);
+};
+
+// |TrackedCompletionCallback| represents a tracked Pepper completion callback.
+class TrackedCompletionCallback : public TrackedCallback {
+ public:
+ // Create a tracked completion callback and register it with the tracker. The
+ // resource ID may be 0 if the callback is not associated to any resource.
+ TrackedCompletionCallback(const scoped_refptr<CallbackTracker>& tracker,
+ PP_Resource resource_id,
+ const PP_CompletionCallback& callback);
+
+ // Run the callback with the given result. If the callback had previously been
+ // marked as to be aborted (by |PostAbort()|), |result| will be ignored and
+ // the callback will be run with result |PP_ERROR_ABORTED|.
+ void Run(int32_t result);
+
+ protected:
+ // |TrackedCallback| method:
+ virtual void AbortImpl();
+
+ private:
+ PP_CompletionCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrackedCompletionCallback);
+};
+
+} // namespace ppapi
+} // namespace webkit
+
+#endif // WEBKIT_PLUGINS_PPAPI_CALLBACKS_H_
diff --git a/webkit/plugins/ppapi/callbacks_unittest.cc b/webkit/plugins/ppapi/callbacks_unittest.cc
new file mode 100644
index 0000000..2619264
--- /dev/null
+++ b/webkit/plugins/ppapi/callbacks_unittest.cc
@@ -0,0 +1,249 @@
+// Copyright (c) 2010 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 "webkit/plugins/ppapi/ppapi_unittest.h"
+
+#include "base/message_loop.h"
+#include "base/ref_counted.h"
+#include "ppapi/c/pp_completion_callback.h"
+#include "ppapi/c/pp_errors.h"
+#include "webkit/plugins/ppapi/callbacks.h"
+#include "webkit/plugins/ppapi/mock_resource.h"
+#include "webkit/plugins/ppapi/plugin_module.h"
+#include "webkit/plugins/ppapi/resource_tracker.h"
+
+namespace webkit {
+namespace ppapi {
+
+namespace {
+
+struct CallbackRunInfo {
+ // All valid results (PP_OK, PP_ERROR_...) are nonpositive.
+ CallbackRunInfo() : run_count(0), result(1) {}
+ unsigned run_count;
+ int32_t result;
+};
+
+void TestCallback(void* user_data, int32_t result) {
+ CallbackRunInfo* info = reinterpret_cast<CallbackRunInfo*>(user_data);
+ info->run_count++;
+ if (info->run_count == 1)
+ info->result = result;
+}
+
+} // namespace
+
+// CallbackShutdownTest --------------------------------------------------------
+
+// Tests that callbacks are properly aborted on module shutdown.
+class CallbackShutdownTest : public PpapiUnittest {
+ public:
+ CallbackShutdownTest() {}
+
+ // Cases:
+ // (1) A callback which is run (so shouldn't be aborted on shutdown).
+ // (2) A callback which is aborted (so shouldn't be aborted on shutdown).
+ // (3) A callback which isn't run (so should be aborted on shutdown).
+ CallbackRunInfo& info_did_run() { return info_did_run_; } // (1)
+ CallbackRunInfo& info_did_abort() { return info_did_abort_; } // (2)
+ CallbackRunInfo& info_didnt_run() { return info_didnt_run_; } // (3)
+
+ private:
+ CallbackRunInfo info_did_run_;
+ CallbackRunInfo info_did_abort_;
+ CallbackRunInfo info_didnt_run_;
+};
+
+TEST_F(CallbackShutdownTest, AbortOnShutdown) {
+ // Set up case (1) (see above).
+ EXPECT_EQ(0U, info_did_run().run_count);
+ scoped_refptr<TrackedCompletionCallback> callback_did_run =
+ new TrackedCompletionCallback(
+ module()->GetCallbackTracker(),
+ 0,
+ PP_MakeCompletionCallback(&TestCallback, &info_did_run()));
+ EXPECT_EQ(0U, info_did_run().run_count);
+ callback_did_run->Run(PP_OK);
+ EXPECT_EQ(1U, info_did_run().run_count);
+ EXPECT_EQ(PP_OK, info_did_run().result);
+
+ // Set up case (2).
+ EXPECT_EQ(0U, info_did_abort().run_count);
+ scoped_refptr<TrackedCompletionCallback> callback_did_abort =
+ new TrackedCompletionCallback(
+ module()->GetCallbackTracker(),
+ 0,
+ PP_MakeCompletionCallback(&TestCallback, &info_did_abort()));
+ EXPECT_EQ(0U, info_did_abort().run_count);
+ callback_did_abort->Abort();
+ EXPECT_EQ(1U, info_did_abort().run_count);
+ EXPECT_EQ(PP_ERROR_ABORTED, info_did_abort().result);
+
+
+ // Set up case (3).
+ EXPECT_EQ(0U, info_didnt_run().run_count);
+ scoped_refptr<TrackedCompletionCallback> callback_didnt_run =
+ new TrackedCompletionCallback(
+ module()->GetCallbackTracker(),
+ 0,
+ PP_MakeCompletionCallback(&TestCallback, &info_didnt_run()));
+ EXPECT_EQ(0U, info_didnt_run().run_count);
+
+ ShutdownModule();
+
+ // Check case (1).
+ EXPECT_EQ(1U, info_did_run().run_count);
+
+ // Check case (2).
+ EXPECT_EQ(1U, info_did_abort().run_count);
+
+ // Check case (3).
+ EXPECT_EQ(1U, info_didnt_run().run_count);
+ EXPECT_EQ(PP_ERROR_ABORTED, info_didnt_run().result);
+}
+
+// -----------------------------------------------------------------------------
+
+namespace {
+
+class CallbackMockResource : public MockResource {
+ public:
+ CallbackMockResource(PluginModule* module) : MockResource(module) {}
+ ~CallbackMockResource() {}
+
+ PP_Resource SetupForTest() {
+ PP_Resource resource_id = GetReference();
+ EXPECT_NE(0, resource_id);
+
+ callback_did_run_ = new TrackedCompletionCallback(
+ module()->GetCallbackTracker(),
+ resource_id,
+ PP_MakeCompletionCallback(&TestCallback, &info_did_run_));
+ EXPECT_EQ(0U, info_did_run_.run_count);
+
+ callback_did_abort_ = new TrackedCompletionCallback(
+ module()->GetCallbackTracker(),
+ resource_id,
+ PP_MakeCompletionCallback(&TestCallback, &info_did_abort_));
+ EXPECT_EQ(0U, info_did_abort_.run_count);
+
+ callback_didnt_run_ = new TrackedCompletionCallback(
+ module()->GetCallbackTracker(),
+ resource_id,
+ PP_MakeCompletionCallback(&TestCallback, &info_didnt_run_));
+ EXPECT_EQ(0U, info_didnt_run_.run_count);
+
+ callback_did_run_->Run(PP_OK);
+ callback_did_abort_->Abort();
+
+ CheckIntermediateState();
+
+ return resource_id;
+ }
+
+ void CheckIntermediateState() {
+ EXPECT_EQ(1U, info_did_run_.run_count);
+ EXPECT_EQ(PP_OK, info_did_run_.result);
+
+ EXPECT_EQ(1U, info_did_abort_.run_count);
+ EXPECT_EQ(PP_ERROR_ABORTED, info_did_abort_.result);
+
+ EXPECT_EQ(0U, info_didnt_run_.run_count);
+ }
+
+ void CheckFinalState() {
+ EXPECT_EQ(1U, info_did_run_.run_count);
+ EXPECT_EQ(PP_OK, info_did_run_.result);
+ EXPECT_EQ(1U, info_did_abort_.run_count);
+ EXPECT_EQ(PP_ERROR_ABORTED, info_did_abort_.result);
+ EXPECT_EQ(1U, info_didnt_run_.run_count);
+ EXPECT_EQ(PP_ERROR_ABORTED, info_didnt_run_.result);
+ }
+
+ scoped_refptr<TrackedCompletionCallback> callback_did_run_;
+ CallbackRunInfo info_did_run_;
+
+ scoped_refptr<TrackedCompletionCallback> callback_did_abort_;
+ CallbackRunInfo info_did_abort_;
+
+ scoped_refptr<TrackedCompletionCallback> callback_didnt_run_;
+ CallbackRunInfo info_didnt_run_;
+};
+
+} // namespace
+
+class CallbackResourceTest : public PpapiUnittest {
+ public:
+ CallbackResourceTest() {}
+};
+
+// Test that callbacks get aborted on the last resource unref.
+TEST_F(CallbackResourceTest, AbortOnNoRef) {
+ ResourceTracker* resource_tracker = ResourceTracker::Get();
+
+ // Test several things: Unref-ing a resource (to zero refs) with callbacks
+ // which (1) have been run, (2) have been aborted, (3) haven't been completed.
+ // Check that the uncompleted one gets aborted, and that the others don't get
+ // called again.
+ scoped_refptr<CallbackMockResource> resource_1(
+ new CallbackMockResource(module()));
+ PP_Resource resource_1_id = resource_1->SetupForTest();
+
+ // Also do the same for a second resource, and make sure that unref-ing the
+ // first resource doesn't much up the second resource.
+ scoped_refptr<CallbackMockResource> resource_2(
+ new CallbackMockResource(module()));
+ PP_Resource resource_2_id = resource_2->SetupForTest();
+
+ // Double-check that resource #1 is still okay.
+ resource_1->CheckIntermediateState();
+
+ // Kill resource #1, spin the message loop to run posted calls, and check that
+ // things are in the expected states.
+ resource_tracker->UnrefResource(resource_1_id);
+ MessageLoop::current()->RunAllPending();
+ resource_1->CheckFinalState();
+ resource_2->CheckIntermediateState();
+
+ // Kill resource #2.
+ resource_tracker->UnrefResource(resource_2_id);
+ MessageLoop::current()->RunAllPending();
+ resource_1->CheckFinalState();
+ resource_2->CheckFinalState();
+
+ // This shouldn't be needed, but make sure there are no stranded tasks.
+ MessageLoop::current()->RunAllPending();
+}
+
+// Test that "resurrecting" a resource (getting a new ID for a |Resource|)
+// doesn't resurrect callbacks.
+TEST_F(CallbackResourceTest, Resurrection) {
+ ResourceTracker* resource_tracker = ResourceTracker::Get();
+
+ scoped_refptr<CallbackMockResource> resource(
+ new CallbackMockResource(module()));
+ PP_Resource resource_id = resource->SetupForTest();
+
+ // Unref it, spin the message loop to run posted calls, and check that things
+ // are in the expected states.
+ resource_tracker->UnrefResource(resource_id);
+ MessageLoop::current()->RunAllPending();
+ resource->CheckFinalState();
+
+ // "Resurrect" it and check that the callbacks are still dead.
+ PP_Resource new_resource_id = resource->GetReference();
+ MessageLoop::current()->RunAllPending();
+ resource->CheckFinalState();
+
+ // Unref it again and do the same.
+ resource_tracker->UnrefResource(new_resource_id);
+ MessageLoop::current()->RunAllPending();
+ resource->CheckFinalState();
+
+ // This shouldn't be needed, but make sure there are no stranded tasks.
+ MessageLoop::current()->RunAllPending();
+}
+
+} // namespace ppapi
+} // namespace webkit
diff --git a/webkit/plugins/ppapi/file_callbacks.cc b/webkit/plugins/ppapi/file_callbacks.cc
index 9f991be..4c57f33 100644
--- a/webkit/plugins/ppapi/file_callbacks.cc
+++ b/webkit/plugins/ppapi/file_callbacks.cc
@@ -9,8 +9,10 @@
#include "ppapi/c/dev/ppb_file_system_dev.h"
#include "ppapi/c/dev/pp_file_info_dev.h"
#include "ppapi/c/pp_errors.h"
-#include "webkit/plugins/ppapi/ppb_directory_reader_impl.h"
+#include "webkit/plugins/ppapi/callbacks.h"
#include "webkit/plugins/ppapi/error_util.h"
+#include "webkit/plugins/ppapi/plugin_module.h"
+#include "webkit/plugins/ppapi/ppb_directory_reader_impl.h"
#include "webkit/plugins/ppapi/ppb_file_system_impl.h"
#include "webkit/fileapi/file_system_types.h"
@@ -19,12 +21,14 @@ namespace ppapi {
FileCallbacks::FileCallbacks(
const base::WeakPtr<PluginModule>& module,
+ PP_Resource resource_id,
PP_CompletionCallback callback,
PP_FileInfo_Dev* info,
scoped_refptr<PPB_FileSystem_Impl> file_system,
scoped_refptr<PPB_DirectoryReader_Impl> directory_reader)
- : module_(module),
- callback_(callback),
+ : callback_(new TrackedCompletionCallback(module->GetCallbackTracker(),
+ resource_id,
+ callback)),
info_(info),
file_system_(file_system),
directory_reader_(directory_reader) {
@@ -33,15 +37,15 @@ FileCallbacks::FileCallbacks(
FileCallbacks::~FileCallbacks() {}
void FileCallbacks::DidSucceed() {
- if (!module_.get() || !callback_.func)
+ if (callback_->completed())
return;
- PP_RunCompletionCallback(&callback_, PP_OK);
+ callback_->Run(PP_OK);
}
void FileCallbacks::DidReadMetadata(
const base::PlatformFileInfo& file_info) {
- if (!module_.get() || !callback_.func)
+ if (callback_->completed())
return;
DCHECK(info_);
@@ -56,30 +60,30 @@ void FileCallbacks::DidReadMetadata(
else
info_->type = PP_FILETYPE_REGULAR;
- PP_RunCompletionCallback(&callback_, PP_OK);
+ callback_->Run(PP_OK);
}
void FileCallbacks::DidReadDirectory(
const std::vector<base::FileUtilProxy::Entry>& entries, bool has_more) {
- if (!module_.get() || !callback_.func)
+ if (callback_->completed())
return;
DCHECK(directory_reader_);
directory_reader_->AddNewEntries(entries, has_more);
- PP_RunCompletionCallback(&callback_, PP_OK);
+ callback_->Run(PP_OK);
}
void FileCallbacks::DidOpenFileSystem(const std::string&,
const FilePath& root_path) {
- if (!module_.get() || !callback_.func)
+ if (callback_->completed())
return;
DCHECK(file_system_);
file_system_->set_root_path(root_path);
file_system_->set_opened(true);
- PP_RunCompletionCallback(&callback_, PP_OK);
+ callback_->Run(PP_OK);
}
void FileCallbacks::DidFail(base::PlatformFileError error_code) {
@@ -91,13 +95,11 @@ void FileCallbacks::DidWrite(int64 bytes, bool complete) {
}
void FileCallbacks::RunCallback(base::PlatformFileError error_code) {
- if (!module_.get() || !callback_.func)
+ if (callback_->completed())
return;
- PP_RunCompletionCallback(
- &callback_, PlatformFileErrorToPepperError(error_code));
+ callback_->Run(PlatformFileErrorToPepperError(error_code));
}
} // namespace ppapi
} // namespace webkit
-
diff --git a/webkit/plugins/ppapi/file_callbacks.h b/webkit/plugins/ppapi/file_callbacks.h
index 4b96d1c..4408b17 100644
--- a/webkit/plugins/ppapi/file_callbacks.h
+++ b/webkit/plugins/ppapi/file_callbacks.h
@@ -6,8 +6,10 @@
#define WEBKIT_PLUGINS_PPAPI_FILE_CALLBACKS_H_
#include "base/platform_file.h"
+#include "base/ref_counted.h"
#include "base/weak_ptr.h"
#include "ppapi/c/pp_completion_callback.h"
+#include "ppapi/c/pp_resource.h"
#include "webkit/fileapi/file_system_callback_dispatcher.h"
struct PP_FileInfo_Dev;
@@ -22,11 +24,13 @@ namespace ppapi {
class PPB_DirectoryReader_Impl;
class PPB_FileSystem_Impl;
class PluginModule;
+class TrackedCompletionCallback;
// Instances of this class are deleted by FileSystemDispatcher.
class FileCallbacks : public fileapi::FileSystemCallbackDispatcher {
public:
FileCallbacks(const base::WeakPtr<PluginModule>& module,
+ PP_Resource resource_id,
PP_CompletionCallback callback,
PP_FileInfo_Dev* info,
scoped_refptr<PPB_FileSystem_Impl> file_system,
@@ -43,11 +47,12 @@ class FileCallbacks : public fileapi::FileSystemCallbackDispatcher {
virtual void DidFail(base::PlatformFileError error_code);
virtual void DidWrite(int64 bytes, bool complete);
+ scoped_refptr<TrackedCompletionCallback> GetTrackedCompletionCallback() const;
+
private:
void RunCallback(base::PlatformFileError error_code);
- base::WeakPtr<PluginModule> module_;
- PP_CompletionCallback callback_;
+ scoped_refptr<TrackedCompletionCallback> callback_;
PP_FileInfo_Dev* info_;
scoped_refptr<PPB_FileSystem_Impl> file_system_;
scoped_refptr<PPB_DirectoryReader_Impl> directory_reader_;
diff --git a/webkit/plugins/ppapi/plugin_module.cc b/webkit/plugins/ppapi/plugin_module.cc
index 374ca7a..186df13 100644
--- a/webkit/plugins/ppapi/plugin_module.cc
+++ b/webkit/plugins/ppapi/plugin_module.cc
@@ -49,6 +49,7 @@
#include "ppapi/c/private/ppb_nacl_private.h"
#include "ppapi/c/trusted/ppb_image_data_trusted.h"
#include "ppapi/c/trusted/ppb_url_loader_trusted.h"
+#include "webkit/plugins/ppapi/callbacks.h"
#include "webkit/plugins/ppapi/common.h"
#include "webkit/plugins/ppapi/plugin_object.h"
#include "webkit/plugins/ppapi/ppapi_plugin_instance.h"
@@ -336,7 +337,9 @@ PluginModule::EntryPoints::EntryPoints()
// PluginModule ----------------------------------------------------------------
-PluginModule::PluginModule() : library_(NULL) {
+PluginModule::PluginModule()
+ : callback_tracker_(new CallbackTracker),
+ library_(NULL) {
pp_module_ = ResourceTracker::Get()->AddModule(this);
GetMainThreadMessageLoop(); // Initialize the main thread message loop.
GetLivePluginSet()->insert(this);
@@ -360,6 +363,8 @@ PluginModule::~PluginModule() {
GetLivePluginSet()->erase(this);
+ callback_tracker_->AbortAll();
+
if (entry_points_.shutdown_module)
entry_points_.shutdown_module();
@@ -487,6 +492,10 @@ void PluginModule::RemovePluginObject(PluginObject* plugin_object) {
live_plugin_objects_.erase(plugin_object);
}
+scoped_refptr<CallbackTracker> PluginModule::GetCallbackTracker() {
+ return callback_tracker_;
+}
+
bool PluginModule::InitializeModule() {
DCHECK(!out_of_process_proxy_.get()) << "Don't call for proxied modules.";
int retval = entry_points_.initialize_module(pp_module(), &GetInterface);
diff --git a/webkit/plugins/ppapi/plugin_module.h b/webkit/plugins/ppapi/plugin_module.h
index 39b5f1d..d4096c61 100644
--- a/webkit/plugins/ppapi/plugin_module.h
+++ b/webkit/plugins/ppapi/plugin_module.h
@@ -41,6 +41,7 @@ struct ChannelHandle;
namespace webkit {
namespace ppapi {
+class CallbackTracker;
class ObjectVar;
class PluginDelegate;
class PluginInstance;
@@ -133,12 +134,18 @@ class PluginModule : public base::RefCounted<PluginModule>,
void AddPluginObject(PluginObject* plugin_object);
void RemovePluginObject(PluginObject* plugin_object);
+ scoped_refptr<CallbackTracker> GetCallbackTracker();
+
private:
// Calls the InitializeModule entrypoint. The entrypoint must have been
// set and the plugin must not be out of process (we don't maintain
// entrypoints in that case).
bool InitializeModule();
+ // Tracker for completion callbacks, used mainly to ensure that all callbacks
+ // are properly aborted on module shutdown.
+ scoped_refptr<CallbackTracker> callback_tracker_;
+
PP_Module pp_module_;
// Manages the out of process proxy interface. The presence of this
@@ -167,7 +174,7 @@ class PluginModule : public base::RefCounted<PluginModule>,
// Tracks all live ObjectVars used by this module so we can map NPObjects to
// the corresponding object. These are non-owning references.
- typedef std::map<NPObject*, ObjectVar*> NPObjectToObjectVarMap;;
+ typedef std::map<NPObject*, ObjectVar*> NPObjectToObjectVarMap;
NPObjectToObjectVarMap np_object_to_object_var_;
typedef std::set<PluginObject*> PluginObjectSet;
diff --git a/webkit/plugins/ppapi/ppapi_unittest.cc b/webkit/plugins/ppapi/ppapi_unittest.cc
index 12cb99d..479871b 100644
--- a/webkit/plugins/ppapi/ppapi_unittest.cc
+++ b/webkit/plugins/ppapi/ppapi_unittest.cc
@@ -108,6 +108,12 @@ const void* PpapiUnittest::GetMockInterface(const char* interface_name) const {
return NULL;
}
+void PpapiUnittest::ShutdownModule() {
+ DCHECK(instance_->HasOneRef());
+ instance_ = NULL;
+ DCHECK(module_->HasOneRef());
+ module_ = NULL;
+}
+
} // namespace ppapi
} // namespace webkit
-
diff --git a/webkit/plugins/ppapi/ppapi_unittest.h b/webkit/plugins/ppapi/ppapi_unittest.h
index ae15467..b9ccf5e 100644
--- a/webkit/plugins/ppapi/ppapi_unittest.h
+++ b/webkit/plugins/ppapi/ppapi_unittest.h
@@ -32,6 +32,9 @@ class PpapiUnittest : public testing::Test {
// implements PPP_INSTANCE.
virtual const void* GetMockInterface(const char* interface_name) const;
+ // Deletes the instance and module to simulate module shutdown.
+ void ShutdownModule();
+
private:
scoped_ptr<MockPluginDelegate> delegate_;
diff --git a/webkit/plugins/ppapi/ppb_directory_reader_impl.cc b/webkit/plugins/ppapi/ppb_directory_reader_impl.cc
index 1f5c97c..c650be5 100644
--- a/webkit/plugins/ppapi/ppb_directory_reader_impl.cc
+++ b/webkit/plugins/ppapi/ppb_directory_reader_impl.cc
@@ -109,9 +109,12 @@ int32_t PPB_DirectoryReader_Impl::GetNextEntry(
}
PluginInstance* instance = directory_ref_->GetFileSystem()->instance();
+ PP_Resource resource_id = GetReferenceNoAddRef();
+ DCHECK(resource_id != 0);
if (!instance->delegate()->ReadDirectory(
directory_ref_->GetSystemPath(),
new FileCallbacks(instance->module()->AsWeakPtr(),
+ resource_id,
callback, NULL, NULL, this)))
return PP_ERROR_FAILED;
diff --git a/webkit/plugins/ppapi/ppb_file_ref_impl.cc b/webkit/plugins/ppapi/ppb_file_ref_impl.cc
index 79676df..f400ce4 100644
--- a/webkit/plugins/ppapi/ppb_file_ref_impl.cc
+++ b/webkit/plugins/ppapi/ppb_file_ref_impl.cc
@@ -124,7 +124,7 @@ int32_t MakeDirectory(PP_Resource directory_ref_id,
PluginInstance* instance = file_system->instance();
if (!instance->delegate()->MakeDirectory(
directory_ref->GetSystemPath(), PPBoolToBool(make_ancestors),
- new FileCallbacks(instance->module()->AsWeakPtr(),
+ new FileCallbacks(instance->module()->AsWeakPtr(), directory_ref_id,
callback, NULL, NULL, NULL)))
return PP_ERROR_FAILED;
@@ -147,7 +147,7 @@ int32_t Query(PP_Resource file_ref_id,
PluginInstance* instance = file_system->instance();
if (!instance->delegate()->Query(
file_ref->GetSystemPath(),
- new FileCallbacks(instance->module()->AsWeakPtr(),
+ new FileCallbacks(instance->module()->AsWeakPtr(), file_ref_id,
callback, info, file_system, NULL)))
return PP_ERROR_FAILED;
@@ -172,7 +172,7 @@ int32_t Touch(PP_Resource file_ref_id,
if (!instance->delegate()->Touch(
file_ref->GetSystemPath(), base::Time::FromDoubleT(last_access_time),
base::Time::FromDoubleT(last_modified_time),
- new FileCallbacks(instance->module()->AsWeakPtr(),
+ new FileCallbacks(instance->module()->AsWeakPtr(), file_ref_id,
callback, NULL, NULL, NULL)))
return PP_ERROR_FAILED;
@@ -194,7 +194,7 @@ int32_t Delete(PP_Resource file_ref_id,
PluginInstance* instance = file_system->instance();
if (!instance->delegate()->Delete(
file_ref->GetSystemPath(),
- new FileCallbacks(instance->module()->AsWeakPtr(),
+ new FileCallbacks(instance->module()->AsWeakPtr(), file_ref_id,
callback, NULL, NULL, NULL)))
return PP_ERROR_FAILED;
@@ -220,10 +220,12 @@ int32_t Rename(PP_Resource file_ref_id,
(file_system->type() == PP_FILESYSTEMTYPE_EXTERNAL))
return PP_ERROR_NOACCESS;
+ // TODO(viettrungluu): Also cancel when the new file ref is destroyed?
+ // http://crbug.com/67624
PluginInstance* instance = file_system->instance();
if (!instance->delegate()->Rename(
file_ref->GetSystemPath(), new_file_ref->GetSystemPath(),
- new FileCallbacks(instance->module()->AsWeakPtr(),
+ new FileCallbacks(instance->module()->AsWeakPtr(), file_ref_id,
callback, NULL, NULL, NULL)))
return PP_ERROR_FAILED;
diff --git a/webkit/plugins/ppapi/ppb_file_system_impl.cc b/webkit/plugins/ppapi/ppb_file_system_impl.cc
index fea8327..cec5cec 100644
--- a/webkit/plugins/ppapi/ppb_file_system_impl.cc
+++ b/webkit/plugins/ppapi/ppb_file_system_impl.cc
@@ -59,7 +59,7 @@ int32_t Open(PP_Resource file_system_id,
if (!instance->delegate()->OpenFileSystem(
instance->container()->element().document().frame()->url(),
file_system_type, expected_size,
- new FileCallbacks(instance->module()->AsWeakPtr(),
+ new FileCallbacks(instance->module()->AsWeakPtr(), file_system_id,
callback, NULL, file_system, NULL)))
return PP_ERROR_FAILED;
diff --git a/webkit/plugins/ppapi/resource.cc b/webkit/plugins/ppapi/resource.cc
index 0f8fbf9..4f77104 100644
--- a/webkit/plugins/ppapi/resource.cc
+++ b/webkit/plugins/ppapi/resource.cc
@@ -5,6 +5,8 @@
#include "webkit/plugins/ppapi/resource.h"
#include "base/logging.h"
+#include "webkit/plugins/ppapi/callbacks.h"
+#include "webkit/plugins/ppapi/plugin_module.h"
#include "webkit/plugins/ppapi/resource_tracker.h"
namespace webkit {
@@ -32,6 +34,7 @@ PP_Resource Resource::GetReferenceNoAddRef() const {
void Resource::StoppedTracking() {
DCHECK(resource_id_ != 0);
+ module_->GetCallbackTracker()->PostAbortForResource(resource_id_);
resource_id_ = 0;
}
diff --git a/webkit/plugins/ppapi/resource_tracker_unittest.cc b/webkit/plugins/ppapi/resource_tracker_unittest.cc
index 60910df..4170460 100644
--- a/webkit/plugins/ppapi/resource_tracker_unittest.cc
+++ b/webkit/plugins/ppapi/resource_tracker_unittest.cc
@@ -4,8 +4,8 @@
#include "webkit/plugins/ppapi/ppapi_unittest.h"
-#include "webkit/plugins/ppapi/resource_tracker.h"
#include "webkit/plugins/ppapi/mock_resource.h"
+#include "webkit/plugins/ppapi/resource_tracker.h"
namespace webkit {
namespace ppapi {
@@ -116,4 +116,3 @@ TEST_F(ResourceTrackerTest, ForceDelete) {
} // namespace ppapi
} // namespace webkit
-
diff --git a/webkit/tools/test_shell/test_shell.gypi b/webkit/tools/test_shell/test_shell.gypi
index bec7e53..3fa6fa4 100644
--- a/webkit/tools/test_shell/test_shell.gypi
+++ b/webkit/tools/test_shell/test_shell.gypi
@@ -415,6 +415,7 @@
'../../mocks/mock_resource_loader_bridge.h',
'../../mocks/mock_webframe.h',
'../../mocks/mock_weburlloader.h',
+ '../../plugins/ppapi/callbacks_unittest.cc',
'../../plugins/ppapi/mock_plugin_delegate.cc',
'../../plugins/ppapi/mock_plugin_delegate.h',
'../../plugins/ppapi/mock_resource.h',