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