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