1 | /* |
2 | * Copyright (C) 2012 Google Inc. All rights reserved. |
3 | * Copyright (C) 2015-2017 Apple Inc. All rights reserved. |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions are |
7 | * met: |
8 | * |
9 | * * Redistributions of source code must retain the above copyright |
10 | * notice, this list of conditions and the following disclaimer. |
11 | * * Redistributions in binary form must reproduce the above |
12 | * copyright notice, this list of conditions and the following disclaimer |
13 | * in the documentation and/or other materials provided with the |
14 | * distribution. |
15 | * * Neither the name of Google Inc. nor the names of its |
16 | * contributors may be used to endorse or promote products derived from |
17 | * this software without specific prior written permission. |
18 | * |
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
30 | */ |
31 | |
32 | #include "config.h" |
33 | #include "InspectorIndexedDBAgent.h" |
34 | |
35 | #if ENABLE(INDEXED_DATABASE) |
36 | |
37 | #include "DOMStringList.h" |
38 | #include "DOMWindow.h" |
39 | #include "DOMWindowIndexedDatabase.h" |
40 | #include "Document.h" |
41 | #include "Event.h" |
42 | #include "EventListener.h" |
43 | #include "EventNames.h" |
44 | #include "EventTarget.h" |
45 | #include "Frame.h" |
46 | #include "IDBBindingUtilities.h" |
47 | #include "IDBCursor.h" |
48 | #include "IDBCursorWithValue.h" |
49 | #include "IDBDatabase.h" |
50 | #include "IDBFactory.h" |
51 | #include "IDBIndex.h" |
52 | #include "IDBKey.h" |
53 | #include "IDBKeyPath.h" |
54 | #include "IDBKeyRange.h" |
55 | #include "IDBObjectStore.h" |
56 | #include "IDBOpenDBRequest.h" |
57 | #include "IDBRequest.h" |
58 | #include "IDBTransaction.h" |
59 | #include "InspectorPageAgent.h" |
60 | #include "InstrumentingAgents.h" |
61 | #include "ScriptState.h" |
62 | #include "SecurityOrigin.h" |
63 | #include <JavaScriptCore/HeapInlines.h> |
64 | #include <JavaScriptCore/InjectedScript.h> |
65 | #include <JavaScriptCore/InjectedScriptManager.h> |
66 | #include <JavaScriptCore/InspectorFrontendDispatchers.h> |
67 | #include <JavaScriptCore/InspectorFrontendRouter.h> |
68 | #include <wtf/JSONValues.h> |
69 | #include <wtf/NeverDestroyed.h> |
70 | #include <wtf/Vector.h> |
71 | #include <wtf/text/StringConcatenateNumbers.h> |
72 | |
73 | using JSON::ArrayOf; |
74 | using Inspector::Protocol::IndexedDB::DatabaseWithObjectStores; |
75 | using Inspector::Protocol::IndexedDB::DataEntry; |
76 | using Inspector::Protocol::IndexedDB::Key; |
77 | using Inspector::Protocol::IndexedDB::KeyPath; |
78 | using Inspector::Protocol::IndexedDB::KeyRange; |
79 | using Inspector::Protocol::IndexedDB::ObjectStore; |
80 | using Inspector::Protocol::IndexedDB::ObjectStoreIndex; |
81 | |
82 | typedef Inspector::BackendDispatcher::CallbackBase RequestCallback; |
83 | typedef Inspector::IndexedDBBackendDispatcherHandler::RequestDatabaseNamesCallback RequestDatabaseNamesCallback; |
84 | typedef Inspector::IndexedDBBackendDispatcherHandler::RequestDatabaseCallback RequestDatabaseCallback; |
85 | typedef Inspector::IndexedDBBackendDispatcherHandler::RequestDataCallback RequestDataCallback; |
86 | typedef Inspector::IndexedDBBackendDispatcherHandler::ClearObjectStoreCallback ClearObjectStoreCallback; |
87 | |
88 | typedef String ErrorString; |
89 | |
90 | template <typename T> |
91 | using ErrorStringOr = Expected<T, ErrorString>; |
92 | |
93 | namespace WebCore { |
94 | |
95 | using namespace Inspector; |
96 | |
97 | namespace { |
98 | |
99 | class ExecutableWithDatabase : public RefCounted<ExecutableWithDatabase> { |
100 | public: |
101 | ExecutableWithDatabase(ScriptExecutionContext* context) |
102 | : m_context(context) { } |
103 | virtual ~ExecutableWithDatabase() = default; |
104 | void start(IDBFactory*, SecurityOrigin*, const String& databaseName); |
105 | virtual void execute(IDBDatabase&) = 0; |
106 | virtual RequestCallback& requestCallback() = 0; |
107 | ScriptExecutionContext* context() const { return m_context; } |
108 | private: |
109 | ScriptExecutionContext* m_context; |
110 | }; |
111 | |
112 | class OpenDatabaseCallback final : public EventListener { |
113 | public: |
114 | static Ref<OpenDatabaseCallback> create(ExecutableWithDatabase& executableWithDatabase) |
115 | { |
116 | return adoptRef(*new OpenDatabaseCallback(executableWithDatabase)); |
117 | } |
118 | |
119 | bool operator==(const EventListener& other) const final |
120 | { |
121 | return this == &other; |
122 | } |
123 | |
124 | void handleEvent(ScriptExecutionContext&, Event& event) final |
125 | { |
126 | if (event.type() != eventNames().successEvent) { |
127 | m_executableWithDatabase->requestCallback().sendFailure("Unexpected event type." ); |
128 | return; |
129 | } |
130 | |
131 | auto& request = static_cast<IDBOpenDBRequest&>(*event.target()); |
132 | |
133 | auto result = request.result(); |
134 | if (result.hasException()) { |
135 | m_executableWithDatabase->requestCallback().sendFailure("Could not get result in callback." ); |
136 | return; |
137 | } |
138 | |
139 | auto resultValue = result.releaseReturnValue(); |
140 | if (!WTF::holds_alternative<RefPtr<IDBDatabase>>(resultValue)) { |
141 | m_executableWithDatabase->requestCallback().sendFailure("Unexpected result type." ); |
142 | return; |
143 | } |
144 | |
145 | auto databaseResult = WTF::get<RefPtr<IDBDatabase>>(resultValue); |
146 | m_executableWithDatabase->execute(*databaseResult); |
147 | databaseResult->close(); |
148 | } |
149 | |
150 | private: |
151 | OpenDatabaseCallback(ExecutableWithDatabase& executableWithDatabase) |
152 | : EventListener(EventListener::CPPEventListenerType) |
153 | , m_executableWithDatabase(executableWithDatabase) { } |
154 | Ref<ExecutableWithDatabase> m_executableWithDatabase; |
155 | }; |
156 | |
157 | void ExecutableWithDatabase::start(IDBFactory* idbFactory, SecurityOrigin*, const String& databaseName) |
158 | { |
159 | if (!context()) { |
160 | requestCallback().sendFailure("Could not open database." ); |
161 | return; |
162 | } |
163 | |
164 | auto result = idbFactory->open(*context(), databaseName, WTF::nullopt); |
165 | if (result.hasException()) { |
166 | requestCallback().sendFailure("Could not open database." ); |
167 | return; |
168 | } |
169 | |
170 | result.releaseReturnValue()->addEventListener(eventNames().successEvent, OpenDatabaseCallback::create(*this), false); |
171 | } |
172 | |
173 | |
174 | static RefPtr<KeyPath> keyPathFromIDBKeyPath(const Optional<IDBKeyPath>& idbKeyPath) |
175 | { |
176 | if (!idbKeyPath) |
177 | return KeyPath::create().setType(KeyPath::Type::Null).release(); |
178 | |
179 | auto visitor = WTF::makeVisitor([](const String& string) { |
180 | auto keyPath = KeyPath::create().setType(KeyPath::Type::String).release(); |
181 | keyPath->setString(string); |
182 | return keyPath; |
183 | }, [](const Vector<String>& vector) { |
184 | auto array = JSON::ArrayOf<String>::create(); |
185 | for (auto& string : vector) |
186 | array->addItem(string); |
187 | auto keyPath = KeyPath::create().setType(KeyPath::Type::Array).release(); |
188 | keyPath->setArray(WTFMove(array)); |
189 | return keyPath; |
190 | }); |
191 | return WTF::visit(visitor, idbKeyPath.value()); |
192 | } |
193 | |
194 | static RefPtr<IDBTransaction> transactionForDatabase(IDBDatabase* idbDatabase, const String& objectStoreName, IDBTransactionMode mode = IDBTransactionMode::Readonly) |
195 | { |
196 | auto result = idbDatabase->transaction(objectStoreName, mode); |
197 | if (result.hasException()) |
198 | return nullptr; |
199 | return result.releaseReturnValue(); |
200 | } |
201 | |
202 | static RefPtr<IDBObjectStore> objectStoreForTransaction(IDBTransaction* idbTransaction, const String& objectStoreName) |
203 | { |
204 | auto result = idbTransaction->objectStore(objectStoreName); |
205 | if (result.hasException()) |
206 | return nullptr; |
207 | return result.releaseReturnValue(); |
208 | } |
209 | |
210 | static RefPtr<IDBIndex> indexForObjectStore(IDBObjectStore* idbObjectStore, const String& indexName) |
211 | { |
212 | auto index = idbObjectStore->index(indexName); |
213 | if (index.hasException()) |
214 | return nullptr; |
215 | return index.releaseReturnValue(); |
216 | } |
217 | |
218 | class DatabaseLoader final : public ExecutableWithDatabase { |
219 | public: |
220 | static Ref<DatabaseLoader> create(ScriptExecutionContext* context, Ref<RequestDatabaseCallback>&& requestCallback) |
221 | { |
222 | return adoptRef(*new DatabaseLoader(context, WTFMove(requestCallback))); |
223 | } |
224 | |
225 | virtual ~DatabaseLoader() = default; |
226 | |
227 | void execute(IDBDatabase& database) override |
228 | { |
229 | if (!requestCallback().isActive()) |
230 | return; |
231 | |
232 | auto& databaseInfo = database.info(); |
233 | auto objectStores = JSON::ArrayOf<Inspector::Protocol::IndexedDB::ObjectStore>::create(); |
234 | auto objectStoreNames = databaseInfo.objectStoreNames(); |
235 | for (auto& name : objectStoreNames) { |
236 | auto* objectStoreInfo = databaseInfo.infoForExistingObjectStore(name); |
237 | if (!objectStoreInfo) |
238 | continue; |
239 | |
240 | auto indexes = JSON::ArrayOf<Inspector::Protocol::IndexedDB::ObjectStoreIndex>::create(); |
241 | |
242 | for (auto& indexInfo : objectStoreInfo->indexMap().values()) { |
243 | auto objectStoreIndex = ObjectStoreIndex::create() |
244 | .setName(indexInfo.name()) |
245 | .setKeyPath(keyPathFromIDBKeyPath(indexInfo.keyPath())) |
246 | .setUnique(indexInfo.unique()) |
247 | .setMultiEntry(indexInfo.multiEntry()) |
248 | .release(); |
249 | indexes->addItem(WTFMove(objectStoreIndex)); |
250 | } |
251 | |
252 | auto objectStore = ObjectStore::create() |
253 | .setName(objectStoreInfo->name()) |
254 | .setKeyPath(keyPathFromIDBKeyPath(objectStoreInfo->keyPath())) |
255 | .setAutoIncrement(objectStoreInfo->autoIncrement()) |
256 | .setIndexes(WTFMove(indexes)) |
257 | .release(); |
258 | objectStores->addItem(WTFMove(objectStore)); |
259 | } |
260 | |
261 | auto result = DatabaseWithObjectStores::create() |
262 | .setName(databaseInfo.name()) |
263 | .setVersion(databaseInfo.version()) |
264 | .setObjectStores(WTFMove(objectStores)) |
265 | .release(); |
266 | m_requestCallback->sendSuccess(WTFMove(result)); |
267 | } |
268 | |
269 | RequestCallback& requestCallback() override { return m_requestCallback.get(); } |
270 | private: |
271 | DatabaseLoader(ScriptExecutionContext* context, Ref<RequestDatabaseCallback>&& requestCallback) |
272 | : ExecutableWithDatabase(context) |
273 | , m_requestCallback(WTFMove(requestCallback)) { } |
274 | Ref<RequestDatabaseCallback> m_requestCallback; |
275 | }; |
276 | |
277 | static RefPtr<IDBKey> idbKeyFromInspectorObject(JSON::Object* key) |
278 | { |
279 | String type; |
280 | if (!key->getString("type" , type)) |
281 | return nullptr; |
282 | |
283 | static NeverDestroyed<const String> numberType(MAKE_STATIC_STRING_IMPL("number" )); |
284 | static NeverDestroyed<const String> stringType(MAKE_STATIC_STRING_IMPL("string" )); |
285 | static NeverDestroyed<const String> dateType(MAKE_STATIC_STRING_IMPL("date" )); |
286 | static NeverDestroyed<const String> arrayType(MAKE_STATIC_STRING_IMPL("array" )); |
287 | |
288 | RefPtr<IDBKey> idbKey; |
289 | if (type == numberType) { |
290 | double number; |
291 | if (!key->getDouble("number" , number)) |
292 | return nullptr; |
293 | idbKey = IDBKey::createNumber(number); |
294 | } else if (type == stringType) { |
295 | String string; |
296 | if (!key->getString("string" , string)) |
297 | return nullptr; |
298 | idbKey = IDBKey::createString(string); |
299 | } else if (type == dateType) { |
300 | double date; |
301 | if (!key->getDouble("date" , date)) |
302 | return nullptr; |
303 | idbKey = IDBKey::createDate(date); |
304 | } else if (type == arrayType) { |
305 | Vector<RefPtr<IDBKey>> keyArray; |
306 | RefPtr<JSON::Array> array; |
307 | if (!key->getArray("array" , array)) |
308 | return nullptr; |
309 | for (size_t i = 0; i < array->length(); ++i) { |
310 | RefPtr<JSON::Value> value = array->get(i); |
311 | RefPtr<JSON::Object> object; |
312 | if (!value->asObject(object)) |
313 | return nullptr; |
314 | keyArray.append(idbKeyFromInspectorObject(object.get())); |
315 | } |
316 | idbKey = IDBKey::createArray(keyArray); |
317 | } else |
318 | return nullptr; |
319 | |
320 | return idbKey; |
321 | } |
322 | |
323 | static RefPtr<IDBKeyRange> idbKeyRangeFromKeyRange(const JSON::Object* keyRange) |
324 | { |
325 | RefPtr<IDBKey> idbLower; |
326 | RefPtr<JSON::Object> lower; |
327 | if (keyRange->getObject("lower"_s , lower)) { |
328 | idbLower = idbKeyFromInspectorObject(lower.get()); |
329 | if (!idbLower) |
330 | return nullptr; |
331 | } |
332 | |
333 | RefPtr<IDBKey> idbUpper; |
334 | RefPtr<JSON::Object> upper; |
335 | if (keyRange->getObject("upper"_s , upper)) { |
336 | idbUpper = idbKeyFromInspectorObject(upper.get()); |
337 | if (!idbUpper) |
338 | return nullptr; |
339 | } |
340 | |
341 | bool lowerOpen; |
342 | if (!keyRange->getBoolean("lowerOpen"_s , lowerOpen)) |
343 | return nullptr; |
344 | |
345 | bool upperOpen; |
346 | if (!keyRange->getBoolean("upperOpen"_s , upperOpen)) |
347 | return nullptr; |
348 | |
349 | return IDBKeyRange::create(WTFMove(idbLower), WTFMove(idbUpper), lowerOpen, upperOpen); |
350 | } |
351 | |
352 | class OpenCursorCallback final : public EventListener { |
353 | public: |
354 | static Ref<OpenCursorCallback> create(InjectedScript injectedScript, Ref<RequestDataCallback>&& requestCallback, int skipCount, unsigned pageSize) |
355 | { |
356 | return adoptRef(*new OpenCursorCallback(injectedScript, WTFMove(requestCallback), skipCount, pageSize)); |
357 | } |
358 | |
359 | virtual ~OpenCursorCallback() = default; |
360 | |
361 | bool operator==(const EventListener& other) const override |
362 | { |
363 | return this == &other; |
364 | } |
365 | |
366 | void handleEvent(ScriptExecutionContext& context, Event& event) override |
367 | { |
368 | if (event.type() != eventNames().successEvent) { |
369 | m_requestCallback->sendFailure("Unexpected event type." ); |
370 | return; |
371 | } |
372 | |
373 | auto& request = static_cast<IDBRequest&>(*event.target()); |
374 | |
375 | auto result = request.result(); |
376 | if (result.hasException()) { |
377 | m_requestCallback->sendFailure("Could not get result in callback." ); |
378 | return; |
379 | } |
380 | |
381 | auto resultValue = result.releaseReturnValue(); |
382 | if (!WTF::holds_alternative<RefPtr<IDBCursor>>(resultValue)) { |
383 | end(false); |
384 | return; |
385 | } |
386 | |
387 | auto cursor = WTF::get<RefPtr<IDBCursor>>(resultValue); |
388 | |
389 | if (m_skipCount) { |
390 | if (cursor->advance(m_skipCount).hasException()) |
391 | m_requestCallback->sendFailure("Could not advance cursor." ); |
392 | m_skipCount = 0; |
393 | return; |
394 | } |
395 | |
396 | if (m_result->length() == m_pageSize) { |
397 | end(true); |
398 | return; |
399 | } |
400 | |
401 | // Continue cursor before making injected script calls, otherwise transaction might be finished. |
402 | if (cursor->continueFunction(nullptr).hasException()) { |
403 | m_requestCallback->sendFailure("Could not continue cursor." ); |
404 | return; |
405 | } |
406 | |
407 | auto* state = context.execState(); |
408 | auto key = toJS(*state, *state->lexicalGlobalObject(), cursor->key()); |
409 | auto primaryKey = toJS(*state, *state->lexicalGlobalObject(), cursor->primaryKey()); |
410 | auto value = deserializeIDBValueToJSValue(*state, cursor->value()); |
411 | auto dataEntry = DataEntry::create() |
412 | .setKey(m_injectedScript.wrapObject(key, String(), true)) |
413 | .setPrimaryKey(m_injectedScript.wrapObject(primaryKey, String(), true)) |
414 | .setValue(m_injectedScript.wrapObject(value, String(), true)) |
415 | .release(); |
416 | m_result->addItem(WTFMove(dataEntry)); |
417 | } |
418 | |
419 | void end(bool hasMore) |
420 | { |
421 | if (!m_requestCallback->isActive()) |
422 | return; |
423 | m_requestCallback->sendSuccess(WTFMove(m_result), hasMore); |
424 | } |
425 | |
426 | private: |
427 | OpenCursorCallback(InjectedScript injectedScript, Ref<RequestDataCallback>&& requestCallback, int skipCount, unsigned pageSize) |
428 | : EventListener(EventListener::CPPEventListenerType) |
429 | , m_injectedScript(injectedScript) |
430 | , m_requestCallback(WTFMove(requestCallback)) |
431 | , m_result(JSON::ArrayOf<DataEntry>::create()) |
432 | , m_skipCount(skipCount) |
433 | , m_pageSize(pageSize) |
434 | { |
435 | } |
436 | InjectedScript m_injectedScript; |
437 | Ref<RequestDataCallback> m_requestCallback; |
438 | Ref<JSON::ArrayOf<DataEntry>> m_result; |
439 | int m_skipCount; |
440 | unsigned m_pageSize; |
441 | }; |
442 | |
443 | class DataLoader final : public ExecutableWithDatabase { |
444 | public: |
445 | static Ref<DataLoader> create(ScriptExecutionContext* context, Ref<RequestDataCallback>&& requestCallback, const InjectedScript& injectedScript, const String& objectStoreName, const String& indexName, RefPtr<IDBKeyRange>&& idbKeyRange, int skipCount, unsigned pageSize) |
446 | { |
447 | return adoptRef(*new DataLoader(context, WTFMove(requestCallback), injectedScript, objectStoreName, indexName, WTFMove(idbKeyRange), skipCount, pageSize)); |
448 | } |
449 | |
450 | virtual ~DataLoader() = default; |
451 | |
452 | void execute(IDBDatabase& database) override |
453 | { |
454 | if (!requestCallback().isActive()) |
455 | return; |
456 | |
457 | auto idbTransaction = transactionForDatabase(&database, m_objectStoreName); |
458 | if (!idbTransaction) { |
459 | m_requestCallback->sendFailure("Could not get transaction" ); |
460 | return; |
461 | } |
462 | |
463 | auto idbObjectStore = objectStoreForTransaction(idbTransaction.get(), m_objectStoreName); |
464 | if (!idbObjectStore) { |
465 | m_requestCallback->sendFailure("Could not get object store" ); |
466 | return; |
467 | } |
468 | |
469 | TransactionActivator activator(idbTransaction.get()); |
470 | RefPtr<IDBRequest> idbRequest; |
471 | auto* exec = context() ? context()->execState() : nullptr; |
472 | if (!m_indexName.isEmpty()) { |
473 | auto idbIndex = indexForObjectStore(idbObjectStore.get(), m_indexName); |
474 | if (!idbIndex) { |
475 | m_requestCallback->sendFailure("Could not get index" ); |
476 | return; |
477 | } |
478 | |
479 | if (exec) { |
480 | auto result = idbIndex->openCursor(*exec, m_idbKeyRange.get(), IDBCursorDirection::Next); |
481 | if (!result.hasException()) |
482 | idbRequest = result.releaseReturnValue(); |
483 | } |
484 | } else { |
485 | if (exec) { |
486 | auto result = idbObjectStore->openCursor(*exec, m_idbKeyRange.get(), IDBCursorDirection::Next); |
487 | if (!result.hasException()) |
488 | idbRequest = result.releaseReturnValue(); |
489 | } |
490 | } |
491 | |
492 | if (!idbRequest) { |
493 | m_requestCallback->sendFailure("Could not open cursor to populate database data" ); |
494 | return; |
495 | } |
496 | |
497 | auto openCursorCallback = OpenCursorCallback::create(m_injectedScript, m_requestCallback.copyRef(), m_skipCount, m_pageSize); |
498 | idbRequest->addEventListener(eventNames().successEvent, WTFMove(openCursorCallback), false); |
499 | } |
500 | |
501 | RequestCallback& requestCallback() override { return m_requestCallback.get(); } |
502 | DataLoader(ScriptExecutionContext* scriptExecutionContext, Ref<RequestDataCallback>&& requestCallback, const InjectedScript& injectedScript, const String& objectStoreName, const String& indexName, RefPtr<IDBKeyRange> idbKeyRange, int skipCount, unsigned pageSize) |
503 | : ExecutableWithDatabase(scriptExecutionContext) |
504 | , m_requestCallback(WTFMove(requestCallback)) |
505 | , m_injectedScript(injectedScript) |
506 | , m_objectStoreName(objectStoreName) |
507 | , m_indexName(indexName) |
508 | , m_idbKeyRange(WTFMove(idbKeyRange)) |
509 | , m_skipCount(skipCount) |
510 | , m_pageSize(pageSize) { } |
511 | Ref<RequestDataCallback> m_requestCallback; |
512 | InjectedScript m_injectedScript; |
513 | String m_objectStoreName; |
514 | String m_indexName; |
515 | RefPtr<IDBKeyRange> m_idbKeyRange; |
516 | int m_skipCount; |
517 | unsigned m_pageSize; |
518 | }; |
519 | |
520 | } // namespace |
521 | |
522 | InspectorIndexedDBAgent::InspectorIndexedDBAgent(PageAgentContext& context) |
523 | : InspectorAgentBase("IndexedDB"_s , context) |
524 | , m_injectedScriptManager(context.injectedScriptManager) |
525 | , m_backendDispatcher(Inspector::IndexedDBBackendDispatcher::create(context.backendDispatcher, this)) |
526 | , m_inspectedPage(context.inspectedPage) |
527 | { |
528 | } |
529 | |
530 | void InspectorIndexedDBAgent::didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*) |
531 | { |
532 | } |
533 | |
534 | void InspectorIndexedDBAgent::willDestroyFrontendAndBackend(Inspector::DisconnectReason) |
535 | { |
536 | ErrorString unused; |
537 | disable(unused); |
538 | } |
539 | |
540 | void InspectorIndexedDBAgent::enable(ErrorString&) |
541 | { |
542 | } |
543 | |
544 | void InspectorIndexedDBAgent::disable(ErrorString&) |
545 | { |
546 | } |
547 | |
548 | static ErrorStringOr<Document*> documentFromFrame(Frame* frame) |
549 | { |
550 | Document* document = frame ? frame->document() : nullptr; |
551 | if (!document) |
552 | return makeUnexpected("No document for given frame found"_s ); |
553 | |
554 | return document; |
555 | } |
556 | |
557 | static ErrorStringOr<IDBFactory*> IDBFactoryFromDocument(Document* document) |
558 | { |
559 | DOMWindow* domWindow = document->domWindow(); |
560 | if (!domWindow) |
561 | return makeUnexpected("No IndexedDB factory for given frame found"_s ); |
562 | |
563 | IDBFactory* idbFactory = DOMWindowIndexedDatabase::indexedDB(*domWindow); |
564 | if (!idbFactory) |
565 | makeUnexpected("No IndexedDB factory for given frame found"_s ); |
566 | |
567 | return idbFactory; |
568 | } |
569 | |
570 | static bool getDocumentAndIDBFactoryFromFrameOrSendFailure(Frame* frame, Document*& out_document, IDBFactory*& out_idbFactory, BackendDispatcher::CallbackBase& callback) |
571 | { |
572 | ErrorStringOr<Document*> document = documentFromFrame(frame); |
573 | if (!document.has_value()) { |
574 | callback.sendFailure(document.error()); |
575 | return false; |
576 | } |
577 | |
578 | ErrorStringOr<IDBFactory*> idbFactory = IDBFactoryFromDocument(document.value()); |
579 | if (!idbFactory.has_value()) { |
580 | callback.sendFailure(idbFactory.error()); |
581 | return false; |
582 | } |
583 | |
584 | out_document = document.value(); |
585 | out_idbFactory = idbFactory.value(); |
586 | return true; |
587 | } |
588 | |
589 | void InspectorIndexedDBAgent::requestDatabaseNames(const String& securityOrigin, Ref<RequestDatabaseNamesCallback>&& callback) |
590 | { |
591 | auto* frame = InspectorPageAgent::findFrameWithSecurityOrigin(m_inspectedPage, securityOrigin); |
592 | Document* document; |
593 | IDBFactory* idbFactory; |
594 | if (!getDocumentAndIDBFactoryFromFrameOrSendFailure(frame, document, idbFactory, callback)) |
595 | return; |
596 | |
597 | auto& openingOrigin = document->securityOrigin(); |
598 | auto& topOrigin = document->topOrigin(); |
599 | idbFactory->getAllDatabaseNames(topOrigin, openingOrigin, [callback = WTFMove(callback)](auto& databaseNames) { |
600 | if (!callback->isActive()) |
601 | return; |
602 | |
603 | Ref<JSON::ArrayOf<String>> databaseNameArray = JSON::ArrayOf<String>::create(); |
604 | for (auto& databaseName : databaseNames) |
605 | databaseNameArray->addItem(databaseName); |
606 | |
607 | callback->sendSuccess(WTFMove(databaseNameArray)); |
608 | }); |
609 | } |
610 | |
611 | void InspectorIndexedDBAgent::requestDatabase(const String& securityOrigin, const String& databaseName, Ref<RequestDatabaseCallback>&& callback) |
612 | { |
613 | auto* frame = InspectorPageAgent::findFrameWithSecurityOrigin(m_inspectedPage, securityOrigin); |
614 | Document* document; |
615 | IDBFactory* idbFactory; |
616 | if (!getDocumentAndIDBFactoryFromFrameOrSendFailure(frame, document, idbFactory, callback)) |
617 | return; |
618 | |
619 | Ref<DatabaseLoader> databaseLoader = DatabaseLoader::create(document, WTFMove(callback)); |
620 | databaseLoader->start(idbFactory, &document->securityOrigin(), databaseName); |
621 | } |
622 | |
623 | void InspectorIndexedDBAgent::requestData(const String& securityOrigin, const String& databaseName, const String& objectStoreName, const String& indexName, int skipCount, int pageSize, const JSON::Object* keyRange, Ref<RequestDataCallback>&& callback) |
624 | { |
625 | auto* frame = InspectorPageAgent::findFrameWithSecurityOrigin(m_inspectedPage, securityOrigin); |
626 | Document* document; |
627 | IDBFactory* idbFactory; |
628 | if (!getDocumentAndIDBFactoryFromFrameOrSendFailure(frame, document, idbFactory, callback)) |
629 | return; |
630 | |
631 | InjectedScript injectedScript = m_injectedScriptManager.injectedScriptFor(mainWorldExecState(frame)); |
632 | RefPtr<IDBKeyRange> idbKeyRange = keyRange ? idbKeyRangeFromKeyRange(keyRange) : nullptr; |
633 | if (keyRange && !idbKeyRange) { |
634 | callback->sendFailure("Can not parse key range."_s ); |
635 | return; |
636 | } |
637 | |
638 | Ref<DataLoader> dataLoader = DataLoader::create(document, WTFMove(callback), injectedScript, objectStoreName, indexName, WTFMove(idbKeyRange), skipCount, pageSize); |
639 | dataLoader->start(idbFactory, &document->securityOrigin(), databaseName); |
640 | } |
641 | |
642 | namespace { |
643 | |
644 | class ClearObjectStoreListener final : public EventListener { |
645 | WTF_MAKE_NONCOPYABLE(ClearObjectStoreListener); |
646 | public: |
647 | static Ref<ClearObjectStoreListener> create(Ref<ClearObjectStoreCallback> requestCallback) |
648 | { |
649 | return adoptRef(*new ClearObjectStoreListener(WTFMove(requestCallback))); |
650 | } |
651 | |
652 | virtual ~ClearObjectStoreListener() = default; |
653 | |
654 | bool operator==(const EventListener& other) const override |
655 | { |
656 | return this == &other; |
657 | } |
658 | |
659 | void handleEvent(ScriptExecutionContext&, Event& event) override |
660 | { |
661 | if (!m_requestCallback->isActive()) |
662 | return; |
663 | if (event.type() != eventNames().completeEvent) { |
664 | m_requestCallback->sendFailure("Unexpected event type." ); |
665 | return; |
666 | } |
667 | |
668 | m_requestCallback->sendSuccess(); |
669 | } |
670 | private: |
671 | ClearObjectStoreListener(Ref<ClearObjectStoreCallback>&& requestCallback) |
672 | : EventListener(EventListener::CPPEventListenerType) |
673 | , m_requestCallback(WTFMove(requestCallback)) |
674 | { |
675 | } |
676 | |
677 | Ref<ClearObjectStoreCallback> m_requestCallback; |
678 | }; |
679 | |
680 | class ClearObjectStore final : public ExecutableWithDatabase { |
681 | public: |
682 | static Ref<ClearObjectStore> create(ScriptExecutionContext* context, const String& objectStoreName, Ref<ClearObjectStoreCallback>&& requestCallback) |
683 | { |
684 | return adoptRef(*new ClearObjectStore(context, objectStoreName, WTFMove(requestCallback))); |
685 | } |
686 | |
687 | ClearObjectStore(ScriptExecutionContext* context, const String& objectStoreName, Ref<ClearObjectStoreCallback>&& requestCallback) |
688 | : ExecutableWithDatabase(context) |
689 | , m_objectStoreName(objectStoreName) |
690 | , m_requestCallback(WTFMove(requestCallback)) |
691 | { |
692 | } |
693 | |
694 | void execute(IDBDatabase& database) override |
695 | { |
696 | if (!requestCallback().isActive()) |
697 | return; |
698 | |
699 | auto idbTransaction = transactionForDatabase(&database, m_objectStoreName, IDBTransactionMode::Readwrite); |
700 | if (!idbTransaction) { |
701 | m_requestCallback->sendFailure("Could not get transaction" ); |
702 | return; |
703 | } |
704 | |
705 | auto idbObjectStore = objectStoreForTransaction(idbTransaction.get(), m_objectStoreName); |
706 | if (!idbObjectStore) { |
707 | m_requestCallback->sendFailure("Could not get object store" ); |
708 | return; |
709 | } |
710 | |
711 | TransactionActivator activator(idbTransaction.get()); |
712 | RefPtr<IDBRequest> idbRequest; |
713 | if (auto* exec = context() ? context()->execState() : nullptr) { |
714 | auto result = idbObjectStore->clear(*exec); |
715 | ASSERT(!result.hasException()); |
716 | if (result.hasException()) { |
717 | m_requestCallback->sendFailure(makeString("Could not clear object store '" , m_objectStoreName, "': " , static_cast<int>(result.releaseException().code()))); |
718 | return; |
719 | } |
720 | idbRequest = result.releaseReturnValue(); |
721 | } |
722 | |
723 | idbTransaction->addEventListener(eventNames().completeEvent, ClearObjectStoreListener::create(m_requestCallback.copyRef()), false); |
724 | } |
725 | |
726 | RequestCallback& requestCallback() override { return m_requestCallback.get(); } |
727 | private: |
728 | const String m_objectStoreName; |
729 | Ref<ClearObjectStoreCallback> m_requestCallback; |
730 | }; |
731 | |
732 | } // anonymous namespace |
733 | |
734 | void InspectorIndexedDBAgent::clearObjectStore(const String& securityOrigin, const String& databaseName, const String& objectStoreName, Ref<ClearObjectStoreCallback>&& callback) |
735 | { |
736 | auto* frame = InspectorPageAgent::findFrameWithSecurityOrigin(m_inspectedPage, securityOrigin); |
737 | Document* document; |
738 | IDBFactory* idbFactory; |
739 | if (!getDocumentAndIDBFactoryFromFrameOrSendFailure(frame, document, idbFactory, callback)) |
740 | return; |
741 | |
742 | Ref<ClearObjectStore> clearObjectStore = ClearObjectStore::create(document, objectStoreName, WTFMove(callback)); |
743 | clearObjectStore->start(idbFactory, &document->securityOrigin(), databaseName); |
744 | } |
745 | |
746 | } // namespace WebCore |
747 | #endif // ENABLE(INDEXED_DATABASE) |
748 | |