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 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include "config.h"
30 #include "modules/indexeddb/IDBRequest.h"
31
32 #include "bindings/core/v8/ExceptionState.h"
33 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
34 #include "bindings/modules/v8/IDBBindingUtilities.h"
35 #include "core/dom/ExecutionContext.h"
36 #include "core/events/EventQueue.h"
37 #include "modules/IndexedDBNames.h"
38 #include "modules/indexeddb/IDBCursorWithValue.h"
39 #include "modules/indexeddb/IDBDatabase.h"
40 #include "modules/indexeddb/IDBEventDispatcher.h"
41 #include "modules/indexeddb/IDBPendingTransactionMonitor.h"
42 #include "modules/indexeddb/IDBTracing.h"
43 #include "platform/SharedBuffer.h"
44 #include "public/platform/WebBlobInfo.h"
45
46 using blink::WebIDBCursor;
47
48 namespace blink {
49
create(ScriptState * scriptState,IDBAny * source,IDBTransaction * transaction)50 IDBRequest* IDBRequest::create(ScriptState* scriptState, IDBAny* source, IDBTransaction* transaction)
51 {
52 IDBRequest* request = adoptRefCountedGarbageCollectedWillBeNoop(new IDBRequest(scriptState, source, transaction));
53 request->suspendIfNeeded();
54 // Requests associated with IDBFactory (open/deleteDatabase/getDatabaseNames) are not associated with transactions.
55 if (transaction)
56 transaction->registerRequest(request);
57 return request;
58 }
59
IDBRequest(ScriptState * scriptState,IDBAny * source,IDBTransaction * transaction)60 IDBRequest::IDBRequest(ScriptState* scriptState, IDBAny* source, IDBTransaction* transaction)
61 : ActiveDOMObject(scriptState->executionContext())
62 , m_contextStopped(false)
63 , m_transaction(transaction)
64 , m_readyState(PENDING)
65 , m_requestAborted(false)
66 , m_scriptState(scriptState)
67 , m_source(source)
68 , m_hasPendingActivity(true)
69 , m_cursorType(IndexedDB::CursorKeyAndValue)
70 , m_cursorDirection(WebIDBCursorDirectionNext)
71 , m_pendingCursor(nullptr)
72 , m_didFireUpgradeNeededEvent(false)
73 , m_preventPropagation(false)
74 , m_resultDirty(true)
75 {
76 }
77
~IDBRequest()78 IDBRequest::~IDBRequest()
79 {
80 ASSERT(m_readyState == DONE || m_readyState == EarlyDeath || !executionContext());
81 ASSERT(!m_blobInfo || m_blobInfo->size() == 0);
82 }
83
dispose()84 void IDBRequest::dispose()
85 {
86 handleBlobAcks();
87 }
88
trace(Visitor * visitor)89 void IDBRequest::trace(Visitor* visitor)
90 {
91 visitor->trace(m_transaction);
92 visitor->trace(m_source);
93 visitor->trace(m_result);
94 visitor->trace(m_error);
95 #if ENABLE(OILPAN)
96 visitor->trace(m_enqueuedEvents);
97 #endif
98 visitor->trace(m_pendingCursor);
99 visitor->trace(m_cursorKey);
100 visitor->trace(m_cursorPrimaryKey);
101 EventTargetWithInlineData::trace(visitor);
102 }
103
result(ExceptionState & exceptionState)104 ScriptValue IDBRequest::result(ExceptionState& exceptionState)
105 {
106 if (m_readyState != DONE) {
107 exceptionState.throwDOMException(InvalidStateError, IDBDatabase::requestNotFinishedErrorMessage);
108 return ScriptValue();
109 }
110 if (m_contextStopped || !executionContext())
111 return ScriptValue();
112 m_resultDirty = false;
113 ScriptValue value = idbAnyToScriptValue(m_scriptState.get(), m_result);
114 handleBlobAcks();
115 return value;
116 }
117
error(ExceptionState & exceptionState) const118 PassRefPtrWillBeRawPtr<DOMError> IDBRequest::error(ExceptionState& exceptionState) const
119 {
120 if (m_readyState != DONE) {
121 exceptionState.throwDOMException(InvalidStateError, IDBDatabase::requestNotFinishedErrorMessage);
122 return nullptr;
123 }
124 return m_error;
125 }
126
source() const127 ScriptValue IDBRequest::source() const
128 {
129 if (m_contextStopped || !executionContext())
130 return ScriptValue();
131
132 return idbAnyToScriptValue(m_scriptState.get(), m_source);
133 }
134
readyState() const135 const String& IDBRequest::readyState() const
136 {
137 ASSERT(m_readyState == PENDING || m_readyState == DONE);
138
139 if (m_readyState == PENDING)
140 return IndexedDBNames::pending;
141
142 return IndexedDBNames::done;
143 }
144
abort()145 void IDBRequest::abort()
146 {
147 ASSERT(!m_requestAborted);
148 if (m_contextStopped || !executionContext())
149 return;
150 ASSERT(m_readyState == PENDING || m_readyState == DONE);
151 if (m_readyState == DONE)
152 return;
153
154 EventQueue* eventQueue = executionContext()->eventQueue();
155 for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) {
156 bool removed = eventQueue->cancelEvent(m_enqueuedEvents[i].get());
157 ASSERT_UNUSED(removed, removed);
158 }
159 m_enqueuedEvents.clear();
160
161 m_error.clear();
162 m_result.clear();
163 onError(DOMError::create(AbortError, "The transaction was aborted, so the request cannot be fulfilled."));
164 m_requestAborted = true;
165 }
166
setCursorDetails(IndexedDB::CursorType cursorType,WebIDBCursorDirection direction)167 void IDBRequest::setCursorDetails(IndexedDB::CursorType cursorType, WebIDBCursorDirection direction)
168 {
169 ASSERT(m_readyState == PENDING);
170 ASSERT(!m_pendingCursor);
171 m_cursorType = cursorType;
172 m_cursorDirection = direction;
173 }
174
setPendingCursor(IDBCursor * cursor)175 void IDBRequest::setPendingCursor(IDBCursor* cursor)
176 {
177 ASSERT(m_readyState == DONE);
178 ASSERT(executionContext());
179 ASSERT(m_transaction);
180 ASSERT(!m_pendingCursor);
181 ASSERT(cursor == getResultCursor());
182
183 m_hasPendingActivity = true;
184 m_pendingCursor = cursor;
185 setResult(0);
186 m_readyState = PENDING;
187 m_error.clear();
188 m_transaction->registerRequest(this);
189 }
190
getResultCursor() const191 IDBCursor* IDBRequest::getResultCursor() const
192 {
193 if (!m_result)
194 return 0;
195 if (m_result->type() == IDBAny::IDBCursorType)
196 return m_result->idbCursor();
197 if (m_result->type() == IDBAny::IDBCursorWithValueType)
198 return m_result->idbCursorWithValue();
199 return 0;
200 }
201
setResultCursor(IDBCursor * cursor,IDBKey * key,IDBKey * primaryKey,PassRefPtr<SharedBuffer> value,PassOwnPtr<Vector<WebBlobInfo>> blobInfo)202 void IDBRequest::setResultCursor(IDBCursor* cursor, IDBKey* key, IDBKey* primaryKey, PassRefPtr<SharedBuffer> value, PassOwnPtr<Vector<WebBlobInfo> > blobInfo)
203 {
204 ASSERT(m_readyState == PENDING);
205 m_cursorKey = key;
206 m_cursorPrimaryKey = primaryKey;
207 m_cursorValue = value;
208 setBlobInfo(blobInfo);
209
210 onSuccessInternal(IDBAny::create(cursor));
211 }
212
setBlobInfo(PassOwnPtr<Vector<WebBlobInfo>> blobInfo)213 void IDBRequest::setBlobInfo(PassOwnPtr<Vector<WebBlobInfo>> blobInfo)
214 {
215 ASSERT(!m_blobInfo);
216 m_blobInfo = blobInfo;
217 if (m_blobInfo && m_blobInfo->size() > 0)
218 V8PerIsolateData::from(scriptState()->isolate())->ensureIDBPendingTransactionMonitor()->registerRequest(*this);
219 }
220
handleBlobAcks()221 void IDBRequest::handleBlobAcks()
222 {
223 if (m_blobInfo.get() && m_blobInfo->size()) {
224 m_transaction->db()->ackReceivedBlobs(m_blobInfo.get());
225 m_blobInfo.clear();
226 V8PerIsolateData::from(scriptState()->isolate())->ensureIDBPendingTransactionMonitor()->unregisterRequest(*this);
227 }
228 }
229
shouldEnqueueEvent() const230 bool IDBRequest::shouldEnqueueEvent() const
231 {
232 if (m_contextStopped || !executionContext())
233 return false;
234 ASSERT(m_readyState == PENDING || m_readyState == DONE);
235 if (m_requestAborted)
236 return false;
237 ASSERT(m_readyState == PENDING);
238 ASSERT(!m_error && !m_result);
239 return true;
240 }
241
onError(PassRefPtrWillBeRawPtr<DOMError> error)242 void IDBRequest::onError(PassRefPtrWillBeRawPtr<DOMError> error)
243 {
244 IDB_TRACE("IDBRequest::onError()");
245 if (!shouldEnqueueEvent())
246 return;
247
248 m_error = error;
249 setResult(IDBAny::createUndefined());
250 m_pendingCursor.clear();
251 enqueueEvent(Event::createCancelableBubble(EventTypeNames::error));
252 }
253
onSuccess(const Vector<String> & stringList)254 void IDBRequest::onSuccess(const Vector<String>& stringList)
255 {
256 IDB_TRACE("IDBRequest::onSuccess(StringList)");
257 if (!shouldEnqueueEvent())
258 return;
259
260 RefPtrWillBeRawPtr<DOMStringList> domStringList = DOMStringList::create();
261 for (size_t i = 0; i < stringList.size(); ++i)
262 domStringList->append(stringList[i]);
263 onSuccessInternal(IDBAny::create(domStringList.release()));
264 }
265
onSuccess(PassOwnPtr<WebIDBCursor> backend,IDBKey * key,IDBKey * primaryKey,PassRefPtr<SharedBuffer> value,PassOwnPtr<Vector<WebBlobInfo>> blobInfo)266 void IDBRequest::onSuccess(PassOwnPtr<WebIDBCursor> backend, IDBKey* key, IDBKey* primaryKey, PassRefPtr<SharedBuffer> value, PassOwnPtr<Vector<WebBlobInfo> > blobInfo)
267 {
268 IDB_TRACE("IDBRequest::onSuccess(IDBCursor)");
269 if (!shouldEnqueueEvent())
270 return;
271
272 ASSERT(!m_pendingCursor);
273 IDBCursor* cursor = 0;
274 switch (m_cursorType) {
275 case IndexedDB::CursorKeyOnly:
276 cursor = IDBCursor::create(backend, m_cursorDirection, this, m_source.get(), m_transaction.get());
277 break;
278 case IndexedDB::CursorKeyAndValue:
279 cursor = IDBCursorWithValue::create(backend, m_cursorDirection, this, m_source.get(), m_transaction.get());
280 break;
281 default:
282 ASSERT_NOT_REACHED();
283 }
284 setResultCursor(cursor, key, primaryKey, value, blobInfo);
285 }
286
onSuccess(IDBKey * idbKey)287 void IDBRequest::onSuccess(IDBKey* idbKey)
288 {
289 IDB_TRACE("IDBRequest::onSuccess(IDBKey)");
290 if (!shouldEnqueueEvent())
291 return;
292
293 if (idbKey && idbKey->isValid())
294 onSuccessInternal(IDBAny::create(idbKey));
295 else
296 onSuccessInternal(IDBAny::createUndefined());
297 }
298
onSuccess(PassRefPtr<SharedBuffer> valueBuffer,PassOwnPtr<Vector<WebBlobInfo>> blobInfo)299 void IDBRequest::onSuccess(PassRefPtr<SharedBuffer> valueBuffer, PassOwnPtr<Vector<WebBlobInfo> > blobInfo)
300 {
301 IDB_TRACE("IDBRequest::onSuccess(SharedBuffer)");
302 if (!shouldEnqueueEvent())
303 return;
304
305 if (m_pendingCursor) {
306 // Value should be null, signifying the end of the cursor's range.
307 ASSERT(!valueBuffer.get());
308 ASSERT(!blobInfo->size());
309 m_pendingCursor->close();
310 m_pendingCursor.clear();
311 }
312
313 setBlobInfo(blobInfo);
314 onSuccessInternal(IDBAny::create(valueBuffer, m_blobInfo.get()));
315 }
316
317 #if ENABLE(ASSERT)
effectiveObjectStore(IDBAny * source)318 static IDBObjectStore* effectiveObjectStore(IDBAny* source)
319 {
320 if (source->type() == IDBAny::IDBObjectStoreType)
321 return source->idbObjectStore();
322 if (source->type() == IDBAny::IDBIndexType)
323 return source->idbIndex()->objectStore();
324
325 ASSERT_NOT_REACHED();
326 return 0;
327 }
328 #endif
329
onSuccess(PassRefPtr<SharedBuffer> prpValueBuffer,PassOwnPtr<Vector<WebBlobInfo>> blobInfo,IDBKey * prpPrimaryKey,const IDBKeyPath & keyPath)330 void IDBRequest::onSuccess(PassRefPtr<SharedBuffer> prpValueBuffer, PassOwnPtr<Vector<WebBlobInfo> > blobInfo, IDBKey* prpPrimaryKey, const IDBKeyPath& keyPath)
331 {
332 IDB_TRACE("IDBRequest::onSuccess(SharedBuffer, IDBKey, IDBKeyPath)");
333 if (!shouldEnqueueEvent())
334 return;
335
336 ASSERT(keyPath == effectiveObjectStore(m_source)->metadata().keyPath);
337
338 RefPtr<SharedBuffer> valueBuffer = prpValueBuffer;
339 IDBKey* primaryKey = prpPrimaryKey;
340 setBlobInfo(blobInfo);
341
342 #if ENABLE(ASSERT)
343 assertPrimaryKeyValidOrInjectable(m_scriptState.get(), valueBuffer, m_blobInfo.get(), primaryKey, keyPath);
344 #endif
345
346 onSuccessInternal(IDBAny::create(valueBuffer, m_blobInfo.get(), primaryKey, keyPath));
347 }
348
onSuccess(int64_t value)349 void IDBRequest::onSuccess(int64_t value)
350 {
351 IDB_TRACE("IDBRequest::onSuccess(int64_t)");
352 if (!shouldEnqueueEvent())
353 return;
354 onSuccessInternal(IDBAny::create(value));
355 }
356
onSuccess()357 void IDBRequest::onSuccess()
358 {
359 IDB_TRACE("IDBRequest::onSuccess()");
360 if (!shouldEnqueueEvent())
361 return;
362 onSuccessInternal(IDBAny::createUndefined());
363 }
364
onSuccessInternal(IDBAny * result)365 void IDBRequest::onSuccessInternal(IDBAny* result)
366 {
367 ASSERT(!m_contextStopped);
368 ASSERT(!m_pendingCursor);
369 setResult(result);
370 enqueueEvent(Event::create(EventTypeNames::success));
371 }
372
setResult(IDBAny * result)373 void IDBRequest::setResult(IDBAny* result)
374 {
375 m_result = result;
376 m_resultDirty = true;
377 }
378
onSuccess(IDBKey * key,IDBKey * primaryKey,PassRefPtr<SharedBuffer> value,PassOwnPtr<Vector<WebBlobInfo>> blobInfo)379 void IDBRequest::onSuccess(IDBKey* key, IDBKey* primaryKey, PassRefPtr<SharedBuffer> value, PassOwnPtr<Vector<WebBlobInfo> > blobInfo)
380 {
381 IDB_TRACE("IDBRequest::onSuccess(key, primaryKey, value)");
382 if (!shouldEnqueueEvent())
383 return;
384
385 ASSERT(m_pendingCursor);
386 setResultCursor(m_pendingCursor.release(), key, primaryKey, value, blobInfo);
387 }
388
hasPendingActivity() const389 bool IDBRequest::hasPendingActivity() const
390 {
391 // FIXME: In an ideal world, we should return true as long as anyone has a or can
392 // get a handle to us and we have event listeners. This is order to handle
393 // user generated events properly.
394 return m_hasPendingActivity && !m_contextStopped;
395 }
396
stop()397 void IDBRequest::stop()
398 {
399 if (m_contextStopped)
400 return;
401
402 m_contextStopped = true;
403
404 if (m_readyState == PENDING) {
405 m_readyState = EarlyDeath;
406 if (m_transaction) {
407 m_transaction->unregisterRequest(this);
408 m_transaction.clear();
409 }
410 }
411
412 m_enqueuedEvents.clear();
413 if (m_source)
414 m_source->contextWillBeDestroyed();
415 if (m_result)
416 m_result->contextWillBeDestroyed();
417 if (m_pendingCursor)
418 m_pendingCursor->contextWillBeDestroyed();
419 }
420
interfaceName() const421 const AtomicString& IDBRequest::interfaceName() const
422 {
423 return EventTargetNames::IDBRequest;
424 }
425
executionContext() const426 ExecutionContext* IDBRequest::executionContext() const
427 {
428 return ActiveDOMObject::executionContext();
429 }
430
dispatchEvent(PassRefPtrWillBeRawPtr<Event> event)431 bool IDBRequest::dispatchEvent(PassRefPtrWillBeRawPtr<Event> event)
432 {
433 IDB_TRACE("IDBRequest::dispatchEvent");
434 if (m_contextStopped || !executionContext())
435 return false;
436 ASSERT(m_readyState == PENDING);
437 ASSERT(m_hasPendingActivity);
438 ASSERT(m_enqueuedEvents.size());
439 ASSERT(event->target() == this);
440
441 ScriptState::Scope scope(m_scriptState.get());
442
443 if (event->type() != EventTypeNames::blocked)
444 m_readyState = DONE;
445 dequeueEvent(event.get());
446
447 WillBeHeapVector<RefPtrWillBeMember<EventTarget> > targets;
448 targets.append(this);
449 if (m_transaction && !m_preventPropagation) {
450 targets.append(m_transaction);
451 // If there ever are events that are associated with a database but
452 // that do not have a transaction, then this will not work and we need
453 // this object to actually hold a reference to the database (to ensure
454 // it stays alive).
455 targets.append(m_transaction->db());
456 }
457
458 // Cursor properties should not be updated until the success event is being dispatched.
459 IDBCursor* cursorToNotify = 0;
460 if (event->type() == EventTypeNames::success) {
461 cursorToNotify = getResultCursor();
462 if (cursorToNotify) {
463 if (m_blobInfo && m_blobInfo->size() > 0)
464 V8PerIsolateData::from(scriptState()->isolate())->ensureIDBPendingTransactionMonitor()->unregisterRequest(*this);
465 cursorToNotify->setValueReady(m_cursorKey.release(), m_cursorPrimaryKey.release(), m_cursorValue.release(), m_blobInfo.release());
466 }
467 }
468
469 if (event->type() == EventTypeNames::upgradeneeded) {
470 ASSERT(!m_didFireUpgradeNeededEvent);
471 m_didFireUpgradeNeededEvent = true;
472 }
473
474 // FIXME: When we allow custom event dispatching, this will probably need to change.
475 ASSERT_WITH_MESSAGE(event->type() == EventTypeNames::success || event->type() == EventTypeNames::error || event->type() == EventTypeNames::blocked || event->type() == EventTypeNames::upgradeneeded, "event type was %s", event->type().utf8().data());
476 const bool setTransactionActive = m_transaction && (event->type() == EventTypeNames::success || event->type() == EventTypeNames::upgradeneeded || (event->type() == EventTypeNames::error && !m_requestAborted));
477
478 if (setTransactionActive)
479 m_transaction->setActive(true);
480
481 bool dontPreventDefault = IDBEventDispatcher::dispatch(event.get(), targets);
482
483 if (m_transaction) {
484 if (m_readyState == DONE)
485 m_transaction->unregisterRequest(this);
486
487 // Possibly abort the transaction. This must occur after unregistering (so this request
488 // doesn't receive a second error) and before deactivating (which might trigger commit).
489 if (event->type() == EventTypeNames::error && dontPreventDefault && !m_requestAborted) {
490 m_transaction->setError(m_error);
491 m_transaction->abort(IGNORE_EXCEPTION);
492 }
493
494 // If this was the last request in the transaction's list, it may commit here.
495 if (setTransactionActive)
496 m_transaction->setActive(false);
497 }
498
499 if (cursorToNotify)
500 cursorToNotify->postSuccessHandlerCallback();
501
502 // An upgradeneeded event will always be followed by a success or error event, so must
503 // be kept alive.
504 if (m_readyState == DONE && event->type() != EventTypeNames::upgradeneeded)
505 m_hasPendingActivity = false;
506
507 return dontPreventDefault;
508 }
509
uncaughtExceptionInEventHandler()510 void IDBRequest::uncaughtExceptionInEventHandler()
511 {
512 if (m_transaction && !m_requestAborted) {
513 m_transaction->setError(DOMError::create(AbortError, "Uncaught exception in event handler."));
514 m_transaction->abort(IGNORE_EXCEPTION);
515 }
516 }
517
transactionDidFinishAndDispatch()518 void IDBRequest::transactionDidFinishAndDispatch()
519 {
520 ASSERT(m_transaction);
521 ASSERT(m_transaction->isVersionChange());
522 ASSERT(m_didFireUpgradeNeededEvent);
523 ASSERT(m_readyState == DONE);
524 ASSERT(executionContext());
525 m_transaction.clear();
526
527 if (m_contextStopped)
528 return;
529
530 m_readyState = PENDING;
531 }
532
enqueueEvent(PassRefPtrWillBeRawPtr<Event> event)533 void IDBRequest::enqueueEvent(PassRefPtrWillBeRawPtr<Event> event)
534 {
535 ASSERT(m_readyState == PENDING || m_readyState == DONE);
536
537 if (m_contextStopped || !executionContext())
538 return;
539
540 ASSERT_WITH_MESSAGE(m_readyState == PENDING || m_didFireUpgradeNeededEvent, "When queueing event %s, m_readyState was %d", event->type().utf8().data(), m_readyState);
541
542 EventQueue* eventQueue = executionContext()->eventQueue();
543 event->setTarget(this);
544
545 // Keep track of enqueued events in case we need to abort prior to dispatch,
546 // in which case these must be cancelled. If the events not dispatched for
547 // other reasons they must be removed from this list via dequeueEvent().
548 if (eventQueue->enqueueEvent(event.get()))
549 m_enqueuedEvents.append(event);
550 }
551
dequeueEvent(Event * event)552 void IDBRequest::dequeueEvent(Event* event)
553 {
554 for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) {
555 if (m_enqueuedEvents[i].get() == event)
556 m_enqueuedEvents.remove(i);
557 }
558 }
559
560 } // namespace blink
561