chromium/third_party/blink/web_tests/storage/indexeddb/cursor-request-cycle.html

<!DOCTYPE html>
<script src="../../resources/gc.js"></script>
<script src="../../resources/js-test.js"></script>
<script src="resources/shared.js"></script>
<script>

description("Verify that that cursors weakly hold request, and work if request is GC'd");

indexedDBTest(prepareDatabase, onOpen);

function prepareDatabase(evt)
{
    preamble(evt);
    evalAndLog("db = event.target.result");
    evalAndLog("store = db.createObjectStore('store')");
    store.put("value1", "key1");
    store.put("value2", "key2");
}

let finishJsCalls = 0;
// Similar to `base::BarrierCallback`, this function must be called twice for the test
// to be considered finished.
function halfFinishJsTest() {
  if (++finishJsCalls == 2) finishJSTest();
}

function onOpen(evt)
{
    preamble(evt);
    evalAndLog("db = event.target.result");
    evalAndLog("tx = db.transaction('store', 'readonly', {durability: 'relaxed'})");
    evalAndLog("store = tx.objectStore('store')");

    evalAndLog("cursorRequest = store.openCursor()");
    cursorRequest.onsuccess = function openCursorRequestSuccess(evt) {
        preamble(evt);
        debug("Result will be checked later, to ensure that lazy access is safe");
    };

    evalAndLog("otherRequest = store.get(0)");
    otherRequest.onsuccess = function otherRequestSuccess(evt) {
        preamble(evt);

        debug("Verify that the request's result can be accessed lazily:");
        evalAndLog("gc()");

        evalAndLog("cursor = cursorRequest.result");
        shouldBeNonNull("cursor");
        shouldBeEqualToString("cursor.key", "key1");
        shouldBeEqualToString("cursor.value", "value1");
        evalAndLog("cursorRequest.extra = 123");
        evalAndLog("cursor.extra = 456");

        // Assign a new handler to inspect the request and cursor indirectly.
        cursorRequest.onsuccess = function cursorContinueSuccess(evt) {
            preamble(evt);
            evalAndLog("cursor = event.target.result");
            shouldBeNonNull("cursor");
            shouldBeEqualToString("cursor.key", "key2");
            shouldBeEqualToString("cursor.value", "value2");
            shouldBe("event.target.extra", "123");
            shouldBe("cursor.extra", "456");
        };

        debug("Ensure request is not released if cursor is still around.");
        cursorRequestObservation = internals.observeGC(cursorRequest);
        evalAndLog("cursorRequest = null");
        evalAndLog("gc()");
        shouldBeFalse("cursorRequestObservation.wasCollected");

        evalAndLog("cursor.continue()");

        cursorObservation = internals.observeGC(cursor);
        evalAndLog("cursor = null");
        evalAndLog("gc()");
        shouldBeFalse("cursorObservation.wasCollected");

        evalAndLog("finalRequest = store.get(0)");
        finalRequest.onsuccess = function finalRequestSuccess(evt) {
            preamble(evt);
            shouldBeEqualToString("cursor.key", "key2");
            shouldBeEqualToString("cursor.value", "value2");

            // Access objects in an inner function to avoid references to
            // objects remaining live on this function's stack frame
            // (http://crbug.com/595672/).
            (() => { cursorObservation = internals.observeGC(cursor); })();
            evalAndLog("cursor = null");
            asyncGC(function () {
                shouldBeTrue("cursorRequestObservation.wasCollected");
                shouldBeTrue("cursorObservation.wasCollected");
                halfFinishJsTest();
            });
        };
    };

    tx.oncomplete = halfFinishJsTest;
}


</script>