diff options
author | viettrungluu@chromium.org <viettrungluu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-21 00:45:43 +0000 |
---|---|---|
committer | viettrungluu@chromium.org <viettrungluu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-21 00:45:43 +0000 |
commit | 64264efa8b4a0bf37b19042f295f9990a7f388c9 (patch) | |
tree | 5e7cbf44e04f00388078da62d6e0eed878da3d6d | |
parent | c85b0ba71b5a55647b01de9d345e46896979033d (diff) | |
download | chromium_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.cc | 116 | ||||
-rw-r--r-- | ppapi/tests/test_file_ref.cc | 140 | ||||
-rw-r--r-- | ppapi/tests/test_utils.cc | 4 | ||||
-rw-r--r-- | ppapi/tests/test_utils.h | 4 | ||||
-rw-r--r-- | webkit/glue/webkit_glue.gypi | 2 | ||||
-rw-r--r-- | webkit/plugins/ppapi/callbacks.cc | 155 | ||||
-rw-r--r-- | webkit/plugins/ppapi/callbacks.h | 204 | ||||
-rw-r--r-- | webkit/plugins/ppapi/callbacks_unittest.cc | 249 | ||||
-rw-r--r-- | webkit/plugins/ppapi/file_callbacks.cc | 32 | ||||
-rw-r--r-- | webkit/plugins/ppapi/file_callbacks.h | 9 | ||||
-rw-r--r-- | webkit/plugins/ppapi/plugin_module.cc | 11 | ||||
-rw-r--r-- | webkit/plugins/ppapi/plugin_module.h | 9 | ||||
-rw-r--r-- | webkit/plugins/ppapi/ppapi_unittest.cc | 8 | ||||
-rw-r--r-- | webkit/plugins/ppapi/ppapi_unittest.h | 3 | ||||
-rw-r--r-- | webkit/plugins/ppapi/ppb_directory_reader_impl.cc | 3 | ||||
-rw-r--r-- | webkit/plugins/ppapi/ppb_file_ref_impl.cc | 12 | ||||
-rw-r--r-- | webkit/plugins/ppapi/ppb_file_system_impl.cc | 2 | ||||
-rw-r--r-- | webkit/plugins/ppapi/resource.cc | 3 | ||||
-rw-r--r-- | webkit/plugins/ppapi/resource_tracker_unittest.cc | 3 | ||||
-rw-r--r-- | webkit/tools/test_shell/test_shell.gypi | 1 |
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', |