xref: /third_party/node/src/node_blob.cc (revision 1cb0ef41)
1#include "node_blob.h"
2#include "async_wrap-inl.h"
3#include "base_object-inl.h"
4#include "env-inl.h"
5#include "memory_tracker-inl.h"
6#include "node_errors.h"
7#include "node_external_reference.h"
8#include "threadpoolwork-inl.h"
9#include "v8.h"
10
11#include <algorithm>
12
13namespace node {
14
15using v8::Array;
16using v8::ArrayBuffer;
17using v8::ArrayBufferView;
18using v8::BackingStore;
19using v8::Context;
20using v8::EscapableHandleScope;
21using v8::Function;
22using v8::FunctionCallbackInfo;
23using v8::FunctionTemplate;
24using v8::HandleScope;
25using v8::Isolate;
26using v8::Local;
27using v8::MaybeLocal;
28using v8::Number;
29using v8::Object;
30using v8::String;
31using v8::Uint32;
32using v8::Undefined;
33using v8::Value;
34
35void Blob::Initialize(
36    Local<Object> target,
37    Local<Value> unused,
38    Local<Context> context,
39    void* priv) {
40  Realm* realm = Realm::GetCurrent(context);
41
42  BlobBindingData* const binding_data =
43      realm->AddBindingData<BlobBindingData>(context, target);
44  if (binding_data == nullptr) return;
45
46  SetMethod(context, target, "createBlob", New);
47  SetMethod(context, target, "storeDataObject", StoreDataObject);
48  SetMethod(context, target, "getDataObject", GetDataObject);
49  SetMethod(context, target, "revokeDataObject", RevokeDataObject);
50  FixedSizeBlobCopyJob::Initialize(realm->env(), target);
51}
52
53Local<FunctionTemplate> Blob::GetConstructorTemplate(Environment* env) {
54  Local<FunctionTemplate> tmpl = env->blob_constructor_template();
55  if (tmpl.IsEmpty()) {
56    Isolate* isolate = env->isolate();
57    tmpl = NewFunctionTemplate(isolate, nullptr);
58    tmpl->InstanceTemplate()->SetInternalFieldCount(
59        BaseObject::kInternalFieldCount);
60    tmpl->Inherit(BaseObject::GetConstructorTemplate(env));
61    tmpl->SetClassName(
62        FIXED_ONE_BYTE_STRING(env->isolate(), "Blob"));
63    SetProtoMethod(isolate, tmpl, "toArrayBuffer", ToArrayBuffer);
64    SetProtoMethod(isolate, tmpl, "slice", ToSlice);
65    env->set_blob_constructor_template(tmpl);
66  }
67  return tmpl;
68}
69
70bool Blob::HasInstance(Environment* env, v8::Local<v8::Value> object) {
71  return GetConstructorTemplate(env)->HasInstance(object);
72}
73
74BaseObjectPtr<Blob> Blob::Create(Environment* env,
75                                 const std::vector<BlobEntry>& store,
76                                 size_t length) {
77  HandleScope scope(env->isolate());
78
79  Local<Function> ctor;
80  if (!GetConstructorTemplate(env)->GetFunction(env->context()).ToLocal(&ctor))
81    return BaseObjectPtr<Blob>();
82
83  Local<Object> obj;
84  if (!ctor->NewInstance(env->context()).ToLocal(&obj))
85    return BaseObjectPtr<Blob>();
86
87  return MakeBaseObject<Blob>(env, obj, store, length);
88}
89
90void Blob::New(const FunctionCallbackInfo<Value>& args) {
91  Environment* env = Environment::GetCurrent(args);
92  CHECK(args[0]->IsArray());  // sources
93  CHECK(args[1]->IsUint32());  // length
94
95  std::vector<BlobEntry> entries;
96
97  size_t length = args[1].As<Uint32>()->Value();
98  size_t len = 0;
99  Local<Array> ary = args[0].As<Array>();
100  for (size_t n = 0; n < ary->Length(); n++) {
101    Local<Value> entry;
102    if (!ary->Get(env->context(), n).ToLocal(&entry))
103      return;
104    CHECK(entry->IsArrayBufferView() || Blob::HasInstance(env, entry));
105    if (entry->IsArrayBufferView()) {
106      Local<ArrayBufferView> view = entry.As<ArrayBufferView>();
107      CHECK_EQ(view->ByteOffset(), 0);
108      std::shared_ptr<BackingStore> store = view->Buffer()->GetBackingStore();
109      size_t byte_length = view->ByteLength();
110      view->Buffer()->Detach();  // The Blob will own the backing store now.
111      entries.emplace_back(BlobEntry{std::move(store), byte_length, 0});
112      len += byte_length;
113    } else {
114      Blob* blob;
115      ASSIGN_OR_RETURN_UNWRAP(&blob, entry);
116      auto source = blob->entries();
117      entries.insert(entries.end(), source.begin(), source.end());
118      len += blob->length();
119    }
120  }
121  CHECK_EQ(length, len);
122
123  BaseObjectPtr<Blob> blob = Create(env, entries, length);
124  if (blob)
125    args.GetReturnValue().Set(blob->object());
126}
127
128void Blob::ToArrayBuffer(const FunctionCallbackInfo<Value>& args) {
129  Environment* env = Environment::GetCurrent(args);
130  Blob* blob;
131  ASSIGN_OR_RETURN_UNWRAP(&blob, args.Holder());
132  Local<Value> ret;
133  if (blob->GetArrayBuffer(env).ToLocal(&ret))
134    args.GetReturnValue().Set(ret);
135}
136
137void Blob::ToSlice(const FunctionCallbackInfo<Value>& args) {
138  Environment* env = Environment::GetCurrent(args);
139  Blob* blob;
140  ASSIGN_OR_RETURN_UNWRAP(&blob, args.Holder());
141  CHECK(args[0]->IsUint32());
142  CHECK(args[1]->IsUint32());
143  size_t start = args[0].As<Uint32>()->Value();
144  size_t end = args[1].As<Uint32>()->Value();
145  BaseObjectPtr<Blob> slice = blob->Slice(env, start, end);
146  if (slice)
147    args.GetReturnValue().Set(slice->object());
148}
149
150void Blob::MemoryInfo(MemoryTracker* tracker) const {
151  tracker->TrackFieldWithSize("store", length_);
152}
153
154MaybeLocal<Value> Blob::GetArrayBuffer(Environment* env) {
155  EscapableHandleScope scope(env->isolate());
156  size_t len = length();
157  std::shared_ptr<BackingStore> store =
158      ArrayBuffer::NewBackingStore(env->isolate(), len);
159  if (len > 0) {
160    unsigned char* dest = static_cast<unsigned char*>(store->Data());
161    size_t total = 0;
162    for (const auto& entry : entries()) {
163      unsigned char* src = static_cast<unsigned char*>(entry.store->Data());
164      src += entry.offset;
165      memcpy(dest, src, entry.length);
166      dest += entry.length;
167      total += entry.length;
168      CHECK_LE(total, len);
169    }
170  }
171
172  return scope.Escape(ArrayBuffer::New(env->isolate(), store));
173}
174
175BaseObjectPtr<Blob> Blob::Slice(Environment* env, size_t start, size_t end) {
176  CHECK_LE(start, length());
177  CHECK_LE(end, length());
178  CHECK_LE(start, end);
179
180  std::vector<BlobEntry> slices;
181  size_t total = end - start;
182  size_t remaining = total;
183
184  if (total == 0) return Create(env, slices, 0);
185
186  for (const auto& entry : entries()) {
187    if (start + entry.offset > entry.store->ByteLength()) {
188      start -= entry.length;
189      continue;
190    }
191
192    size_t offset = entry.offset + start;
193    size_t len = std::min(remaining, entry.store->ByteLength() - offset);
194    slices.emplace_back(BlobEntry{entry.store, len, offset});
195
196    remaining -= len;
197    start = 0;
198
199    if (remaining == 0)
200      break;
201  }
202
203  return Create(env, slices, total);
204}
205
206Blob::Blob(
207    Environment* env,
208    v8::Local<v8::Object> obj,
209    const std::vector<BlobEntry>& store,
210    size_t length)
211    : BaseObject(env, obj),
212      store_(store),
213      length_(length) {
214  MakeWeak();
215}
216
217BaseObjectPtr<BaseObject>
218Blob::BlobTransferData::Deserialize(
219    Environment* env,
220    Local<Context> context,
221    std::unique_ptr<worker::TransferData> self) {
222  if (context != env->context()) {
223    THROW_ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE(env);
224    return {};
225  }
226  return Blob::Create(env, store_, length_);
227}
228
229BaseObject::TransferMode Blob::GetTransferMode() const {
230  return BaseObject::TransferMode::kCloneable;
231}
232
233std::unique_ptr<worker::TransferData> Blob::CloneForMessaging() const {
234  return std::make_unique<BlobTransferData>(store_, length_);
235}
236
237void Blob::StoreDataObject(const v8::FunctionCallbackInfo<v8::Value>& args) {
238  Environment* env = Environment::GetCurrent(args);
239  BlobBindingData* binding_data = Realm::GetBindingData<BlobBindingData>(args);
240
241  CHECK(args[0]->IsString());  // ID key
242  CHECK(Blob::HasInstance(env, args[1]));  // Blob
243  CHECK(args[2]->IsUint32());  // Length
244  CHECK(args[3]->IsString());  // Type
245
246  Utf8Value key(env->isolate(), args[0]);
247  Blob* blob;
248  ASSIGN_OR_RETURN_UNWRAP(&blob, args[1]);
249
250  size_t length = args[2].As<Uint32>()->Value();
251  Utf8Value type(env->isolate(), args[3]);
252
253  binding_data->store_data_object(
254      std::string(*key, key.length()),
255      BlobBindingData::StoredDataObject(
256        BaseObjectPtr<Blob>(blob),
257        length,
258        std::string(*type, type.length())));
259}
260
261void Blob::RevokeDataObject(const v8::FunctionCallbackInfo<v8::Value>& args) {
262  BlobBindingData* binding_data = Realm::GetBindingData<BlobBindingData>(args);
263
264  Environment* env = Environment::GetCurrent(args);
265  CHECK(args[0]->IsString());  // ID key
266
267  Utf8Value key(env->isolate(), args[0]);
268
269  binding_data->revoke_data_object(std::string(*key, key.length()));
270}
271
272void Blob::GetDataObject(const v8::FunctionCallbackInfo<v8::Value>& args) {
273  BlobBindingData* binding_data = Realm::GetBindingData<BlobBindingData>(args);
274
275  Environment* env = Environment::GetCurrent(args);
276  CHECK(args[0]->IsString());
277
278  Utf8Value key(env->isolate(), args[0]);
279
280  BlobBindingData::StoredDataObject stored =
281      binding_data->get_data_object(std::string(*key, key.length()));
282  if (stored.blob) {
283    Local<Value> type;
284    if (!String::NewFromUtf8(
285            env->isolate(),
286            stored.type.c_str(),
287            v8::NewStringType::kNormal,
288            static_cast<int>(stored.type.length())).ToLocal(&type)) {
289      return;
290    }
291
292    Local<Value> values[] = {
293      stored.blob->object(),
294      Uint32::NewFromUnsigned(env->isolate(), stored.length),
295      type
296    };
297
298    args.GetReturnValue().Set(
299        Array::New(
300            env->isolate(),
301            values,
302            arraysize(values)));
303  }
304}
305
306FixedSizeBlobCopyJob::FixedSizeBlobCopyJob(Environment* env,
307                                           Local<Object> object,
308                                           Blob* blob,
309                                           FixedSizeBlobCopyJob::Mode mode)
310    : AsyncWrap(env, object, AsyncWrap::PROVIDER_FIXEDSIZEBLOBCOPY),
311      ThreadPoolWork(env, "blob"),
312      mode_(mode) {
313  if (mode == FixedSizeBlobCopyJob::Mode::SYNC) MakeWeak();
314  source_ = blob->entries();
315  length_ = blob->length();
316}
317
318void FixedSizeBlobCopyJob::AfterThreadPoolWork(int status) {
319  Environment* env = AsyncWrap::env();
320  CHECK_EQ(mode_, Mode::ASYNC);
321  CHECK(status == 0 || status == UV_ECANCELED);
322  std::unique_ptr<FixedSizeBlobCopyJob> ptr(this);
323  HandleScope handle_scope(env->isolate());
324  Context::Scope context_scope(env->context());
325  Local<Value> args[2];
326
327  if (status == UV_ECANCELED) {
328    args[0] = Number::New(env->isolate(), status),
329    args[1] = Undefined(env->isolate());
330  } else {
331    args[0] = Undefined(env->isolate());
332    args[1] = ArrayBuffer::New(env->isolate(), destination_);
333  }
334
335  ptr->MakeCallback(env->ondone_string(), arraysize(args), args);
336}
337
338void FixedSizeBlobCopyJob::DoThreadPoolWork() {
339  unsigned char* dest = static_cast<unsigned char*>(destination_->Data());
340  if (length_ > 0) {
341    size_t total = 0;
342    for (const auto& entry : source_) {
343      unsigned char* src = static_cast<unsigned char*>(entry.store->Data());
344      src += entry.offset;
345      memcpy(dest, src, entry.length);
346      dest += entry.length;
347      total += entry.length;
348      CHECK_LE(total, length_);
349    }
350  }
351}
352
353void FixedSizeBlobCopyJob::MemoryInfo(MemoryTracker* tracker) const {
354  tracker->TrackFieldWithSize("source", length_);
355  tracker->TrackFieldWithSize(
356      "destination",
357      destination_ ? destination_->ByteLength() : 0);
358}
359
360void FixedSizeBlobCopyJob::Initialize(Environment* env, Local<Object> target) {
361  Isolate* isolate = env->isolate();
362  v8::Local<v8::FunctionTemplate> job = NewFunctionTemplate(isolate, New);
363  job->Inherit(AsyncWrap::GetConstructorTemplate(env));
364  job->InstanceTemplate()->SetInternalFieldCount(
365      AsyncWrap::kInternalFieldCount);
366  SetProtoMethod(isolate, job, "run", Run);
367  SetConstructorFunction(env->context(), target, "FixedSizeBlobCopyJob", job);
368}
369
370void FixedSizeBlobCopyJob::New(const FunctionCallbackInfo<Value>& args) {
371  static constexpr size_t kMaxSyncLength = 4096;
372  static constexpr size_t kMaxEntryCount = 4;
373
374  Environment* env = Environment::GetCurrent(args);
375  CHECK(args.IsConstructCall());
376  CHECK(args[0]->IsObject());
377  CHECK(Blob::HasInstance(env, args[0]));
378
379  Blob* blob;
380  ASSIGN_OR_RETURN_UNWRAP(&blob, args[0]);
381
382  // This is a fairly arbitrary heuristic. We want to avoid deferring to
383  // the threadpool if the amount of data being copied is small and there
384  // aren't that many entries to copy.
385  FixedSizeBlobCopyJob::Mode mode =
386      (blob->length() < kMaxSyncLength &&
387       blob->entries().size() < kMaxEntryCount) ?
388          FixedSizeBlobCopyJob::Mode::SYNC :
389          FixedSizeBlobCopyJob::Mode::ASYNC;
390
391  new FixedSizeBlobCopyJob(env, args.This(), blob, mode);
392}
393
394void FixedSizeBlobCopyJob::Run(const FunctionCallbackInfo<Value>& args) {
395  Environment* env = Environment::GetCurrent(args);
396  FixedSizeBlobCopyJob* job;
397  ASSIGN_OR_RETURN_UNWRAP(&job, args.Holder());
398  job->destination_ =
399      ArrayBuffer::NewBackingStore(env->isolate(), job->length_);
400  if (job->mode() == FixedSizeBlobCopyJob::Mode::ASYNC)
401    return job->ScheduleWork();
402
403  job->DoThreadPoolWork();
404  args.GetReturnValue().Set(
405      ArrayBuffer::New(env->isolate(), job->destination_));
406}
407
408void FixedSizeBlobCopyJob::RegisterExternalReferences(
409    ExternalReferenceRegistry* registry) {
410  registry->Register(New);
411  registry->Register(Run);
412}
413
414void BlobBindingData::StoredDataObject::MemoryInfo(
415    MemoryTracker* tracker) const {
416  tracker->TrackField("blob", blob);
417  tracker->TrackFieldWithSize("type", type.length());
418}
419
420BlobBindingData::StoredDataObject::StoredDataObject(
421    const BaseObjectPtr<Blob>& blob_,
422    size_t length_,
423    const std::string& type_)
424    : blob(blob_),
425      length(length_),
426      type(type_) {}
427
428BlobBindingData::BlobBindingData(Realm* realm, Local<Object> wrap)
429    : SnapshotableObject(realm, wrap, type_int) {
430  MakeWeak();
431}
432
433void BlobBindingData::MemoryInfo(MemoryTracker* tracker) const {
434  tracker->TrackField("data_objects", data_objects_);
435}
436
437void BlobBindingData::store_data_object(
438    const std::string& uuid,
439    const BlobBindingData::StoredDataObject& object) {
440  data_objects_[uuid] = object;
441}
442
443void BlobBindingData::revoke_data_object(const std::string& uuid) {
444  if (data_objects_.find(uuid) == data_objects_.end()) {
445    return;
446  }
447  data_objects_.erase(uuid);
448  CHECK_EQ(data_objects_.find(uuid), data_objects_.end());
449}
450
451BlobBindingData::StoredDataObject BlobBindingData::get_data_object(
452    const std::string& uuid) {
453  auto entry = data_objects_.find(uuid);
454  if (entry == data_objects_.end())
455    return BlobBindingData::StoredDataObject {};
456  return entry->second;
457}
458
459void BlobBindingData::Deserialize(Local<Context> context,
460                                  Local<Object> holder,
461                                  int index,
462                                  InternalFieldInfoBase* info) {
463  DCHECK_EQ(index, BaseObject::kEmbedderType);
464  HandleScope scope(context->GetIsolate());
465  Realm* realm = Realm::GetCurrent(context);
466  BlobBindingData* binding =
467      realm->AddBindingData<BlobBindingData>(context, holder);
468  CHECK_NOT_NULL(binding);
469}
470
471bool BlobBindingData::PrepareForSerialization(Local<Context> context,
472                                              v8::SnapshotCreator* creator) {
473  // Stored blob objects are not actually persisted.
474  // Return true because we need to maintain the reference to the binding from
475  // JS land.
476  return true;
477}
478
479InternalFieldInfoBase* BlobBindingData::Serialize(int index) {
480  DCHECK_EQ(index, BaseObject::kEmbedderType);
481  InternalFieldInfo* info =
482      InternalFieldInfoBase::New<InternalFieldInfo>(type());
483  return info;
484}
485
486void Blob::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
487  registry->Register(Blob::New);
488  registry->Register(Blob::ToArrayBuffer);
489  registry->Register(Blob::ToSlice);
490  registry->Register(Blob::StoreDataObject);
491  registry->Register(Blob::GetDataObject);
492  registry->Register(Blob::RevokeDataObject);
493
494  FixedSizeBlobCopyJob::RegisterExternalReferences(registry);
495}
496
497}  // namespace node
498
499NODE_BINDING_CONTEXT_AWARE_INTERNAL(blob, node::Blob::Initialize)
500NODE_BINDING_EXTERNAL_REFERENCE(blob, node::Blob::RegisterExternalReferences)
501