Shaka Player Embedded
js_wrappers.cc
Go to the documentation of this file.
1 // Copyright 2017 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 
16 
17 #include <fstream>
18 
20 #include "src/mapping/convert_js.h"
21 #include "src/mapping/js_utils.h"
22 #include "src/util/file_system.h"
23 
24 namespace shaka {
25 
26 namespace {
27 
28 JSClassDefinition wrapper_class_def = {
29  .version = 1,
30  .className = "<pointer wrapper>",
31 };
32 
33 JSClassRef GetWrapperClass() {
34  static JSClassRef class_ = nullptr;
35  if (!class_) {
36  class_ = JSClassCreate(&wrapper_class_def);
37  CHECK(class_);
38  }
39 
40  return class_;
41 }
42 
43 bool IsInstanceOfStandardType(Handle<JsValue> value, const std::string& type) {
44  JSContextRef cx = GetContext();
45  JSValueRef ctor = GetMemberRaw(JSContextGetGlobalObject(cx), type);
46  return JSValueIsInstanceOfConstructor(cx, value, UnsafeJsCast<JsObject>(ctor),
47  nullptr);
48 }
49 
50 } // namespace
51 
52 namespace util {
53 
54 template <>
55 struct RefTypeTraits<JSPropertyNameArrayRef> {
56  static constexpr const bool AcquireWithRaw = false;
57 
58  static JSPropertyNameArrayRef Duplicate(JSPropertyNameArrayRef arg) {
59  if (arg)
60  return JSPropertyNameArrayRetain(arg);
61  else
62  return nullptr;
63  }
64 
65  static void Release(JSPropertyNameArrayRef arg) {
66  if (arg)
67  JSPropertyNameArrayRelease(arg);
68  }
69 };
70 
71 } // namespace util
72 
73 // \cond Doxygen_Skip
74 
75 CallbackArguments::CallbackArguments(const JSValueRef* args, size_t count,
76  JSObjectRef callee, JSObjectRef thisv,
77  JSValueRef* except)
78  : except_(except),
79  callee_(callee),
80  this_(thisv),
81  args_(args),
82  count_(count) {}
83 CallbackArguments::~CallbackArguments() {}
84 
85 ReturnVal<JsValue> CallbackArguments::operator[](size_t i) const {
86  if (i >= count_)
87  return nullptr;
88  return args_[i];
89 }
90 
91 void CallbackArguments::SetReturn(Handle<JsValue> ret) const {
92  DCHECK(!ret_);
93  ret_ = ret;
94 }
95 
96 void CallbackArguments::SetException(Handle<JsValue> except) const {
97  DCHECK(!ret_);
98  *except_ = except;
99 }
100 
101 
102 std::vector<std::string> GetMemberNames(Handle<JsObject> object) {
103  auto* cx = GetContext();
104  util::CFRef<JSPropertyNameArrayRef> props =
105  JSObjectCopyPropertyNames(cx, object);
106  const size_t max = JSPropertyNameArrayGetCount(props);
107 
108  std::vector<std::string> ret;
109  ret.reserve(max);
110  for (size_t i = 0; i < max; i++) {
111  LocalVar<JsString> name(util::acquire_ref,
112  JSPropertyNameArrayGetNameAtIndex(props, i));
113  ret.emplace_back(ConvertToString(RawToJsValue(name)));
114  }
115  return ret;
116 }
117 
118 ReturnVal<JsValue> GetMemberRaw(Handle<JsObject> object,
119  const std::string& name,
120  LocalVar<JsValue>* exception) {
121  JSValueRef raw_except = nullptr;
122  auto* ret = JSObjectGetProperty(GetContext(), object, JsStringFromUtf8(name),
123  &raw_except);
124  if (exception) {
125  *exception = raw_except;
126  }
127  return ret;
128 }
129 
130 ReturnVal<JsValue> GetArrayIndexRaw(Handle<JsObject> object, size_t index,
131  LocalVar<JsValue>* exception) {
132  JSValueRef raw_except = nullptr;
133  auto* ret =
134  JSObjectGetPropertyAtIndex(GetContext(), object, index, &raw_except);
135  if (exception) {
136  *exception = raw_except;
137  }
138  return ret;
139 }
140 
141 void SetMemberRaw(Handle<JsObject> object, const std::string& name,
142  Handle<JsValue> value) {
143  JSObjectSetProperty(GetContext(), object, JsStringFromUtf8(name), value,
144  kJSPropertyAttributeNone, nullptr);
145 }
146 
147 void SetArrayIndexRaw(Handle<JsObject> object, size_t i,
148  Handle<JsValue> value) {
149  JSObjectSetPropertyAtIndex(GetContext(), object, i, value, nullptr);
150 }
151 
152 void SetGenericPropertyRaw(Handle<JsObject> object, const std::string& name,
153  Handle<JsFunction> getter,
154  Handle<JsFunction> setter) {
155  // TODO: Find a better way to do this.
156  // This works by effectively running the following JavaScript:
157  // Object.defineProperty($object, $name, {get: $getter, set: $setter});
158  LocalVar<JsValue> js_Object =
159  GetMemberRaw(JSContextGetGlobalObject(GetContext()), "Object");
160  CHECK(js_Object && IsObject(js_Object));
161  LocalVar<JsValue> define_prop =
162  GetMemberRaw(UnsafeJsCast<JsObject>(js_Object), "defineProperty");
163  CHECK(define_prop && GetValueType(define_prop) == proto::ValueType::Function);
164 
165  LocalVar<JsObject> props(CreateObject());
166  SetMemberRaw(props, "get", getter);
167  if (setter)
168  SetMemberRaw(props, "set", setter);
169 
170  LocalVar<JsValue> args[] = {object, ToJsValue(name), props};
171  LocalVar<JsValue> except;
172  CHECK(InvokeMethod(UnsafeJsCast<JsObject>(define_prop),
173  UnsafeJsCast<JsObject>(js_Object), 3, args, &except))
174  << ConvertToString(except);
175 }
176 
177 bool InvokeConstructor(Handle<JsFunction> ctor, int argc,
178  LocalVar<JsValue>* argv,
179  LocalVar<JsValue>* result_or_except) {
180  JSValueRef except = nullptr;
181  std::vector<JSValueRef> args(argv, argv + argc);
182  LocalVar<JsValue> ret =
183  JSObjectCallAsConstructor(GetContext(), ctor, argc, args.data(), &except);
184  if (except) {
185  *result_or_except = except;
186  return false;
187  } else {
188  *result_or_except = ret;
189  return true;
190  }
191 }
192 
193 bool InvokeMethod(Handle<JsFunction> func, Handle<JsObject> that, int argc,
194  LocalVar<JsValue>* argv,
195  LocalVar<JsValue>* result_or_except) {
196  JSValueRef except = nullptr;
197  std::vector<JSValueRef> args(argv, argv + argc);
198  LocalVar<JsValue> ret = JSObjectCallAsFunction(GetContext(), func, that, argc,
199  args.data(), &except);
200  if (except) {
201  *result_or_except = except;
202  return false;
203  } else {
204  *result_or_except = ret;
205  return true;
206  }
207 }
208 
209 std::string ConvertToString(Handle<JsValue> value) {
210  LocalVar<JsString> str(JSValueToStringCopy(GetContext(), value, nullptr));
211  if (!str)
212  return "";
213 
214  const size_t max_size = JSStringGetMaximumUTF8CStringSize(str);
215  std::string ret(max_size, '\0');
216  const size_t real_size = JSStringGetUTF8CString(str, &ret[0], ret.size());
217  ret.resize(real_size - 1); // Subtract 1 to remove the null-terminator.
218  return ret;
219 }
220 
221 ReturnVal<JsValue> WrapPointer(void* ptr) {
222  return JSObjectMake(GetContext(), GetWrapperClass(), ptr);
223 }
224 
225 void* MaybeUnwrapPointer(Handle<JsValue> value) {
226  if (!JSValueIsObjectOfClass(GetContext(), value, GetWrapperClass()))
227  return nullptr;
228  return JSObjectGetPrivate(JSValueToObject(GetContext(), value, nullptr));
229 }
230 
231 BackingObject* GetInternalPointer(Handle<JsValue> value) {
232  LocalVar<JsObject> obj(JSValueToObject(GetContext(), value, nullptr));
233  if (!obj)
234  return nullptr;
235  return reinterpret_cast<BackingObject*>(JSObjectGetPrivate(obj));
236 }
237 
238 bool IsDerivedFrom(BackingObject* ptr, const std::string& name) {
239  return ptr ? ptr->DerivedFrom(name) : false;
240 }
241 
242 bool RunScript(const std::string& path) {
243  util::FileSystem fs;
244  std::vector<uint8_t> code;
245  CHECK(fs.ReadFile(path, &code));
246  return RunScript(path, code.data(), code.size());
247 }
248 
249 bool RunScript(const std::string& path, const uint8_t* data, size_t size) {
250  LocalVar<JsString> code = JsStringFromUtf8(data, size);
251  LocalVar<JsString> source = JsStringFromUtf8(path);
252 
253  JSValueRef except = nullptr;
254  (void)JSEvaluateScript(GetContext(), code, nullptr, source, 0, &except);
255  if (except) {
256  OnUncaughtException(except, false);
257  return false;
258  }
259  return true;
260 }
261 
262 ReturnVal<JsValue> ParseJsonString(const std::string& json) {
263  LocalVar<JsString> input = JsStringFromUtf8(json);
264  return JSValueMakeFromJSONString(GetContext(), input);
265 }
266 
267 ReturnVal<JsString> JsStringFromUtf8(const uint8_t* data, size_t size) {
268  util::CFRef<CFStringRef> cf_str(CFStringCreateWithBytes(
269  nullptr, data, size, kCFStringEncodingUTF8, false));
270  return JSStringCreateWithCFString(cf_str);
271 }
272 
273 ReturnVal<JsString> JsStringFromUtf8(const std::string& str) {
274  util::CFRef<CFStringRef> cf_str(CFStringCreateWithBytes(
275  nullptr, reinterpret_cast<const uint8_t*>(str.data()), str.size(),
276  kCFStringEncodingUTF8, false));
277  return JSStringCreateWithCFString(cf_str);
278 }
279 
280 ReturnVal<JsValue> JsUndefined() {
281  return JSValueMakeUndefined(GetContext());
282 }
283 
284 ReturnVal<JsValue> JsNull() {
285  return JSValueMakeNull(GetContext());
286 }
287 
288 ReturnVal<JsObject> CreateArray(size_t length) {
289  JSContextRef cx = GetContext();
290  LocalVar<JsObject> arr(JSObjectMakeArray(cx, 0, nullptr, nullptr));
291  SetMemberRaw(arr, "length", JSValueMakeNumber(cx, length));
292  return arr;
293 }
294 
295 ReturnVal<JsObject> CreateObject() {
296  return JSObjectMake(GetContext(), nullptr, nullptr);
297 }
298 
299 ReturnVal<JsMap> CreateMap() {
300  return UnsafeJsCast<JsObject>(CreateNativeObject("Map", nullptr, 0));
301 }
302 
303 void SetMapValue(Handle<JsMap> map, Handle<JsValue> key,
304  Handle<JsValue> value) {
305  LocalVar<JsValue> func_value = GetMemberRaw(map, "set");
306  DCHECK(GetValueType(func_value) == proto::ValueType::Function);
307  LocalVar<JsFunction> func = UnsafeJsCast<JsFunction>(func_value);
308 
309  LocalVar<JsValue> args[] = {key, value};
310  LocalVar<JsValue> ignored;
311  CHECK(InvokeMethod(func, map, 2, args, &ignored));
312 }
313 
314 bool IsNullOrUndefined(Handle<JsValue> value) {
315  JSContextRef cx = GetContext();
316  return !value || JSValueIsNull(cx, value) || JSValueIsUndefined(cx, value);
317 }
318 
319 bool IsObject(Handle<JsValue> value) {
320  return JSValueIsObject(GetContext(), value);
321 }
322 
323 bool IsBuiltInObject(Handle<JsObject> object) {
324  LocalVar<JsValue> to_string_val =
325  GetDescendant(JSContextGetGlobalObject(GetContext()),
326  {"Object", "prototype", "toString"});
327  CHECK(IsObject(to_string_val));
328 
329  LocalVar<JsObject> to_string = UnsafeJsCast<JsObject>(to_string_val);
330  LocalVar<JsValue> value;
331  CHECK(InvokeMethod(to_string, object, 0, nullptr, &value));
332  return ConvertToString(value) != "[object Object]";
333 }
334 
335 proto::ValueType GetValueType(Handle<JsValue> value) {
336  JSContextRef cx = GetContext();
337  switch (JSValueGetType(cx, value)) {
338  case kJSTypeUndefined:
340  case kJSTypeNull:
341  return proto::ValueType::Null;
342  case kJSTypeBoolean:
344  case kJSTypeNumber:
346  case kJSTypeString:
348  case kJSTypeObject:
349  break;
350  default:
351  LOG(FATAL) << "Unknown JavaScript value type";
352  }
353  // Note JSC doesn't support symbols.
354 
355  if (JSValueIsArray(cx, value))
357  if (JSObjectIsFunction(cx, JSValueToObject(cx, value, nullptr)))
359 
360  switch (JSValueGetTypedArrayType(cx, value, nullptr)) {
361  case kJSTypedArrayTypeArrayBuffer:
363  case kJSTypedArrayTypeFloat32Array:
365  case kJSTypedArrayTypeFloat64Array:
367  case kJSTypedArrayTypeInt16Array:
369  case kJSTypedArrayTypeInt32Array:
371  case kJSTypedArrayTypeInt8Array:
373  case kJSTypedArrayTypeUint16Array:
375  case kJSTypedArrayTypeUint32Array:
377  case kJSTypedArrayTypeUint8Array:
379  case kJSTypedArrayTypeUint8ClampedArray:
381 
382  case kJSTypedArrayTypeNone:
383  break;
384  default:
385  LOG(FATAL) << "Unknown JavaScript TypedArray type";
386  }
387 
388  if (IsInstanceOfStandardType(value, "Boolean"))
390  if (IsInstanceOfStandardType(value, "String"))
392  if (IsInstanceOfStandardType(value, "Number"))
394  if (IsInstanceOfStandardType(value, "Promise"))
396 
398 }
399 
400 double NumberFromValue(Handle<JsValue> value) {
401  JSContextRef cx = GetContext();
402  DCHECK(JSValueIsNumber(cx, value) ||
403  IsInstanceOfStandardType(value, "Number"));
404  return JSValueToNumber(cx, value, nullptr);
405 }
406 
407 bool BooleanFromValue(Handle<JsValue> value) {
408  JSContextRef cx = GetContext();
409  if (IsInstanceOfStandardType(value, "Boolean")) {
410  LocalVar<JsValue> func =
411  GetMemberRaw(UnsafeJsCast<JsObject>(value), "valueOf");
413  LocalVar<JsValue> except;
414  CHECK(InvokeMethod(UnsafeJsCast<JsFunction>(func),
415  UnsafeJsCast<JsObject>(value), 0, nullptr, &except));
416  value = except;
417  } else {
418  DCHECK(JSValueIsBoolean(cx, value));
419  }
420  return JSValueToBoolean(cx, value);
421 }
422 
423 // \endcond Doxygen_Skip
424 
425 } // namespace shaka
void SetArrayIndexRaw(Handle< JsObject > object, size_t i, Handle< JsValue > value)
Definition: js_wrappers.cc:152
ReturnVal< JsValue > ParseJsonString(const std::string &json)
Definition: js_wrappers.cc:256
ReturnVal< JsObject > CreateObject()
Definition: js_wrappers.cc:296
ReturnVal< JsValue > JsUndefined()
Definition: js_wrappers.cc:284
bool IsObject(Handle< JsValue > value)
Definition: js_wrappers.cc:315
bool IsNullOrUndefined(Handle< JsValue > value)
Definition: js_wrappers.cc:311
std::vector< std::string > GetMemberNames(Handle< JsObject > object)
Definition: js_wrappers.cc:118
ReturnVal< JsValue > JsNull()
Definition: js_wrappers.cc:288
ReturnVal< JsValue > GetArrayIndexRaw(Handle< JsObject > object, size_t index, LocalVar< JsValue > *exception=nullptr)
Definition: js_wrappers.cc:142
ReturnVal< JsValue > GetDescendant(Handle< JsObject > root, const std::vector< std::string > &names)
Definition: js_utils.cc:52
bool InvokeMethod(Handle< JsFunction > method, Handle< JsObject > that, int argc, LocalVar< JsValue > *argv, LocalVar< JsValue > *result_or_except)
Definition: js_wrappers.cc:182
BackingObject * GetInternalPointer(Handle< JsValue > value)
Definition: js_wrappers.cc:223
const char * name
bool BooleanFromValue(Handle< JsValue > value)
Definition: js_wrappers.cc:394
bool IsDerivedFrom(BackingObject *ptr, const std::string &name)
Definition: js_wrappers.cc:235
ReturnVal< JsObject > CreateArray(size_t length)
Definition: js_wrappers.cc:292
static void Release(JSPropertyNameArrayRef arg)
Definition: js_wrappers.cc:65
ReturnVal< JsValue > ToJsValue(T &&source)
Definition: convert_js.h:381
const char * source
Definition: media_utils.cc:30
std::string to_string(VideoReadyState state)
Definition: media_player.cc:32
ReturnVal< JsValue > RawToJsValue(Handle< T > source)
Definition: js_wrappers.h:405
bool RunScript(const std::string &path)
Definition: js_wrappers.cc:239
JSValueRef CreateNativeObject(const std::string &name, JSValueRef *args, size_t argc)
Definition: jsc_utils.cc:40
ExceptionCode type
proto::ValueType GetValueType(Handle< JsValue > value)
Definition: js_wrappers.cc:329
double NumberFromValue(Handle< JsValue > value)
Definition: js_wrappers.cc:385
ReturnVal< JsMap > CreateMap()
Definition: js_wrappers.cc:300
virtual MUST_USE_RESULT bool ReadFile(const std::string &path, std::vector< uint8_t > *data) const
Definition: file_system.cc:79
static JSPropertyNameArrayRef Duplicate(JSPropertyNameArrayRef arg)
Definition: js_wrappers.cc:58
void SetMemberRaw(Handle< JsObject > object, const std::string &name, Handle< JsValue > value)
Definition: js_wrappers.cc:147
void SetMapValue(Handle< JsMap > map, Handle< JsValue > key, Handle< JsValue > value)
Definition: js_wrappers.cc:304
bool DerivedFrom(const std::string &base)
std::string ConvertToString(Handle< JsValue > value)
Definition: js_wrappers.cc:203
JSContextRef GetContext()
Definition: jsc_utils.cc:24
ReturnVal< JsValue > WrapPointer(void *ptr)
Definition: js_wrappers.cc:213
void OnUncaughtException(JSValueRef exception, bool in_promise)
Definition: jsc_utils.cc:28
ReturnVal< JsString > JsStringFromUtf8(const std::string &str)
Definition: js_wrappers.cc:266
bool InvokeConstructor(Handle< JsFunction > ctor, int argc, LocalVar< JsValue > *argv, LocalVar< JsValue > *result_or_except)
Definition: js_wrappers.cc:165
ReturnVal< JsValue > GetMemberRaw(Handle< JsObject > object, const std::string &name, LocalVar< JsValue > *exception=nullptr)
Definition: js_wrappers.cc:136
void * MaybeUnwrapPointer(Handle< JsValue > value)
Definition: js_wrappers.cc:217
bool IsBuiltInObject(Handle< JsObject > object)
Definition: js_wrappers.cc:319
void SetGenericPropertyRaw(Handle< JsObject > object, const std::string &name, Handle< JsFunction > getter, Handle< JsFunction > setter)
Definition: js_wrappers.cc:158