Shaka Player Embedded
register_member.h
Go to the documentation of this file.
1 // Copyright 2016 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #ifndef SHAKA_EMBEDDED_MAPPING_REGISTER_MEMBER_H_
16 #define SHAKA_EMBEDDED_MAPPING_REGISTER_MEMBER_H_
17 
18 #include <string>
19 #include <type_traits>
20 #include <utility>
21 
22 #include "src/js/js_error.h"
24 #include "src/mapping/convert_js.h"
25 #include "src/mapping/enum.h"
27 #include "src/mapping/js_engine.h"
29 #include "src/mapping/names.h"
30 #include "src/mapping/promise.h"
31 #include "src/util/templates.h"
32 #include "src/util/utils.h"
33 
34 namespace shaka {
35 namespace impl {
36 
57 #if defined(USING_V8)
58 # define SET_RETURN_VALUE(args, value) (args).GetReturnValue().Set(value)
59 # define SET_EXCEPTION(args, except) GetIsolate()->ThrowException(except)
60 # define GET_ARG_THIS(args) (static_cast<v8::Local<v8::Value>>((args).This()))
61 #elif defined(USING_JSC)
62 # define SET_EXCEPTION(args, except) (args).SetException(except)
63 # define SET_RETURN_VALUE(args, value) (args).SetReturn(value)
64 # define GET_ARG_THIS(args) ((args).thisv())
65 #endif
66 
67 
68 template <typename T>
69 using RawType =
71 
80 template <bool ReturnPromise>
81 struct ThrowError {
82  static constexpr const size_t kBufferSize = 4096;
83 
84  static bool IllegalInvocation(const CallbackArguments* args,
85  const std::string& func_name,
86  const std::string& target_name) {
87  return General(args, func_name, target_name, "Illegal invocation");
88  }
89 
90  static bool NotEnoughArgs(const CallbackArguments* args,
91  const std::string& name, const std::string& target,
92  size_t required, size_t given) {
93  constexpr const char* kFormat =
94  "%zu arguments required, but only %zu present.";
95  const std::string buffer = util::StringPrintf(kFormat, required, given);
96  return General(args, name, target, buffer);
97  }
98 
99  static bool CannotConvert(const CallbackArguments* args,
100  const std::string& name, const std::string& target,
101  const std::string& given,
102  const std::string& required) {
103  constexpr const char* kFormat = "Cannot convert '%s' to '%s'.";
104  const std::string buffer =
105  util::StringPrintf(kFormat, given.c_str(), required.c_str());
106  return General(args, name, target, buffer);
107  }
108 
109  static bool General(const CallbackArguments* args, const std::string& name,
110  const std::string& target, const std::string& message) {
111  constexpr const char* kFormat = "Failed to execute '%s' on '%s': %s";
112  const std::string buffer = util::StringPrintf(
113  kFormat, name.c_str(), target.c_str(), message.c_str());
114  return Raw(args, js::JsError::TypeError(buffer));
115  }
116 
117  static bool Raw(const CallbackArguments* args, const js::JsError& value) {
118  if (ReturnPromise) {
119  Promise ret = Promise::Rejected(value);
120  SET_RETURN_VALUE(*args, ret.ToJsValue());
121  return true;
122  }
123 
124  LocalVar<JsValue> except = value.error();
125  SET_EXCEPTION(*args, except);
126  return false;
127  }
128 };
129 
146 bool ConstructWrapperObject(const CallbackArguments& arguments,
147  BackingObject* that);
148 
149 
150 template <typename Ret>
152  static bool SetReturn(const CallbackArguments& arguments, const Ret& ret) {
153  SET_RETURN_VALUE(arguments, ToJsValue(ret));
154  return true;
155  }
156 };
157 template <>
159  static bool SetReturn(const CallbackArguments& arguments,
160  const ExceptionOr<void>& ret) {
161  if (holds_alternative<js::JsError>(ret)) {
162  LocalVar<JsValue> except = get<js::JsError>(ret).error();
163  SET_EXCEPTION(arguments, except);
164  return false;
165  }
166  return true;
167  }
168 };
169 template <typename T>
171  static bool SetReturn(const CallbackArguments& arguments,
172  const ExceptionOr<T>& ret) {
173  if (holds_alternative<T>(ret)) {
174  SET_RETURN_VALUE(arguments, ToJsValue(get<T>(ret)));
175  return true;
176  } else {
177  LocalVar<JsValue> except = get<js::JsError>(ret).error();
178  SET_EXCEPTION(arguments, except);
179  return false;
180  }
181  }
182 };
183 
184 
189 template <typename Ret>
191  template <typename Func, typename... Args>
192  static bool Call(const CallbackArguments& arguments, Func callback,
193  Args&&... args) {
194  static_assert(!std::is_rvalue_reference<Ret>::value,
195  "Cannot return an rvalue reference.");
196  Ret ret = callback(std::forward<Args>(args)...);
197  return HandleSetReturn<Ret>::SetReturn(arguments, ret);
198  }
199 };
200 template <>
201 struct CallAndSetReturn<void> {
202  template <typename Func, typename... Args>
203  static bool Call(const CallbackArguments& arguments, Func callback,
204  Args&&... args) {
205  callback(std::forward<Args>(args)...);
206  return true;
207  }
208 };
209 
210 
211 // Iterative case.
212 template <size_t ArgIndex, size_t ArgCount>
213 struct CallHelper {
214  template <typename Func, typename... GivenArgs>
215  static bool ConvertAndCallFunction(const std::string& func_name,
216  const std::string& target_name,
217  bool is_member_func,
218  const CallbackArguments& arguments,
219  Func&& callback, GivenArgs&&... args) {
220  using CurType =
221  typename util::func_traits<Func>::template argument_type<ArgIndex>;
222  return Helper<CurType>::ConvertAndCallFunction(
223  func_name, target_name, is_member_func, arguments,
224  std::forward<Func>(callback), std::forward<GivenArgs>(args)...);
225  }
226 
227  private:
228  template <typename CurType, typename = void>
229  struct Helper {
230  template <typename Func, typename... GivenArgs>
231  static bool ConvertAndCallFunction(const std::string& func_name,
232  const std::string& target_name,
233  bool is_member_func,
234  const CallbackArguments& arguments,
235  Func&& callback,
236  GivenArgs&&... args) {
237  using Ret = typename util::func_traits<Func>::return_type;
238  RawType<CurType> arg;
239  if (ArgumentCount(arguments) + is_member_func <= ArgIndex) {
240  if (!is_optional<RawType<CurType>>::value) {
241  const size_t arg_count = util::func_traits<Func>::argument_count;
243  &arguments, func_name, target_name, arg_count,
244  ArgumentCount(arguments));
245  }
246  } else {
247  // Only convert the argument if it is present. A missing argument
248  // should always be Nothing even if we can convert undefined to a value.
249  LocalVar<JsValue> source = is_member_func && ArgIndex == 0
250  ? GET_ARG_THIS(arguments)
251  : arguments[ArgIndex - is_member_func];
252  if (!FromJsValue(source, &arg)) {
253  const std::string type_name = TypeName<RawType<CurType>>::name();
254  const std::string value = ConvertToString(source);
256  &arguments, func_name, target_name, value, type_name);
257  }
258  }
259 
261  func_name, target_name, is_member_func, arguments,
262  std::forward<Func>(callback), std::forward<GivenArgs>(args)...,
263  std::move(arg));
264  }
265  };
266  // Special case for CallbackArguments.
267  template <typename T>
268  struct Helper<const CallbackArguments&, T> {
269  static_assert(ArgIndex + 1 == ArgCount,
270  "CallbackArguments must appear last");
271  template <typename Func, typename... GivenArgs>
272  static bool ConvertAndCallFunction(const std::string& func_name,
273  const std::string& target_name,
274  bool is_member_func,
275  const CallbackArguments& arguments,
276  Func&& callback,
277  GivenArgs&&... args) {
279  func_name, target_name, is_member_func, arguments,
280  std::forward<Func>(callback), std::forward<GivenArgs>(args)...,
281  arguments);
282  }
283  };
284 };
285 
286 // Base-case.
287 template <size_t ArgCount>
288 struct CallHelper<ArgCount, ArgCount> {
289  template <typename Func, typename... GivenArgs>
290  static bool ConvertAndCallFunction(const std::string& func_name,
291  const std::string& target_name,
292  bool is_member_func,
293  const CallbackArguments& arguments,
294  Func&& callback, GivenArgs&&... args) {
295  using Ret = typename util::func_traits<Func>::return_type;
296  return CallAndSetReturn<Ret>::Call(arguments, std::forward<Func>(callback),
297  std::forward<GivenArgs>(args)...);
298  }
299 };
300 
301 
302 // Contains extra data passed to registered methods to help call the correct
303 // backing methods.
306 };
307 
308 template <typename Func>
310  template <typename T>
311  explicit InternalCallbackData(T&& arg) : callback(std::forward<T>(arg)) {}
312 
314  std::string name;
315  std::string target;
317 };
318 
319 constexpr const char* kHiddenPropertyName = "$__shaka_extra_data";
320 
321 #if defined(USING_JSC)
322 
323 inline void InternalCallbackDataFinalize(JSObjectRef object) {
324  void* ptr = JSObjectGetPrivate(object);
325  delete reinterpret_cast<InternalCallbackDataBase*>(ptr);
326 }
327 
328 const JSClassDefinition kInternalCallbackDataDefinition = {
329  .version = 1,
330  .className = "InternalCallbackData",
331  .finalize = &InternalCallbackDataFinalize,
332 };
333 
334 inline JSClassRef GetCallbackDataClass() {
335  static JSClassRef cls = nullptr;
336  if (!cls) {
337  CHECK(cls = JSClassCreate(&kInternalCallbackDataDefinition));
338  }
339  return cls;
340 }
341 
342 #endif
343 
344 
350 template <typename T, typename Arg>
351 ReturnVal<JsValue> CreateInternalData(T** extra_data, Arg&& arg) {
352 #ifdef USING_V8
353  v8::Local<v8::ArrayBuffer> ret =
354  v8::ArrayBuffer::New(GetIsolate(), sizeof(T));
355 
356  void* ptr = ret->GetContents().Data();
357  *extra_data = new (ptr) T(std::forward<Arg>(arg));
358  // Sanity check: verify the new object pointer is at the start of the data.
359  DCHECK_EQ(reinterpret_cast<void*>(*extra_data), ptr);
360  JsEngine::Instance()->AddDestructor(ptr, [](void* obj) {
361  reinterpret_cast<T*>(obj)->~T();
362  });
363 
364  return ret;
365 #elif defined(USING_JSC)
366  *extra_data = new T(std::forward<Arg>(arg));
367 
368  // IMPORTANT: We need to cast since the argument is a void*. The finalize
369  // method will cast to this type, so the static_cast ensures we pass the
370  // correct sub-object pointer.
371  LocalVar<JsObject> ret =
372  JSObjectMake(GetContext(), GetCallbackDataClass(),
373  static_cast<InternalCallbackDataBase*>(*extra_data));
374  CHECK(ret);
375  return ret;
376 #endif
377 }
378 
388 #ifdef USING_V8
389 template <typename T, typename Info>
390 T* GetInternalData(const Info& arguments) {
391  if (arguments.Data().IsEmpty() || !arguments.Data()->IsArrayBuffer()) {
392  ThrowError</* ReturnPromise */ false>::General(
393  nullptr, "", "", "INTERNAL: Invalid function data.");
394  return nullptr;
395  }
396  v8::Local<v8::ArrayBuffer> extra =
397  arguments.Data().template As<v8::ArrayBuffer>();
398  return reinterpret_cast<T*>(extra->GetContents().Data());
399 }
400 #elif defined(USING_JSC)
401 template <typename T>
402 T* GetInternalData(const CallbackArguments& arguments) {
403  JSValueRef data = GetMemberRaw(arguments.callee(), kHiddenPropertyName);
404  void* ptr = IsObject(data) ? JSObjectGetPrivate(UnsafeJsCast<JsObject>(data))
405  : nullptr;
406  if (!ptr) {
407  ThrowError</* ReturnPromise */ false>::General(
408  nullptr, "", "", "INTERNAL: Invalid function data.");
409  return nullptr;
410  }
411  return static_cast<T*>(reinterpret_cast<InternalCallbackDataBase*>(ptr));
412 }
413 #endif
414 
415 
416 template <typename Func>
417 class JsCallback {
418  public:
419 #if defined(USING_V8)
420  static void Call(const CallbackArguments& arguments) {
421  v8::HandleScope handle_scope(GetIsolate());
422  CallRaw(arguments);
423  }
424 #elif defined(USING_JSC)
425  static JSValueRef Call(JSContextRef cx, JSObjectRef callee,
426  JSObjectRef thisv, size_t arg_count,
427  const JSValueRef* args, JSValueRef* except) {
428  // TODO(modmaker): Why is this not true? Should we force use of cx?
429  // DCHECK_EQ(cx, GetContext());
430  DCHECK(except);
431  CallbackArguments arguments(args, arg_count, callee, thisv, except);
432  if (!CallRaw(arguments))
433  return nullptr;
434  return arguments.ret();
435  }
436 #endif
437 
438  private:
439  static bool CallRaw(const CallbackArguments& arguments) {
440  auto data = GetInternalData<InternalCallbackData<Func>>(arguments);
441  if (!data)
442  return false;
443 
444  constexpr const size_t ArgCount = util::func_traits<Func>::argument_count;
446  data->name, data->target, data->is_member_func, arguments,
447  data->callback);
448  }
449 };
450 
451 
457 template <typename This, typename = void>
459  using type = void;
460 };
461 template <typename This>
462 struct get_create_type<This, decltype(void(&This::Create))> {
463  using type = decltype(&This::Create);
464 };
465 
475 template <typename This>
476 struct JsConstructorCreateOrThrow<This, void> {
477  static bool CreateOrThrow(const CallbackArguments& arguments) {
478  // There is no Create method.
479  const std::string type_name = TypeName<This>::name();
480  return ThrowError</* ReturnPromise */ false>::IllegalInvocation(
481  nullptr, "constructor", type_name);
482  }
483 };
484 template <typename This, typename... Args>
485 struct JsConstructorCreateOrThrow<This, This*(*)(Args...)> {
486  static bool CreateOrThrow(const CallbackArguments& arguments) {
487  // There is a Create method.
488  const std::string type_name = TypeName<This>::name();
489  auto ctor = [&](Args... args) {
490  BackingObject* backing = This::Create(std::forward<Args>(args)...);
491  CHECK(ConstructWrapperObject(arguments, backing));
492  };
493  return CallHelper<0, sizeof...(Args)>::ConvertAndCallFunction(
494  "constructor", type_name, false, arguments, std::move(ctor));
495  }
496 };
497 
498 
509 template <typename This>
511  public:
512 #if defined(USING_V8)
513  static void Call(const v8::FunctionCallbackInfo<v8::Value>& arguments) {
514  v8::HandleScope handle_scope(GetIsolate());
515 
516  const bool is_valid = !arguments.This().IsEmpty() &&
517  arguments.This()->InternalFieldCount() ==
519  CallRaw(arguments, is_valid);
520  }
521 #elif defined(USING_JSC)
522  static JSObjectRef Call(JSContextRef cx, JSObjectRef callee, size_t arg_count,
523  const JSValueRef* args, JSValueRef* except) {
524  // TODO(modmaker): Why is this not true? Should we force use of cx?
525  // DCHECK_EQ(cx, GetContext());
526  DCHECK(except);
527  CallbackArguments arguments(args, arg_count, callee, nullptr, except);
528  if (!CallRaw(arguments, true))
529  return nullptr;
530  return UnsafeJsCast<JsObject>(arguments.ret());
531  }
532 #endif
533 
534  private:
535  static bool CallRaw(const CallbackArguments& arguments, bool is_valid) {
536  if (!is_valid) {
537  const std::string type_name = TypeName<This>::name();
538  return ThrowError</* ReturnPromise */ false>::IllegalInvocation(
539  nullptr, "constructor", type_name);
540  }
541 
542  // Special case to wrap existing object. Single object of type v8::External
543  if (ArgumentCount(arguments) == 1) {
544  void* ptr = MaybeUnwrapPointer(arguments[0]);
545  if (ptr) {
546  auto* backing = reinterpret_cast<BackingObject*>(ptr);
547  return ConstructWrapperObject(arguments, backing);
548  }
549  }
550 
552  }
553 };
554 
555 
556 template <typename Func>
557 ReturnVal<JsFunction> CreateJsFunctionFromCallback(const std::string& target,
558  const std::string& name,
559  Func&& callback,
560  bool is_member_func) {
562  LocalVar<JsValue> js_value =
563  impl::CreateInternalData(&data, std::forward<Func>(callback));
564  // data->callback already set.
565  data->name = name;
566  data->target = target;
567  data->is_member_func = is_member_func;
568 
569 #ifdef USING_V8
570  return v8::Function::New(GetIsolate()->GetCurrentContext(),
573  v8::ConstructorBehavior::kThrow).ToLocalChecked();
574 #elif defined(USING_JSC)
575  JSContextRef cx = GetContext();
576  LocalVar<JsObject> ret =
577  JSObjectMakeFunctionWithCallback(cx, JsStringFromUtf8(name),
579 
580  const int attributes = kJSPropertyAttributeReadOnly |
581  kJSPropertyAttributeDontEnum |
582  kJSPropertyAttributeDontDelete;
583  JSObjectSetProperty(cx, ret, JsStringFromUtf8(impl::kHiddenPropertyName),
584  js_value, attributes, nullptr);
585  return ret;
586 #endif
587 }
588 
589 } // namespace impl
590 
603 template <typename Func>
604 ReturnVal<JsFunction> CreateStaticFunction(const std::string& target,
605  const std::string& name,
606  Func&& callback) {
608  target, name, std::forward<Func>(callback), false);
609 }
610 
616 template <typename Func>
617 ReturnVal<JsFunction> CreateMemberFunction(const std::string& target,
618  const std::string& name,
619  Func&& callback) {
620  return impl::CreateJsFunctionFromCallback(target, name,
621  std::forward<Func>(callback), true);
622 }
623 
631 template <typename Func>
632 void RegisterGlobalFunction(const std::string& name, Func&& callback) {
633  LocalVar<JsFunction> function =
634  CreateStaticFunction("window", name, std::forward<Func>(callback));
635  LocalVar<JsValue> value(RawToJsValue(function));
636  SetMemberRaw(JsEngine::Instance()->global_handle(), name, value);
637 }
638 
639 namespace impl {
640 
641 // Allow passing callable objects to JavaScript through ToJsValue().
642 template <typename Func>
644  template <typename T>
645  static ReturnVal<JsValue> ToJsValue(T&& source) {
646  auto func = CreateStaticFunction("", "", std::forward<T>(source));
647  return RawToJsValue(func);
648  }
649 };
650 
651 } // namespace impl
652 
653 } // namespace shaka
654 
655 #endif // SHAKA_EMBEDDED_MAPPING_REGISTER_MEMBER_H_
static bool SetReturn(const CallbackArguments &arguments, const Ret &ret)
static bool NotEnoughArgs(const CallbackArguments *args, const std::string &name, const std::string &target, size_t required, size_t given)
ReturnVal< JsFunction > CreateStaticFunction(const std::string &target, const std::string &name, Func &&callback)
static bool SetReturn(const CallbackArguments &arguments, const ExceptionOr< void > &ret)
static ReturnVal< JsValue > ToJsValue(T &&source)
bool IsObject(Handle< JsValue > value)
Definition: js_wrappers.cc:315
bool FromJsValue(Handle< JsValue > source, T *dest)
Definition: convert_js.h:370
std::string StringPrintf(const char *format,...)
Definition: utils.cc:49
bool ConstructWrapperObject(const CallbackArguments &arguments, BackingObject *that)
size_t ArgumentCount(const CallbackArguments &arguments)
Definition: js_wrappers.h:209
const char * name
static bool Call(const CallbackArguments &arguments, Func callback, Args &&... args)
ReturnVal< JsValue > ToJsValue(T &&source)
Definition: convert_js.h:381
ReturnVal< JsFunction > CreateJsFunctionFromCallback(const std::string &target, const std::string &name, Func &&callback, bool is_member_func)
const char * source
Definition: media_utils.cc:30
static bool Raw(const CallbackArguments *args, const js::JsError &value)
ReturnVal< JsValue > ToJsValue() const override
Definition: promise.cc:87
static bool IllegalInvocation(const CallbackArguments *args, const std::string &func_name, const std::string &target_name)
constexpr const char * kHiddenPropertyName
ReturnVal< JsValue > CreateInternalData(T **extra_data, Arg &&arg)
ReturnVal< JsValue > RawToJsValue(Handle< T > source)
Definition: js_wrappers.h:405
ExceptionCode type
typename std::remove_cv< typename std::remove_reference< T >::type >::type RawType
static constexpr const size_t kInternalFieldCount
ReturnVal< JsValue > error() const
Definition: js_error.h:52
static Promise Rejected(const js::JsError &error)
Definition: promise.h:77
static bool ConvertAndCallFunction(const std::string &func_name, const std::string &target_name, bool is_member_func, const CallbackArguments &arguments, Func &&callback, GivenArgs &&... args)
static bool SetReturn(const CallbackArguments &arguments, const ExceptionOr< T > &ret)
static bool CreateOrThrow(const CallbackArguments &arguments)
void SetMemberRaw(Handle< JsObject > object, const std::string &name, Handle< JsValue > value)
Definition: js_wrappers.cc:147
void RegisterGlobalFunction(const std::string &name, Func &&callback)
std::decay< Func >::type callback
std::string ConvertToString(Handle< JsValue > value)
Definition: js_wrappers.cc:203
static constexpr const size_t kBufferSize
static JsError TypeError(const std::string &message)
Definition: js_error.cc:81
JSContextRef GetContext()
Definition: jsc_utils.cc:24
const char * message
static bool ConvertAndCallFunction(const std::string &func_name, const std::string &target_name, bool is_member_func, const CallbackArguments &arguments, Func &&callback, GivenArgs &&... args)
ReturnVal< JsString > JsStringFromUtf8(const std::string &str)
Definition: js_wrappers.cc:266
static bool Call(const CallbackArguments &arguments, Func callback, Args &&... args)
v8::Isolate * GetIsolate()
Definition: v8_utils.cc:27
static std::string name()
Definition: names.h:48
static bool CannotConvert(const CallbackArguments *args, const std::string &name, const std::string &target, const std::string &given, const std::string &required)
ReturnVal< JsValue > GetMemberRaw(Handle< JsObject > object, const std::string &name, LocalVar< JsValue > *exception=nullptr)
Definition: js_wrappers.cc:136
static bool General(const CallbackArguments *args, const std::string &name, const std::string &target, const std::string &message)
void * MaybeUnwrapPointer(Handle< JsValue > value)
Definition: js_wrappers.cc:217
static bool CreateOrThrow(const CallbackArguments &arguments)
ReturnVal< JsFunction > CreateMemberFunction(const std::string &target, const std::string &name, Func &&callback)