Commit 96f63f77 authored by Joshua Bell's avatar Joshua Bell Committed by Commit Bot

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 <dmurph@chromium.org>
Reviewed-by: default avatarDaniel Murphy <dmurph@chromium.org>
Cr-Commit-Position: refs/heads/master@{#728285}
parent 2b9bbfbe
...@@ -521,11 +521,18 @@ void IDBDatabase::EnqueueEvent(Event* event) { ...@@ -521,11 +521,18 @@ void IDBDatabase::EnqueueEvent(Event* event) {
DispatchEventResult IDBDatabase::DispatchEventInternal(Event& event) { DispatchEventResult IDBDatabase::DispatchEventInternal(Event& event) {
IDB_TRACE("IDBDatabase::dispatchEvent"); 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 || DCHECK(event.type() == event_type_names::kVersionchange ||
event.type() == event_type_names::kClose); event.type() == event_type_names::kClose);
if (!GetExecutionContext())
return DispatchEventResult::kCanceledBeforeDispatch;
DispatchEventResult dispatch_result = DispatchEventResult dispatch_result =
EventTarget::DispatchEventInternal(event); EventTarget::DispatchEventInternal(event);
if (event.type() == event_type_names::kVersionchange && !close_pending_ && if (event.type() == event_type_names::kVersionchange && !close_pending_ &&
......
...@@ -180,6 +180,15 @@ bool IDBOpenDBRequest::ShouldEnqueueEvent() const { ...@@ -180,6 +180,15 @@ bool IDBOpenDBRequest::ShouldEnqueueEvent() const {
} }
DispatchEventResult IDBOpenDBRequest::DispatchEventInternal(Event& event) { 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 // If the connection closed between onUpgradeNeeded and the delivery of the
// "success" event, an "error" event should be fired instead. // "success" event, an "error" event should be fired instead.
if (event.type() == event_type_names::kSuccess && if (event.type() == event_type_names::kSuccess &&
......
...@@ -625,26 +625,38 @@ ExecutionContext* IDBRequest::GetExecutionContext() const { ...@@ -625,26 +625,38 @@ ExecutionContext* IDBRequest::GetExecutionContext() const {
DispatchEventResult IDBRequest::DispatchEventInternal(Event& event) { DispatchEventResult IDBRequest::DispatchEventInternal(Event& event) {
IDB_TRACE("IDBRequest::dispatchEvent"); IDB_TRACE("IDBRequest::dispatchEvent");
if (!GetExecutionContext())
return DispatchEventResult::kCanceledBeforeDispatch;
DCHECK_EQ(ready_state_, PENDING);
DCHECK(has_pending_activity_);
DCHECK_EQ(event.target(), this);
if (event.type() != event_type_names::kBlocked) event.SetTarget(this);
ready_state_ = DONE;
HeapVector<Member<EventTarget>> targets; HeapVector<Member<EventTarget>> targets;
targets.push_back(this); targets.push_back(this);
if (transaction_ && !prevent_propagation_) { if (transaction_ && !prevent_propagation_) {
// Per spec: "A request's get the parent algorithm returns the request’s
// transaction."
targets.push_back(transaction_); targets.push_back(transaction_);
// If there ever are events that are associated with a database but // Per spec: "A transaction's get the parent algorithm returns the
// that do not have a transaction, then this will not work and we need // transaction’s connection."
// this object to actually hold a reference to the database (to ensure
// it stays alive).
targets.push_back(transaction_->db()); 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);
DCHECK(has_pending_activity_);
DCHECK_EQ(event.target(), this);
if (event.type() != event_type_names::kBlocked)
ready_state_ = DONE;
// Cursor properties should not be updated until the success event is being // Cursor properties should not be updated until the success event is being
// dispatched. // dispatched.
IDBCursor* cursor_to_notify = nullptr; IDBCursor* cursor_to_notify = nullptr;
...@@ -662,13 +674,6 @@ DispatchEventResult IDBRequest::DispatchEventInternal(Event& event) { ...@@ -662,13 +674,6 @@ DispatchEventResult IDBRequest::DispatchEventInternal(Event& event) {
did_fire_upgrade_needed_event_ = true; 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 = const bool set_transaction_active =
transaction_ && transaction_ &&
(event.type() == event_type_names::kSuccess || (event.type() == event_type_names::kSuccess ||
...@@ -692,7 +697,6 @@ DispatchEventResult IDBRequest::DispatchEventInternal(Event& event) { ...@@ -692,7 +697,6 @@ DispatchEventResult IDBRequest::DispatchEventInternal(Event& event) {
// has completed. // has completed.
metrics_.RecordAndReset(); metrics_.RecordAndReset();
event.SetTarget(this);
DispatchEventResult dispatch_result = DispatchEventResult dispatch_result =
IDBEventDispatcher::Dispatch(event, targets); IDBEventDispatcher::Dispatch(event, targets);
......
...@@ -564,6 +564,21 @@ const char* IDBTransaction::InactiveErrorMessage() const { ...@@ -564,6 +564,21 @@ const char* IDBTransaction::InactiveErrorMessage() const {
DispatchEventResult IDBTransaction::DispatchEventInternal(Event& event) { DispatchEventResult IDBTransaction::DispatchEventInternal(Event& event) {
IDB_TRACE1("IDBTransaction::dispatchEvent", "txn.id", id_); 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()) { if (!GetExecutionContext()) {
state_ = kFinished; state_ = kFinished;
return DispatchEventResult::kCanceledBeforeDispatch; return DispatchEventResult::kCanceledBeforeDispatch;
...@@ -574,14 +589,6 @@ DispatchEventResult IDBTransaction::DispatchEventInternal(Event& event) { ...@@ -574,14 +589,6 @@ DispatchEventResult IDBTransaction::DispatchEventInternal(Event& event) {
DCHECK_EQ(event.target(), this); DCHECK_EQ(event.target(), this);
state_ = kFinished; 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 = DispatchEventResult dispatch_result =
IDBEventDispatcher::Dispatch(event, targets); IDBEventDispatcher::Dispatch(event, targets);
// FIXME: Try to construct a test where |this| outlives openDBRequest and we // FIXME: Try to construct a test where |this| outlives openDBRequest and we
......
<!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>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment