Bug 1886946: Remove incorrect assertion that buffer is not-pinned. r=sfink
[gecko.git] / dom / indexedDB / IndexedDatabase.cpp
bloba73d9fbf5a61d0ec7060dde848345de467b0984e
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/IndexedDatabase.h"
8 #include "IndexedDatabaseInlines.h"
10 #include "IDBDatabase.h"
12 #include "mozilla/dom/FileBlobImpl.h"
13 #include "mozilla/dom/StructuredCloneTags.h"
14 #include "mozilla/dom/URLSearchParams.h"
15 #include "mozilla/dom/WorkerScope.h"
16 #include "MainThreadUtils.h"
17 #include "jsapi.h"
18 #include "nsIFile.h"
19 #include "nsIGlobalObject.h"
20 #include "nsQueryObject.h"
21 #include "nsString.h"
23 namespace mozilla::dom::indexedDB {
24 namespace {
25 struct MOZ_STACK_CLASS MutableFileData final {
26 nsString type;
27 nsString name;
29 MOZ_COUNTED_DEFAULT_CTOR(MutableFileData)
31 MOZ_COUNTED_DTOR(MutableFileData)
34 struct MOZ_STACK_CLASS BlobOrFileData final {
35 uint32_t tag = 0;
36 uint64_t size = 0;
37 nsString type;
38 nsString name;
39 int64_t lastModifiedDate = INT64_MAX;
41 MOZ_COUNTED_DEFAULT_CTOR(BlobOrFileData)
43 MOZ_COUNTED_DTOR(BlobOrFileData)
46 struct MOZ_STACK_CLASS WasmModuleData final {
47 uint32_t bytecodeIndex;
48 uint32_t compiledIndex;
49 uint32_t flags;
51 explicit WasmModuleData(uint32_t aFlags)
52 : bytecodeIndex(0), compiledIndex(0), flags(aFlags) {
53 MOZ_COUNT_CTOR(WasmModuleData);
56 MOZ_COUNTED_DTOR(WasmModuleData)
59 bool StructuredCloneReadString(JSStructuredCloneReader* aReader,
60 nsCString& aString) {
61 uint32_t length;
62 if (!JS_ReadBytes(aReader, &length, sizeof(uint32_t))) {
63 NS_WARNING("Failed to read length!");
64 return false;
66 length = NativeEndian::swapFromLittleEndian(length);
68 if (!aString.SetLength(length, fallible)) {
69 NS_WARNING("Out of memory?");
70 return false;
72 char* const buffer = aString.BeginWriting();
74 if (!JS_ReadBytes(aReader, buffer, length)) {
75 NS_WARNING("Failed to read type!");
76 return false;
79 return true;
82 bool ReadFileHandle(JSStructuredCloneReader* aReader,
83 MutableFileData* aRetval) {
84 static_assert(SCTAG_DOM_MUTABLEFILE == 0xFFFF8004, "Update me!");
85 MOZ_ASSERT(aReader && aRetval);
87 nsCString type;
88 if (!StructuredCloneReadString(aReader, type)) {
89 return false;
91 CopyUTF8toUTF16(type, aRetval->type);
93 nsCString name;
94 if (!StructuredCloneReadString(aReader, name)) {
95 return false;
97 CopyUTF8toUTF16(name, aRetval->name);
99 return true;
102 bool ReadBlobOrFile(JSStructuredCloneReader* aReader, uint32_t aTag,
103 BlobOrFileData* aRetval) {
104 static_assert(SCTAG_DOM_BLOB == 0xffff8001 &&
105 SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE == 0xffff8002 &&
106 SCTAG_DOM_FILE == 0xffff8005,
107 "Update me!");
109 MOZ_ASSERT(aReader);
110 MOZ_ASSERT(aTag == SCTAG_DOM_FILE ||
111 aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE ||
112 aTag == SCTAG_DOM_BLOB);
113 MOZ_ASSERT(aRetval);
115 aRetval->tag = aTag;
117 uint64_t size;
118 if (NS_WARN_IF(!JS_ReadBytes(aReader, &size, sizeof(uint64_t)))) {
119 return false;
122 aRetval->size = NativeEndian::swapFromLittleEndian(size);
124 nsCString type;
125 if (NS_WARN_IF(!StructuredCloneReadString(aReader, type))) {
126 return false;
129 CopyUTF8toUTF16(type, aRetval->type);
131 // Blobs are done.
132 if (aTag == SCTAG_DOM_BLOB) {
133 return true;
136 MOZ_ASSERT(aTag == SCTAG_DOM_FILE ||
137 aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE);
139 int64_t lastModifiedDate;
140 if (aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE) {
141 lastModifiedDate = INT64_MAX;
142 } else {
143 if (NS_WARN_IF(!JS_ReadBytes(aReader, &lastModifiedDate,
144 sizeof(lastModifiedDate)))) {
145 return false;
147 lastModifiedDate = NativeEndian::swapFromLittleEndian(lastModifiedDate);
150 aRetval->lastModifiedDate = lastModifiedDate;
152 nsCString name;
153 if (NS_WARN_IF(!StructuredCloneReadString(aReader, name))) {
154 return false;
157 CopyUTF8toUTF16(name, aRetval->name);
159 return true;
162 bool ReadWasmModule(JSStructuredCloneReader* aReader, WasmModuleData* aRetval) {
163 static_assert(SCTAG_DOM_WASM_MODULE == 0xFFFF8006, "Update me!");
164 MOZ_ASSERT(aReader && aRetval);
166 uint32_t bytecodeIndex;
167 uint32_t compiledIndex;
168 if (NS_WARN_IF(!JS_ReadUint32Pair(aReader, &bytecodeIndex, &compiledIndex))) {
169 return false;
172 aRetval->bytecodeIndex = bytecodeIndex;
173 aRetval->compiledIndex = compiledIndex;
175 return true;
178 template <typename StructuredCloneFile>
179 class ValueDeserializationHelper;
181 class ValueDeserializationHelperBase {
182 public:
183 static bool CreateAndWrapWasmModule(JSContext* aCx,
184 const StructuredCloneFileBase& aFile,
185 const WasmModuleData& aData,
186 JS::MutableHandle<JSObject*> aResult) {
187 MOZ_ASSERT(aCx);
188 MOZ_ASSERT(aFile.Type() == StructuredCloneFileBase::eWasmBytecode);
190 // Both on the parent and child side, just create a plain object here,
191 // support for de-serialization of WebAssembly.Modules has been removed in
192 // bug 1561876. Full removal is tracked in bug 1487479.
194 JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
195 if (NS_WARN_IF(!obj)) {
196 return false;
199 aResult.set(obj);
200 return true;
203 template <typename StructuredCloneFile>
204 static bool CreateAndWrapBlobOrFile(JSContext* aCx, IDBDatabase* aDatabase,
205 const StructuredCloneFile& aFile,
206 const BlobOrFileData& aData,
207 JS::MutableHandle<JSObject*> aResult) {
208 MOZ_ASSERT(aCx);
209 MOZ_ASSERT(aData.tag == SCTAG_DOM_FILE ||
210 aData.tag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE ||
211 aData.tag == SCTAG_DOM_BLOB);
212 MOZ_ASSERT(aFile.Type() == StructuredCloneFileBase::eBlob);
214 const auto blob = ValueDeserializationHelper<StructuredCloneFile>::GetBlob(
215 aCx, aDatabase, aFile);
216 if (NS_WARN_IF(!blob)) {
217 return false;
220 if (aData.tag == SCTAG_DOM_BLOB) {
221 blob->Impl()->SetLazyData(VoidString(), aData.type, aData.size,
222 INT64_MAX);
223 MOZ_ASSERT(!blob->IsFile());
225 // XXX The comment below is somewhat confusing, since it seems to imply
226 // that this branch is only executed when called from ActorsParent, but
227 // it's executed from both the parent and the child side code.
229 // ActorsParent sends here a kind of half blob and half file wrapped into
230 // a DOM File object. DOM File and DOM Blob are a WebIDL wrapper around a
231 // BlobImpl object. SetLazyData() has just changed the BlobImpl to be a
232 // Blob (see the previous assert), but 'blob' still has the WebIDL DOM
233 // File wrapping.
234 // Before exposing it to content, we must recreate a DOM Blob object.
236 const RefPtr<Blob> exposedBlob =
237 Blob::Create(blob->GetParentObject(), blob->Impl());
238 if (NS_WARN_IF(!exposedBlob)) {
239 return false;
242 return WrapAsJSObject(aCx, exposedBlob, aResult);
245 blob->Impl()->SetLazyData(aData.name, aData.type, aData.size,
246 aData.lastModifiedDate * PR_USEC_PER_MSEC);
248 MOZ_ASSERT(blob->IsFile());
249 const RefPtr<File> file = blob->ToFile();
250 MOZ_ASSERT(file);
252 return WrapAsJSObject(aCx, file, aResult);
256 template <>
257 class ValueDeserializationHelper<StructuredCloneFileParent>
258 : public ValueDeserializationHelperBase {
259 public:
260 static bool CreateAndWrapMutableFile(JSContext* aCx,
261 StructuredCloneFileParent& aFile,
262 const MutableFileData& aData,
263 JS::MutableHandle<JSObject*> aResult) {
264 MOZ_ASSERT(aCx);
265 MOZ_ASSERT(aFile.Type() == StructuredCloneFileBase::eBlob);
267 // We are in an IDB SQLite schema upgrade where we don't care about a real
268 // 'MutableFile', but we just care of having a proper |mType| flag.
270 aFile.MutateType(StructuredCloneFileBase::eMutableFile);
272 // Just make a dummy object.
273 JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
275 if (NS_WARN_IF(!obj)) {
276 return false;
279 aResult.set(obj);
280 return true;
283 static RefPtr<Blob> GetBlob(JSContext* aCx, IDBDatabase* aDatabase,
284 const StructuredCloneFileParent& aFile) {
285 // This is chrome code, so there is no parent, but still we want to set a
286 // correct parent for the new File object.
287 const auto global = [aDatabase, aCx]() -> nsCOMPtr<nsIGlobalObject> {
288 if (NS_IsMainThread()) {
289 if (aDatabase && aDatabase->GetParentObject()) {
290 return aDatabase->GetParentObject();
292 return xpc::CurrentNativeGlobal(aCx);
294 const WorkerPrivate* const workerPrivate =
295 GetCurrentThreadWorkerPrivate();
296 MOZ_ASSERT(workerPrivate);
298 WorkerGlobalScope* const globalScope = workerPrivate->GlobalScope();
299 MOZ_ASSERT(globalScope);
301 return do_QueryObject(globalScope);
302 }();
304 MOZ_ASSERT(global);
306 // We do not have an mBlob but do have a DatabaseFileInfo.
308 // If we are creating an index, we do need a real-looking Blob/File instance
309 // because the index's key path can reference their properties. Rather than
310 // create a fake-looking object, create a real Blob.
312 // If we are in a schema upgrade, we don't strictly need that, but we do not
313 // need to optimize for that, and create it anyway.
314 const nsCOMPtr<nsIFile> file = aFile.FileInfo().GetFileForFileInfo();
315 if (!file) {
316 return nullptr;
319 const auto impl = MakeRefPtr<FileBlobImpl>(file);
320 impl->SetFileId(aFile.FileInfo().Id());
321 return File::Create(global, impl);
325 template <>
326 class ValueDeserializationHelper<StructuredCloneFileChild>
327 : public ValueDeserializationHelperBase {
328 public:
329 static bool CreateAndWrapMutableFile(JSContext* aCx,
330 StructuredCloneFileChild& aFile,
331 const MutableFileData& aData,
332 JS::MutableHandle<JSObject*> aResult) {
333 MOZ_ASSERT(aCx);
334 MOZ_ASSERT(aFile.Type() == StructuredCloneFileBase::eMutableFile);
336 return false;
339 static RefPtr<Blob> GetBlob(JSContext* aCx, IDBDatabase* aDatabase,
340 const StructuredCloneFileChild& aFile) {
341 if (aFile.HasBlob()) {
342 return aFile.BlobPtr();
345 MOZ_CRASH("Expected a StructuredCloneFile with a Blob");
349 } // namespace
351 template <typename StructuredCloneReadInfo>
352 JSObject* CommonStructuredCloneReadCallback(
353 JSContext* aCx, JSStructuredCloneReader* aReader,
354 const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag, uint32_t aData,
355 StructuredCloneReadInfo* aCloneReadInfo, IDBDatabase* aDatabase) {
356 // We need to statically assert that our tag values are what we expect
357 // so that if people accidentally change them they notice.
358 static_assert(SCTAG_DOM_BLOB == 0xffff8001 &&
359 SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE == 0xffff8002 &&
360 SCTAG_DOM_MUTABLEFILE == 0xffff8004 &&
361 SCTAG_DOM_FILE == 0xffff8005 &&
362 SCTAG_DOM_WASM_MODULE == 0xffff8006 &&
363 SCTAG_DOM_URLSEARCHPARAMS == 0xffff8014,
364 "You changed our structured clone tag values and just ate "
365 "everyone's IndexedDB data. I hope you are happy.");
367 if (aTag == SCTAG_DOM_URLSEARCHPARAMS) {
368 // Protect the result from a moving GC in ~RefPtr.
369 JS::Rooted<JSObject*> result(aCx);
372 // Scope for the RefPtr below.
374 nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
375 if (!global) {
376 return nullptr;
379 RefPtr<URLSearchParams> params = new URLSearchParams(global);
381 uint32_t paramCount;
382 uint32_t zero;
383 if (!JS_ReadUint32Pair(aReader, &paramCount, &zero)) {
384 return nullptr;
387 nsAutoString key;
388 nsAutoString value;
389 for (uint32_t index = 0; index < paramCount; index++) {
390 if (!StructuredCloneHolder::ReadString(aReader, key) ||
391 !StructuredCloneHolder::ReadString(aReader, value)) {
392 return nullptr;
394 params->Append(key, value);
397 if (!WrapAsJSObject(aCx, params, &result)) {
398 return nullptr;
402 return result;
405 using StructuredCloneFile =
406 typename StructuredCloneReadInfo::StructuredCloneFile;
408 if (aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE ||
409 aTag == SCTAG_DOM_BLOB || aTag == SCTAG_DOM_FILE ||
410 aTag == SCTAG_DOM_MUTABLEFILE || aTag == SCTAG_DOM_WASM_MODULE) {
411 JS::Rooted<JSObject*> result(aCx);
413 if (aTag == SCTAG_DOM_WASM_MODULE) {
414 WasmModuleData data(aData);
415 if (NS_WARN_IF(!ReadWasmModule(aReader, &data))) {
416 return nullptr;
419 MOZ_ASSERT(data.compiledIndex == data.bytecodeIndex + 1);
420 MOZ_ASSERT(!data.flags);
422 const auto& files = aCloneReadInfo->Files();
423 if (data.bytecodeIndex >= files.Length() ||
424 data.compiledIndex >= files.Length()) {
425 MOZ_ASSERT(false, "Bad index value!");
426 return nullptr;
429 const auto& file = files[data.bytecodeIndex];
431 if (NS_WARN_IF(!ValueDeserializationHelper<StructuredCloneFile>::
432 CreateAndWrapWasmModule(aCx, file, data, &result))) {
433 return nullptr;
436 return result;
439 if (aData >= aCloneReadInfo->Files().Length()) {
440 MOZ_ASSERT(false, "Bad index value!");
441 return nullptr;
444 auto& file = aCloneReadInfo->MutableFile(aData);
446 if (aTag == SCTAG_DOM_MUTABLEFILE) {
447 MutableFileData data;
448 if (NS_WARN_IF(!ReadFileHandle(aReader, &data))) {
449 return nullptr;
452 if (NS_WARN_IF(!ValueDeserializationHelper<StructuredCloneFile>::
453 CreateAndWrapMutableFile(aCx, file, data, &result))) {
454 return nullptr;
457 return result;
460 BlobOrFileData data;
461 if (NS_WARN_IF(!ReadBlobOrFile(aReader, aTag, &data))) {
462 return nullptr;
465 if (NS_WARN_IF(!ValueDeserializationHelper<
466 StructuredCloneFile>::CreateAndWrapBlobOrFile(aCx, aDatabase,
467 file, data,
468 &result))) {
469 return nullptr;
472 return result;
475 return StructuredCloneHolder::ReadFullySerializableObjects(aCx, aReader, aTag,
476 true);
479 template JSObject* CommonStructuredCloneReadCallback(
480 JSContext* aCx, JSStructuredCloneReader* aReader,
481 const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag, uint32_t aData,
482 StructuredCloneReadInfoChild* aCloneReadInfo, IDBDatabase* aDatabase);
484 template JSObject* CommonStructuredCloneReadCallback(
485 JSContext* aCx, JSStructuredCloneReader* aReader,
486 const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag, uint32_t aData,
487 StructuredCloneReadInfoParent* aCloneReadInfo, IDBDatabase* aDatabase);
488 } // namespace mozilla::dom::indexedDB