| Differences between
and this patch
- a/Source/WebCore/ChangeLog +25 lines
Lines 1-3 a/Source/WebCore/ChangeLog_sec1
1
2018-08-24  Youenn Fablet  <youenn@apple.com>
2
3
        Various IndexDB tests abandon documents
4
        https://bugs.webkit.org/show_bug.cgi?id=188728
5
        <rdar://problem/43651095>
6
7
        Reviewed by NOBODY (OOPS!).
8
9
        Some IDB objects implement hasPendingActivity but there are some possibilities that they continue returning true after being stopped. 
10
        Enforce that these objects return false to hasPendingActivity once being stopped.
11
        This ensures that they can be garbage collected once their document is preparing for destruction.
12
13
        Test: http/tests/IndexedDB/collect-IDB-objects.https.html
14
15
        * Modules/indexeddb/IDBCursor.cpp:
16
        (WebCore::IDBCursor::hasPendingActivity const):
17
        * Modules/indexeddb/IDBCursor.h:
18
        * Modules/indexeddb/IDBIndex.cpp:
19
        (WebCore::IDBIndex::hasPendingActivity const):
20
        * Modules/indexeddb/IDBObjectStore.cpp:
21
        (WebCore::IDBObjectStore::hasPendingActivity const):
22
        * Modules/indexeddb/IDBRequest.cpp:
23
        (WebCore::IDBRequest::hasPendingActivity const):
24
        (WebCore::IDBRequest::enqueueEvent):
25
1
2018-08-23  Youenn Fablet  <youenn@apple.com>
26
2018-08-23  Youenn Fablet  <youenn@apple.com>
2
27
3
        Update libwebrtc up to 984f1a80c0
28
        Update libwebrtc up to 984f1a80c0
- a/Source/WebCore/Modules/indexeddb/IDBCursor.cpp -1 / +1 lines
Lines 369-375 bool IDBCursor::canSuspendForDocumentSuspension() const a/Source/WebCore/Modules/indexeddb/IDBCursor.cpp_sec1
369
369
370
bool IDBCursor::hasPendingActivity() const
370
bool IDBCursor::hasPendingActivity() const
371
{
371
{
372
    return m_outstandingRequestCount;
372
    return !m_contextStopped && m_outstandingRequestCount;
373
}
373
}
374
374
375
void IDBCursor::decrementOutstandingRequestCount()
375
void IDBCursor::decrementOutstandingRequestCount()
- a/Source/WebCore/Modules/indexeddb/IDBCursor.h +2 lines
Lines 85-90 protected: a/Source/WebCore/Modules/indexeddb/IDBCursor.h_sec1
85
private:
85
private:
86
    const char* activeDOMObjectName() const final;
86
    const char* activeDOMObjectName() const final;
87
    bool canSuspendForDocumentSuspension() const final;
87
    bool canSuspendForDocumentSuspension() const final;
88
    void stop() final { m_contextStopped = true; }
88
89
89
    bool sourcesDeleted() const;
90
    bool sourcesDeleted() const;
90
    IDBObjectStore& effectiveObjectStore() const;
91
    IDBObjectStore& effectiveObjectStore() const;
Lines 101-106 private: a/Source/WebCore/Modules/indexeddb/IDBCursor.h_sec2
101
    IDBRequest* m_request { nullptr };
102
    IDBRequest* m_request { nullptr };
102
103
103
    bool m_gotValue { false };
104
    bool m_gotValue { false };
105
    bool m_contextStopped { false };
104
106
105
    IDBKeyData m_currentKeyData;
107
    IDBKeyData m_currentKeyData;
106
    IDBKeyData m_currentPrimaryKeyData;
108
    IDBKeyData m_currentPrimaryKeyData;
- a/Source/WebCore/Modules/indexeddb/IDBIndex.cpp -1 / +1 lines
Lines 69-75 bool IDBIndex::canSuspendForDocumentSuspension() const a/Source/WebCore/Modules/indexeddb/IDBIndex.cpp_sec1
69
69
70
bool IDBIndex::hasPendingActivity() const
70
bool IDBIndex::hasPendingActivity() const
71
{
71
{
72
    return !m_objectStore.transaction().isFinished();
72
    return m_objectStore.transaction().hasPendingActivity();
73
}
73
}
74
74
75
const String& IDBIndex::name() const
75
const String& IDBIndex::name() const
- a/Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp -1 / +1 lines
Lines 82-88 bool IDBObjectStore::canSuspendForDocumentSuspension() const a/Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp_sec1
82
82
83
bool IDBObjectStore::hasPendingActivity() const
83
bool IDBObjectStore::hasPendingActivity() const
84
{
84
{
85
    return !m_transaction.isFinished();
85
    return m_transaction.hasPendingActivity();
86
}
86
}
87
87
88
const String& IDBObjectStore::name() const
88
const String& IDBObjectStore::name() const
- a/Source/WebCore/Modules/indexeddb/IDBRequest.cpp -2 / +2 lines
Lines 266-272 bool IDBRequest::canSuspendForDocumentSuspension() const a/Source/WebCore/Modules/indexeddb/IDBRequest.cpp_sec1
266
bool IDBRequest::hasPendingActivity() const
266
bool IDBRequest::hasPendingActivity() const
267
{
267
{
268
    ASSERT(&originThread() == &Thread::current() || mayBeGCThread());
268
    ASSERT(&originThread() == &Thread::current() || mayBeGCThread());
269
    return m_hasPendingActivity;
269
    return !m_contextStopped && m_hasPendingActivity;
270
}
270
}
271
271
272
void IDBRequest::stop()
272
void IDBRequest::stop()
Lines 289-295 void IDBRequest::cancelForStop() a/Source/WebCore/Modules/indexeddb/IDBRequest.cpp_sec2
289
void IDBRequest::enqueueEvent(Ref<Event>&& event)
289
void IDBRequest::enqueueEvent(Ref<Event>&& event)
290
{
290
{
291
    ASSERT(&originThread() == &Thread::current());
291
    ASSERT(&originThread() == &Thread::current());
292
    if (!scriptExecutionContext() || m_contextStopped)
292
    if (m_contextStopped)
293
        return;
293
        return;
294
294
295
    event->setTarget(this);
295
    event->setTarget(this);
- a/LayoutTests/ChangeLog +13 lines
Lines 1-3 a/LayoutTests/ChangeLog_sec1
1
2018-08-24  Youenn Fablet  <youenn@apple.com>
2
3
        Various IndexDB tests abandon documents
4
        https://bugs.webkit.org/show_bug.cgi?id=188728
5
        <rdar://problem/43651095>
6
7
        Reviewed by NOBODY (OOPS!).
8
9
        * http/tests/IndexedDB/resources/myidbframe.htm: Added.
10
        * http/tests/IndexedDB/resources/support.js: Added.
11
        * http/tests/IndexedDB/collect-IDB-ojects.https-expected.txt: Added.
12
        * http/tests/IndexedDB/collect-IDB-ojects.https.html: Added.
13
1
2018-08-23  Youenn Fablet  <youenn@apple.com>
14
2018-08-23  Youenn Fablet  <youenn@apple.com>
2
15
3
        Update libwebrtc up to 984f1a80c0
16
        Update libwebrtc up to 984f1a80c0
- a/LayoutTests/http/tests/IndexedDB/collect-IDB-objects.https-expected.txt +4 lines
Line 0 a/LayoutTests/http/tests/IndexedDB/collect-IDB-objects.https-expected.txt_sec1
1
2
3
PASS Ensuring frame document gets collected after being stopped in the mmiddle of IDB operations 
4
- a/LayoutTests/http/tests/IndexedDB/collect-IDB-objects.https.html +45 lines
Line 0 a/LayoutTests/http/tests/IndexedDB/collect-IDB-objects.https.html_sec1
1
<!DOCTYPE html>
2
<meta charset="utf-8">
3
<script src="/resources/testharness.js"></script>
4
<script src="/resources/testharnessreport.js"></script>
5
<script>
6
function waitFor(duration)
7
{
8
    return new Promise((resolve) => setTimeout(resolve, duration));
9
}
10
11
var resolveCallback, rejectCallback;
12
var promise = new Promise((resolve, reject) => {
13
    resolveCallback = resolve;
14
    rejectCallback = reject;
15
});
16
17
async function done()
18
{
19
    try {
20
        const frameIdentifier = internals.documentIdentifier(iframe.contentDocument);
21
        iframe.src = "non-existent-frame";
22
        let counter = 0;
23
        while (++counter < 10) {
24
            if (!internals.isDocumentAlive(frameIdentifier)) {
25
                resolveCallback();
26
                return;
27
            }
28
            if (window.GCController)
29
                GCController.collect();
30
31
            await waitFor(50);
32
        }
33
    } finally {
34
        rejectCallback("Test failed");
35
    }
36
}
37
38
promise_test((test) => {
39
    if (!window.internals)
40
        rejectCallback("Test require internals API");
41
    return promise;
42
}, "Ensuring frame document gets collected after being stopped in the mmiddle of IDB operations");
43
44
</script>
45
<iframe src="resources/myidbframe.htm" id="iframe"></iframe>
- a/LayoutTests/http/tests/IndexedDB/resources/myidbframe.htm +26 lines
Line 0 a/LayoutTests/http/tests/IndexedDB/resources/myidbframe.htm_sec1
1
<!DOCTYPE html>
2
<script src="../../resources/testharness.js"></script>
3
<script src="support.js"></script>
4
5
<script>
6
var t = async_test();
7
createdb(t).onupgradeneeded = function(e) {
8
    e.target.result
9
            .createObjectStore("store")
10
            .add(new Date(), 1);
11
12
    e.target.onsuccess = t.step_func(function(e) {
13
        e.target.result
14
                .transaction("store")
15
                .objectStore("store")
16
                .get(1)
17
                .onsuccess = t.step_func(function(e)
18
        {
19
            t.done();
20
            parent.done();
21
        });
22
    });
23
};
24
</script>
25
26
<div id="log"></div>
- a/LayoutTests/http/tests/IndexedDB/resources/support.js +194 lines
Line 0 a/LayoutTests/http/tests/IndexedDB/resources/support.js_sec1
1
var databaseName = "database";
2
var databaseVersion = 1;
3
4
/* Delete created databases
5
 *
6
 * Go through each finished test, see if it has an associated database. Close
7
 * that and delete the database. */
8
add_completion_callback(function(tests)
9
{
10
    for (var i in tests)
11
    {
12
        if(tests[i].db)
13
        {
14
            tests[i].db.close();
15
            self.indexedDB.deleteDatabase(tests[i].db.name);
16
        }
17
    }
18
});
19
20
function fail(test, desc) {
21
    return test.step_func(function(e) {
22
        if (e && e.message && e.target.error)
23
            assert_unreached(desc + " (" + e.target.error.name + ": " + e.message + ")");
24
        else if (e && e.message)
25
            assert_unreached(desc + " (" + e.message + ")");
26
        else if (e && e.target.readyState === 'done' && e.target.error)
27
            assert_unreached(desc + " (" + e.target.error.name + ")");
28
        else
29
            assert_unreached(desc);
30
    });
31
}
32
33
function createdb(test, dbname, version)
34
{
35
    var rq_open = createdb_for_multiple_tests(dbname, version);
36
    return rq_open.setTest(test);
37
}
38
39
function createdb_for_multiple_tests(dbname, version) {
40
    var rq_open,
41
        fake_open = {},
42
        test = null,
43
        dbname = (dbname ? dbname : "testdb-" + new Date().getTime() + Math.random() );
44
45
    if (version)
46
        rq_open = self.indexedDB.open(dbname, version);
47
    else
48
        rq_open = self.indexedDB.open(dbname);
49
50
    function auto_fail(evt, current_test) {
51
        /* Fail handlers, if we haven't set on/whatever/, don't
52
         * expect to get event whatever. */
53
        rq_open.manually_handled = {};
54
55
        rq_open.addEventListener(evt, function(e) {
56
            if (current_test !== test) {
57
                return;
58
            }
59
60
            test.step(function() {
61
                if (!rq_open.manually_handled[evt]) {
62
                    assert_unreached("unexpected open." + evt + " event");
63
                }
64
65
                if (e.target.result + '' == '[object IDBDatabase]' &&
66
                    !this.db) {
67
                  this.db = e.target.result;
68
69
                  this.db.onerror = fail(test, 'unexpected db.error');
70
                  this.db.onabort = fail(test, 'unexpected db.abort');
71
                  this.db.onversionchange =
72
                      fail(test, 'unexpected db.versionchange');
73
                }
74
            });
75
        });
76
        rq_open.__defineSetter__("on" + evt, function(h) {
77
            rq_open.manually_handled[evt] = true;
78
            if (!h)
79
                rq_open.addEventListener(evt, function() {});
80
            else
81
                rq_open.addEventListener(evt, test.step_func(h));
82
        });
83
    }
84
85
    // add a .setTest method to the IDBOpenDBRequest object
86
    Object.defineProperty(rq_open, 'setTest', {
87
        enumerable: false,
88
        value: function(t) {
89
            test = t;
90
91
            auto_fail("upgradeneeded", test);
92
            auto_fail("success", test);
93
            auto_fail("blocked", test);
94
            auto_fail("error", test);
95
96
            return this;
97
        }
98
    });
99
100
    return rq_open;
101
}
102
103
function assert_key_equals(actual, expected, description) {
104
  assert_equals(indexedDB.cmp(actual, expected), 0, description);
105
}
106
107
function indexeddb_test(upgrade_func, open_func, description, options) {
108
  async_test(function(t) {
109
    options = Object.assign({upgrade_will_abort: false}, options);
110
    var dbname = location + '-' + t.name;
111
    var del = indexedDB.deleteDatabase(dbname);
112
    del.onerror = t.unreached_func('deleteDatabase should succeed');
113
    var open = indexedDB.open(dbname, 1);
114
    open.onupgradeneeded = t.step_func(function() {
115
      var db = open.result;
116
      t.add_cleanup(function() {
117
        // If open didn't succeed already, ignore the error.
118
        open.onerror = function(e) {
119
          e.preventDefault();
120
        };
121
        db.close();
122
        indexedDB.deleteDatabase(db.name);
123
      });
124
      var tx = open.transaction;
125
      upgrade_func(t, db, tx, open);
126
    });
127
    if (options.upgrade_will_abort) {
128
      open.onsuccess = t.unreached_func('open should not succeed');
129
    } else {
130
      open.onerror = t.unreached_func('open should succeed');
131
      open.onsuccess = t.step_func(function() {
132
        var db = open.result;
133
        if (open_func)
134
          open_func(t, db, open);
135
      });
136
    }
137
  }, description);
138
}
139
140
// Call with a Test and an array of expected results in order. Returns
141
// a function; call the function when a result arrives and when the
142
// expected number appear the order will be asserted and test
143
// completed.
144
function expect(t, expected) {
145
  var results = [];
146
  return result => {
147
    results.push(result);
148
    if (results.length === expected.length) {
149
      assert_array_equals(results, expected);
150
      t.done();
151
    }
152
  };
153
}
154
155
// Checks to see if the passed transaction is active (by making
156
// requests against the named store).
157
function is_transaction_active(tx, store_name) {
158
  try {
159
    const request = tx.objectStore(store_name).get(0);
160
    request.onerror = e => {
161
      e.preventDefault();
162
      e.stopPropagation();
163
    };
164
    return true;
165
  } catch (ex) {
166
    assert_equals(ex.name, 'TransactionInactiveError',
167
                  'Active check should either not throw anything, or throw ' +
168
                  'TransactionInactiveError');
169
    return false;
170
  }
171
}
172
173
// Keep the passed transaction alive indefinitely (by making requests
174
// against the named store). Returns a function to to let the
175
// transaction finish, and asserts that the transaction is not yet
176
// finished.
177
function keep_alive(tx, store_name) {
178
  let completed = false;
179
  tx.addEventListener('complete', () => { completed = true; });
180
181
  let pin = true;
182
183
  function spin() {
184
    if (!pin)
185
      return;
186
    tx.objectStore(store_name).get(0).onsuccess = spin;
187
  }
188
  spin();
189
190
  return () => {
191
    assert_false(completed, 'Transaction completed while kept alive');
192
    pin = false;
193
  };
194
}

Return to Bug 188728