PPAPI: Make CompletionCallbacks work right on background threads.

Now, TrackedCallback::Run will:
-Run the callback immediately if it is on the right thread.
-PostRun to the correct thread if it is not.

This was preceded by https://chromiumcodereview.appspot.com/10909244/, which removed ClearAndRun and does some other little cleanups to TrackedCallback to make it usable on background threads.


BUG=92909

Review URL: https://chromiumcodereview.appspot.com/10910099

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@166719 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/ppapi/api/pp_completion_callback.idl b/ppapi/api/pp_completion_callback.idl
index 0dff263..6db9e2eb 100644
--- a/ppapi/api/pp_completion_callback.idl
+++ b/ppapi/api/pp_completion_callback.idl
@@ -32,7 +32,13 @@
    * without blocking.
    *
    * The method taking such callback will always return PP_OK_COMPLETIONPENDING.
-   * The callback will be invoked on the main thread of PPAPI execution.
+   * The callback will be invoked on the same thread on which the method was
+   * invoked.
+   *
+   * NOTE: If the method taking the callback is invoked on a background
+   * thread that has no valid PPB_MessageLoop resource attached, the system has
+   * no way to run the callback on the correct thread. In this case, a log
+   * message will be emitted and the plugin will be made to crash.
    */
   PP_COMPLETIONCALLBACK_FLAG_NONE = 0 << 0,
   /**
@@ -44,32 +50,70 @@
    * On synchronous method completion, the completion result will be returned
    * by the method itself. Otherwise, the method will return
    * PP_OK_COMPLETIONPENDING, and the callback will be invoked asynchronously on
-   * the main thread of PPAPI execution.
+   * the same thread on which the method was invoked. If there is no valid
+   * PPB_MessageLoop attached to that thread, and the callback would normally
+   * run asynchronously, the invoked method will return
+   * PP_ERROR_NO_MESSAGE_LOOP.
    */
   PP_COMPLETIONCALLBACK_FLAG_OPTIONAL = 1 << 0
 };
 
 
 /**
- * Any method that takes a <code>PP_CompletionCallback</code> has the option of
- * completing asynchronously if the operation would block.  Such a method
- * should return <code>PP_OK_COMPLETIONPENDING</code> to indicate that the
- * method will complete asynchronously and notify the caller and will always be
- * invoked from the main thread of PPAPI execution.  If the completion callback
- * is NULL, then the operation will block if necessary to complete its work.
- * <code>PP_BlockUntilComplete()</code> provides a convenient way to specify
- * blocking behavior. Refer to <code>PP_BlockUntilComplete</code> for more
- * information.
+ * <code>PP_CompletionCallback</code> is a common mechanism for supporting
+ * potentially asynchronous calls in browser interfaces. Any method that takes a
+ * <code>PP_CompletionCallback</code> can be used in one of three different
+ * ways:
+ *   - Required: The callback will always be invoked asynchronously on the
+ *               thread where the associated PPB method was invoked. The method
+ *               will always return <code>PP_OK_COMPLETIONPENDING when a
+ *               required callback, and the callback will be invoked later
+ *               (barring system or thread shutdown; see PPB_MessageLoop for
+ *               details). Required callbacks are the default.
  *
- * The result parameter passed to <code>func</code> is an int32_t that, if
- * negative indicates an error code whose meaning is specific to the calling
- * method (refer to <code>pp_error.h</code> for further information). A
- * positive or 0 value is a return result indicating success whose meaning
- * depends on the calling method (e.g. number of bytes read).
+ *               NOTE: If you use a required callback on a background thread,
+ *               you must have created and attached a PPB_MessageLoop.
+ *               Otherwise, the system can not run your callback on that thread,
+ *               and will instead emit a log message and crash your plugin to
+ *               make the problem more obvious.
+ *
+ *   - Optional: The callback may be invoked asynchronously, or the PPB method
+ *               may complete synchronously if it can do so without blocking.
+ *               If the method will complete asynchronously, it will return
+ *               PP_OK_COMPLETIONPENDING. Otherwise, it will complete
+ *               synchronously and return an appropriate code (see below for
+ *               more information on the return code). Optional callbacks are
+ *               generally more difficult to use correctly than Required
+ *               callbacks, but can provide better performance for some APIs
+ *               (especially APIs with buffered reads, such as PPB_URLLoader or
+ *               PPB_FileIO).
+ *
+ *               NOTE: If you use an optional callback on a background thread,
+ *               and you have not created and attached a PPB_MessageLoop, then
+ *               the method you invoke will fail without running and return
+ *               PP_ERROR_NO_MESSAGE_LOOP.
+ *
+ *   - Blocking: In this case, the callback's function pointer is NULL, and the
+ *               invoked method must complete synchronously. The method will
+ *               run to completion and return an appropriate code when finished
+ *               (see below for more information). Blocking completion
+ *               callbacks are only supported on background threads.
+ *
+ *               <code>PP_BlockUntilComplete()</code> provides a convenient way
+ *               to specify blocking behavior. Refer to
+ *                <code>PP_BlockUntilComplete</code> for more information.
+ *
+ * When the callback is run asynchronously, the result parameter passed to
+ * <code>func</code> is an int32_t that, if negative indicates an error code
+ * whose meaning is specific to the calling method (refer to
+ * <code>pp_error.h</code> for further information). A positive or 0 value is a
+ * return result indicating success whose meaning depends on the calling method
+ * (e.g. number of bytes read).
  */
 [passByValue] struct PP_CompletionCallback {
   /**
-   * This value is a callback function that will be called.
+   * This value is a callback function that will be called, or NULL if this is
+   * a blocking completion callback.
    */
   PP_CompletionCallback_Func func;
   /**
@@ -95,7 +139,13 @@
  * PP_MakeCompletionCallback() is used to create a
  * <code>PP_CompletionCallback</code>.
  *
- * <strong>Example:</strong>
+ * <strong>Example, creating a Required callback:</strong>
+ *
+ * <code>
+ * struct PP_CompletionCallback cc = PP_MakeCompletionCallback(Foo, NULL);
+ * </code>
+ *
+ * <strong>Example, creating an Optional callback:</strong>
  *
  * <code>
  * struct PP_CompletionCallback cc = PP_MakeCompletionCallback(Foo, NULL);
diff --git a/ppapi/c/pp_completion_callback.h b/ppapi/c/pp_completion_callback.h
index 911302ff..6619ca4 100644
--- a/ppapi/c/pp_completion_callback.h
+++ b/ppapi/c/pp_completion_callback.h
@@ -3,7 +3,7 @@
  * found in the LICENSE file.
  */
 
-/* From pp_completion_callback.idl modified Wed Oct  5 14:06:02 2011. */
+/* From pp_completion_callback.idl modified Wed Nov  7 11:20:18 2012. */
 
 #ifndef PPAPI_C_PP_COMPLETION_CALLBACK_H_
 #define PPAPI_C_PP_COMPLETION_CALLBACK_H_
@@ -51,7 +51,13 @@
    * without blocking.
    *
    * The method taking such callback will always return PP_OK_COMPLETIONPENDING.
-   * The callback will be invoked on the main thread of PPAPI execution.
+   * The callback will be invoked on the same thread on which the method was
+   * invoked.
+   *
+   * NOTE: If the method taking the callback is invoked on a background
+   * thread that has no valid PPB_MessageLoop resource attached, the system has
+   * no way to run the callback on the correct thread. In this case, a log
+   * message will be emitted and the plugin will be made to crash.
    */
   PP_COMPLETIONCALLBACK_FLAG_NONE = 0 << 0,
   /**
@@ -63,7 +69,10 @@
    * On synchronous method completion, the completion result will be returned
    * by the method itself. Otherwise, the method will return
    * PP_OK_COMPLETIONPENDING, and the callback will be invoked asynchronously on
-   * the main thread of PPAPI execution.
+   * the same thread on which the method was invoked. If there is no valid
+   * PPB_MessageLoop attached to that thread, and the callback would normally
+   * run asynchronously, the invoked method will return
+   * PP_ERROR_NO_MESSAGE_LOOP.
    */
   PP_COMPLETIONCALLBACK_FLAG_OPTIONAL = 1 << 0
 } PP_CompletionCallback_Flag;
@@ -77,25 +86,60 @@
  * @{
  */
 /**
- * Any method that takes a <code>PP_CompletionCallback</code> has the option of
- * completing asynchronously if the operation would block.  Such a method
- * should return <code>PP_OK_COMPLETIONPENDING</code> to indicate that the
- * method will complete asynchronously and notify the caller and will always be
- * invoked from the main thread of PPAPI execution.  If the completion callback
- * is NULL, then the operation will block if necessary to complete its work.
- * <code>PP_BlockUntilComplete()</code> provides a convenient way to specify
- * blocking behavior. Refer to <code>PP_BlockUntilComplete</code> for more
- * information.
+ * <code>PP_CompletionCallback</code> is a common mechanism for supporting
+ * potentially asynchronous calls in browser interfaces. Any method that takes a
+ * <code>PP_CompletionCallback</code> can be used in one of three different
+ * ways:
+ *   - Required: The callback will always be invoked asynchronously on the
+ *               thread where the associated PPB method was invoked. The method
+ *               will always return <code>PP_OK_COMPLETIONPENDING when a
+ *               required callback, and the callback will be invoked later
+ *               (barring system or thread shutdown; see PPB_MessageLoop for
+ *               details). Required callbacks are the default.
  *
- * The result parameter passed to <code>func</code> is an int32_t that, if
- * negative indicates an error code whose meaning is specific to the calling
- * method (refer to <code>pp_error.h</code> for further information). A
- * positive or 0 value is a return result indicating success whose meaning
- * depends on the calling method (e.g. number of bytes read).
+ *               NOTE: If you use a required callback on a background thread,
+ *               you must have created and attached a PPB_MessageLoop.
+ *               Otherwise, the system can not run your callback on that thread,
+ *               and will instead emit a log message and crash your plugin to
+ *               make the problem more obvious.
+ *
+ *   - Optional: The callback may be invoked asynchronously, or the PPB method
+ *               may complete synchronously if it can do so without blocking.
+ *               If the method will complete asynchronously, it will return
+ *               PP_OK_COMPLETIONPENDING. Otherwise, it will complete
+ *               synchronously and return an appropriate code (see below for
+ *               more information on the return code). Optional callbacks are
+ *               generally more difficult to use correctly than Required
+ *               callbacks, but can provide better performance for some APIs
+ *               (especially APIs with buffered reads, such as PPB_URLLoader or
+ *               PPB_FileIO).
+ *
+ *               NOTE: If you use an optional callback on a background thread,
+ *               and you have not created and attached a PPB_MessageLoop, then
+ *               the method you invoke will fail without running and return
+ *               PP_ERROR_NO_MESSAGE_LOOP.
+ *
+ *   - Blocking: In this case, the callback's function pointer is NULL, and the
+ *               invoked method must complete synchronously. The method will
+ *               run to completion and return an appropriate code when finished
+ *               (see below for more information). Blocking completion
+ *               callbacks are only supported on background threads.
+ *
+ *               <code>PP_BlockUntilComplete()</code> provides a convenient way
+ *               to specify blocking behavior. Refer to
+ *                <code>PP_BlockUntilComplete</code> for more information.
+ *
+ * When the callback is run asynchronously, the result parameter passed to
+ * <code>func</code> is an int32_t that, if negative indicates an error code
+ * whose meaning is specific to the calling method (refer to
+ * <code>pp_error.h</code> for further information). A positive or 0 value is a
+ * return result indicating success whose meaning depends on the calling method
+ * (e.g. number of bytes read).
  */
 struct PP_CompletionCallback {
   /**
-   * This value is a callback function that will be called.
+   * This value is a callback function that will be called, or NULL if this is
+   * a blocking completion callback.
    */
   PP_CompletionCallback_Func func;
   /**
@@ -122,7 +166,13 @@
  * PP_MakeCompletionCallback() is used to create a
  * <code>PP_CompletionCallback</code>.
  *
- * <strong>Example:</strong>
+ * <strong>Example, creating a Required callback:</strong>
+ *
+ * <code>
+ * struct PP_CompletionCallback cc = PP_MakeCompletionCallback(Foo, NULL);
+ * </code>
+ *
+ * <strong>Example, creating an Optional callback:</strong>
  *
  * <code>
  * struct PP_CompletionCallback cc = PP_MakeCompletionCallback(Foo, NULL);
diff --git a/ppapi/cpp/completion_callback.h b/ppapi/cpp/completion_callback.h
index 2391a3a..ec892f6f 100644
--- a/ppapi/cpp/completion_callback.h
+++ b/ppapi/cpp/completion_callback.h
@@ -99,7 +99,7 @@
   /// On synchronous method completion, the completion result will be returned
   /// by the method itself. Otherwise, the method will return
   /// PP_OK_COMPLETIONPENDING, and the callback will be invoked asynchronously
-  /// on the main thread of Pepper execution.
+  /// on the same thread where the PPB method was invoked.
   ///
   /// @return true if this callback is optional, otherwise false.
   bool IsOptional() const {
@@ -150,6 +150,7 @@
   int32_t MayForce(int32_t result) const {
     if (result == PP_OK_COMPLETIONPENDING || IsOptional())
       return result;
+    // FIXME(dmichael): Use pp::MessageLoop here once it's out of Dev.
     Module::Get()->core()->CallOnMainThread(0, *this, result);
     return PP_OK_COMPLETIONPENDING;
   }
diff --git a/ppapi/cpp/resource.h b/ppapi/cpp/resource.h
index f214672..8f5a92d2e 100644
--- a/ppapi/cpp/resource.h
+++ b/ppapi/cpp/resource.h
@@ -91,4 +91,8 @@
   return lhs.pp_resource() == rhs.pp_resource();
 }
 
+inline bool operator!=(const pp::Resource& lhs, const pp::Resource& rhs) {
+  return !(lhs == rhs);
+}
+
 #endif // PPAPI_CPP_RESOURCE_H_
diff --git a/ppapi/native_client/src/untrusted/pnacl_irt_shim/pnacl_shim.c b/ppapi/native_client/src/untrusted/pnacl_irt_shim/pnacl_shim.c
index 9c63608..7dc2ff3 100644
--- a/ppapi/native_client/src/untrusted/pnacl_irt_shim/pnacl_shim.c
+++ b/ppapi/native_client/src/untrusted/pnacl_irt_shim/pnacl_shim.c
@@ -3,7 +3,7 @@
  * found in the LICENSE file.
  */
 
-/* Last generated from IDL: Wed Nov  7 13:45:01 2012. */
+/* Last generated from IDL: Thu Nov  8 08:56:35 2012. */
 #include "ppapi/generators/pnacl_shim.h"
 
 #include "ppapi/c/ppb.h"
diff --git a/ppapi/ppapi_shared.gypi b/ppapi/ppapi_shared.gypi
index fffc465..1205f09 100644
--- a/ppapi/ppapi_shared.gypi
+++ b/ppapi/ppapi_shared.gypi
@@ -70,6 +70,8 @@
           'shared_impl/ppb_instance_shared.cc',
           'shared_impl/ppb_instance_shared.h',
           'shared_impl/ppb_memory_shared.cc',
+          'shared_impl/ppb_message_loop_shared.cc',
+          'shared_impl/ppb_message_loop_shared.h',
           'shared_impl/ppb_network_list_private_shared.cc',
           'shared_impl/ppb_network_list_private_shared.h',
           'shared_impl/ppb_opengles2_shared.cc',
diff --git a/ppapi/proxy/plugin_globals.cc b/ppapi/proxy/plugin_globals.cc
index 4932d32b..75369d0 100644
--- a/ppapi/proxy/plugin_globals.cc
+++ b/ppapi/proxy/plugin_globals.cc
@@ -105,6 +105,10 @@
   LogWithSource(0, level, source, value);
 }
 
+MessageLoopShared* PluginGlobals::GetCurrentMessageLoop() {
+  return MessageLoopResource::GetCurrent();
+}
+
 MessageLoopResource* PluginGlobals::loop_for_main_thread() {
   return loop_for_main_thread_.get();
 }
diff --git a/ppapi/proxy/plugin_globals.h b/ppapi/proxy/plugin_globals.h
index 2bbc3f9..215ad63 100644
--- a/ppapi/proxy/plugin_globals.h
+++ b/ppapi/proxy/plugin_globals.h
@@ -56,6 +56,7 @@
                                       PP_LogLevel_Dev level,
                                       const std::string& source,
                                       const std::string& value) OVERRIDE;
+  virtual MessageLoopShared* GetCurrentMessageLoop() OVERRIDE;
 
   // Getters for the plugin-specific versions.
   PluginResourceTracker* plugin_resource_tracker() {
diff --git a/ppapi/proxy/ppb_message_loop_proxy.cc b/ppapi/proxy/ppb_message_loop_proxy.cc
index 916002d..b86ecf0 100644
--- a/ppapi/proxy/ppb_message_loop_proxy.cc
+++ b/ppapi/proxy/ppb_message_loop_proxy.cc
@@ -27,15 +27,15 @@
 }
 
 MessageLoopResource::MessageLoopResource(PP_Instance instance)
-    : Resource(OBJECT_IS_PROXY, instance),
+    : MessageLoopShared(instance),
       nested_invocations_(0),
       destroyed_(false),
       should_destroy_(false),
       is_main_thread_loop_(false) {
 }
 
-MessageLoopResource::MessageLoopResource(ForMainThread)
-    : Resource(Resource::Untracked()),
+MessageLoopResource::MessageLoopResource(ForMainThread for_main_thread)
+    : MessageLoopShared(for_main_thread),
       nested_invocations_(0),
       destroyed_(false),
       should_destroy_(false),
@@ -80,7 +80,7 @@
     if (slot->Get())
       return PP_ERROR_INPROGRESS;
   }
-  // TODO(brettw) check that the current thread can support a message loop.
+  // TODO(dmichael) check that the current thread can support a message loop.
 
   // Take a ref to the MessageLoop on behalf of the TLS. Note that this is an
   // internal ref and not a plugin ref so the plugin can't accidentally
@@ -140,20 +140,24 @@
   if (PP_ToBool(should_destroy))
     should_destroy_ = true;
 
-  if (IsCurrent())
+  if (IsCurrent() && nested_invocations_ > 0)
     loop_->Quit();
   else
     PostClosure(FROM_HERE, MessageLoop::QuitClosure(), 0);
   return PP_OK;
 }
 
-void MessageLoopResource::DetachFromThread() {
-  // Never detach the main thread from its loop resource. Other plugin instances
-  // might need it.
-  if (is_main_thread_loop_)
-    return;
+// static
+MessageLoopResource* MessageLoopResource::GetCurrent() {
+  PluginGlobals* globals = PluginGlobals::Get();
+  if (!globals->msg_loop_slot())
+    return NULL;
+  return reinterpret_cast<MessageLoopResource*>(
+      globals->msg_loop_slot()->Get());
+}
 
-  // Note that the message loop must be destroyed on the thread is was created
+void MessageLoopResource::DetachFromThread() {
+  // Note that the message loop must be destroyed on the thread it was created
   // on.
   loop_proxy_ = NULL;
   loop_.reset();
@@ -208,12 +212,10 @@
 }
 
 PP_Resource GetCurrent() {
-  PluginGlobals* globals = PluginGlobals::Get();
-  if (!globals->msg_loop_slot())
-    return 0;
-  MessageLoopResource* loop = reinterpret_cast<MessageLoopResource*>(
-      globals->msg_loop_slot()->Get());
-  return loop->GetReference();
+  Resource* resource = MessageLoopResource::GetCurrent();
+  if (resource)
+    return resource->GetReference();
+  return 0;
 }
 
 int32_t AttachToCurrentThread(PP_Resource message_loop) {
diff --git a/ppapi/proxy/ppb_message_loop_proxy.h b/ppapi/proxy/ppb_message_loop_proxy.h
index 325be78..689e439 100644
--- a/ppapi/proxy/ppb_message_loop_proxy.h
+++ b/ppapi/proxy/ppb_message_loop_proxy.h
@@ -11,7 +11,7 @@
 #include "base/memory/scoped_ptr.h"
 #include "base/message_loop.h"
 #include "ppapi/proxy/interface_proxy.h"
-#include "ppapi/shared_impl/resource.h"
+#include "ppapi/shared_impl/ppb_message_loop_shared.h"
 #include "ppapi/thunk/ppb_message_loop_api.h"
 
 struct PPB_MessageLoop_Dev_0_1;
@@ -19,13 +19,12 @@
 namespace ppapi {
 namespace proxy {
 
-class MessageLoopResource : public Resource, public thunk::PPB_MessageLoop_API {
+class MessageLoopResource : public MessageLoopShared {
  public:
   explicit MessageLoopResource(PP_Instance instance);
   // Construct the one MessageLoopResource for the main thread. This must be
   // invoked on the main thread.
-  struct ForMainThread {};
-  MessageLoopResource(ForMainThread);
+  explicit MessageLoopResource(ForMainThread);
   virtual ~MessageLoopResource();
 
   // Resource overrides.
@@ -38,6 +37,7 @@
                            int64_t delay_ms) OVERRIDE;
   virtual int32_t PostQuit(PP_Bool should_destroy) OVERRIDE;
 
+  static MessageLoopResource* GetCurrent();
   void DetachFromThread();
   bool is_main_thread_loop() const {
     return is_main_thread_loop_;
@@ -58,9 +58,9 @@
   // NOTE: The given closure will be run *WITHOUT* acquiring the Proxy lock.
   //       This only makes sense for user code and completely thread-safe
   //       proxy operations (e.g., MessageLoop::QuitClosure).
-  void PostClosure(const tracked_objects::Location& from_here,
-                   const base::Closure& closure,
-                   int64 delay_ms);
+  virtual void PostClosure(const tracked_objects::Location& from_here,
+                           const base::Closure& closure,
+                           int64 delay_ms) OVERRIDE;
 
   // TLS destructor function.
   static void ReleaseMessageLoop(void* value);
diff --git a/ppapi/proxy/ppb_testing_proxy.cc b/ppapi/proxy/ppb_testing_proxy.cc
index b8c9ce0..576302f 100644
--- a/ppapi/proxy/ppb_testing_proxy.cc
+++ b/ppapi/proxy/ppb_testing_proxy.cc
@@ -53,12 +53,14 @@
 
 void RunMessageLoop(PP_Instance instance) {
   MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current());
-  // TODO(dmichael): We should probably assert that this is the main thread.
+  CHECK(PpapiGlobals::Get()->GetMainThreadMessageLoop()->
+            BelongsToCurrentThread());
   MessageLoop::current()->Run();
 }
 
 void QuitMessageLoop(PP_Instance instance) {
-  // TODO(dmichael): We should probably assert that this is the main thread.
+  CHECK(PpapiGlobals::Get()->GetMainThreadMessageLoop()->
+            BelongsToCurrentThread());
   MessageLoop::current()->QuitNow();
 }
 
diff --git a/ppapi/shared_impl/ppapi_globals.cc b/ppapi/shared_impl/ppapi_globals.cc
index 0336e0c..270d7e8 100644
--- a/ppapi/shared_impl/ppapi_globals.cc
+++ b/ppapi/shared_impl/ppapi_globals.cc
@@ -23,12 +23,12 @@
 PpapiGlobals::PpapiGlobals() {
   DCHECK(!ppapi_globals_);
   ppapi_globals_ = this;
-  message_loop_proxy_ = base::MessageLoopProxy::current();
+  main_loop_proxy_ = base::MessageLoopProxy::current();
 }
 
 PpapiGlobals::PpapiGlobals(ForTest) {
   DCHECK(!ppapi_globals_);
-  message_loop_proxy_ = base::MessageLoopProxy::current();
+  main_loop_proxy_ = base::MessageLoopProxy::current();
 }
 
 PpapiGlobals::~PpapiGlobals() {
@@ -45,7 +45,7 @@
 }
 
 base::MessageLoopProxy* PpapiGlobals::GetMainThreadMessageLoop() {
-  return message_loop_proxy_.get();
+  return main_loop_proxy_.get();
 }
 
 bool PpapiGlobals::IsHostGlobals() const {
diff --git a/ppapi/shared_impl/ppapi_globals.h b/ppapi/shared_impl/ppapi_globals.h
index e95cf48..b03d35c 100644
--- a/ppapi/shared_impl/ppapi_globals.h
+++ b/ppapi/shared_impl/ppapi_globals.h
@@ -24,6 +24,7 @@
 namespace ppapi {
 
 class CallbackTracker;
+class MessageLoopShared;
 class ResourceTracker;
 class VarTracker;
 
@@ -35,6 +36,7 @@
 // Abstract base class
 class PPAPI_SHARED_EXPORT PpapiGlobals {
  public:
+  // Must be created on the main thread.
   PpapiGlobals();
 
   // This constructor is to be used only for making a PpapiGlobal for testing
@@ -42,7 +44,7 @@
   // tests that use this feature, the "test" PpapiGlobals should be constructed
   // using this method. See SetPpapiGlobalsOnThreadForTest for more information.
   struct ForTest {};
-  PpapiGlobals(ForTest);
+  explicit PpapiGlobals(ForTest);
 
   virtual ~PpapiGlobals();
 
@@ -71,6 +73,7 @@
   virtual VarTracker* GetVarTracker() = 0;
   virtual CallbackTracker* GetCallbackTrackerForInstance(
       PP_Instance instance) = 0;
+
   virtual base::Lock* GetProxyLock() = 0;
 
   // Logs the given string to the JS console. If "source" is empty, the name of
@@ -103,11 +106,15 @@
   // failure.
   virtual PP_Module GetModuleForInstance(PP_Instance instance) = 0;
 
-  // Returns the base::MessageLoopProxy for the main thread. Note that this must
-  // be called on the main thread the first time so that it can initialize
-  // its static data.
+  // Returns the base::MessageLoopProxy for the main thread. This is set in the
+  // constructor, so PpapiGlobals must be created on the main thread.
   base::MessageLoopProxy* GetMainThreadMessageLoop();
 
+  // Return the MessageLoopShared of the current thread, if any. This will
+  // always return NULL on the host side, where PPB_MessageLoop is not
+  // supported.
+  virtual MessageLoopShared* GetCurrentMessageLoop() = 0;
+
   // Returns the command line for the process.
   virtual std::string GetCmdLine() = 0;
 
@@ -127,7 +134,7 @@
 
   static PpapiGlobals* ppapi_globals_;
 
-  scoped_refptr<base::MessageLoopProxy> message_loop_proxy_;
+  scoped_refptr<base::MessageLoopProxy> main_loop_proxy_;
 
   DISALLOW_COPY_AND_ASSIGN(PpapiGlobals);
 };
diff --git a/ppapi/shared_impl/ppb_message_loop_shared.cc b/ppapi/shared_impl/ppb_message_loop_shared.cc
new file mode 100644
index 0000000..9c0c92d8
--- /dev/null
+++ b/ppapi/shared_impl/ppb_message_loop_shared.cc
@@ -0,0 +1,20 @@
+// Copyright (c) 2012 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 "ppapi/shared_impl/ppb_message_loop_shared.h"
+
+namespace ppapi {
+
+MessageLoopShared::MessageLoopShared(PP_Instance instance)
+    : Resource(OBJECT_IS_PROXY, instance) {
+}
+
+MessageLoopShared::MessageLoopShared(ForMainThread)
+    : Resource(Resource::Untracked()) {
+}
+
+MessageLoopShared::~MessageLoopShared() {
+}
+
+}  // namespace ppapi
diff --git a/ppapi/shared_impl/ppb_message_loop_shared.h b/ppapi/shared_impl/ppb_message_loop_shared.h
new file mode 100644
index 0000000..fd6d1618
--- /dev/null
+++ b/ppapi/shared_impl/ppb_message_loop_shared.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 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 PPAPI_SHARED_IMPL_PPB_MESSAGE_LOOP_SHARED_H_
+#define PPAPI_SHARED_IMPL_PPB_MESSAGE_LOOP_SHARED_H_
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/location.h"
+#include "ppapi/c/pp_instance.h"
+#include "ppapi/shared_impl/ppapi_shared_export.h"
+#include "ppapi/shared_impl/resource.h"
+#include "ppapi/thunk/ppb_message_loop_api.h"
+
+namespace tracked_objects {
+class Location;
+}
+
+namespace ppapi {
+
+// MessageLoopShared doesn't really do anything interesting. It exists so that
+// shared code (in particular, TrackedCallback) can keep a pointer to a
+// MessageLoop resource. In the host side, there is not a concrete class that
+// implements this. So pointers to MessageLoopShared can only really be valid
+// on the plugin side.
+class PPAPI_SHARED_EXPORT MessageLoopShared
+    : public Resource,
+      public thunk::PPB_MessageLoop_API {
+ public:
+  explicit MessageLoopShared(PP_Instance instance);
+  // Construct the one MessageLoopShared for the main thread. This must be
+  // invoked on the main thread.
+  struct ForMainThread {};
+  explicit MessageLoopShared(ForMainThread);
+  virtual ~MessageLoopShared();
+
+  // Handles posting to the message loop if there is one, or the pending queue
+  // if there isn't.
+  // NOTE: The given closure will be run *WITHOUT* acquiring the Proxy lock.
+  //       This only makes sense for user code and completely thread-safe
+  //       proxy operations (e.g., MessageLoop::QuitClosure).
+  virtual void PostClosure(const tracked_objects::Location& from_here,
+                           const base::Closure& closure,
+                           int64 delay_ms) = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(MessageLoopShared);
+};
+
+}  // namespace ppapi
+
+#endif  // PPAPI_SHARED_IMPL_PPB_MESSAGE_LOOP_SHARED_H_
diff --git a/ppapi/shared_impl/test_globals.cc b/ppapi/shared_impl/test_globals.cc
index f8997dc..b1dc0fe0 100644
--- a/ppapi/shared_impl/test_globals.cc
+++ b/ppapi/shared_impl/test_globals.cc
@@ -69,4 +69,14 @@
                                          const std::string& value) {
 }
 
+MessageLoopShared* TestGlobals::GetCurrentMessageLoop() {
+  return NULL;
+}
+
+bool TestGlobals::IsHostGlobals() const {
+  // Pretend to be the host-side, for code that expects one or the other.
+  // TODO(dmichael): just make it settable which one we're pretending to be?
+  return true;
+}
+
 }  // namespace ppapi
diff --git a/ppapi/shared_impl/test_globals.h b/ppapi/shared_impl/test_globals.h
index 3675347..ca67e77 100644
--- a/ppapi/shared_impl/test_globals.h
+++ b/ppapi/shared_impl/test_globals.h
@@ -53,6 +53,10 @@
                                       PP_LogLevel_Dev level,
                                       const std::string& source,
                                       const std::string& value) OVERRIDE;
+  virtual MessageLoopShared* GetCurrentMessageLoop() OVERRIDE;
+
+  // PpapiGlobals overrides:
+  virtual bool IsHostGlobals() const OVERRIDE;
 
  private:
   ResourceTracker resource_tracker_;
diff --git a/ppapi/shared_impl/tracked_callback.cc b/ppapi/shared_impl/tracked_callback.cc
index 65d94be..bd011dfc 100644
--- a/ppapi/shared_impl/tracked_callback.cc
+++ b/ppapi/shared_impl/tracked_callback.cc
@@ -14,11 +14,21 @@
 #include "ppapi/c/pp_errors.h"
 #include "ppapi/shared_impl/callback_tracker.h"
 #include "ppapi/shared_impl/ppapi_globals.h"
+#include "ppapi/shared_impl/ppb_message_loop_shared.h"
 #include "ppapi/shared_impl/proxy_lock.h"
 #include "ppapi/shared_impl/resource.h"
 
 namespace ppapi {
 
+namespace {
+
+bool IsMainThread() {
+  return
+      PpapiGlobals::Get()->GetMainThreadMessageLoop()->BelongsToCurrentThread();
+}
+
+}  // namespace
+
 // TrackedCallback -------------------------------------------------------------
 
 // Note: don't keep a Resource* since it may go out of scope before us.
@@ -30,7 +40,12 @@
       completed_(false),
       aborted_(false),
       callback_(callback),
+      target_loop_(PpapiGlobals::Get()->GetCurrentMessageLoop()),
       result_for_blocked_callback_(PP_OK) {
+  // Note that target_loop_ may be NULL at this point, if the plugin has not
+  // attached a loop to this thread, or if this is an in-process plugin.
+  // The Enter class should handle checking this for us.
+
   // TODO(dmichael): Add tracking at the instance level, for callbacks that only
   // have an instance (e.g. for MouseLock).
   if (resource) {
@@ -40,19 +55,27 @@
   }
 
   base::Lock* proxy_lock = PpapiGlobals::Get()->GetProxyLock();
-  // We only need a ConditionVariable if the lock is valid (i.e., we're out-of-
-  // process) and the callback is blocking.
-  if (proxy_lock && is_blocking())
-    operation_completed_condvar_.reset(new base::ConditionVariable(proxy_lock));
+  if (proxy_lock) {
+    // If the proxy_lock is valid, we're running out-of-process, and locking
+    // is enabled.
+    if (is_blocking()) {
+      // This is a blocking completion callback, so we will need a condition
+      // variable for blocking & signalling the calling thread.
+      operation_completed_condvar_.reset(
+          new base::ConditionVariable(proxy_lock));
+    } else {
+      // It's a non-blocking callback, so we should have a MessageLoopResource
+      // to dispatch to. Note that we don't error check here, though. Later,
+      // EnterResource::SetResult will check to make sure the callback is valid
+      // and take appropriate action.
+    }
+  }
 }
 
 TrackedCallback::~TrackedCallback() {
 }
 
 void TrackedCallback::Abort() {
-  // It doesn't make sense to abort a callback that's not associated with a
-  // resource.
-  DCHECK(resource_id_);
   Run(PP_ERROR_ABORTED);
 }
 
@@ -71,12 +94,10 @@
   if (result == PP_ERROR_ABORTED)
     aborted_ = true;
 
-  // Copy |callback_| and look at |aborted()| now, since |MarkAsCompleted()|
-  // may delete us.
-  PP_CompletionCallback callback = callback_;
   // Note that this call of Run() may have been scheduled prior to Abort() or
   // PostAbort() being called. If we have been told to Abort, that always
-  // trumps a result that was scheduled before.
+  // trumps a result that was scheduled before, so we should make sure to pass
+  // PP_ERROR_ABORTED.
   if (aborted())
     result = PP_ERROR_ABORTED;
 
@@ -100,6 +121,15 @@
     // Wait()s.
     operation_completed_condvar_->Signal();
   } else {
+    // If there's a target_loop_, and we're not on the right thread, we need to
+    // post to target_loop_.
+    if (target_loop_ &&
+        target_loop_ != PpapiGlobals::Get()->GetCurrentMessageLoop()) {
+      PostRun(result);
+      return;
+    }
+    // Copy |callback_| now, since |MarkAsCompleted()| may delete us.
+    PP_CompletionCallback callback = callback_;
     // Do this before running the callback in case of reentrancy (which
     // shouldn't happen, but avoid strange failures).
     MarkAsCompleted();
@@ -122,7 +152,15 @@
 
   base::Closure callback_closure(
       RunWhileLocked(base::Bind(&TrackedCallback::Run, this, result)));
-  MessageLoop::current()->PostTask(FROM_HERE, callback_closure);
+  if (!target_loop_) {
+    // We must be running in-process and on the main thread (the Enter
+    // classes protect against having a null target_loop_ otherwise).
+    DCHECK(IsMainThread());
+    DCHECK(PpapiGlobals::Get()->IsHostGlobals());
+    MessageLoop::current()->PostTask(FROM_HERE, callback_closure);
+  } else {
+    target_loop_->PostClosure(FROM_HERE, callback_closure, 0);
+  }
   is_scheduled_ = true;
 }
 
diff --git a/ppapi/shared_impl/tracked_callback.h b/ppapi/shared_impl/tracked_callback.h
index 1fa7b1bc..03bf1626 100644
--- a/ppapi/shared_impl/tracked_callback.h
+++ b/ppapi/shared_impl/tracked_callback.h
@@ -16,10 +16,12 @@
 #include "ppapi/c/pp_instance.h"
 #include "ppapi/c/pp_resource.h"
 #include "ppapi/shared_impl/ppapi_shared_export.h"
+#include "ppapi/shared_impl/ppb_message_loop_shared.h"
 
 namespace ppapi {
 
 class CallbackTracker;
+class MessageLoopShared;
 class Resource;
 
 namespace thunk {
@@ -76,7 +78,6 @@
   // Run() will invoke the call immediately, if invoked from the target thread
   // (as determined by target_loop_). If invoked on a different thread, the
   // callback will be scheduled to run later on target_loop_.
-  // TODO(dmichael): Make the above part about different threads actually true.
   void Run(int32_t result);
   // PostRun is like Run(), except it guarantees that the callback will be run
   // later. In particular, if you invoke PostRun on the same thread on which the
@@ -116,6 +117,9 @@
     return (callback_.func &&
             (callback_.flags & PP_COMPLETIONCALLBACK_FLAG_OPTIONAL));
   }
+  bool has_null_target_loop() const {
+    return target_loop_ == NULL;
+  }
 
  private:
   // TrackedCallback and EnterBase manage dealing with how to invoke callbacks
@@ -146,6 +150,10 @@
   bool aborted_;
   PP_CompletionCallback callback_;
 
+  // The MessageLoopShared on which this callback should be run. This will be
+  // NULL if we're in-process.
+  scoped_refptr<MessageLoopShared> target_loop_;
+
   int32_t result_for_blocked_callback_;
   // Used for pausing/waking the blocked thread if this is a blocking completion
   // callback. Note that in-process, there is no lock, blocking callbacks are
diff --git a/ppapi/tests/test_case.h b/ppapi/tests/test_case.h
index 2cf2ddf..aa35046e 100644
--- a/ppapi/tests/test_case.h
+++ b/ppapi/tests/test_case.h
@@ -122,11 +122,12 @@
              "Chrome, use the --enable-pepper-testing flag.";
     }
     // These tests are only valid if running out-of-process (threading is not
-    // supported in-process). Just consider it a pass.
+    // supported in-process). For in-process, just consider it a pass.
     if (!testing_interface_->IsOutOfProcess())
       return std::string();
+    pp::MessageLoop_Dev background_loop(instance_);
     ThreadedTestRunner<T> runner(instance_->pp_instance(),
-        static_cast<T*>(this), test_to_run);
+        static_cast<T*>(this), test_to_run, background_loop);
     RunOnThreadInternal(&ThreadedTestRunner<T>::ThreadFunction, &runner,
                         testing_interface_);
     return runner.result();
@@ -162,10 +163,12 @@
     typedef std::string(T::*TestMethodType)();
     ThreadedTestRunner(PP_Instance instance,
                        T* test_case,
-                       TestMethodType test_to_run)
+                       TestMethodType test_to_run,
+                       pp::MessageLoop_Dev loop)
         : instance_(instance),
           test_case_(test_case),
-          test_to_run_(test_to_run) {
+          test_to_run_(test_to_run),
+          loop_(loop) {
     }
     const std::string& result() { return result_; }
     static void ThreadFunction(void* runner) {
@@ -174,9 +177,11 @@
 
    private:
     void Run() {
-      // TODO(dmichael): Create and attach a pp::MessageLoop for this thread so
-      //                 nested loops work.
+      PP_DCHECK(PP_OK == loop_.AttachToCurrentThread());
       result_ = (test_case_->*test_to_run_)();
+      // Now give the loop a chance to clean up.
+      loop_.PostQuit(true /* should_destroy */);
+      loop_.Run();
       // Tell the main thread to quit its nested message loop, now that the test
       // is complete.
       TestCase::QuitMainMessageLoop(instance_);
@@ -186,6 +191,7 @@
     PP_Instance instance_;
     T* test_case_;
     TestMethodType test_to_run_;
+    pp::MessageLoop_Dev loop_;
   };
 
   // The internals for RunOnThread. This allows us to avoid including
@@ -275,6 +281,12 @@
         CheckResourcesAndVars(RunOnThread(&test_case::Test##name))); \
   }
 
+#define RUN_TEST_BACKGROUND(test_case, name, test_filter) \
+  if (MatchesFilter(#name, test_filter)) { \
+    instance_->LogTest(#name"Background", \
+        CheckResourcesAndVars(RunOnThread(&test_case::Test##name))); \
+  }
+
 #define RUN_TEST_FORCEASYNC_AND_NOT(name, test_filter) \
   do { \
     RUN_TEST_FORCEASYNC(name, test_filter); \
@@ -287,6 +299,7 @@
     RUN_TEST_FORCEASYNC(name, test_filter); \
     RUN_TEST(name, test_filter); \
     RUN_TEST_BLOCKING(test_case, name, test_filter); \
+    RUN_TEST_BACKGROUND(test_case, name, test_filter); \
   } while (false)
 
 #define RUN_TEST_WITH_REFERENCE_CHECK(name, test_filter) \
diff --git a/ppapi/tests/test_utils.cc b/ppapi/tests/test_utils.cc
index 9776e34..20a8a81 100644
--- a/ppapi/tests/test_utils.cc
+++ b/ppapi/tests/test_utils.cc
@@ -13,6 +13,7 @@
 #endif
 
 #include "ppapi/c/pp_errors.h"
+#include "ppapi/cpp/dev/message_loop_dev.h"
 #include "ppapi/cpp/module.h"
 #include "ppapi/cpp/var.h"
 
@@ -138,7 +139,7 @@
   errors_.clear();
   if (!have_result_) {
     post_quit_task_ = true;
-    GetTestingInterface()->RunMessageLoop(instance_);
+    RunMessageLoop();
   }
   return result_;
 }
@@ -150,7 +151,7 @@
   if (result == PP_OK_COMPLETIONPENDING) {
     if (!have_result_) {
       post_quit_task_ = true;
-      GetTestingInterface()->RunMessageLoop(instance_);
+      RunMessageLoop();
     }
     if (callback_type_ == PP_BLOCKING) {
       errors_.assign(
@@ -200,6 +201,7 @@
     return pp::CompletionCallback();
   else if (callback_type_ == PP_OPTIONAL)
     flags = PP_COMPLETIONCALLBACK_FLAG_OPTIONAL;
+  target_loop_ = pp::MessageLoop_Dev::GetCurrent();
   return pp::CompletionCallback(&TestCompletionCallback::Handler,
                                 const_cast<TestCompletionCallback*>(this),
                                 flags);
@@ -229,7 +231,36 @@
     callback->delegate_->OnCallback(user_data, result);
   if (callback->post_quit_task_) {
     callback->post_quit_task_ = false;
-    GetTestingInterface()->QuitMessageLoop(callback->instance_);
+    callback->QuitMessageLoop();
+  }
+  if (callback->target_loop_ != pp::MessageLoop_Dev::GetCurrent()) {
+    // Note, in-process, loop_ and GetCurrent() will both be NULL, so should
+    // still be equal.
+    callback->errors_.assign(
+        ReportError("TestCompletionCallback: Callback ran on the wrong message "
+                    "loop!",
+                    result));
   }
 }
 
+void TestCompletionCallback::RunMessageLoop() {
+  pp::MessageLoop_Dev loop(pp::MessageLoop_Dev::GetCurrent());
+  // If we don't have a message loop, we're probably running in process, where
+  // PPB_MessageLoop is not supported. Just use the Testing message loop.
+  if (loop.is_null() || loop == pp::MessageLoop_Dev::GetForMainThread())
+    GetTestingInterface()->RunMessageLoop(instance_);
+  else
+    loop.Run();
+}
+
+void TestCompletionCallback::QuitMessageLoop() {
+  pp::MessageLoop_Dev loop(pp::MessageLoop_Dev::GetCurrent());
+  // If we don't have a message loop, we're probably running in process, where
+  // PPB_MessageLoop is not supported. Just use the Testing message loop.
+  if (loop.is_null() || loop == pp::MessageLoop_Dev::GetForMainThread()) {
+    GetTestingInterface()->QuitMessageLoop(instance_);
+  } else {
+    const bool should_quit = false;
+    loop.PostQuit(should_quit);
+  }
+}
diff --git a/ppapi/tests/test_utils.h b/ppapi/tests/test_utils.h
index ab87dd3..60f7fc4 100644
--- a/ppapi/tests/test_utils.h
+++ b/ppapi/tests/test_utils.h
@@ -11,6 +11,7 @@
 #include "ppapi/c/pp_instance.h"
 #include "ppapi/c/pp_stdint.h"
 #include "ppapi/cpp/completion_callback.h"
+#include "ppapi/cpp/dev/message_loop_dev.h"
 #include "ppapi/utility/completion_callback_factory.h"
 
 // Timeout to wait for some action to complete.
@@ -152,6 +153,8 @@
 
  private:
   static void Handler(void* user_data, int32_t result);
+  void RunMessageLoop();
+  void QuitMessageLoop();
 
   // Used to check that WaitForResult is only called once for each usage of the
   // callback.
@@ -166,6 +169,7 @@
   unsigned run_count_;
   PP_Instance instance_;
   Delegate* delegate_;
+  pp::MessageLoop_Dev target_loop_;
 };
 
 // Verifies that the callback didn't record any errors. If the callback is run
diff --git a/ppapi/thunk/enter.cc b/ppapi/thunk/enter.cc
index c89c969..296a8b4 100644
--- a/ppapi/thunk/enter.cc
+++ b/ppapi/thunk/enter.cc
@@ -87,8 +87,6 @@
     // The function completed synchronously.
     if (callback_->is_required()) {
       // This is a required callback, so we must issue it asynchronously.
-      // TODO(dmichael) make this work so that a call from a background thread
-      // goes back to that thread.
       callback_->PostRun(result);
       retval_ = PP_OK_COMPLETIONPENDING;
     } else {
@@ -108,25 +106,54 @@
 }
 
 void EnterBase::SetStateForCallbackError(bool report_error) {
-  if (!CallbackIsValid()) {
-    callback_->MarkAsCompleted();
-    callback_ = NULL;
-    retval_ = PP_ERROR_BLOCKS_MAIN_THREAD;
-    if (report_error) {
-      std::string message(
-          "Blocking callbacks are not allowed on the main thread.");
-      PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
-                                                  std::string(), message);
+  if (PpapiGlobals::Get()->IsHostGlobals()) {
+    // In-process plugins can't make PPAPI calls off the main thread.
+    CHECK(IsMainThread());
+  }
+  if (callback_) {
+    if (callback_->is_blocking() && IsMainThread()) {
+      // Blocking callbacks are never allowed on the main thread.
+      callback_->MarkAsCompleted();
+      callback_ = NULL;
+      retval_ = PP_ERROR_BLOCKS_MAIN_THREAD;
+      if (report_error) {
+        std::string message(
+            "Blocking callbacks are not allowed on the main thread.");
+        PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
+                                                    std::string(), message);
+      }
+    } else if (!IsMainThread() &&
+               callback_->has_null_target_loop() &&
+               !callback_->is_blocking()) {
+      // On a non-main thread, there must be a valid target loop for non-
+      // blocking callbacks, or we will have no place to run them.
+
+      // If the callback is required, there's no nice way to tell the plugin.
+      // We can't run their callback asynchronously without a message loop, and
+      // the plugin won't expect any return code other than
+      // PP_OK_COMPLETIONPENDING. So we crash to make the problem more obvious.
+      if (callback_->is_required()) {
+        std::string message("Attempted to use a required callback, but there"
+                            "is no attached message loop on which to run the"
+                            "callback.");
+        PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
+                                                    std::string(), message);
+        LOG(FATAL) << message;
+      }
+
+      callback_->MarkAsCompleted();
+      callback_ = NULL;
+      retval_ = PP_ERROR_NO_MESSAGE_LOOP;
+      if (report_error) {
+        std::string message(
+            "The calling thread must have a message loop attached.");
+        PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
+                                                    std::string(), message);
+      }
     }
   }
 }
 
-bool EnterBase::CallbackIsValid() const {
-  // A callback is only considered invalid if it is blocking and we're on the
-  // main thread.
-  return !callback_ || !callback_->is_blocking() || !IsMainThread();
-}
-
 void EnterBase::ClearCallback() {
   callback_ = NULL;
 }
@@ -145,8 +172,6 @@
     return;  // Everything worked.
 
   if (callback_ && callback_->is_required()) {
-    // TODO(dmichael) make this work so that a call from a background thread
-    // goes back to that thread.
     callback_->PostRun(static_cast<int32_t>(PP_ERROR_BADRESOURCE));
     callback_ = NULL;
     retval_ = PP_OK_COMPLETIONPENDING;
diff --git a/ppapi/thunk/ppb_message_loop_api.h b/ppapi/thunk/ppb_message_loop_api.h
index 52660cad..efc1862 100644
--- a/ppapi/thunk/ppb_message_loop_api.h
+++ b/ppapi/thunk/ppb_message_loop_api.h
@@ -9,6 +9,7 @@
 #include "ppapi/c/pp_bool.h"
 #include "ppapi/c/pp_completion_callback.h"
 #include "ppapi/c/pp_stdint.h"
+#include "ppapi/thunk/ppapi_thunk_export.h"
 
 namespace ppapi {
 
@@ -16,7 +17,7 @@
 
 namespace thunk {
 
-class PPB_MessageLoop_API {
+class PPAPI_THUNK_EXPORT PPB_MessageLoop_API {
  public:
   virtual ~PPB_MessageLoop_API() {}
 
diff --git a/webkit/plugins/ppapi/host_globals.cc b/webkit/plugins/ppapi/host_globals.cc
index 643ea8c..7ab611d 100644
--- a/webkit/plugins/ppapi/host_globals.cc
+++ b/webkit/plugins/ppapi/host_globals.cc
@@ -178,6 +178,10 @@
      (*i)->element().document().frame()->addMessageToConsole(message);
 }
 
+::ppapi::MessageLoopShared* HostGlobals::GetCurrentMessageLoop() {
+  return NULL;
+}
+
 PP_Module HostGlobals::AddModule(PluginModule* module) {
 #ifndef NDEBUG
   // Make sure we're not adding one more than once.
diff --git a/webkit/plugins/ppapi/host_globals.h b/webkit/plugins/ppapi/host_globals.h
index fb46d70..2cc98c7 100644
--- a/webkit/plugins/ppapi/host_globals.h
+++ b/webkit/plugins/ppapi/host_globals.h
@@ -54,6 +54,7 @@
                                       PP_LogLevel_Dev level,
                                       const std::string& source,
                                       const std::string& value) OVERRIDE;
+  virtual ::ppapi::MessageLoopShared* GetCurrentMessageLoop() OVERRIDE;
 
   HostVarTracker* host_var_tracker() {
     return &host_var_tracker_;