1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ppapi/thunk/enter.h"
6
7#include "base/bind.h"
8#include "base/logging.h"
9#include "base/message_loop/message_loop.h"
10#include "base/strings/stringprintf.h"
11#include "base/synchronization/lock.h"
12#include "ppapi/c/pp_errors.h"
13#include "ppapi/shared_impl/ppapi_globals.h"
14#include "ppapi/shared_impl/tracked_callback.h"
15#include "ppapi/thunk/ppb_instance_api.h"
16#include "ppapi/thunk/resource_creation_api.h"
17
18namespace ppapi {
19namespace {
20
21bool IsMainThread() {
22  return
23      PpapiGlobals::Get()->GetMainThreadMessageLoop()->BelongsToCurrentThread();
24}
25
26bool CurrentThreadHandlingBlockingMessage() {
27  ppapi::MessageLoopShared* current =
28      PpapiGlobals::Get()->GetCurrentMessageLoop();
29  return current && current->CurrentlyHandlingBlockingMessage();
30}
31
32}  // namespace
33
34namespace thunk {
35
36namespace subtle {
37
38EnterBase::EnterBase()
39    : resource_(NULL),
40      retval_(PP_OK) {
41  PpapiGlobals::Get()->MarkPluginIsActive();
42}
43
44EnterBase::EnterBase(PP_Resource resource)
45    : resource_(GetResource(resource)),
46      retval_(PP_OK) {
47  PpapiGlobals::Get()->MarkPluginIsActive();
48}
49
50EnterBase::EnterBase(PP_Instance instance, SingletonResourceID resource_id)
51    : resource_(GetSingletonResource(instance, resource_id)),
52      retval_(PP_OK) {
53  PpapiGlobals::Get()->MarkPluginIsActive();
54}
55
56EnterBase::EnterBase(PP_Resource resource,
57                     const PP_CompletionCallback& callback)
58    : resource_(GetResource(resource)),
59      retval_(PP_OK) {
60  callback_ = new TrackedCallback(resource_, callback);
61  PpapiGlobals::Get()->MarkPluginIsActive();
62}
63
64EnterBase::EnterBase(PP_Instance instance, SingletonResourceID resource_id,
65                     const PP_CompletionCallback& callback)
66    : resource_(GetSingletonResource(instance, resource_id)),
67      retval_(PP_OK) {
68  if (!resource_)
69    retval_ = PP_ERROR_BADARGUMENT;
70  callback_ = new TrackedCallback(resource_, callback);
71  PpapiGlobals::Get()->MarkPluginIsActive();
72}
73
74EnterBase::~EnterBase() {
75  // callback_ is cleared any time it is run, scheduled to be run, or once we
76  // know it will be completed asynchronously. So by this point it should be
77  // NULL.
78  DCHECK(!callback_.get())
79      << "|callback_| is not NULL. Did you forget to call "
80         "|EnterBase::SetResult| in the interface's thunk?";
81}
82
83int32_t EnterBase::SetResult(int32_t result) {
84  if (!callback_.get()) {
85    // It doesn't make sense to call SetResult if there is no callback.
86    NOTREACHED();
87    retval_ = result;
88    return result;
89  }
90  if (result == PP_OK_COMPLETIONPENDING) {
91    retval_ = result;
92    if (callback_->is_blocking()) {
93      DCHECK(!IsMainThread());  // We should have returned an error before this.
94      retval_ = callback_->BlockUntilComplete();
95    } else {
96      // The callback is not blocking and the operation will complete
97      // asynchronously, so there's nothing to do.
98      retval_ = result;
99    }
100  } else {
101    // The function completed synchronously.
102    if (callback_->is_required()) {
103      // This is a required callback, so we must issue it asynchronously.
104      callback_->PostRun(result);
105      retval_ = PP_OK_COMPLETIONPENDING;
106    } else {
107      // The callback is blocking or optional, so all we need to do is mark
108      // the callback as completed so that it won't be issued later.
109      callback_->MarkAsCompleted();
110      retval_ = result;
111    }
112  }
113  callback_ = NULL;
114  return retval_;
115}
116
117// static
118Resource* EnterBase::GetResource(PP_Resource resource) {
119  return PpapiGlobals::Get()->GetResourceTracker()->GetResource(resource);
120}
121
122// static
123Resource* EnterBase::GetSingletonResource(PP_Instance instance,
124                                          SingletonResourceID resource_id) {
125  PPB_Instance_API* ppb_instance =
126      PpapiGlobals::Get()->GetInstanceAPI(instance);
127  if (!ppb_instance)
128    return NULL;
129
130  return ppb_instance->GetSingletonResource(instance, resource_id);
131}
132
133void EnterBase::SetStateForCallbackError(bool report_error) {
134  if (PpapiGlobals::Get()->IsHostGlobals()) {
135    // In-process plugins can't make PPAPI calls off the main thread.
136    CHECK(IsMainThread());
137  }
138  if (callback_.get()) {
139    if (callback_->is_blocking() && IsMainThread()) {
140      // Blocking callbacks are never allowed on the main thread.
141      callback_->MarkAsCompleted();
142      callback_ = NULL;
143      retval_ = PP_ERROR_BLOCKS_MAIN_THREAD;
144      if (report_error) {
145        std::string message(
146            "Blocking callbacks are not allowed on the main thread.");
147        PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
148                                                    std::string(), message);
149      }
150    } else if (callback_->is_blocking() &&
151               CurrentThreadHandlingBlockingMessage()) {
152      // Blocking callbacks are not allowed while handling a blocking message.
153      callback_->MarkAsCompleted();
154      callback_ = NULL;
155      retval_ = PP_ERROR_WOULD_BLOCK_THREAD;
156      if (report_error) {
157        std::string message("Blocking callbacks are not allowed while handling "
158                            "a blocking message from JavaScript.");
159        PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
160                                                    std::string(), message);
161      }
162    } else if (!IsMainThread() &&
163               callback_->has_null_target_loop() &&
164               !callback_->is_blocking()) {
165      // On a non-main thread, there must be a valid target loop for non-
166      // blocking callbacks, or we will have no place to run them.
167
168      // If the callback is required, there's no nice way to tell the plugin.
169      // We can't run their callback asynchronously without a message loop, and
170      // the plugin won't expect any return code other than
171      // PP_OK_COMPLETIONPENDING. So we crash to make the problem more obvious.
172      if (callback_->is_required()) {
173        std::string message("Attempted to use a required callback, but there "
174                            "is no attached message loop on which to run the "
175                            "callback.");
176        PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
177                                                    std::string(), message);
178        LOG(FATAL) << message;
179      }
180
181      callback_->MarkAsCompleted();
182      callback_ = NULL;
183      retval_ = PP_ERROR_NO_MESSAGE_LOOP;
184      if (report_error) {
185        std::string message(
186            "The calling thread must have a message loop attached.");
187        PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
188                                                    std::string(), message);
189      }
190    }
191  }
192}
193
194void EnterBase::ClearCallback() {
195  callback_ = NULL;
196}
197
198void EnterBase::SetStateForResourceError(PP_Resource pp_resource,
199                                         Resource* resource_base,
200                                         void* object,
201                                         bool report_error) {
202  // Check for callback errors. If we get any, SetStateForCallbackError will
203  // emit a log message. But we also want to check for resource errors. If there
204  // are both kinds of errors, we'll emit two log messages and return
205  // PP_ERROR_BADRESOURCE.
206  SetStateForCallbackError(report_error);
207
208  if (object)
209    return;  // Everything worked.
210
211  if (callback_.get() && callback_->is_required()) {
212    callback_->PostRun(static_cast<int32_t>(PP_ERROR_BADRESOURCE));
213    callback_ = NULL;
214    retval_ = PP_OK_COMPLETIONPENDING;
215  } else {
216    if (callback_.get())
217      callback_->MarkAsCompleted();
218    callback_ = NULL;
219    retval_ = PP_ERROR_BADRESOURCE;
220  }
221
222  // We choose to silently ignore the error when the pp_resource is null
223  // because this is a pretty common case and we don't want to have lots
224  // of errors in the log. This should be an obvious case to debug.
225  if (report_error && pp_resource) {
226    std::string message;
227    if (resource_base) {
228      message = base::StringPrintf(
229          "0x%X is not the correct type for this function.",
230          pp_resource);
231    } else {
232      message = base::StringPrintf(
233          "0x%X is not a valid resource ID.",
234          pp_resource);
235    }
236    PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
237                                                std::string(), message);
238  }
239}
240
241void EnterBase::SetStateForFunctionError(PP_Instance pp_instance,
242                                         void* object,
243                                         bool report_error) {
244  // Check for callback errors. If we get any, SetStateForCallbackError will
245  // emit a log message. But we also want to check for instance errors. If there
246  // are both kinds of errors, we'll emit two log messages and return
247  // PP_ERROR_BADARGUMENT.
248  SetStateForCallbackError(report_error);
249
250  if (object)
251    return;  // Everything worked.
252
253  if (callback_.get() && callback_->is_required()) {
254    callback_->PostRun(static_cast<int32_t>(PP_ERROR_BADARGUMENT));
255    callback_ = NULL;
256    retval_ = PP_OK_COMPLETIONPENDING;
257  } else {
258    if (callback_.get())
259      callback_->MarkAsCompleted();
260    callback_ = NULL;
261    retval_ = PP_ERROR_BADARGUMENT;
262  }
263
264  // We choose to silently ignore the error when the pp_instance is null as
265  // for PP_Resources above.
266  if (report_error && pp_instance) {
267    std::string message;
268    message = base::StringPrintf(
269        "0x%X is not a valid instance ID.",
270        pp_instance);
271    PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
272                                                std::string(), message);
273  }
274}
275
276}  // namespace subtle
277
278EnterInstance::EnterInstance(PP_Instance instance)
279    : EnterBase(),
280      functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
281  SetStateForFunctionError(instance, functions_, true);
282}
283
284EnterInstance::EnterInstance(PP_Instance instance,
285                             const PP_CompletionCallback& callback)
286    : EnterBase(0 /* resource */, callback),
287      // TODO(dmichael): This means that the callback_ we get is not associated
288      //                 even with the instance, but we should handle that for
289      //                 MouseLock (maybe others?).
290      functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
291  SetStateForFunctionError(instance, functions_, true);
292}
293
294EnterInstance::~EnterInstance() {
295}
296
297EnterInstanceNoLock::EnterInstanceNoLock(PP_Instance instance)
298    : EnterBase(),
299      functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
300  SetStateForFunctionError(instance, functions_, true);
301}
302
303EnterInstanceNoLock::EnterInstanceNoLock(
304    PP_Instance instance,
305    const PP_CompletionCallback& callback)
306    : EnterBase(0 /* resource */, callback),
307      // TODO(dmichael): This means that the callback_ we get is not associated
308      //                 even with the instance, but we should handle that for
309      //                 MouseLock (maybe others?).
310      functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
311  SetStateForFunctionError(instance, functions_, true);
312}
313
314EnterInstanceNoLock::~EnterInstanceNoLock() {
315}
316
317EnterResourceCreation::EnterResourceCreation(PP_Instance instance)
318    : EnterBase(),
319      functions_(PpapiGlobals::Get()->GetResourceCreationAPI(instance)) {
320  SetStateForFunctionError(instance, functions_, true);
321}
322
323EnterResourceCreation::~EnterResourceCreation() {
324}
325
326EnterResourceCreationNoLock::EnterResourceCreationNoLock(PP_Instance instance)
327    : EnterBase(),
328      functions_(PpapiGlobals::Get()->GetResourceCreationAPI(instance)) {
329  SetStateForFunctionError(instance, functions_, true);
330}
331
332EnterResourceCreationNoLock::~EnterResourceCreationNoLock() {
333}
334
335}  // namespace thunk
336}  // namespace ppapi
337