Shaka Player Embedded
js_wrappers.cc
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 
16 
18 #include "src/mapping/convert_js.h"
19 #include "src/util/file_system.h"
20 
21 namespace shaka {
22 
23 namespace {
24 
25 class StaticExternalResource
26  : public v8::String::ExternalOneByteStringResource {
27  public:
28  StaticExternalResource(const uint8_t* data, size_t data_size)
29  : data_(data), data_size_(data_size) {}
30 
31  SHAKA_NON_COPYABLE_OR_MOVABLE_TYPE(StaticExternalResource);
32 
33  const char* data() const override {
34  return reinterpret_cast<const char*>(data_);
35  }
36 
37  size_t length() const override {
38  return data_size_;
39  }
40 
41  protected:
42  void Dispose() override {
43  delete this;
44  }
45 
46  private:
47  ~StaticExternalResource() override {}
48 
49  const uint8_t* data_;
50  size_t data_size_;
51 };
52 
53 v8::Local<v8::String> MakeExternalString(const uint8_t* data,
54  size_t data_size) {
55 #ifndef NDEBUG
56  for (size_t i = 0; i < data_size; i++)
57  CHECK_LT(data[i], 0x80) << "External string must be ASCII";
58 #endif
59 
60  auto* res = new StaticExternalResource(data, data_size);
61  auto temp = v8::String::NewExternalOneByte(GetIsolate(), res); // NOLINT
62  return temp.ToLocalChecked();
63 }
64 
65 template <typename T>
66 ReturnVal<JsValue> GetMemberImpl(Handle<JsObject> object, T ind,
67  LocalVar<JsValue>* exception) {
68  v8::Isolate* isolate = GetIsolate();
69  v8::Local<v8::Context> context = isolate->GetCurrentContext();
70 
71  v8::TryCatch trycatch(isolate);
72  v8::MaybeLocal<v8::Value> maybe_field = object->Get(context, ind);
73  v8::Local<v8::Value> field;
74  if (!maybe_field.ToLocal(&field)) {
75  if (exception) {
76  *exception = trycatch.Exception();
77  }
78  return v8::Undefined(isolate);
79  }
80  return field;
81 }
82 
83 template <typename T>
84 void SetMemberImpl(Handle<JsObject> object, T ind, Handle<JsValue> value) {
85  auto context = GetIsolate()->GetCurrentContext();
86  // Set returns Maybe<bool>, which is nothing on error.
87  CHECK(object->Set(context, ind, value).IsJust());
88 }
89 
90 bool RunScriptImpl(const std::string& path, Handle<JsString> source) {
91  v8::Isolate* isolate = v8::Isolate::GetCurrent();
92  auto context = isolate->GetCurrentContext();
93  v8::HandleScope handle_scope(GetIsolate());
94  v8::ScriptOrigin origin(ToJsValue(path));
95 
96  // Compile the script.
97  v8::TryCatch trycatch(isolate);
98  v8::MaybeLocal<v8::Script> maybe_script =
99  v8::Script::Compile(context, source, &origin);
100  v8::Local<v8::Script> script;
101  if (!maybe_script.ToLocal(&script) || script.IsEmpty()) {
102  LOG(ERROR) << "Error loading script " << path;
103  OnUncaughtException(trycatch.Exception(), false);
104  return false;
105  }
106 
107  // Run the script. Run() returns the return value from the script, which
108  // will be empty if the script failed to execute.
109  if (script->Run(context).IsEmpty()) {
110  OnUncaughtException(trycatch.Exception(), false);
111  return false;
112  }
113  return true;
114 }
115 
116 } // namespace
117 
118 std::vector<std::string> GetMemberNames(Handle<JsObject> object) {
119  std::vector<std::string> names;
120 
121  v8::Isolate* isolate = GetIsolate();
122  v8::Local<v8::Context> context = isolate->GetCurrentContext();
123 
124  v8::MaybeLocal<v8::Array> maybe_field = object->GetOwnPropertyNames(context);
125  v8::Local<v8::Array> array;
126  if (maybe_field.ToLocal(&array)) {
127  for (size_t i = 0; i < array->Length(); i++) {
128  v8::Local<v8::Value> name = GetArrayIndexRaw(array, i);
129  names.push_back(ConvertToString(name));
130  }
131  }
132 
133  return names;
134 }
135 
136 ReturnVal<JsValue> GetMemberRaw(Handle<JsObject> object,
137  const std::string& name,
138  LocalVar<JsValue>* exception) {
139  return GetMemberImpl(object, JsStringFromUtf8(name), exception);
140 }
141 
142 ReturnVal<JsValue> GetArrayIndexRaw(Handle<JsObject> object, size_t index,
143  LocalVar<JsValue>* exception) {
144  return GetMemberImpl(object, index, exception);
145 }
146 
147 void SetMemberRaw(Handle<JsObject> object, const std::string& name,
148  Handle<JsValue> value) {
149  SetMemberImpl(object, JsStringFromUtf8(name), value);
150 }
151 
152 void SetArrayIndexRaw(Handle<JsObject> object, size_t i,
153  Handle<JsValue> value) {
154  SetMemberImpl(object, i, value);
155 }
156 
157 
158 void SetGenericPropertyRaw(Handle<JsObject> object, const std::string& name,
159  Handle<JsFunction> getter,
160  Handle<JsFunction> setter) {
161  object->SetAccessorProperty(JsStringFromUtf8(name), getter, setter);
162 }
163 
164 
165 bool InvokeConstructor(Handle<JsFunction> ctor, int argc,
166  LocalVar<JsValue>* argv,
167  LocalVar<JsValue>* result_or_except) {
168  v8::Isolate* isolate = GetIsolate();
169  v8::Local<v8::Context> context = isolate->GetCurrentContext();
170  v8::EscapableHandleScope handles(isolate);
171 
172  v8::TryCatch trycatch(isolate);
173  v8::MaybeLocal<v8::Object> result = ctor->NewInstance(context, argc, argv);
174  if (result.IsEmpty()) {
175  *result_or_except = handles.Escape(trycatch.Exception());
176  return false;
177  }
178  *result_or_except = handles.Escape(result.ToLocalChecked());
179  return true;
180 }
181 
182 bool InvokeMethod(Handle<JsFunction> method, Handle<JsObject> that, int argc,
183  LocalVar<JsValue>* argv,
184  LocalVar<JsValue>* result_or_except) {
185  v8::Isolate* isolate = GetIsolate();
186  v8::Local<v8::Context> context = isolate->GetCurrentContext();
187  v8::EscapableHandleScope handles(isolate);
188 
189  if (that.IsEmpty())
190  that = context->Global();
191 
192  v8::TryCatch trycatch(isolate);
193  v8::MaybeLocal<v8::Value> result = method->Call(context, that, argc, argv);
194  if (result.IsEmpty()) {
195  *result_or_except = handles.Escape(trycatch.Exception());
196  return false;
197  }
198  *result_or_except = handles.Escape(result.ToLocalChecked());
199  return true;
200 }
201 
202 
203 std::string ConvertToString(Handle<JsValue> value) {
204  if (!value.IsEmpty() && value->IsSymbol()) {
205  LocalVar<v8::Value> name = value.As<v8::Symbol>()->Name();
206  return name.IsEmpty() || name->IsUndefined() ? "" : ConvertToString(name);
207  }
208 
209  v8::String::Utf8Value str(GetIsolate(), value);
210  return std::string(*str, str.length());
211 }
212 
213 ReturnVal<JsValue> WrapPointer(void* ptr) {
214  return v8::External::New(GetIsolate(), ptr);
215 }
216 
217 void* MaybeUnwrapPointer(Handle<JsValue> value) {
218  if (value.IsEmpty() || !value->IsExternal())
219  return nullptr;
220  return value.As<v8::External>()->Value();
221 }
222 
223 BackingObject* GetInternalPointer(Handle<JsValue> value) {
224  if (value.IsEmpty() || !value->IsObject())
225  return nullptr;
226 
227  v8::Local<v8::Object> object = value.As<v8::Object>();
228  if (object->InternalFieldCount() != BackingObject::kInternalFieldCount)
229  return nullptr;
230 
231  return reinterpret_cast<BackingObject*>(
232  object->GetAlignedPointerFromInternalField(0));
233 }
234 
235 bool IsDerivedFrom(BackingObject* ptr, const std::string& name) {
236  return ptr ? ptr->DerivedFrom(name) : false;
237 }
238 
239 bool RunScript(const std::string& path) {
240  util::FileSystem fs;
241  std::vector<uint8_t> source;
242  CHECK(fs.ReadFile(path, &source));
243  v8::Local<v8::String> code =
244  v8::String::NewFromUtf8(GetIsolate(),
245  reinterpret_cast<const char*>(source.data()),
246  v8::NewStringType::kNormal, source.size())
247  .ToLocalChecked();
248  return RunScriptImpl(path, code);
249 }
250 
251 bool RunScript(const std::string& path, const uint8_t* data, size_t data_size) {
252  v8::Local<v8::String> source = JsStringFromUtf8(data, data_size);
253  return RunScriptImpl(path, source);
254 }
255 
256 ReturnVal<JsValue> ParseJsonString(const std::string& json) {
257  v8::Local<v8::String> source = MakeExternalString(
258  reinterpret_cast<const uint8_t*>(json.data()), json.size());
259  v8::MaybeLocal<v8::Value> value =
260  v8::JSON::Parse(GetIsolate()->GetCurrentContext(), source);
261  if (value.IsEmpty())
262  return {};
263  return value.ToLocalChecked();
264 }
265 
266 ReturnVal<JsString> JsStringFromUtf8(const std::string& str) {
267  // NewStringType determines where to put the string.
268  // - kNormal is for "normal", short-lived strings.
269  // - kInternalized is for common strings and are cached (taking up more space)
270  // TODO: Investigate using kInternalized for property names which are static
271  // and may be common, Chromium has a v8AtomicString method for this.
272  return v8::String::NewFromUtf8(GetIsolate(), str.c_str(),
273  v8::NewStringType::kNormal, str.size())
274  .ToLocalChecked();
275 }
276 
277 ReturnVal<JsString> JsStringFromUtf8(const uint8_t* data, size_t size) {
278  return v8::String::NewFromUtf8(GetIsolate(),
279  reinterpret_cast<const char*>(data),
280  v8::NewStringType::kNormal, size)
281  .ToLocalChecked();
282 }
283 
284 ReturnVal<JsValue> JsUndefined() {
285  return v8::Undefined(GetIsolate());
286 }
287 
288 ReturnVal<JsValue> JsNull() {
289  return v8::Null(GetIsolate());
290 }
291 
292 ReturnVal<JsObject> CreateArray(size_t length) {
293  return v8::Array::New(GetIsolate(), length);
294 }
295 
296 ReturnVal<JsObject> CreateObject() {
297  return v8::Object::New(GetIsolate());
298 }
299 
300 ReturnVal<JsMap> CreateMap() {
301  return v8::Map::New(GetIsolate());
302 }
303 
304 void SetMapValue(Handle<JsMap> map, Handle<JsValue> key,
305  Handle<JsValue> value) {
306  LocalVar<v8::Context> ctx = GetIsolate()->GetCurrentContext();
307  CHECK(!map->Set(ctx, key, value).IsEmpty());
308 }
309 
310 
311 bool IsNullOrUndefined(Handle<JsValue> value) {
312  return value.IsEmpty() || value->IsNull() || value->IsUndefined();
313 }
314 
315 bool IsObject(Handle<JsValue> value) {
316  return !value.IsEmpty() && value->IsObject();
317 }
318 
319 bool IsBuiltInObject(Handle<JsObject> object) {
320  // This calls Object.prototype.toString, which will produce something like
321  // '[object Promise]' for built-in types.
322  v8::MaybeLocal<v8::String> to_string =
323  object->ObjectProtoToString(GetIsolate()->GetCurrentContext());
324  if (to_string.IsEmpty())
325  return false;
326  return ConvertToString(to_string.ToLocalChecked()) != "[object Object]";
327 }
328 
329 proto::ValueType GetValueType(Handle<JsValue> value) {
330  if (value.IsEmpty())
332  if (value->IsUndefined())
334  if (value->IsNull())
335  return proto::ValueType::Null;
336  if (value->IsBoolean())
338  if (value->IsNumber())
340  if (value->IsString())
342  if (value->IsFunction())
344  if (value->IsArray())
346  if (value->IsPromise())
348  if (value->IsBooleanObject())
350  if (value->IsNumberObject())
352  if (value->IsStringObject())
354  if (value->IsArrayBuffer())
356  if (value->IsInt8Array())
358  if (value->IsUint8Array())
360  if (value->IsUint8ClampedArray())
362  if (value->IsInt16Array())
364  if (value->IsUint16Array())
366  if (value->IsInt32Array())
368  if (value->IsUint32Array())
370  if (value->IsFloat32Array())
372  if (value->IsFloat64Array())
374  if (value->IsDataView())
376  if (value->IsObject())
378 
379  // The only thing a value can be is a primitive or an object. We should
380  // be checking every primitive above so it should be an object.
381  LOG(WARNING) << "Unknown JavaScript value given=" << ConvertToString(value);
383 }
384 
385 double NumberFromValue(Handle<JsValue> value) {
386  DCHECK(!value.IsEmpty());
387  if (value->IsNumber()) {
388  return value.As<v8::Number>()->Value();
389  }
390  DCHECK(value->IsNumberObject());
391  return value.As<v8::NumberObject>()->ValueOf();
392 }
393 
394 bool BooleanFromValue(Handle<JsValue> value) {
395  DCHECK(!value.IsEmpty());
396  if (value->IsBoolean()) {
397  return value->IsTrue();
398  }
399  DCHECK(value->IsBooleanObject());
400  return value.As<v8::BooleanObject>()->ValueOf();
401 }
402 
403 } // 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
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
ReturnVal< JsValue > ToJsValue(T &&source)
Definition: convert_js.h:381
#define SHAKA_NON_COPYABLE_OR_MOVABLE_TYPE(Type)
Definition: macros.h:51
const char * source
Definition: media_utils.cc:30
std::string to_string(VideoReadyState state)
Definition: media_player.cc:32
bool RunScript(const std::string &path)
Definition: js_wrappers.cc:239
static constexpr const size_t kInternalFieldCount
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
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
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
v8::Isolate * GetIsolate()
Definition: v8_utils.cc:27
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