Indexed DB: Correctly dispatch events from script
Events dispatched via dispatchEvent() in script should not trigger
side effects in Indexed DB objects. For untrusted events, propagate
correctly but otherwise early-exit from the dispatch functions.
Bug: 1032890
Change-Id: If4057ad2820419ef363e8e5f21670b3565946388
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1983272
Commit-Queue: Daniel Murphy <[email protected]>
Reviewed-by: Daniel Murphy <[email protected]>
Cr-Commit-Position: refs/heads/master@{#728285}
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_database.cc b/third_party/blink/renderer/modules/indexeddb/idb_database.cc
index 97f5b50..5cd42e5b 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_database.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_database.cc
@@ -521,11 +521,18 @@
DispatchEventResult IDBDatabase::DispatchEventInternal(Event& event) {
IDB_TRACE("IDBDatabase::dispatchEvent");
- if (!GetExecutionContext())
- return DispatchEventResult::kCanceledBeforeDispatch;
+
+ event.SetTarget(this);
+
+ // If this event originated from script, it should have no side effects.
+ if (!event.isTrusted())
+ return EventTarget::DispatchEventInternal(event);
DCHECK(event.type() == event_type_names::kVersionchange ||
event.type() == event_type_names::kClose);
+ if (!GetExecutionContext())
+ return DispatchEventResult::kCanceledBeforeDispatch;
+
DispatchEventResult dispatch_result =
EventTarget::DispatchEventInternal(event);
if (event.type() == event_type_names::kVersionchange && !close_pending_ &&
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_open_db_request.cc b/third_party/blink/renderer/modules/indexeddb/idb_open_db_request.cc
index d342cc5..44e60df 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_open_db_request.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_open_db_request.cc
@@ -180,6 +180,15 @@
}
DispatchEventResult IDBOpenDBRequest::DispatchEventInternal(Event& event) {
+ // If this event originated from script, it should have no side effects.
+ if (!event.isTrusted())
+ return IDBRequest::DispatchEventInternal(event);
+ DCHECK(event.type() == event_type_names::kSuccess ||
+ event.type() == event_type_names::kError ||
+ event.type() == event_type_names::kBlocked ||
+ event.type() == event_type_names::kUpgradeneeded)
+ << "event type was " << event.type();
+
// If the connection closed between onUpgradeNeeded and the delivery of the
// "success" event, an "error" event should be fired instead.
if (event.type() == event_type_names::kSuccess &&
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_request.cc b/third_party/blink/renderer/modules/indexeddb/idb_request.cc
index cb73df4..59dd47d 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_request.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_request.cc
@@ -625,6 +625,29 @@
DispatchEventResult IDBRequest::DispatchEventInternal(Event& event) {
IDB_TRACE("IDBRequest::dispatchEvent");
+
+ event.SetTarget(this);
+
+ HeapVector<Member<EventTarget>> targets;
+ targets.push_back(this);
+ if (transaction_ && !prevent_propagation_) {
+ // Per spec: "A request's get the parent algorithm returns the request’s
+ // transaction."
+ targets.push_back(transaction_);
+ // Per spec: "A transaction's get the parent algorithm returns the
+ // transaction’s connection."
+ targets.push_back(transaction_->db());
+ }
+
+ // If this event originated from script, it should have no side effects.
+ if (!event.isTrusted())
+ return IDBEventDispatcher::Dispatch(event, targets);
+ DCHECK(event.type() == event_type_names::kSuccess ||
+ event.type() == event_type_names::kError ||
+ event.type() == event_type_names::kBlocked ||
+ event.type() == event_type_names::kUpgradeneeded)
+ << "event type was " << event.type();
+
if (!GetExecutionContext())
return DispatchEventResult::kCanceledBeforeDispatch;
DCHECK_EQ(ready_state_, PENDING);
@@ -634,17 +657,6 @@
if (event.type() != event_type_names::kBlocked)
ready_state_ = DONE;
- HeapVector<Member<EventTarget>> targets;
- targets.push_back(this);
- if (transaction_ && !prevent_propagation_) {
- targets.push_back(transaction_);
- // If there ever are events that are associated with a database but
- // that do not have a transaction, then this will not work and we need
- // this object to actually hold a reference to the database (to ensure
- // it stays alive).
- targets.push_back(transaction_->db());
- }
-
// Cursor properties should not be updated until the success event is being
// dispatched.
IDBCursor* cursor_to_notify = nullptr;
@@ -662,13 +674,6 @@
did_fire_upgrade_needed_event_ = true;
}
- // FIXME: When we allow custom event dispatching, this will probably need to
- // change.
- DCHECK(event.type() == event_type_names::kSuccess ||
- event.type() == event_type_names::kError ||
- event.type() == event_type_names::kBlocked ||
- event.type() == event_type_names::kUpgradeneeded)
- << "event type was " << event.type();
const bool set_transaction_active =
transaction_ &&
(event.type() == event_type_names::kSuccess ||
@@ -692,7 +697,6 @@
// has completed.
metrics_.RecordAndReset();
- event.SetTarget(this);
DispatchEventResult dispatch_result =
IDBEventDispatcher::Dispatch(event, targets);
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_transaction.cc b/third_party/blink/renderer/modules/indexeddb/idb_transaction.cc
index 28bf2cc..36b43df2 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_transaction.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_transaction.cc
@@ -564,6 +564,21 @@
DispatchEventResult IDBTransaction::DispatchEventInternal(Event& event) {
IDB_TRACE1("IDBTransaction::dispatchEvent", "txn.id", id_);
+
+ event.SetTarget(this);
+
+ // Per spec: "A transaction's get the parent algorithm returns the
+ // transaction’s connection."
+ HeapVector<Member<EventTarget>> targets;
+ targets.push_back(this);
+ targets.push_back(db());
+
+ // If this event originated from script, it should have no side effects.
+ if (!event.isTrusted())
+ return IDBEventDispatcher::Dispatch(event, targets);
+ DCHECK(event.type() == event_type_names::kComplete ||
+ event.type() == event_type_names::kAbort);
+
if (!GetExecutionContext()) {
state_ = kFinished;
return DispatchEventResult::kCanceledBeforeDispatch;
@@ -574,14 +589,6 @@
DCHECK_EQ(event.target(), this);
state_ = kFinished;
- HeapVector<Member<EventTarget>> targets;
- targets.push_back(this);
- targets.push_back(db());
-
- // FIXME: When we allow custom event dispatching, this will probably need to
- // change.
- DCHECK(event.type() == event_type_names::kComplete ||
- event.type() == event_type_names::kAbort);
DispatchEventResult dispatch_result =
IDBEventDispatcher::Dispatch(event, targets);
// FIXME: Try to construct a test where |this| outlives openDBRequest and we
diff --git a/third_party/blink/web_tests/storage/indexeddb/dispatch-events.html b/third_party/blink/web_tests/storage/indexeddb/dispatch-events.html
new file mode 100644
index 0000000..569fe7c
--- /dev/null
+++ b/third_party/blink/web_tests/storage/indexeddb/dispatch-events.html
@@ -0,0 +1,266 @@
+<!DOCTYPE html>
+<title>IndexedDB: Dispatching events from script</title>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script>
+
+function idb_test(func, name) {
+ async_test(t => {
+ const dbname = location.pathname + ' - ' + t.name;
+ const open_request = indexedDB.open(dbname);
+ t.add_cleanup(() => { indexedDB.deleteDatabase(dbname); });
+ func(t, open_request);
+ }, name);
+}
+
+//
+// IDBOpenDBRequest
+//
+
+// A regression test for http://crbug.com/1032890
+idb_test((t, open_request) => {
+ open_request.onerror = t.unreached_func();
+ open_request.onsuccess = t.step_func_done();
+ open_request.dispatchEvent(new ErrorEvent({}));
+
+}, 'Dispatching a generic event at an IDBOpenDBRequest should not crash');
+
+idb_test((t, open_request) => {
+ let events_seen = 0;
+ open_request.onerror = t.unreached_func();
+ open_request.onblocked = t.step_func(e => {
+ ++events_seen;
+ });
+ open_request.onsuccess = t.step_func_done(e => {
+ assert_equals(events_seen, 1, 'Should have seen event');
+ });
+ open_request.dispatchEvent(new Event('blocked'));
+}, 'Dispatching a synthetic blocked event at an IDBOpenDBRequest');
+
+idb_test((t, open_request) => {
+ let events_seen = 0;
+ open_request.onerror = t.step_func(e => {
+ ++events_seen;
+ });
+ open_request.onsuccess = t.step_func_done(e => {
+ assert_equals(events_seen, 1, 'Should have seen event');
+ });
+ open_request.dispatchEvent(new Event('error'));
+}, 'Dispatching a synthetic error event at an IDBOpenDBRequest');
+
+idb_test((t, open_request) => {
+ let events_seen = 0;
+ open_request.onerror = t.unreached_func();
+ open_request.onupgradeneeded = t.step_func(e => {
+ ++events_seen;
+ });
+ open_request.onsuccess = t.step_func_done(e => {
+ assert_equals(events_seen, 2, 'Should have seen both events');
+ });
+ open_request.dispatchEvent(new Event('upgradeneeded'));
+}, 'Dispatching a synthetic upgradeneeded event at an IDBOpenDBRequest');
+
+idb_test((t, open_request) => {
+ let events_seen = 0;
+ open_request.onerror = t.unreached_func();
+ open_request.onsuccess = t.step_func(e => {
+ ++events_seen;
+ if (e.isTrusted) {
+ assert_equals(events_seen, 2, 'Should have seen both events');
+ t.done();
+ }
+ });
+ open_request.dispatchEvent(new Event('success'));
+}, 'Dispatching a synthetic success event at an IDBOpenDBRequest');
+
+//
+// IDBTransaction
+//
+
+idb_test((t, open_request) => {
+ open_request.onerror = t.unreached_func();
+ open_request.onupgradeneeded = t.step_func(e => {
+ const tx = open_request.transaction;
+ tx.dispatchEvent(new Event('generic'));
+ });
+ open_request.onsuccess = t.step_func_done();
+}, 'Dispatching a generic event at an IDBTransaction should not crash');
+
+[
+ // Events that would be propagated from an IDBRequest:
+ 'success', 'error',
+
+ // Events that would be fired at an IDBTransaction:
+ 'abort'
+].forEach(type => {
+ idb_test((t, open_request) => {
+ let events_seen = 0;
+ open_request.onerror = t.unreached_func();
+ open_request.onupgradeneeded = t.step_func(e => {
+ const tx = open_request.transaction;
+ tx.addEventListener(type, t.step_func(e => {
+ assert_false(e.isTrusted, 'Event should not be trusted');
+ ++events_seen;
+ }));
+ tx.dispatchEvent(new Event(type));
+ });
+ open_request.onsuccess = t.step_func_done(e => {
+ assert_equals(events_seen, 1, 'Should have seen event');
+ });
+ }, `Dispatching a synthetic ${type} event at an IDBTransaction`);
+});
+
+idb_test((t, open_request) => {
+ let events_seen = 0;
+ open_request.onerror = t.unreached_func();
+ open_request.onupgradeneeded = t.step_func(e => {
+ const tx = open_request.transaction;
+ tx.oncomplete = t.step_func(e => {
+ ++events_seen;
+ });
+ tx.dispatchEvent(new Event('complete'));
+ });
+ open_request.onsuccess = t.step_func_done(e => {
+ assert_equals(events_seen, 2, 'Should have seen both events');
+ });
+}, 'Dispatching a synthetic complete event at an IDBTransaction');
+
+idb_test((t, open_request) => {
+ const targets = []
+ open_request.onerror = t.unreached_func();
+ open_request.onupgradeneeded = t.step_func(e => {
+ const db = open_request.result;
+ const tx = open_request.transaction;
+ [db, tx].forEach(target => target.addEventListener(
+ 'generic',
+ t.step_func(e => { targets.push(e.currentTarget.constructor.name); })
+ ));
+ tx.dispatchEvent(new Event('generic', {bubbles: true}));
+ });
+ open_request.onsuccess = t.step_func_done(e => {
+ assert_equals(targets.length, 2, 'Event should propagate');
+ assert_equals(targets[0], 'IDBTransaction',
+ 'First target should be transaction');
+ assert_equals(targets[1], 'IDBDatabase',
+ 'Second target should be database');
+ });
+}, 'Dispatching a generic event at an IDBTransaction propagates correctly');
+
+
+//
+// IDBRequest
+//
+
+idb_test((t, open_request) => {
+ let events_seen = 0;
+ open_request.onerror = t.unreached_func();
+ open_request.onupgradeneeded = t.step_func(e => {
+ const db = open_request.result;
+ const store = db.createObjectStore('store');
+ const request = store.get(0);
+ request.dispatchEvent(new Event('generic'));
+ });
+ open_request.onsuccess = t.step_func_done();
+}, 'Dispatching a generic event at an IDBRequest should not crash');
+
+idb_test((t, open_request) => {
+ let events_seen = 0;
+ open_request.onerror = t.unreached_func();
+ open_request.onupgradeneeded = t.step_func(e => {
+ const db = open_request.result;
+ const store = db.createObjectStore('store');
+ const request = store.get(0);
+ request.onerror = t.step_func(e => {
+ ++events_seen;
+ });
+ request.dispatchEvent(new Event('error'));
+ });
+ open_request.onsuccess = t.step_func_done(e => {
+ assert_equals(events_seen, 1, 'Should have seen the event');
+ });
+}, 'Dispatching a synthetic error event at an IDBRequest');
+
+idb_test((t, open_request) => {
+ let events_seen = 0;
+ open_request.onerror = t.unreached_func();
+ open_request.onupgradeneeded = t.step_func(e => {
+ const db = open_request.result;
+ const store = db.createObjectStore('store');
+ const request = store.get(0);
+ request.onsuccess = t.step_func(e => {
+ ++events_seen;
+ });
+ request.dispatchEvent(new Event('success'));
+ });
+ open_request.onsuccess = t.step_func_done(e => {
+ assert_equals(events_seen, 2, 'Should have seen both events');
+ });
+}, 'Dispatching a synthetic success event at an IDBRequest');
+
+idb_test((t, open_request) => {
+ const targets = []
+ open_request.onerror = t.unreached_func();
+ open_request.onupgradeneeded = t.step_func(e => {
+ const db = open_request.result;
+ const tx = open_request.transaction;
+ const store = db.createObjectStore('store');
+ const request = store.get(0);
+ [db, tx, request].forEach(target => target.addEventListener(
+ 'generic',
+ t.step_func(e => { targets.push(e.currentTarget.constructor.name); })
+ ));
+ request.dispatchEvent(new Event('generic', {bubbles: true}));
+ });
+ open_request.onsuccess = t.step_func_done(e => {
+ assert_equals(targets.length, 3, 'Event should propagate');
+ assert_equals(targets[0], 'IDBRequest',
+ 'First target should be request');
+ assert_equals(targets[1], 'IDBTransaction',
+ 'Second target should be transaction');
+ assert_equals(targets[2], 'IDBDatabase',
+ 'Third target should be database');
+ });
+}, 'Dispatching a generic event at an IDBRequest propagates correctly');
+
+//
+// IDBDatabase
+//
+
+idb_test((t, open_request) => {
+ let events_seen = 0;
+ open_request.onerror = t.unreached_func();
+ open_request.onupgradeneeded = t.step_func(e => {
+ const db = open_request.result;
+ db.dispatchEvent(new Event('generic'));
+ });
+ open_request.onsuccess = t.step_func_done();
+}, 'Dispatching a generic event at an IDBDatabase');
+
+[
+ // Events that would be propagated from an IDBRequest:
+ 'success', 'error',
+
+ // Events that would be propagated from an IDBTransaction:
+ 'complete', 'abort',
+
+ // Events that would be fired at an IDBDatabase:
+ 'versionchange', 'close'
+].forEach(type => {
+ idb_test((t, open_request) => {
+ let events_seen = 0;
+ open_request.onerror = t.unreached_func();
+ open_request.onupgradeneeded = t.step_func(e => {
+ const db = open_request.result;
+ db.addEventListener(type, t.step_func(e => {
+ assert_false(e.isTrusted, 'Event should not be trusted');
+ ++events_seen;
+ }));
+ db.dispatchEvent(new Event(type));
+ });
+ open_request.onsuccess = t.step_func_done(e => {
+ assert_equals(events_seen, 1, 'Should have seen the event');
+ });
+ }, `Dispatching a synthetic ${type} event at an IDBDatabase`);
+ });
+
+</script>