1 /*
2 * Copyright (C) 2010 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #include "third_party/blink/renderer/modules/indexeddb/idb_object_store.h"
27
28 #include <memory>
29
30 #include "base/feature_list.h"
31 #include "base/memory/ptr_util.h"
32 #include "base/memory/scoped_refptr.h"
33 #include "base/metrics/histogram_macros.h"
34 #include "base/numerics/safe_conversions.h"
35 #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-blink-forward.h"
36 #include "third_party/blink/public/platform/web_blob_info.h"
37 #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value_factory.h"
38 #include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
39 #include "third_party/blink/renderer/bindings/modules/v8/to_v8_for_modules.h"
40 #include "third_party/blink/renderer/bindings/modules/v8/v8_binding_for_modules.h"
41 #include "third_party/blink/renderer/core/dom/dom_string_list.h"
42 #include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
43 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
44 #include "third_party/blink/renderer/modules/indexeddb/idb_any.h"
45 #include "third_party/blink/renderer/modules/indexeddb/idb_cursor_with_value.h"
46 #include "third_party/blink/renderer/modules/indexeddb/idb_database.h"
47 #include "third_party/blink/renderer/modules/indexeddb/idb_key.h"
48 #include "third_party/blink/renderer/modules/indexeddb/idb_key_path.h"
49 #include "third_party/blink/renderer/modules/indexeddb/idb_tracing.h"
50 #include "third_party/blink/renderer/modules/indexeddb/idb_value_wrapping.h"
51 #include "third_party/blink/renderer/modules/indexeddb/web_idb_callbacks_impl.h"
52 #include "third_party/blink/renderer/modules/indexeddb/web_idb_database.h"
53 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
54 #include "third_party/blink/renderer/platform/bindings/script_state.h"
55 #include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
56 #include "v8/include/v8.h"
57
58 namespace blink {
59
IDBObjectStore(scoped_refptr<IDBObjectStoreMetadata> metadata,IDBTransaction * transaction)60 IDBObjectStore::IDBObjectStore(scoped_refptr<IDBObjectStoreMetadata> metadata,
61 IDBTransaction* transaction)
62 : metadata_(std::move(metadata)), transaction_(transaction) {
63 DCHECK(transaction_);
64 DCHECK(metadata_.get());
65 }
66
Trace(Visitor * visitor) const67 void IDBObjectStore::Trace(Visitor* visitor) const {
68 visitor->Trace(transaction_);
69 visitor->Trace(index_map_);
70 ScriptWrappable::Trace(visitor);
71 }
72
setName(const String & name,ExceptionState & exception_state)73 void IDBObjectStore::setName(const String& name,
74 ExceptionState& exception_state) {
75 IDB_TRACE("IDBObjectStore::setName");
76 if (!transaction_->IsVersionChange()) {
77 exception_state.ThrowDOMException(
78 DOMExceptionCode::kInvalidStateError,
79 IDBDatabase::kNotVersionChangeTransactionErrorMessage);
80 return;
81 }
82 if (IsDeleted()) {
83 exception_state.ThrowDOMException(
84 DOMExceptionCode::kInvalidStateError,
85 IDBDatabase::kObjectStoreDeletedErrorMessage);
86 return;
87 }
88 if (!transaction_->IsActive()) {
89 exception_state.ThrowDOMException(
90 DOMExceptionCode::kTransactionInactiveError,
91 transaction_->InactiveErrorMessage());
92 return;
93 }
94
95 if (this->name() == name)
96 return;
97 if (transaction_->db()->ContainsObjectStore(name)) {
98 exception_state.ThrowDOMException(
99 DOMExceptionCode::kConstraintError,
100 IDBDatabase::kObjectStoreNameTakenErrorMessage);
101 return;
102 }
103 if (!BackendDB()) {
104 exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
105 IDBDatabase::kDatabaseClosedErrorMessage);
106 return;
107 }
108
109 transaction_->db()->RenameObjectStore(Id(), name);
110 }
111
keyPath(ScriptState * script_state) const112 ScriptValue IDBObjectStore::keyPath(ScriptState* script_state) const {
113 return ScriptValue::From(script_state, Metadata().key_path);
114 }
115
indexNames() const116 DOMStringList* IDBObjectStore::indexNames() const {
117 IDB_TRACE1("IDBObjectStore::indexNames", "store_name",
118 metadata_->name.Utf8());
119 auto* index_names = MakeGarbageCollected<DOMStringList>();
120 for (const auto& it : Metadata().indexes)
121 index_names->Append(it.value->name);
122 index_names->Sort();
123 return index_names;
124 }
125
get(ScriptState * script_state,const ScriptValue & key,ExceptionState & exception_state)126 IDBRequest* IDBObjectStore::get(ScriptState* script_state,
127 const ScriptValue& key,
128 ExceptionState& exception_state) {
129 IDB_TRACE1("IDBObjectStore::getRequestSetup", "store_name",
130 metadata_->name.Utf8());
131 IDBRequest::AsyncTraceState metrics("IDBObjectStore::get");
132 if (IsDeleted()) {
133 exception_state.ThrowDOMException(
134 DOMExceptionCode::kInvalidStateError,
135 IDBDatabase::kObjectStoreDeletedErrorMessage);
136 return nullptr;
137 }
138 if (!transaction_->IsActive()) {
139 exception_state.ThrowDOMException(
140 DOMExceptionCode::kTransactionInactiveError,
141 transaction_->InactiveErrorMessage());
142 return nullptr;
143 }
144 IDBKeyRange* key_range = IDBKeyRange::FromScriptValue(
145 ExecutionContext::From(script_state), key, exception_state);
146 if (exception_state.HadException())
147 return nullptr;
148 if (!key_range) {
149 exception_state.ThrowDOMException(
150 DOMExceptionCode::kDataError,
151 IDBDatabase::kNoKeyOrKeyRangeErrorMessage);
152 return nullptr;
153 }
154 if (!BackendDB()) {
155 exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
156 IDBDatabase::kDatabaseClosedErrorMessage);
157 return nullptr;
158 }
159
160 IDBRequest* request = IDBRequest::Create(
161 script_state, this, transaction_.Get(), std::move(metrics));
162 BackendDB()->Get(transaction_->Id(), Id(), IDBIndexMetadata::kInvalidId,
163 key_range, /*key_only=*/false,
164 request->CreateWebCallbacks().release());
165 return request;
166 }
167
getKey(ScriptState * script_state,const ScriptValue & key,ExceptionState & exception_state)168 IDBRequest* IDBObjectStore::getKey(ScriptState* script_state,
169 const ScriptValue& key,
170 ExceptionState& exception_state) {
171 IDB_TRACE1("IDBObjectStore::getKeyRequestSetup", "store_name",
172 metadata_->name.Utf8());
173 IDBRequest::AsyncTraceState metrics("IDBObjectStore::getKey");
174 if (IsDeleted()) {
175 exception_state.ThrowDOMException(
176 DOMExceptionCode::kInvalidStateError,
177 IDBDatabase::kObjectStoreDeletedErrorMessage);
178 return nullptr;
179 }
180 if (!transaction_->IsActive()) {
181 exception_state.ThrowDOMException(
182 DOMExceptionCode::kTransactionInactiveError,
183 transaction_->InactiveErrorMessage());
184 return nullptr;
185 }
186 IDBKeyRange* key_range = IDBKeyRange::FromScriptValue(
187 ExecutionContext::From(script_state), key, exception_state);
188 if (exception_state.HadException())
189 return nullptr;
190 if (!key_range) {
191 exception_state.ThrowDOMException(
192 DOMExceptionCode::kDataError,
193 IDBDatabase::kNoKeyOrKeyRangeErrorMessage);
194 return nullptr;
195 }
196 if (!BackendDB()) {
197 exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
198 IDBDatabase::kDatabaseClosedErrorMessage);
199 return nullptr;
200 }
201
202 IDBRequest* request = IDBRequest::Create(
203 script_state, this, transaction_.Get(), std::move(metrics));
204 BackendDB()->Get(transaction_->Id(), Id(), IDBIndexMetadata::kInvalidId,
205 key_range, /*key_only=*/true,
206 request->CreateWebCallbacks().release());
207 return request;
208 }
209
getAll(ScriptState * script_state,const ScriptValue & key_range,ExceptionState & exception_state)210 IDBRequest* IDBObjectStore::getAll(ScriptState* script_state,
211 const ScriptValue& key_range,
212 ExceptionState& exception_state) {
213 return getAll(script_state, key_range, std::numeric_limits<uint32_t>::max(),
214 exception_state);
215 }
216
getAll(ScriptState * script_state,const ScriptValue & key_range,uint32_t max_count,ExceptionState & exception_state)217 IDBRequest* IDBObjectStore::getAll(ScriptState* script_state,
218 const ScriptValue& key_range,
219 uint32_t max_count,
220 ExceptionState& exception_state) {
221 IDB_TRACE1("IDBObjectStore::getAllRequestSetup", "store_name",
222 metadata_->name.Utf8());
223 IDBRequest::AsyncTraceState metrics("IDBObjectStore::getAll");
224 if (!max_count)
225 max_count = std::numeric_limits<uint32_t>::max();
226
227 if (IsDeleted()) {
228 exception_state.ThrowDOMException(
229 DOMExceptionCode::kInvalidStateError,
230 IDBDatabase::kObjectStoreDeletedErrorMessage);
231 return nullptr;
232 }
233 if (!transaction_->IsActive()) {
234 exception_state.ThrowDOMException(
235 DOMExceptionCode::kTransactionInactiveError,
236 transaction_->InactiveErrorMessage());
237 return nullptr;
238 }
239 IDBKeyRange* range = IDBKeyRange::FromScriptValue(
240 ExecutionContext::From(script_state), key_range, exception_state);
241 if (exception_state.HadException())
242 return nullptr;
243 if (!BackendDB()) {
244 exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
245 IDBDatabase::kDatabaseClosedErrorMessage);
246 return nullptr;
247 }
248
249 IDBRequest* request = IDBRequest::Create(
250 script_state, this, transaction_.Get(), std::move(metrics));
251 BackendDB()->GetAll(transaction_->Id(), Id(), IDBIndexMetadata::kInvalidId,
252 range, max_count, false,
253 request->CreateWebCallbacks().release());
254 return request;
255 }
256
getAllKeys(ScriptState * script_state,const ScriptValue & key_range,ExceptionState & exception_state)257 IDBRequest* IDBObjectStore::getAllKeys(ScriptState* script_state,
258 const ScriptValue& key_range,
259 ExceptionState& exception_state) {
260 return getAllKeys(script_state, key_range,
261 std::numeric_limits<uint32_t>::max(), exception_state);
262 }
263
getAllKeys(ScriptState * script_state,const ScriptValue & key_range,uint32_t max_count,ExceptionState & exception_state)264 IDBRequest* IDBObjectStore::getAllKeys(ScriptState* script_state,
265 const ScriptValue& key_range,
266 uint32_t max_count,
267 ExceptionState& exception_state) {
268 IDB_TRACE1("IDBObjectStore::getAllKeysRequestSetup", "store_name",
269 metadata_->name.Utf8());
270 IDBRequest::AsyncTraceState metrics("IDBObjectStore::getAllKeys");
271 if (!max_count)
272 max_count = std::numeric_limits<uint32_t>::max();
273
274 if (IsDeleted()) {
275 exception_state.ThrowDOMException(
276 DOMExceptionCode::kInvalidStateError,
277 IDBDatabase::kObjectStoreDeletedErrorMessage);
278 return nullptr;
279 }
280 if (!transaction_->IsActive()) {
281 exception_state.ThrowDOMException(
282 DOMExceptionCode::kTransactionInactiveError,
283 transaction_->InactiveErrorMessage());
284 return nullptr;
285 }
286 IDBKeyRange* range = IDBKeyRange::FromScriptValue(
287 ExecutionContext::From(script_state), key_range, exception_state);
288 if (exception_state.HadException())
289 return nullptr;
290 if (!BackendDB()) {
291 exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
292 IDBDatabase::kDatabaseClosedErrorMessage);
293 return nullptr;
294 }
295
296 IDBRequest* request = IDBRequest::Create(
297 script_state, this, transaction_.Get(), std::move(metrics));
298 BackendDB()->GetAll(transaction_->Id(), Id(), IDBIndexMetadata::kInvalidId,
299 range, max_count, true,
300 request->CreateWebCallbacks().release());
301 return request;
302 }
303
GenerateIndexKeysForValue(v8::Isolate * isolate,const IDBObjectStoreMetadata & store_metadata,const IDBIndexMetadata & index_metadata,const ScriptValue & object_value)304 static Vector<std::unique_ptr<IDBKey>> GenerateIndexKeysForValue(
305 v8::Isolate* isolate,
306 const IDBObjectStoreMetadata& store_metadata,
307 const IDBIndexMetadata& index_metadata,
308 const ScriptValue& object_value) {
309 NonThrowableExceptionState exception_state;
310
311 // Look up the key using the index's key path.
312 std::unique_ptr<IDBKey> index_key = ScriptValue::To<std::unique_ptr<IDBKey>>(
313 isolate, object_value, exception_state, store_metadata.key_path,
314 index_metadata.key_path);
315
316 // No match. (In the special case for a store with a key generator and in-line
317 // keys and where the store and index key paths match, the back-end will
318 // synthesize an index key.)
319 if (!index_key)
320 return Vector<std::unique_ptr<IDBKey>>();
321
322 // Special case for multi-entry indexes, per spec: if an index's multiEntry
323 // flag is true the computed index key is an array, then an index entry is
324 // created for each subkey, with duplicate and invalid subkeys removed.
325 // https://w3c.github.io/IndexedDB/#store-a-record-into-an-object-store
326 // https://w3c.github.io/IndexedDB/#convert-a-value-to-a-multientry-key
327 if (index_metadata.multi_entry &&
328 index_key->GetType() == mojom::IDBKeyType::Array) {
329 return IDBKey::ToMultiEntryArray(std::move(index_key));
330 }
331
332 // Otherwise, invalid index keys are simply ignored.
333 if (!index_key->IsValid())
334 return Vector<std::unique_ptr<IDBKey>>();
335
336 // And a single key is added for the record in the index.
337 Vector<std::unique_ptr<IDBKey>> index_keys;
338 index_keys.ReserveInitialCapacity(1);
339 index_keys.emplace_back(std::move(index_key));
340 return index_keys;
341 }
342
add(ScriptState * script_state,const ScriptValue & value,ExceptionState & exception_state)343 IDBRequest* IDBObjectStore::add(ScriptState* script_state,
344 const ScriptValue& value,
345 ExceptionState& exception_state) {
346 v8::Isolate* isolate = script_state->GetIsolate();
347 return add(script_state, value, ScriptValue(isolate, v8::Undefined(isolate)),
348 exception_state);
349 }
350
add(ScriptState * script_state,const ScriptValue & value,const ScriptValue & key,ExceptionState & exception_state)351 IDBRequest* IDBObjectStore::add(ScriptState* script_state,
352 const ScriptValue& value,
353 const ScriptValue& key,
354 ExceptionState& exception_state) {
355 IDB_TRACE1("IDBObjectStore::addRequestSetup", "store_name",
356 metadata_->name.Utf8());
357 return DoPut(script_state, mojom::IDBPutMode::AddOnly, value, key,
358 exception_state);
359 }
360
put(ScriptState * script_state,const ScriptValue & value,ExceptionState & exception_state)361 IDBRequest* IDBObjectStore::put(ScriptState* script_state,
362 const ScriptValue& value,
363 ExceptionState& exception_state) {
364 v8::Isolate* isolate = script_state->GetIsolate();
365 return put(script_state, value, ScriptValue(isolate, v8::Undefined(isolate)),
366 exception_state);
367 }
368
putAllValues(ScriptState * script_state,const HeapVector<ScriptValue> & values,ExceptionState & exception_state)369 IDBRequest* IDBObjectStore::putAllValues(ScriptState* script_state,
370 const HeapVector<ScriptValue>& values,
371 ExceptionState& exception_state) {
372 IDB_TRACE1("IDBObjectStore::putAllRequestSetup", "store_name",
373 metadata_->name.Utf8());
374 v8::Isolate* isolate = script_state->GetIsolate();
375 HeapVector<ScriptValue> empty_keys(
376 values.size(), ScriptValue(isolate, v8::Undefined(isolate)));
377 return DoPutAll(script_state, values, empty_keys, exception_state);
378 }
379
DoPutAll(ScriptState * script_state,const HeapVector<ScriptValue> & values,const HeapVector<ScriptValue> & key_values,ExceptionState & exception_state)380 IDBRequest* IDBObjectStore::DoPutAll(ScriptState* script_state,
381 const HeapVector<ScriptValue>& values,
382 const HeapVector<ScriptValue>& key_values,
383 ExceptionState& exception_state) {
384 DCHECK_EQ(values.size(), key_values.size());
385 Vector<mojom::blink::IDBPutParamsPtr> puts;
386 for (size_t i = 0; i < values.size(); i++) {
387 puts.push_back(mojom::blink::IDBPutParams::New());
388 }
389 Vector<std::unique_ptr<IDBKey>> keys;
390 for (const ScriptValue& key_value : key_values) {
391 std::unique_ptr<IDBKey> key_ptr =
392 key_value.IsUndefined()
393 ? nullptr
394 : ScriptValue::To<std::unique_ptr<IDBKey>>(
395 script_state->GetIsolate(), key_value, exception_state);
396 if (exception_state.HadException())
397 return nullptr;
398 keys.push_back(std::move(key_ptr));
399 }
400
401 IDBRequest::Source source = IDBRequest::Source::FromIDBObjectStore(this);
402
403 IDBRequest::AsyncTraceState metrics("IDBObjectStore::putAll");
404 if (IsDeleted()) {
405 exception_state.ThrowDOMException(
406 DOMExceptionCode::kInvalidStateError,
407 IDBDatabase::kObjectStoreDeletedErrorMessage);
408 return nullptr;
409 }
410 if (!transaction_->IsActive()) {
411 exception_state.ThrowDOMException(
412 DOMExceptionCode::kTransactionInactiveError,
413 transaction_->InactiveErrorMessage());
414 return nullptr;
415 }
416 if (transaction_->IsReadOnly()) {
417 exception_state.ThrowDOMException(
418 DOMExceptionCode::kReadOnlyError,
419 IDBDatabase::kTransactionReadOnlyErrorMessage);
420 return nullptr;
421 }
422
423 v8::Isolate* isolate = script_state->GetIsolate();
424 DCHECK(isolate->InContext());
425 transaction_->SetActiveDuringSerialization(false);
426 // TODO(crbug.com/719053): This wasm behavior differs from other browsers.
427 SerializedScriptValue::SerializeOptions::WasmSerializationPolicy wasm_policy =
428 ExecutionContext::From(script_state)->IsSecureContext()
429 ? SerializedScriptValue::SerializeOptions::kSerialize
430 : SerializedScriptValue::SerializeOptions::kBlockedInNonSecureContext;
431 Vector<IDBValueWrapper> value_wrappers;
432 for (auto& value : values) {
433 value_wrappers.emplace_back(isolate, value.V8Value(), wasm_policy,
434 exception_state);
435
436 if (exception_state.HadException())
437 return nullptr;
438 }
439 transaction_->SetActiveDuringSerialization(true);
440
441 const IDBKeyPath& key_path = IdbKeyPath();
442 const bool uses_in_line_keys = !key_path.IsNull();
443 const bool has_key_generator = autoIncrement();
444
445 if (uses_in_line_keys) {
446 for (const auto& key : keys) {
447 if (key) {
448 exception_state.ThrowDOMException(
449 DOMExceptionCode::kDataError,
450 "The object store uses in-line keys and "
451 "the key parameter was provided.");
452 return nullptr;
453 }
454 }
455 }
456
457 if (!uses_in_line_keys && !has_key_generator) {
458 for (const auto& key : keys) {
459 DCHECK(key);
460 }
461 exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
462 "The object store uses out-of-line keys "
463 "and has no key generator and the key "
464 "parameter was not provided.");
465 return nullptr;
466 }
467
468 // Keys that need to be extracted must be taken from a clone so that
469 // side effects (i.e. getters) are not triggered. Construct the
470 // clones lazily since the operation may be expensive.
471 HeapVector<ScriptValue> clones(values.size());
472 // If the primary key is extracted from the values using a key path, this
473 // holds onto the extracted keys for the duration of the method.
474 if (uses_in_line_keys) {
475 std::unique_ptr<IDBKey> key_path_key;
476 DCHECK_EQ(value_wrappers.size(), clones.size());
477 DCHECK_EQ(value_wrappers.size(), keys.size());
478 for (unsigned int i = 0; i < value_wrappers.size(); ++i) {
479 value_wrappers[i].Clone(script_state, &clones[i]);
480 key_path_key = ScriptValue::To<std::unique_ptr<IDBKey>>(
481 script_state->GetIsolate(), clones[i], exception_state, key_path);
482 if (exception_state.HadException())
483 return nullptr;
484 if (key_path_key && !key_path_key->IsValid()) {
485 exception_state.ThrowDOMException(
486 DOMExceptionCode::kDataError,
487 "Evaluating the object store's key path yielded a value that is "
488 "not a valid key.");
489 return nullptr;
490 }
491 if (!key_path_key) {
492 if (!has_key_generator) {
493 exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
494 "Evaluating the object store's key "
495 "path did not yield a value.");
496 return nullptr;
497 }
498 if (!CanInjectIDBKeyIntoScriptValue(script_state->GetIsolate(),
499 clones[i], key_path)) {
500 exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
501 "A generated key could not be "
502 "inserted into the value.");
503 return nullptr;
504 }
505 }
506 keys[i] = std::move(key_path_key);
507 }
508 }
509
510 for (const auto& key : keys) {
511 if (key.get() && !key.get()->IsValid()) {
512 exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
513 IDBDatabase::kNotValidKeyErrorMessage);
514 return nullptr;
515 }
516 }
517
518 if (!BackendDB()) {
519 exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
520 IDBDatabase::kDatabaseClosedErrorMessage);
521 return nullptr;
522 }
523
524 for (unsigned int i = 0; i < value_wrappers.size(); ++i) {
525 if (clones[i].IsEmpty())
526 value_wrappers[i].Clone(script_state, &clones[i]);
527 Vector<IDBIndexKeys> keys_for_value;
528 for (const auto& it : Metadata().indexes) {
529 keys_for_value.emplace_back(IDBIndexKeys{
530 .id = it.key,
531 .keys = GenerateIndexKeysForValue(script_state->GetIsolate(),
532 Metadata(), *it.value, clones[i])});
533 }
534 puts[i]->index_keys = std::move(keys_for_value);
535 }
536 // Records 1KB to 1GB.
537 size_t total_value_wrapper_data_length = 0;
538 for (auto& value_wrapper : value_wrappers) {
539 total_value_wrapper_data_length +=
540 value_wrapper.DataLengthBeforeWrapInBytes() / 1024;
541 }
542 UMA_HISTOGRAM_COUNTS_1M("WebCore.IndexedDB.PutValueSize2",
543 base::saturated_cast<base::HistogramBase::Sample>(
544 total_value_wrapper_data_length / 1024));
545
546 DCHECK_EQ(value_wrappers.size(), puts.size());
547 for (unsigned int i = 0; i < value_wrappers.size(); i++) {
548 value_wrappers[i].DoneCloning();
549 value_wrappers[i].WrapIfBiggerThan(mojom::blink::kIDBWrapThreshold);
550
551 auto idb_value = std::make_unique<IDBValue>(
552 value_wrappers[i].TakeWireBytes(), value_wrappers[i].TakeBlobInfo(),
553 value_wrappers[i].TakeNativeFileSystemTransferTokens());
554 puts[i]->value = std::move(idb_value);
555 }
556
557 IDBRequest* request = IDBRequest::Create(
558 script_state, source, transaction_.Get(), std::move(metrics));
559 for (auto& value_wrapper : value_wrappers) {
560 for (auto& blob_data_handle : value_wrapper.TakeBlobDataHandles()) {
561 request->transit_blob_handles().push_back(std::move(blob_data_handle));
562 }
563 }
564 DCHECK_EQ(keys.size(), puts.size());
565 for (unsigned int i = 0; i < puts.size(); i++) {
566 puts[i]->key = IDBKey::Clone(keys[i]);
567 }
568
569 std::unique_ptr<WebIDBCallbacks> callbacks = request->CreateWebCallbacks();
570 transaction_->transaction_backend()->PutAll(Id(), std::move(puts),
571 std::move(callbacks));
572 return request;
573 }
574
put(ScriptState * script_state,const ScriptValue & value,const ScriptValue & key,ExceptionState & exception_state)575 IDBRequest* IDBObjectStore::put(ScriptState* script_state,
576 const ScriptValue& value,
577 const ScriptValue& key,
578 ExceptionState& exception_state) {
579 IDB_TRACE1("IDBObjectStore::putRequestSetup", "store_name",
580 metadata_->name.Utf8());
581 return DoPut(script_state, mojom::IDBPutMode::AddOrUpdate, value, key,
582 exception_state);
583 }
584
DoPut(ScriptState * script_state,mojom::IDBPutMode put_mode,const ScriptValue & value,const ScriptValue & key_value,ExceptionState & exception_state)585 IDBRequest* IDBObjectStore::DoPut(ScriptState* script_state,
586 mojom::IDBPutMode put_mode,
587 const ScriptValue& value,
588 const ScriptValue& key_value,
589 ExceptionState& exception_state) {
590 std::unique_ptr<IDBKey> key =
591 key_value.IsUndefined()
592 ? nullptr
593 : ScriptValue::To<std::unique_ptr<IDBKey>>(
594 script_state->GetIsolate(), key_value, exception_state);
595 if (exception_state.HadException())
596 return nullptr;
597 return DoPut(script_state, put_mode,
598 IDBRequest::Source::FromIDBObjectStore(this), value, key.get(),
599 exception_state);
600 }
601
DoPut(ScriptState * script_state,mojom::IDBPutMode put_mode,const IDBRequest::Source & source,const ScriptValue & value,const IDBKey * key,ExceptionState & exception_state)602 IDBRequest* IDBObjectStore::DoPut(ScriptState* script_state,
603 mojom::IDBPutMode put_mode,
604 const IDBRequest::Source& source,
605 const ScriptValue& value,
606 const IDBKey* key,
607 ExceptionState& exception_state) {
608 const char* tracing_name = nullptr;
609 switch (put_mode) {
610 case mojom::IDBPutMode::AddOrUpdate:
611 tracing_name = "IDBObjectStore::put";
612 break;
613 case mojom::IDBPutMode::AddOnly:
614 tracing_name = "IDBObjectStore::add";
615 break;
616 case mojom::IDBPutMode::CursorUpdate:
617 tracing_name = "IDBCursor::update";
618 break;
619 }
620 IDBRequest::AsyncTraceState metrics(tracing_name);
621 if (IsDeleted()) {
622 exception_state.ThrowDOMException(
623 DOMExceptionCode::kInvalidStateError,
624 IDBDatabase::kObjectStoreDeletedErrorMessage);
625 return nullptr;
626 }
627 if (!transaction_->IsActive()) {
628 exception_state.ThrowDOMException(
629 DOMExceptionCode::kTransactionInactiveError,
630 transaction_->InactiveErrorMessage());
631 return nullptr;
632 }
633 if (transaction_->IsReadOnly()) {
634 exception_state.ThrowDOMException(
635 DOMExceptionCode::kReadOnlyError,
636 IDBDatabase::kTransactionReadOnlyErrorMessage);
637 return nullptr;
638 }
639
640 v8::Isolate* isolate = script_state->GetIsolate();
641 DCHECK(isolate->InContext());
642 transaction_->SetActiveDuringSerialization(false);
643 // TODO(crbug.com/719053): This wasm behavior differs from other browsers.
644 SerializedScriptValue::SerializeOptions::WasmSerializationPolicy wasm_policy =
645 ExecutionContext::From(script_state)->IsSecureContext()
646 ? SerializedScriptValue::SerializeOptions::kSerialize
647 : SerializedScriptValue::SerializeOptions::kBlockedInNonSecureContext;
648 IDBValueWrapper value_wrapper(isolate, value.V8Value(), wasm_policy,
649 exception_state);
650 transaction_->SetActiveDuringSerialization(true);
651 if (exception_state.HadException())
652 return nullptr;
653
654 // Keys that need to be extracted must be taken from a clone so that
655 // side effects (i.e. getters) are not triggered. Construct the
656 // clone lazily since the operation may be expensive.
657 ScriptValue clone;
658
659 const IDBKeyPath& key_path = IdbKeyPath();
660 const bool uses_in_line_keys = !key_path.IsNull();
661 const bool has_key_generator = autoIncrement();
662
663 if (put_mode != mojom::IDBPutMode::CursorUpdate && uses_in_line_keys && key) {
664 exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
665 "The object store uses in-line keys and "
666 "the key parameter was provided.");
667 return nullptr;
668 }
669
670 // If the primary key is extracted from the value using a key path, this holds
671 // onto the extracted key for the duration of the method.
672 std::unique_ptr<IDBKey> key_path_key;
673
674 // This test logically belongs in IDBCursor, but must operate on the cloned
675 // value.
676 if (put_mode == mojom::IDBPutMode::CursorUpdate && uses_in_line_keys) {
677 DCHECK(key);
678 DCHECK(clone.IsEmpty());
679 value_wrapper.Clone(script_state, &clone);
680
681 DCHECK(!key_path_key);
682 key_path_key = ScriptValue::To<std::unique_ptr<IDBKey>>(
683 script_state->GetIsolate(), clone, exception_state, key_path);
684 if (exception_state.HadException())
685 return nullptr;
686 if (!key_path_key || !key_path_key->IsEqual(key)) {
687 exception_state.ThrowDOMException(
688 DOMExceptionCode::kDataError,
689 "The effective object store of this cursor uses in-line keys and "
690 "evaluating the key path of the value parameter results in a "
691 "different value than the cursor's effective key.");
692 return nullptr;
693 }
694 }
695
696 if (!uses_in_line_keys && !has_key_generator && !key) {
697 exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
698 "The object store uses out-of-line keys "
699 "and has no key generator and the key "
700 "parameter was not provided.");
701 return nullptr;
702 }
703
704 if (uses_in_line_keys) {
705 if (clone.IsEmpty()) {
706 // For an IDBCursor.update(), the value should have been cloned above.
707 DCHECK(put_mode != mojom::IDBPutMode::CursorUpdate);
708 value_wrapper.Clone(script_state, &clone);
709
710 DCHECK(!key_path_key);
711 key_path_key = ScriptValue::To<std::unique_ptr<IDBKey>>(
712 script_state->GetIsolate(), clone, exception_state, key_path);
713 if (exception_state.HadException())
714 return nullptr;
715 if (key_path_key && !key_path_key->IsValid()) {
716 exception_state.ThrowDOMException(
717 DOMExceptionCode::kDataError,
718 "Evaluating the object store's key path yielded a value that is "
719 "not a valid key.");
720 return nullptr;
721 }
722 } else {
723 // The clone was created in the large if block above. The block should
724 // have thrown if key_path_key is not valid.
725 DCHECK(put_mode == mojom::IDBPutMode::CursorUpdate);
726 DCHECK(key_path_key && key_path_key->IsValid());
727 }
728
729 // The clone should have either been created in the if block right above,
730 // or in the large if block further above above. Both if blocks throw if
731 // key_path_key is populated with an invalid key. However, the latter block,
732 // which handles IDBObjectStore.put() and IDBObjectStore.add(), may end up
733 // with a null key_path_key. This is acceptable for object stores with key
734 // generators (autoIncrement is true).
735 DCHECK(!key_path_key || key_path_key->IsValid());
736
737 if (!key_path_key) {
738 if (!has_key_generator) {
739 exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
740 "Evaluating the object store's key "
741 "path did not yield a value.");
742 return nullptr;
743 }
744
745 // Auto-incremented keys must be generated by the backing store, to
746 // ensure uniqueness when the same IndexedDB database is concurrently
747 // accessed by multiple render processes. This check ensures that we'll be
748 // able to inject the generated key into the supplied value when we read
749 // them from the backing store.
750 if (!CanInjectIDBKeyIntoScriptValue(script_state->GetIsolate(), clone,
751 key_path)) {
752 exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
753 "A generated key could not be "
754 "inserted into the value.");
755 return nullptr;
756 }
757 }
758
759 if (key_path_key)
760 key = key_path_key.get();
761 }
762
763 if (key && !key->IsValid()) {
764 exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
765 IDBDatabase::kNotValidKeyErrorMessage);
766 return nullptr;
767 }
768
769 if (!BackendDB()) {
770 exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
771 IDBDatabase::kDatabaseClosedErrorMessage);
772 return nullptr;
773 }
774
775 Vector<IDBIndexKeys> index_keys;
776 index_keys.ReserveInitialCapacity(Metadata().indexes.size());
777 for (const auto& it : Metadata().indexes) {
778 if (clone.IsEmpty())
779 value_wrapper.Clone(script_state, &clone);
780 index_keys.emplace_back(IDBIndexKeys{
781 .id = it.key,
782 .keys = GenerateIndexKeysForValue(script_state->GetIsolate(),
783 Metadata(), *it.value, clone)});
784 }
785 // Records 1KB to 1GB.
786 UMA_HISTOGRAM_COUNTS_1M(
787 "WebCore.IndexedDB.PutValueSize2",
788 base::saturated_cast<base::HistogramBase::Sample>(
789 value_wrapper.DataLengthBeforeWrapInBytes() / 1024));
790
791 IDBRequest* request = IDBRequest::Create(
792 script_state, source, transaction_.Get(), std::move(metrics));
793
794 value_wrapper.DoneCloning();
795
796 value_wrapper.WrapIfBiggerThan(mojom::blink::kIDBWrapThreshold);
797
798 auto idb_value = std::make_unique<IDBValue>(
799 value_wrapper.TakeWireBytes(), value_wrapper.TakeBlobInfo(),
800 value_wrapper.TakeNativeFileSystemTransferTokens());
801
802 request->transit_blob_handles() = value_wrapper.TakeBlobDataHandles();
803 transaction_->transaction_backend()->Put(
804 Id(), std::move(idb_value), IDBKey::Clone(key), put_mode,
805 base::WrapUnique(request->CreateWebCallbacks().release()),
806 std::move(index_keys));
807
808 return request;
809 }
810
Delete(ScriptState * script_state,const ScriptValue & key,ExceptionState & exception_state)811 IDBRequest* IDBObjectStore::Delete(ScriptState* script_state,
812 const ScriptValue& key,
813 ExceptionState& exception_state) {
814 IDB_TRACE1("IDBObjectStore::deleteRequestSetup", "store_name",
815 metadata_->name.Utf8());
816 IDBRequest::AsyncTraceState metrics("IDBObjectStore::delete");
817 if (IsDeleted()) {
818 exception_state.ThrowDOMException(
819 DOMExceptionCode::kInvalidStateError,
820 IDBDatabase::kObjectStoreDeletedErrorMessage);
821 return nullptr;
822 }
823 if (!transaction_->IsActive()) {
824 exception_state.ThrowDOMException(
825 DOMExceptionCode::kTransactionInactiveError,
826 transaction_->InactiveErrorMessage());
827 return nullptr;
828 }
829 if (transaction_->IsReadOnly()) {
830 exception_state.ThrowDOMException(
831 DOMExceptionCode::kReadOnlyError,
832 IDBDatabase::kTransactionReadOnlyErrorMessage);
833 return nullptr;
834 }
835
836 IDBKeyRange* key_range = IDBKeyRange::FromScriptValue(
837 ExecutionContext::From(script_state), key, exception_state);
838 if (exception_state.HadException())
839 return nullptr;
840 if (!key_range) {
841 exception_state.ThrowDOMException(
842 DOMExceptionCode::kDataError,
843 IDBDatabase::kNoKeyOrKeyRangeErrorMessage);
844 return nullptr;
845 }
846 if (!BackendDB()) {
847 exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
848 IDBDatabase::kDatabaseClosedErrorMessage);
849 return nullptr;
850 }
851
852 return deleteFunction(script_state, key_range, std::move(metrics));
853 }
854
deleteFunction(ScriptState * script_state,IDBKeyRange * key_range,IDBRequest::AsyncTraceState metrics)855 IDBRequest* IDBObjectStore::deleteFunction(
856 ScriptState* script_state,
857 IDBKeyRange* key_range,
858 IDBRequest::AsyncTraceState metrics) {
859 IDBRequest* request = IDBRequest::Create(
860 script_state, this, transaction_.Get(), std::move(metrics));
861
862 BackendDB()->DeleteRange(transaction_->Id(), Id(), key_range,
863 request->CreateWebCallbacks().release());
864 return request;
865 }
866
getKeyGeneratorCurrentNumber(ScriptState * script_state,IDBRequest::AsyncTraceState metrics)867 IDBRequest* IDBObjectStore::getKeyGeneratorCurrentNumber(
868 ScriptState* script_state,
869 IDBRequest::AsyncTraceState metrics) {
870 IDBRequest* request = IDBRequest::Create(
871 script_state, this, transaction_.Get(), std::move(metrics));
872
873 BackendDB()->GetKeyGeneratorCurrentNumber(
874 transaction_->Id(), Id(), request->CreateWebCallbacks().release());
875 return request;
876 }
877
clear(ScriptState * script_state,ExceptionState & exception_state)878 IDBRequest* IDBObjectStore::clear(ScriptState* script_state,
879 ExceptionState& exception_state) {
880 IDB_TRACE("IDBObjectStore::clearRequestSetup");
881 IDBRequest::AsyncTraceState metrics("IDBObjectStore::clear");
882 if (IsDeleted()) {
883 exception_state.ThrowDOMException(
884 DOMExceptionCode::kInvalidStateError,
885 IDBDatabase::kObjectStoreDeletedErrorMessage);
886 return nullptr;
887 }
888 if (!transaction_->IsActive()) {
889 exception_state.ThrowDOMException(
890 DOMExceptionCode::kTransactionInactiveError,
891 transaction_->InactiveErrorMessage());
892 return nullptr;
893 }
894 if (transaction_->IsReadOnly()) {
895 exception_state.ThrowDOMException(
896 DOMExceptionCode::kReadOnlyError,
897 IDBDatabase::kTransactionReadOnlyErrorMessage);
898 return nullptr;
899 }
900 if (!BackendDB()) {
901 exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
902 IDBDatabase::kDatabaseClosedErrorMessage);
903 return nullptr;
904 }
905
906 IDBRequest* request = IDBRequest::Create(
907 script_state, this, transaction_.Get(), std::move(metrics));
908 BackendDB()->Clear(transaction_->Id(), Id(),
909 request->CreateWebCallbacks().release());
910 return request;
911 }
912
913 namespace {
914 // This class creates the index keys for a given index by extracting
915 // them from the SerializedScriptValue, for all the existing values in
916 // the object store. It only needs to be kept alive by virtue of being
917 // a listener on an IDBRequest object, in the same way that JavaScript
918 // cursor success handlers are kept alive.
919 class IndexPopulator final : public NativeEventListener {
920 public:
IndexPopulator(ScriptState * script_state,IDBDatabase * database,int64_t transaction_id,int64_t object_store_id,scoped_refptr<const IDBObjectStoreMetadata> store_metadata,scoped_refptr<const IDBIndexMetadata> index_metadata)921 IndexPopulator(ScriptState* script_state,
922 IDBDatabase* database,
923 int64_t transaction_id,
924 int64_t object_store_id,
925 scoped_refptr<const IDBObjectStoreMetadata> store_metadata,
926 scoped_refptr<const IDBIndexMetadata> index_metadata)
927 : script_state_(script_state),
928 database_(database),
929 transaction_id_(transaction_id),
930 object_store_id_(object_store_id),
931 store_metadata_(store_metadata),
932 index_metadata_(std::move(index_metadata)) {
933 DCHECK(index_metadata_.get());
934 }
935
Trace(Visitor * visitor) const936 void Trace(Visitor* visitor) const override {
937 visitor->Trace(script_state_);
938 visitor->Trace(database_);
939 NativeEventListener::Trace(visitor);
940 }
941
942 private:
ObjectStoreMetadata() const943 const IDBObjectStoreMetadata& ObjectStoreMetadata() const {
944 return *store_metadata_;
945 }
IndexMetadata() const946 const IDBIndexMetadata& IndexMetadata() const { return *index_metadata_; }
947
Invoke(ExecutionContext * execution_context,Event * event)948 void Invoke(ExecutionContext* execution_context, Event* event) override {
949 if (!script_state_->ContextIsValid())
950 return;
951 IDB_TRACE("IDBObjectStore::IndexPopulator::Invoke");
952
953 DCHECK_EQ(ExecutionContext::From(script_state_), execution_context);
954 DCHECK_EQ(event->type(), event_type_names::kSuccess);
955 EventTarget* target = event->target();
956 IDBRequest* request = static_cast<IDBRequest*>(target);
957
958 if (!database_->Backend()) // If database is stopped?
959 return;
960
961 ScriptState::Scope scope(script_state_);
962
963 IDBAny* cursor_any = request->ResultAsAny();
964 IDBCursorWithValue* cursor = nullptr;
965 if (cursor_any->GetType() == IDBAny::kIDBCursorWithValueType)
966 cursor = cursor_any->IdbCursorWithValue();
967
968 if (cursor && !cursor->IsDeleted()) {
969 cursor->Continue(nullptr, nullptr, IDBRequest::AsyncTraceState(),
970 ASSERT_NO_EXCEPTION);
971
972 const IDBKey* primary_key = cursor->IdbPrimaryKey();
973 ScriptValue value = cursor->value(script_state_);
974
975 Vector<IDBIndexKeys> index_keys;
976 index_keys.ReserveInitialCapacity(1);
977 index_keys.emplace_back(IDBIndexKeys{
978 .id = IndexMetadata().id,
979 .keys = GenerateIndexKeysForValue(script_state_->GetIsolate(),
980 ObjectStoreMetadata(),
981 IndexMetadata(), value)});
982
983 database_->Backend()->SetIndexKeys(transaction_id_, object_store_id_,
984 IDBKey::Clone(primary_key),
985 std::move(index_keys));
986 } else {
987 // Now that we are done indexing, tell the backend to go
988 // back to processing tasks of type NormalTask.
989 Vector<int64_t> index_ids;
990 index_ids.push_back(IndexMetadata().id);
991 database_->Backend()->SetIndexesReady(transaction_id_, object_store_id_,
992 index_ids);
993 database_.Clear();
994 }
995 }
996
997 Member<ScriptState> script_state_;
998 Member<IDBDatabase> database_;
999 const int64_t transaction_id_;
1000 const int64_t object_store_id_;
1001 scoped_refptr<const IDBObjectStoreMetadata> store_metadata_;
1002 scoped_refptr<const IDBIndexMetadata> index_metadata_;
1003 };
1004 } // namespace
1005
createIndex(ScriptState * script_state,const String & name,const IDBKeyPath & key_path,const IDBIndexParameters * options,ExceptionState & exception_state)1006 IDBIndex* IDBObjectStore::createIndex(ScriptState* script_state,
1007 const String& name,
1008 const IDBKeyPath& key_path,
1009 const IDBIndexParameters* options,
1010 ExceptionState& exception_state) {
1011 IDB_TRACE1("IDBObjectStore::createIndexRequestSetup", "store_name",
1012 metadata_->name.Utf8());
1013 IDBRequest::AsyncTraceState metrics("IDBObjectStore::createIndex");
1014 if (!transaction_->IsVersionChange()) {
1015 exception_state.ThrowDOMException(
1016 DOMExceptionCode::kInvalidStateError,
1017 IDBDatabase::kNotVersionChangeTransactionErrorMessage);
1018 return nullptr;
1019 }
1020 if (IsDeleted()) {
1021 exception_state.ThrowDOMException(
1022 DOMExceptionCode::kInvalidStateError,
1023 IDBDatabase::kObjectStoreDeletedErrorMessage);
1024 return nullptr;
1025 }
1026 if (!transaction_->IsActive()) {
1027 exception_state.ThrowDOMException(
1028 DOMExceptionCode::kTransactionInactiveError,
1029 transaction_->InactiveErrorMessage());
1030 return nullptr;
1031 }
1032 if (ContainsIndex(name)) {
1033 exception_state.ThrowDOMException(DOMExceptionCode::kConstraintError,
1034 IDBDatabase::kIndexNameTakenErrorMessage);
1035 return nullptr;
1036 }
1037 if (!key_path.IsValid()) {
1038 exception_state.ThrowDOMException(
1039 DOMExceptionCode::kSyntaxError,
1040 "The keyPath argument contains an invalid key path.");
1041 return nullptr;
1042 }
1043 if (key_path.GetType() == mojom::IDBKeyPathType::Array &&
1044 options->multiEntry()) {
1045 exception_state.ThrowDOMException(
1046 DOMExceptionCode::kInvalidAccessError,
1047 "The keyPath argument was an array and the multiEntry option is true.");
1048 return nullptr;
1049 }
1050 if (!BackendDB()) {
1051 exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
1052 IDBDatabase::kDatabaseClosedErrorMessage);
1053 return nullptr;
1054 }
1055
1056 int64_t index_id = metadata_->max_index_id + 1;
1057 DCHECK_NE(index_id, IDBIndexMetadata::kInvalidId);
1058 BackendDB()->CreateIndex(transaction_->Id(), Id(), index_id, name, key_path,
1059 options->unique(), options->multiEntry());
1060
1061 ++metadata_->max_index_id;
1062
1063 scoped_refptr<IDBIndexMetadata> index_metadata =
1064 base::AdoptRef(new IDBIndexMetadata(
1065 name, index_id, key_path, options->unique(), options->multiEntry()));
1066 auto* index =
1067 MakeGarbageCollected<IDBIndex>(index_metadata, this, transaction_.Get());
1068 index_map_.Set(name, index);
1069 metadata_->indexes.Set(index_id, index_metadata);
1070
1071 DCHECK(!exception_state.HadException());
1072 if (exception_state.HadException())
1073 return nullptr;
1074
1075 IDBRequest* index_request =
1076 openCursor(script_state, nullptr, mojom::IDBCursorDirection::Next,
1077 mojom::IDBTaskType::Preemptive, std::move(metrics));
1078 index_request->PreventPropagation();
1079
1080 // This is kept alive by being the success handler of the request, which is in
1081 // turn kept alive by the owning transaction.
1082 auto* index_populator = MakeGarbageCollected<IndexPopulator>(
1083 script_state, transaction()->db(), transaction_->Id(), Id(), metadata_,
1084 std::move(index_metadata));
1085 index_request->setOnsuccess(index_populator);
1086 return index;
1087 }
1088
index(const String & name,ExceptionState & exception_state)1089 IDBIndex* IDBObjectStore::index(const String& name,
1090 ExceptionState& exception_state) {
1091 IDB_TRACE1("IDBObjectStore::index", "store_name", metadata_->name.Utf8());
1092 if (IsDeleted()) {
1093 exception_state.ThrowDOMException(
1094 DOMExceptionCode::kInvalidStateError,
1095 IDBDatabase::kObjectStoreDeletedErrorMessage);
1096 return nullptr;
1097 }
1098 if (transaction_->IsFinished() || transaction_->IsFinishing()) {
1099 exception_state.ThrowDOMException(
1100 DOMExceptionCode::kInvalidStateError,
1101 IDBDatabase::kTransactionFinishedErrorMessage);
1102 return nullptr;
1103 }
1104
1105 IDBIndexMap::iterator it = index_map_.find(name);
1106 if (it != index_map_.end())
1107 return it->value;
1108
1109 int64_t index_id = FindIndexId(name);
1110 if (index_id == IDBIndexMetadata::kInvalidId) {
1111 exception_state.ThrowDOMException(DOMExceptionCode::kNotFoundError,
1112 IDBDatabase::kNoSuchIndexErrorMessage);
1113 return nullptr;
1114 }
1115
1116 DCHECK(Metadata().indexes.Contains(index_id));
1117 scoped_refptr<IDBIndexMetadata> index_metadata =
1118 Metadata().indexes.at(index_id);
1119 DCHECK(index_metadata.get());
1120 auto* index = MakeGarbageCollected<IDBIndex>(std::move(index_metadata), this,
1121 transaction_.Get());
1122 index_map_.Set(name, index);
1123 return index;
1124 }
1125
deleteIndex(const String & name,ExceptionState & exception_state)1126 void IDBObjectStore::deleteIndex(const String& name,
1127 ExceptionState& exception_state) {
1128 IDB_TRACE1("IDBObjectStore::deleteIndex", "store_name",
1129 metadata_->name.Utf8());
1130 if (!transaction_->IsVersionChange()) {
1131 exception_state.ThrowDOMException(
1132 DOMExceptionCode::kInvalidStateError,
1133 IDBDatabase::kNotVersionChangeTransactionErrorMessage);
1134 return;
1135 }
1136 if (IsDeleted()) {
1137 exception_state.ThrowDOMException(
1138 DOMExceptionCode::kInvalidStateError,
1139 IDBDatabase::kObjectStoreDeletedErrorMessage);
1140 return;
1141 }
1142 if (!transaction_->IsActive()) {
1143 exception_state.ThrowDOMException(
1144 DOMExceptionCode::kTransactionInactiveError,
1145 transaction_->InactiveErrorMessage());
1146 return;
1147 }
1148 int64_t index_id = FindIndexId(name);
1149 if (index_id == IDBIndexMetadata::kInvalidId) {
1150 exception_state.ThrowDOMException(DOMExceptionCode::kNotFoundError,
1151 IDBDatabase::kNoSuchIndexErrorMessage);
1152 return;
1153 }
1154 if (!BackendDB()) {
1155 exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
1156 IDBDatabase::kDatabaseClosedErrorMessage);
1157 return;
1158 }
1159
1160 BackendDB()->DeleteIndex(transaction_->Id(), Id(), index_id);
1161
1162 metadata_->indexes.erase(index_id);
1163 IDBIndexMap::iterator it = index_map_.find(name);
1164 if (it != index_map_.end()) {
1165 transaction_->IndexDeleted(it->value);
1166 it->value->MarkDeleted();
1167 index_map_.erase(name);
1168 }
1169 }
1170
openCursor(ScriptState * script_state,const ScriptValue & range,const String & direction_string,ExceptionState & exception_state)1171 IDBRequest* IDBObjectStore::openCursor(ScriptState* script_state,
1172 const ScriptValue& range,
1173 const String& direction_string,
1174 ExceptionState& exception_state) {
1175 IDB_TRACE1("IDBObjectStore::openCursorRequestSetup", "store_name",
1176 metadata_->name.Utf8());
1177 IDBRequest::AsyncTraceState metrics("IDBObjectStore::openCursor");
1178 if (IsDeleted()) {
1179 exception_state.ThrowDOMException(
1180 DOMExceptionCode::kInvalidStateError,
1181 IDBDatabase::kObjectStoreDeletedErrorMessage);
1182 return nullptr;
1183 }
1184 if (!transaction_->IsActive()) {
1185 exception_state.ThrowDOMException(
1186 DOMExceptionCode::kTransactionInactiveError,
1187 transaction_->InactiveErrorMessage());
1188 return nullptr;
1189 }
1190
1191 mojom::IDBCursorDirection direction =
1192 IDBCursor::StringToDirection(direction_string);
1193 IDBKeyRange* key_range = IDBKeyRange::FromScriptValue(
1194 ExecutionContext::From(script_state), range, exception_state);
1195 if (exception_state.HadException())
1196 return nullptr;
1197
1198 if (!BackendDB()) {
1199 exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
1200 IDBDatabase::kDatabaseClosedErrorMessage);
1201 return nullptr;
1202 }
1203
1204 return openCursor(script_state, key_range, direction,
1205 mojom::IDBTaskType::Normal, std::move(metrics));
1206 }
1207
openCursor(ScriptState * script_state,IDBKeyRange * range,mojom::IDBCursorDirection direction,mojom::IDBTaskType task_type,IDBRequest::AsyncTraceState metrics)1208 IDBRequest* IDBObjectStore::openCursor(ScriptState* script_state,
1209 IDBKeyRange* range,
1210 mojom::IDBCursorDirection direction,
1211 mojom::IDBTaskType task_type,
1212 IDBRequest::AsyncTraceState metrics) {
1213 IDBRequest* request = IDBRequest::Create(
1214 script_state, this, transaction_.Get(), std::move(metrics));
1215 request->SetCursorDetails(indexed_db::kCursorKeyAndValue, direction);
1216
1217 BackendDB()->OpenCursor(transaction_->Id(), Id(),
1218 IDBIndexMetadata::kInvalidId, range, direction, false,
1219 task_type, request->CreateWebCallbacks().release());
1220 return request;
1221 }
1222
openKeyCursor(ScriptState * script_state,const ScriptValue & range,const String & direction_string,ExceptionState & exception_state)1223 IDBRequest* IDBObjectStore::openKeyCursor(ScriptState* script_state,
1224 const ScriptValue& range,
1225 const String& direction_string,
1226 ExceptionState& exception_state) {
1227 IDB_TRACE1("IDBObjectStore::openKeyCursorRequestSetup", "store_name",
1228 metadata_->name.Utf8());
1229 IDBRequest::AsyncTraceState metrics("IDBObjectStore::openKeyCursor");
1230 if (IsDeleted()) {
1231 exception_state.ThrowDOMException(
1232 DOMExceptionCode::kInvalidStateError,
1233 IDBDatabase::kObjectStoreDeletedErrorMessage);
1234 return nullptr;
1235 }
1236 if (!transaction_->IsActive()) {
1237 exception_state.ThrowDOMException(
1238 DOMExceptionCode::kTransactionInactiveError,
1239 transaction_->InactiveErrorMessage());
1240 return nullptr;
1241 }
1242
1243 mojom::IDBCursorDirection direction =
1244 IDBCursor::StringToDirection(direction_string);
1245 IDBKeyRange* key_range = IDBKeyRange::FromScriptValue(
1246 ExecutionContext::From(script_state), range, exception_state);
1247 if (exception_state.HadException())
1248 return nullptr;
1249
1250 if (!BackendDB()) {
1251 exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
1252 IDBDatabase::kDatabaseClosedErrorMessage);
1253 return nullptr;
1254 }
1255
1256 IDBRequest* request = IDBRequest::Create(
1257 script_state, this, transaction_.Get(), std::move(metrics));
1258 request->SetCursorDetails(indexed_db::kCursorKeyOnly, direction);
1259
1260 BackendDB()->OpenCursor(transaction_->Id(), Id(),
1261 IDBIndexMetadata::kInvalidId, key_range, direction,
1262 true, mojom::IDBTaskType::Normal,
1263 request->CreateWebCallbacks().release());
1264 return request;
1265 }
1266
count(ScriptState * script_state,const ScriptValue & range,ExceptionState & exception_state)1267 IDBRequest* IDBObjectStore::count(ScriptState* script_state,
1268 const ScriptValue& range,
1269 ExceptionState& exception_state) {
1270 IDB_TRACE1("IDBObjectStore::countRequestSetup", "store_name",
1271 metadata_->name.Utf8());
1272 IDBRequest::AsyncTraceState metrics("IDBObjectStore::count");
1273 if (IsDeleted()) {
1274 exception_state.ThrowDOMException(
1275 DOMExceptionCode::kInvalidStateError,
1276 IDBDatabase::kObjectStoreDeletedErrorMessage);
1277 return nullptr;
1278 }
1279 if (!transaction_->IsActive()) {
1280 exception_state.ThrowDOMException(
1281 DOMExceptionCode::kTransactionInactiveError,
1282 transaction_->InactiveErrorMessage());
1283 return nullptr;
1284 }
1285
1286 IDBKeyRange* key_range = IDBKeyRange::FromScriptValue(
1287 ExecutionContext::From(script_state), range, exception_state);
1288 if (exception_state.HadException())
1289 return nullptr;
1290
1291 if (!BackendDB()) {
1292 exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
1293 IDBDatabase::kDatabaseClosedErrorMessage);
1294 return nullptr;
1295 }
1296
1297 IDBRequest* request = IDBRequest::Create(
1298 script_state, this, transaction_.Get(), std::move(metrics));
1299 BackendDB()->Count(transaction_->Id(), Id(), IDBIndexMetadata::kInvalidId,
1300 key_range, request->CreateWebCallbacks().release());
1301 return request;
1302 }
1303
MarkDeleted()1304 void IDBObjectStore::MarkDeleted() {
1305 DCHECK(transaction_->IsVersionChange())
1306 << "An object store got deleted outside a versionchange transaction.";
1307
1308 deleted_ = true;
1309 metadata_->indexes.clear();
1310
1311 for (auto& it : index_map_) {
1312 IDBIndex* index = it.value;
1313 index->MarkDeleted();
1314 }
1315 }
1316
ClearIndexCache()1317 void IDBObjectStore::ClearIndexCache() {
1318 DCHECK(!transaction_->IsActive() || (IsDeleted() && IsNewlyCreated()));
1319
1320 #if DCHECK_IS_ON()
1321 // There is no harm in having ClearIndexCache() happen multiple times for
1322 // the same object. We assert that it is called once to uncover potential
1323 // object store accounting bugs.
1324 DCHECK(!clear_index_cache_called_);
1325 clear_index_cache_called_ = true;
1326 #endif // DCHECK_IS_ON()
1327
1328 index_map_.clear();
1329 }
1330
RevertMetadata(scoped_refptr<IDBObjectStoreMetadata> old_metadata)1331 void IDBObjectStore::RevertMetadata(
1332 scoped_refptr<IDBObjectStoreMetadata> old_metadata) {
1333 DCHECK(transaction_->IsVersionChange());
1334 DCHECK(!transaction_->IsActive());
1335 DCHECK(old_metadata.get());
1336 DCHECK(Id() == old_metadata->id);
1337
1338 for (auto& index : index_map_.Values()) {
1339 const int64_t index_id = index->Id();
1340
1341 if (index->IsNewlyCreated(*old_metadata)) {
1342 // The index was created by this transaction. According to the spec,
1343 // its metadata will remain as-is.
1344 DCHECK(!old_metadata->indexes.Contains(index_id));
1345 index->MarkDeleted();
1346 continue;
1347 }
1348
1349 // The index was created in a previous transaction. We need to revert
1350 // its metadata. The index might have been deleted, so we
1351 // unconditionally reset the deletion marker.
1352 DCHECK(old_metadata->indexes.Contains(index_id));
1353 scoped_refptr<IDBIndexMetadata> old_index_metadata =
1354 old_metadata->indexes.at(index_id);
1355 index->RevertMetadata(std::move(old_index_metadata));
1356 }
1357 metadata_ = std::move(old_metadata);
1358
1359 // An object store's metadata will only get reverted if the index was in the
1360 // database when the versionchange transaction started.
1361 deleted_ = false;
1362 }
1363
RevertDeletedIndexMetadata(IDBIndex & deleted_index)1364 void IDBObjectStore::RevertDeletedIndexMetadata(IDBIndex& deleted_index) {
1365 DCHECK(transaction_->IsVersionChange());
1366 DCHECK(!transaction_->IsActive());
1367 DCHECK(deleted_index.objectStore() == this);
1368 DCHECK(deleted_index.IsDeleted());
1369
1370 const int64_t index_id = deleted_index.Id();
1371 DCHECK(metadata_->indexes.Contains(index_id))
1372 << "The object store's metadata was not correctly reverted";
1373 scoped_refptr<IDBIndexMetadata> old_index_metadata =
1374 metadata_->indexes.at(index_id);
1375 deleted_index.RevertMetadata(std::move(old_index_metadata));
1376 }
1377
RenameIndex(int64_t index_id,const String & new_name)1378 void IDBObjectStore::RenameIndex(int64_t index_id, const String& new_name) {
1379 DCHECK(transaction_->IsVersionChange());
1380 DCHECK(transaction_->IsActive());
1381
1382 BackendDB()->RenameIndex(transaction_->Id(), Id(), index_id, new_name);
1383
1384 auto metadata_iterator = metadata_->indexes.find(index_id);
1385 DCHECK_NE(metadata_iterator, metadata_->indexes.end()) << "Invalid index_id";
1386 const String& old_name = metadata_iterator->value->name;
1387
1388 DCHECK(index_map_.Contains(old_name))
1389 << "The index had to be accessed in order to be renamed.";
1390 DCHECK(!index_map_.Contains(new_name));
1391 index_map_.Set(new_name, index_map_.Take(old_name));
1392
1393 metadata_iterator->value->name = new_name;
1394 }
1395
FindIndexId(const String & name) const1396 int64_t IDBObjectStore::FindIndexId(const String& name) const {
1397 for (const auto& it : Metadata().indexes) {
1398 if (it.value->name == name) {
1399 DCHECK_NE(it.key, IDBIndexMetadata::kInvalidId);
1400 return it.key;
1401 }
1402 }
1403 return IDBIndexMetadata::kInvalidId;
1404 }
1405
BackendDB() const1406 WebIDBDatabase* IDBObjectStore::BackendDB() const {
1407 return transaction_->BackendDB();
1408 }
1409
1410 } // namespace blink
1411