Add a storage limit for IndexedDB
authorsihui_liu@apple.com <sihui_liu@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 1 Nov 2018 21:11:57 +0000 (21:11 +0000)
committersihui_liu@apple.com <sihui_liu@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 1 Nov 2018 21:11:57 +0000 (21:11 +0000)
https://bugs.webkit.org/show_bug.cgi?id=190598
<rdar://problem/44654715>

Reviewed by Chris Dumez.

Source/WebCore:

Set a storage limit in IndexedDB for each pair of mainFrameOrigin and openingOrigin.
IndexedDB will return a QuotaExceededError if limit is reached.

If the size of free disk space is over 1 GB, the default limit is 500 MB; otherwise it is
half the free disk space.

Test: storage/indexeddb/storage-limit.html

* Modules/indexeddb/server/IDBBackingStore.h:
* Modules/indexeddb/server/IDBServer.cpp:
(WebCore::IDBServer::IDBServer::createBackingStore):
(WebCore::IDBServer::IDBServer::setPerOriginQuota):
* Modules/indexeddb/server/IDBServer.h:
(WebCore::IDBServer::IDBServer::perOriginQuota const):
* Modules/indexeddb/server/MemoryIDBBackingStore.h:
* Modules/indexeddb/server/SQLiteIDBBackingStore.cpp:
(WebCore::IDBServer::SQLiteIDBBackingStore::SQLiteIDBBackingStore):
(WebCore::IDBServer::SQLiteIDBBackingStore::quotaForOrigin const):
(WebCore::IDBServer::SQLiteIDBBackingStore::maximumSize const):
(WebCore::IDBServer::SQLiteIDBBackingStore::beginTransaction):
(WebCore::IDBServer::SQLiteIDBBackingStore::createObjectStore):
(WebCore::IDBServer::SQLiteIDBBackingStore::renameObjectStore):
(WebCore::IDBServer::SQLiteIDBBackingStore::createIndex):
(WebCore::IDBServer::SQLiteIDBBackingStore::uncheckedPutIndexRecord):
(WebCore::IDBServer::SQLiteIDBBackingStore::renameIndex):
(WebCore::IDBServer::SQLiteIDBBackingStore::addRecord):
(WebCore::IDBServer::SQLiteIDBBackingStore::uncheckedSetKeyGeneratorValue):
* Modules/indexeddb/server/SQLiteIDBBackingStore.h:
* Modules/indexeddb/server/UniqueIDBDatabase.cpp:
(WebCore::IDBServer::UniqueIDBDatabase::setQuota):
* Modules/indexeddb/server/UniqueIDBDatabase.h:

Source/WebKit:

Add SPI for testing.

* NetworkProcess/NetworkProcess.cpp:
(WebKit::NetworkProcess::NetworkProcess):
(WebKit::NetworkProcess::idbServer):
(WebKit::NetworkProcess::setIDBPerOriginQuota):
* NetworkProcess/NetworkProcess.h:
* NetworkProcess/NetworkProcess.messages.in:
* UIProcess/API/C/WKContext.cpp:
(WKContextSetIDBPerOriginQuota):
* UIProcess/API/C/WKContextPrivate.h:
* UIProcess/WebProcessPool.cpp:
(WebKit::WebProcessPool::setIDBPerOriginQuota):
* UIProcess/WebProcessPool.h:

Source/WebKitLegacy:

* Storage/WebDatabaseProvider.cpp:
(WebDatabaseProvider::idbConnectionToServerForSession):
(WebDatabaseProvider::setIDBPerOriginQuota):
* Storage/WebDatabaseProvider.h:

Source/WebKitLegacy/mac:

* Storage/WebDatabaseManager.mm:
(-[WebDatabaseManager setIDBPerOriginQuota:]):
* Storage/WebDatabaseManagerPrivate.h:

Source/WebKitLegacy/win:

* Interfaces/IWebDatabaseManager.idl:
* WebDatabaseManager.cpp:
(WebDatabaseManager::setIDBPerOriginQuota):
* WebDatabaseManager.h:

Tools:

Add API for testing.

* DumpRenderTree/TestRunner.cpp:
(setIDBPerOriginQuotaCallback):
(TestRunner::staticFunctions):
* DumpRenderTree/TestRunner.h:
* DumpRenderTree/mac/TestRunnerMac.mm:
(TestRunner::setIDBPerOriginQuota):
* DumpRenderTree/win/TestRunnerWin.cpp:
(TestRunner::setIDBPerOriginQuota):
* WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl:
* WebKitTestRunner/InjectedBundle/TestRunner.cpp:
(WTR::TestRunner::setIDBPerOriginQuota):
* WebKitTestRunner/InjectedBundle/TestRunner.h:
* WebKitTestRunner/TestController.cpp:
(WTR::TestController::setIDBPerOriginQuota):
* WebKitTestRunner/TestController.h:
* WebKitTestRunner/TestInvocation.cpp:
(WTR::TestInvocation::didReceiveSynchronousMessageFromInjectedBundle):

LayoutTests:

Some tests will fail after adding storage limit to IndexedDB, so we need to reduce their
size and rebase their expectations.

* storage/indexeddb/key-type-array-expected.txt:
* storage/indexeddb/key-type-array-private-expected.txt:
* storage/indexeddb/modern/idbkey-array-equality-expected.txt:
* storage/indexeddb/modern/idbkey-array-equality-private-expected.txt:
* storage/indexeddb/modern/resources/idbkey-array-equality.js:
(request.onsuccess):
(request.onerror):
(doAdd):
* storage/indexeddb/prefetch-invalidation-expected.txt:
* storage/indexeddb/prefetch-invalidation-private-expected.txt:
* storage/indexeddb/resources/key-type-array.js:
(testValidArrayKeys.getreq.onsuccess):
(testValidArrayKeys.putreq.onsuccess):
(testValidArrayKeys.testArrayPutGet):
* storage/indexeddb/resources/prefetch-invalidation.js:
(cursorRequest.onsuccess):
(continue50Times):
(continue100Times): Deleted.
* storage/indexeddb/resources/storage-limit.js: Added.
(prepareDatabase):
(onOpenSuccess.request.onerror):
(onOpenSuccess.request.onsuccess):
(onOpenSuccess):
* storage/indexeddb/storage-limit-expected.txt: Added.
* storage/indexeddb/storage-limit.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@237700 268f45cc-cd09-0410-ab3c-d52691b4dbfc

51 files changed:
LayoutTests/ChangeLog
LayoutTests/storage/indexeddb/key-type-array-expected.txt
LayoutTests/storage/indexeddb/key-type-array-private-expected.txt
LayoutTests/storage/indexeddb/modern/idbkey-array-equality-expected.txt
LayoutTests/storage/indexeddb/modern/idbkey-array-equality-private-expected.txt
LayoutTests/storage/indexeddb/modern/resources/idbkey-array-equality.js
LayoutTests/storage/indexeddb/prefetch-invalidation-expected.txt
LayoutTests/storage/indexeddb/prefetch-invalidation-private-expected.txt
LayoutTests/storage/indexeddb/resources/key-type-array.js
LayoutTests/storage/indexeddb/resources/prefetch-invalidation.js
LayoutTests/storage/indexeddb/resources/storage-limit.js [new file with mode: 0644]
LayoutTests/storage/indexeddb/storage-limit-expected.txt [new file with mode: 0644]
LayoutTests/storage/indexeddb/storage-limit.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/Modules/indexeddb/server/IDBBackingStore.h
Source/WebCore/Modules/indexeddb/server/IDBServer.cpp
Source/WebCore/Modules/indexeddb/server/IDBServer.h
Source/WebCore/Modules/indexeddb/server/MemoryIDBBackingStore.h
Source/WebCore/Modules/indexeddb/server/SQLiteIDBBackingStore.cpp
Source/WebCore/Modules/indexeddb/server/SQLiteIDBBackingStore.h
Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp
Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.h
Source/WebKit/ChangeLog
Source/WebKit/NetworkProcess/NetworkProcess.cpp
Source/WebKit/NetworkProcess/NetworkProcess.h
Source/WebKit/NetworkProcess/NetworkProcess.messages.in
Source/WebKit/UIProcess/API/C/WKContext.cpp
Source/WebKit/UIProcess/API/C/WKContextPrivate.h
Source/WebKit/UIProcess/WebProcessPool.cpp
Source/WebKit/UIProcess/WebProcessPool.h
Source/WebKitLegacy/ChangeLog
Source/WebKitLegacy/Storage/WebDatabaseProvider.cpp
Source/WebKitLegacy/Storage/WebDatabaseProvider.h
Source/WebKitLegacy/mac/ChangeLog
Source/WebKitLegacy/mac/Storage/WebDatabaseManager.mm
Source/WebKitLegacy/mac/Storage/WebDatabaseManagerPrivate.h
Source/WebKitLegacy/win/ChangeLog
Source/WebKitLegacy/win/Interfaces/IWebDatabaseManager.idl
Source/WebKitLegacy/win/WebDatabaseManager.cpp
Source/WebKitLegacy/win/WebDatabaseManager.h
Tools/ChangeLog
Tools/DumpRenderTree/TestRunner.cpp
Tools/DumpRenderTree/TestRunner.h
Tools/DumpRenderTree/mac/TestRunnerMac.mm
Tools/DumpRenderTree/win/TestRunnerWin.cpp
Tools/WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl
Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp
Tools/WebKitTestRunner/InjectedBundle/TestRunner.h
Tools/WebKitTestRunner/TestController.cpp
Tools/WebKitTestRunner/TestController.h
Tools/WebKitTestRunner/TestInvocation.cpp

index 07cd994..4548009 100644 (file)
@@ -1,3 +1,40 @@
+2018-11-01  Sihui Liu  <sihui_liu@apple.com>
+
+        Add a storage limit for IndexedDB
+        https://bugs.webkit.org/show_bug.cgi?id=190598
+        <rdar://problem/44654715>
+
+        Reviewed by Chris Dumez.
+
+        Some tests will fail after adding storage limit to IndexedDB, so we need to reduce their 
+        size and rebase their expectations.
+
+        * storage/indexeddb/key-type-array-expected.txt:
+        * storage/indexeddb/key-type-array-private-expected.txt:
+        * storage/indexeddb/modern/idbkey-array-equality-expected.txt:
+        * storage/indexeddb/modern/idbkey-array-equality-private-expected.txt:
+        * storage/indexeddb/modern/resources/idbkey-array-equality.js:
+        (request.onsuccess):
+        (request.onerror):
+        (doAdd):
+        * storage/indexeddb/prefetch-invalidation-expected.txt:
+        * storage/indexeddb/prefetch-invalidation-private-expected.txt:
+        * storage/indexeddb/resources/key-type-array.js:
+        (testValidArrayKeys.getreq.onsuccess):
+        (testValidArrayKeys.putreq.onsuccess):
+        (testValidArrayKeys.testArrayPutGet):
+        * storage/indexeddb/resources/prefetch-invalidation.js:
+        (cursorRequest.onsuccess):
+        (continue50Times):
+        (continue100Times): Deleted.
+        * storage/indexeddb/resources/storage-limit.js: Added.
+        (prepareDatabase):
+        (onOpenSuccess.request.onerror):
+        (onOpenSuccess.request.onsuccess):
+        (onOpenSuccess):
+        * storage/indexeddb/storage-limit-expected.txt: Added.
+        * storage/indexeddb/storage-limit.html: Added.
+
 2018-11-01  Chris Dumez  <cdumez@apple.com>
 
         Unreviewed, mark a couple of newly imported WPT tests as flaky on Mac WK1.
index cf487bc..1c25aab 100644 (file)
@@ -12,7 +12,7 @@ db.createObjectStore('store');
 trans = db.transaction('store', 'readwrite')
 store = trans.objectStore('store')
 
-long_array = []; for (i = 0; i < 1000; ++i) { long_array.push('abc', 123, new Date(0), []); }
+long_array = []; for (i = 0; i < 10; ++i) { long_array.push('abc', 123, new Date(0), []); }
 
 array that contains non-numeric self-reference
 self_referrential_array = []; self_referrential_array.self = self_referrential_array;
@@ -21,136 +21,163 @@ testing array key: []
 store.put('value1', []);
 store.get([]);
 PASS getreq.result is "value1"
+store.delete([]);
 
 testing array key: [-Infinity]
 store.put('value2', [-Infinity]);
 store.get([-Infinity]);
 PASS getreq.result is "value2"
+store.delete([-Infinity]);
 
 testing array key: [-Number.MAX_VALUE]
 store.put('value3', [-Number.MAX_VALUE]);
 store.get([-Number.MAX_VALUE]);
 PASS getreq.result is "value3"
+store.delete([-Number.MAX_VALUE]);
 
 testing array key: [-1]
 store.put('value4', [-1]);
 store.get([-1]);
 PASS getreq.result is "value4"
+store.delete([-1]);
 
 testing array key: [-Number.MIN_VALUE]
 store.put('value5', [-Number.MIN_VALUE]);
 store.get([-Number.MIN_VALUE]);
 PASS getreq.result is "value5"
+store.delete([-Number.MIN_VALUE]);
 
 testing array key: [0]
 store.put('value6', [0]);
 store.get([0]);
 PASS getreq.result is "value6"
+store.delete([0]);
 
 testing array key: [Number.MIN_VALUE]
 store.put('value7', [Number.MIN_VALUE]);
 store.get([Number.MIN_VALUE]);
 PASS getreq.result is "value7"
+store.delete([Number.MIN_VALUE]);
 
 testing array key: [1]
 store.put('value8', [1]);
 store.get([1]);
 PASS getreq.result is "value8"
+store.delete([1]);
 
 testing array key: [Number.MAX_VALUE]
 store.put('value9', [Number.MAX_VALUE]);
 store.get([Number.MAX_VALUE]);
 PASS getreq.result is "value9"
+store.delete([Number.MAX_VALUE]);
 
 testing array key: [Infinity]
 store.put('value10', [Infinity]);
 store.get([Infinity]);
 PASS getreq.result is "value10"
+store.delete([Infinity]);
 
 testing array key: [1,2,3]
 store.put('value11', [1,2,3]);
 store.get([1,2,3]);
 PASS getreq.result is "value11"
+store.delete([1,2,3]);
 
 testing array key: [new Date(0)]
 store.put('value12', [new Date(0)]);
 store.get([new Date(0)]);
 PASS getreq.result is "value12"
+store.delete([new Date(0)]);
 
 testing array key: [new Date('2525-01-01T00:00:00Z')]
 store.put('value13', [new Date('2525-01-01T00:00:00Z')]);
 store.get([new Date('2525-01-01T00:00:00Z')]);
 PASS getreq.result is "value13"
+store.delete([new Date('2525-01-01T00:00:00Z')]);
 
 testing array key: [new Date(0), new Date('2525-01-01T00:00:00Z')]
 store.put('value14', [new Date(0), new Date('2525-01-01T00:00:00Z')]);
 store.get([new Date(0), new Date('2525-01-01T00:00:00Z')]);
 PASS getreq.result is "value14"
+store.delete([new Date(0), new Date('2525-01-01T00:00:00Z')]);
 
 testing array key: ['']
 store.put('value15', ['']);
 store.get(['']);
 PASS getreq.result is "value15"
+store.delete(['']);
 
 testing array key: ['']
 store.put('value16', ['']);
 store.get(['']);
 PASS getreq.result is "value16"
+store.delete(['']);
 
 testing array key: ['abc123']
 store.put('value17', ['abc123']);
 store.get(['abc123']);
 PASS getreq.result is "value17"
+store.delete(['abc123']);
 
 testing array key: ['abc', 123]
 store.put('value18', ['abc', 123]);
 store.get(['abc', 123]);
 PASS getreq.result is "value18"
+store.delete(['abc', 123]);
 
 testing array key: [[]]
 store.put('value19', [[]]);
 store.get([[]]);
 PASS getreq.result is "value19"
+store.delete([[]]);
 
 testing array key: [[], []]
 store.put('value20', [[], []]);
 store.get([[], []]);
 PASS getreq.result is "value20"
+store.delete([[], []]);
 
 testing array key: [[], [], []]
 store.put('value21', [[], [], []]);
 store.get([[], [], []]);
 PASS getreq.result is "value21"
+store.delete([[], [], []]);
 
 testing array key: [[[]]]
 store.put('value22', [[[]]]);
 store.get([[[]]]);
 PASS getreq.result is "value22"
+store.delete([[[]]]);
 
 testing array key: [[[[]]]]
 store.put('value23', [[[[]]]]);
 store.get([[[[]]]]);
 PASS getreq.result is "value23"
+store.delete([[[[]]]]);
 
 testing array key: [123, 'abc', new Date(0), []]
 store.put('value24', [123, 'abc', new Date(0), []]);
 store.get([123, 'abc', new Date(0), []]);
 PASS getreq.result is "value24"
+store.delete([123, 'abc', new Date(0), []]);
 
 testing array key: [[123, 'abc', new Date(0), []], [456, 'def', new Date(999), [[]]]]
 store.put('value25', [[123, 'abc', new Date(0), []], [456, 'def', new Date(999), [[]]]]);
 store.get([[123, 'abc', new Date(0), []], [456, 'def', new Date(999), [[]]]]);
 PASS getreq.result is "value25"
+store.delete([[123, 'abc', new Date(0), []], [456, 'def', new Date(999), [[]]]]);
 
 testing array key: long_array
 store.put('value26', long_array);
 store.get(long_array);
 PASS getreq.result is "value26"
+store.delete(long_array);
 
 testing array key: self_referrential_array
 store.put('value27', self_referrential_array);
 store.get(self_referrential_array);
 PASS getreq.result is "value27"
+store.delete(self_referrential_array);
 
 trans = db.transaction('store', 'readwrite')
 store = trans.objectStore('store')
index cf487bc..1c25aab 100644 (file)
@@ -12,7 +12,7 @@ db.createObjectStore('store');
 trans = db.transaction('store', 'readwrite')
 store = trans.objectStore('store')
 
-long_array = []; for (i = 0; i < 1000; ++i) { long_array.push('abc', 123, new Date(0), []); }
+long_array = []; for (i = 0; i < 10; ++i) { long_array.push('abc', 123, new Date(0), []); }
 
 array that contains non-numeric self-reference
 self_referrential_array = []; self_referrential_array.self = self_referrential_array;
@@ -21,136 +21,163 @@ testing array key: []
 store.put('value1', []);
 store.get([]);
 PASS getreq.result is "value1"
+store.delete([]);
 
 testing array key: [-Infinity]
 store.put('value2', [-Infinity]);
 store.get([-Infinity]);
 PASS getreq.result is "value2"
+store.delete([-Infinity]);
 
 testing array key: [-Number.MAX_VALUE]
 store.put('value3', [-Number.MAX_VALUE]);
 store.get([-Number.MAX_VALUE]);
 PASS getreq.result is "value3"
+store.delete([-Number.MAX_VALUE]);
 
 testing array key: [-1]
 store.put('value4', [-1]);
 store.get([-1]);
 PASS getreq.result is "value4"
+store.delete([-1]);
 
 testing array key: [-Number.MIN_VALUE]
 store.put('value5', [-Number.MIN_VALUE]);
 store.get([-Number.MIN_VALUE]);
 PASS getreq.result is "value5"
+store.delete([-Number.MIN_VALUE]);
 
 testing array key: [0]
 store.put('value6', [0]);
 store.get([0]);
 PASS getreq.result is "value6"
+store.delete([0]);
 
 testing array key: [Number.MIN_VALUE]
 store.put('value7', [Number.MIN_VALUE]);
 store.get([Number.MIN_VALUE]);
 PASS getreq.result is "value7"
+store.delete([Number.MIN_VALUE]);
 
 testing array key: [1]
 store.put('value8', [1]);
 store.get([1]);
 PASS getreq.result is "value8"
+store.delete([1]);
 
 testing array key: [Number.MAX_VALUE]
 store.put('value9', [Number.MAX_VALUE]);
 store.get([Number.MAX_VALUE]);
 PASS getreq.result is "value9"
+store.delete([Number.MAX_VALUE]);
 
 testing array key: [Infinity]
 store.put('value10', [Infinity]);
 store.get([Infinity]);
 PASS getreq.result is "value10"
+store.delete([Infinity]);
 
 testing array key: [1,2,3]
 store.put('value11', [1,2,3]);
 store.get([1,2,3]);
 PASS getreq.result is "value11"
+store.delete([1,2,3]);
 
 testing array key: [new Date(0)]
 store.put('value12', [new Date(0)]);
 store.get([new Date(0)]);
 PASS getreq.result is "value12"
+store.delete([new Date(0)]);
 
 testing array key: [new Date('2525-01-01T00:00:00Z')]
 store.put('value13', [new Date('2525-01-01T00:00:00Z')]);
 store.get([new Date('2525-01-01T00:00:00Z')]);
 PASS getreq.result is "value13"
+store.delete([new Date('2525-01-01T00:00:00Z')]);
 
 testing array key: [new Date(0), new Date('2525-01-01T00:00:00Z')]
 store.put('value14', [new Date(0), new Date('2525-01-01T00:00:00Z')]);
 store.get([new Date(0), new Date('2525-01-01T00:00:00Z')]);
 PASS getreq.result is "value14"
+store.delete([new Date(0), new Date('2525-01-01T00:00:00Z')]);
 
 testing array key: ['']
 store.put('value15', ['']);
 store.get(['']);
 PASS getreq.result is "value15"
+store.delete(['']);
 
 testing array key: ['']
 store.put('value16', ['']);
 store.get(['']);
 PASS getreq.result is "value16"
+store.delete(['']);
 
 testing array key: ['abc123']
 store.put('value17', ['abc123']);
 store.get(['abc123']);
 PASS getreq.result is "value17"
+store.delete(['abc123']);
 
 testing array key: ['abc', 123]
 store.put('value18', ['abc', 123]);
 store.get(['abc', 123]);
 PASS getreq.result is "value18"
+store.delete(['abc', 123]);
 
 testing array key: [[]]
 store.put('value19', [[]]);
 store.get([[]]);
 PASS getreq.result is "value19"
+store.delete([[]]);
 
 testing array key: [[], []]
 store.put('value20', [[], []]);
 store.get([[], []]);
 PASS getreq.result is "value20"
+store.delete([[], []]);
 
 testing array key: [[], [], []]
 store.put('value21', [[], [], []]);
 store.get([[], [], []]);
 PASS getreq.result is "value21"
+store.delete([[], [], []]);
 
 testing array key: [[[]]]
 store.put('value22', [[[]]]);
 store.get([[[]]]);
 PASS getreq.result is "value22"
+store.delete([[[]]]);
 
 testing array key: [[[[]]]]
 store.put('value23', [[[[]]]]);
 store.get([[[[]]]]);
 PASS getreq.result is "value23"
+store.delete([[[[]]]]);
 
 testing array key: [123, 'abc', new Date(0), []]
 store.put('value24', [123, 'abc', new Date(0), []]);
 store.get([123, 'abc', new Date(0), []]);
 PASS getreq.result is "value24"
+store.delete([123, 'abc', new Date(0), []]);
 
 testing array key: [[123, 'abc', new Date(0), []], [456, 'def', new Date(999), [[]]]]
 store.put('value25', [[123, 'abc', new Date(0), []], [456, 'def', new Date(999), [[]]]]);
 store.get([[123, 'abc', new Date(0), []], [456, 'def', new Date(999), [[]]]]);
 PASS getreq.result is "value25"
+store.delete([[123, 'abc', new Date(0), []], [456, 'def', new Date(999), [[]]]]);
 
 testing array key: long_array
 store.put('value26', long_array);
 store.get(long_array);
 PASS getreq.result is "value26"
+store.delete(long_array);
 
 testing array key: self_referrential_array
 store.put('value27', self_referrential_array);
 store.get(self_referrential_array);
 PASS getreq.result is "value27"
+store.delete(self_referrential_array);
 
 trans = db.transaction('store', 'readwrite')
 store = trans.objectStore('store')
index ee0e6d4..83f3192 100644 (file)
@@ -7,7 +7,7 @@ indexedDB = self.indexedDB || self.webkitIndexedDB || self.mozIndexedDB || self.
 
 indexedDB.deleteDatabase(dbname)
 indexedDB.open(dbname)
-Successfully added all 500 array keys, without any conflicts.
+Successfully added all 50 array keys, without any conflicts.
 PASS successfullyParsed is true
 
 TEST COMPLETE
index ee0e6d4..83f3192 100644 (file)
@@ -7,7 +7,7 @@ indexedDB = self.indexedDB || self.webkitIndexedDB || self.mozIndexedDB || self.
 
 indexedDB.deleteDatabase(dbname)
 indexedDB.open(dbname)
-Successfully added all 500 array keys, without any conflicts.
+Successfully added all 50 array keys, without any conflicts.
 PASS successfullyParsed is true
 
 TEST COMPLETE
index 0065ee9..0041966 100644 (file)
@@ -2,7 +2,7 @@ description("This test makes sure that array IDBKeys are correctly compared for
 
 indexedDBTest(prepareDatabase);
 
-var iterationCount = 500;
+var iterationCount = 50;
 var successCount = 0;
 function doAdd(objectStore, value)
 {
@@ -10,15 +10,15 @@ function doAdd(objectStore, value)
     var request = objectStore.add("value", key);
     request.onsuccess = function() {
         if (++successCount == iterationCount) {
-            debug("Successfully added all 500 array keys, without any conflicts.");
+            debug("Successfully added all 50 array keys, without any conflicts.");
             finishJSTest();
         }
     };
 
     request.onerror = function(event) {
-        debug("Error putting value into database (" + value + "): " + event.type);
+        debug("Error putting value into database (" + value + "): (" + event.target.error.name +") " + event.target.error.message);
         finishJSTest();
-    }    
+    }
 }
 
 function prepareDatabase(event)
index 34850f3..def87e2 100644 (file)
@@ -19,10 +19,10 @@ db = event.target.result
 
 doPrefetchInvalidationTest():
 store = db.transaction('store', 'readwrite').objectStore('store')
-Populate the store with 200 records.
+Populate the store with 100 records.
 cursorRequest = store.openCursor()
 
-continue100Times():
+continue50Times():
 PASS cursorRequest.result is non-null.
 
 doOperationAndContinue():
@@ -37,10 +37,10 @@ PASS cursorRequest.result is null
 
 doPrefetchInvalidationTest():
 store = db.transaction('store', 'readwrite').objectStore('store')
-Populate the store with 200 records.
+Populate the store with 100 records.
 cursorRequest = store.openCursor()
 
-continue100Times():
+continue50Times():
 PASS cursorRequest.result is non-null.
 
 doOperationAndContinue():
index 34850f3..def87e2 100644 (file)
@@ -19,10 +19,10 @@ db = event.target.result
 
 doPrefetchInvalidationTest():
 store = db.transaction('store', 'readwrite').objectStore('store')
-Populate the store with 200 records.
+Populate the store with 100 records.
 cursorRequest = store.openCursor()
 
-continue100Times():
+continue50Times():
 PASS cursorRequest.result is non-null.
 
 doOperationAndContinue():
@@ -37,10 +37,10 @@ PASS cursorRequest.result is null
 
 doPrefetchInvalidationTest():
 store = db.transaction('store', 'readwrite').objectStore('store')
-Populate the store with 200 records.
+Populate the store with 100 records.
 cursorRequest = store.openCursor()
 
-continue100Times():
+continue50Times():
 PASS cursorRequest.result is non-null.
 
 doOperationAndContinue():
index 0c8c082..d8f4be9 100644 (file)
@@ -20,7 +20,7 @@ function testValidArrayKeys()
     evalAndLog("store = trans.objectStore('store')");
     debug("");
 
-    evalAndLog("long_array = []; for (i = 0; i < 1000; ++i) { long_array.push('abc', 123, new Date(0), []); }");
+    evalAndLog("long_array = []; for (i = 0; i < 10; ++i) { long_array.push('abc', 123, new Date(0), []); }");
     debug("");
 
     debug("array that contains non-numeric self-reference");
@@ -78,6 +78,9 @@ function testValidArrayKeys()
             getreq.onerror = unexpectedErrorCallback;
             getreq.onsuccess = function() {
                 shouldBeEqualToString("getreq.result", value);
+
+                evalAndLog("store.delete(" + key + ");").onerror = unexpectedErrorCallback;
+
                 debug("");
                 callback();
             };
index 640cda1..0643b98 100644 (file)
@@ -40,14 +40,14 @@ function doPrefetchInvalidationTest(operation, callback)
     debug("-------------------------------------------");
     preamble();
     evalAndLog("store = db.transaction('store', 'readwrite').objectStore('store')");
-    debug("Populate the store with 200 records.");
-    for (var i = 0; i < 200; ++i)
+    debug("Populate the store with 100 records.");
+    for (var i = 0; i < 100; ++i)
         store.put(i, i);
     evalAndLog("cursorRequest = store.openCursor()");
-    continue100Times(operation, callback);
+    continue50Times(operation, callback);
 }
 
-function continue100Times(operation, callback)
+function continue50Times(operation, callback)
 {
     preamble();
     var count = 0;
@@ -55,7 +55,7 @@ function continue100Times(operation, callback)
     cursorRequest.onsuccess = function() {
         var cursor = cursorRequest.result;
         ++count;
-        if (count < 100) {
+        if (count < 50) {
             cursor.continue();
             return;
         }
diff --git a/LayoutTests/storage/indexeddb/resources/storage-limit.js b/LayoutTests/storage/indexeddb/resources/storage-limit.js
new file mode 100644 (file)
index 0000000..0d57b6c
--- /dev/null
@@ -0,0 +1,38 @@
+if (this.importScripts) {
+    importScripts('../../../resources/js-test.js');
+    importScripts('shared.js');
+}
+
+var quota = 1024 * 1024; // 1 MB
+description("This test makes sure that storage of indexedDB does not grow unboundedly.");
+
+if (window.testRunner)
+    testRunner.setIDBPerOriginQuota(quota);
+
+indexedDBTest(prepareDatabase, onOpenSuccess);
+
+function prepareDatabase(event)
+{
+    preamble(event);
+    evalAndLog("db = event.target.result");
+    evalAndLog("store = db.createObjectStore('store')");
+}
+
+function onOpenSuccess(event)
+{
+    preamble(event);
+    evalAndLog("db = event.target.result");
+    evalAndLog("store = db.transaction('store', 'readwrite').objectStore('store')");
+    evalAndLog("request = store.add(new Uint8Array(" + (quota + 1) + "), 0)");
+    request.onerror = function(event) {
+        shouldBeTrue("'error' in request");
+        shouldBe("request.error.code", "DOMException.QUOTA_EXCEEDED_ERR");
+        shouldBeEqualToString("request.error.name", "QuotaExceededError");
+        finishJSTest();
+    }
+
+    request.onsuccess = function(event) {
+        testFailed("Add operation should fail because storage limit is reached, but succeeded.");
+        finishJSTest();
+    }
+}
\ No newline at end of file
diff --git a/LayoutTests/storage/indexeddb/storage-limit-expected.txt b/LayoutTests/storage/indexeddb/storage-limit-expected.txt
new file mode 100644 (file)
index 0000000..c314a44
--- /dev/null
@@ -0,0 +1,25 @@
+This test makes sure that storage of indexedDB does not grow unboundedly.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+indexedDB = self.indexedDB || self.webkitIndexedDB || self.mozIndexedDB || self.msIndexedDB || self.OIndexedDB;
+
+indexedDB.deleteDatabase(dbname)
+indexedDB.open(dbname)
+
+prepareDatabase():
+db = event.target.result
+store = db.createObjectStore('store')
+
+onOpenSuccess():
+db = event.target.result
+store = db.transaction('store', 'readwrite').objectStore('store')
+request = store.add(new Uint8Array(1048577), 0)
+PASS 'error' in request is true
+PASS request.error.code is DOMException.QUOTA_EXCEEDED_ERR
+PASS request.error.name is "QuotaExceededError"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/storage/indexeddb/storage-limit.html b/LayoutTests/storage/indexeddb/storage-limit.html
new file mode 100644 (file)
index 0000000..45acfb0
--- /dev/null
@@ -0,0 +1,9 @@
+<html>
+<head>
+<script src="../../resources/js-test.js"></script>
+<script src="resources/shared.js"></script>
+</head>
+<body>
+<script src="resources/storage-limit.js"></script>
+</body>
+</html>
\ No newline at end of file
index 6ce92ef..b1e25d3 100644 (file)
@@ -1,3 +1,43 @@
+2018-11-01  Sihui Liu  <sihui_liu@apple.com>
+
+        Add a storage limit for IndexedDB
+        https://bugs.webkit.org/show_bug.cgi?id=190598
+        <rdar://problem/44654715>
+
+        Reviewed by Chris Dumez.
+
+        Set a storage limit in IndexedDB for each pair of mainFrameOrigin and openingOrigin. 
+        IndexedDB will return a QuotaExceededError if limit is reached.
+
+        If the size of free disk space is over 1 GB, the default limit is 500 MB; otherwise it is 
+        half the free disk space.
+
+        Test: storage/indexeddb/storage-limit.html
+
+        * Modules/indexeddb/server/IDBBackingStore.h:
+        * Modules/indexeddb/server/IDBServer.cpp:
+        (WebCore::IDBServer::IDBServer::createBackingStore):
+        (WebCore::IDBServer::IDBServer::setPerOriginQuota):
+        * Modules/indexeddb/server/IDBServer.h:
+        (WebCore::IDBServer::IDBServer::perOriginQuota const):
+        * Modules/indexeddb/server/MemoryIDBBackingStore.h:
+        * Modules/indexeddb/server/SQLiteIDBBackingStore.cpp:
+        (WebCore::IDBServer::SQLiteIDBBackingStore::SQLiteIDBBackingStore):
+        (WebCore::IDBServer::SQLiteIDBBackingStore::quotaForOrigin const):
+        (WebCore::IDBServer::SQLiteIDBBackingStore::maximumSize const):
+        (WebCore::IDBServer::SQLiteIDBBackingStore::beginTransaction):
+        (WebCore::IDBServer::SQLiteIDBBackingStore::createObjectStore):
+        (WebCore::IDBServer::SQLiteIDBBackingStore::renameObjectStore):
+        (WebCore::IDBServer::SQLiteIDBBackingStore::createIndex):
+        (WebCore::IDBServer::SQLiteIDBBackingStore::uncheckedPutIndexRecord):
+        (WebCore::IDBServer::SQLiteIDBBackingStore::renameIndex):
+        (WebCore::IDBServer::SQLiteIDBBackingStore::addRecord):
+        (WebCore::IDBServer::SQLiteIDBBackingStore::uncheckedSetKeyGeneratorValue):
+        * Modules/indexeddb/server/SQLiteIDBBackingStore.h:
+        * Modules/indexeddb/server/UniqueIDBDatabase.cpp:
+        (WebCore::IDBServer::UniqueIDBDatabase::setQuota):
+        * Modules/indexeddb/server/UniqueIDBDatabase.h:
+
 2018-11-01  Justin Michaud  <justin_michaud@apple.com>
 
         CSS Custom Properties API Should Support syntax="*" and "<length>", and handle cycles properly
index dc91f1e..bbf356b 100644 (file)
@@ -100,6 +100,7 @@ public:
     virtual bool supportsSimultaneousTransactions() = 0;
     virtual bool isEphemeral() = 0;
 
+    virtual void setQuota(uint64_t) = 0;
 protected:
     IDBBackingStore() { RELEASE_ASSERT(!isMainThread()); }
 };
index 059aeb5..cb17ba1 100644 (file)
@@ -126,7 +126,7 @@ std::unique_ptr<IDBBackingStore> IDBServer::createBackingStore(const IDBDatabase
     if (m_databaseDirectoryPath.isEmpty())
         return MemoryIDBBackingStore::create(identifier);
 
-    return std::make_unique<SQLiteIDBBackingStore>(identifier, m_databaseDirectoryPath, m_backingStoreTemporaryFileHandler);
+    return std::make_unique<SQLiteIDBBackingStore>(identifier, m_databaseDirectoryPath, m_backingStoreTemporaryFileHandler, m_perOriginQuota);
 }
 
 void IDBServer::openDatabase(const IDBRequestData& requestData)
@@ -647,6 +647,14 @@ void IDBServer::didPerformCloseAndDeleteDatabases(uint64_t callbackID)
     callback();
 }
 
+void IDBServer::setPerOriginQuota(uint64_t quota)
+{
+    m_perOriginQuota = quota;
+
+    for (auto& database : m_uniqueIDBDatabaseMap.values())
+        database->setQuota(quota);
+}
+
 } // namespace IDBServer
 } // namespace WebCore
 
index 265f4db..fa89aa5 100644 (file)
@@ -48,6 +48,8 @@ struct IDBGetRecordData;
 
 namespace IDBServer {
 
+const uint64_t defaultPerOriginQuota = 500 * MB;
+
 class IDBBackingStoreTemporaryFileHandler;
 
 class IDBServer : public RefCounted<IDBServer>, public CrossThreadTaskHandler {
@@ -104,6 +106,9 @@ public:
     WEBCORE_EXPORT void closeAndDeleteDatabasesModifiedSince(WallTime, Function<void ()>&& completionHandler);
     WEBCORE_EXPORT void closeAndDeleteDatabasesForOrigins(const Vector<SecurityOriginData>&, Function<void ()>&& completionHandler);
 
+    uint64_t perOriginQuota() const { return m_perOriginQuota; }
+    WEBCORE_EXPORT void setPerOriginQuota(uint64_t);
+
 private:
     IDBServer(IDBBackingStoreTemporaryFileHandler&);
     IDBServer(const String& databaseDirectoryPath, IDBBackingStoreTemporaryFileHandler&);
@@ -127,6 +132,8 @@ private:
 
     String m_databaseDirectoryPath;
     IDBBackingStoreTemporaryFileHandler& m_backingStoreTemporaryFileHandler;
+
+    uint64_t m_perOriginQuota { defaultPerOriginQuota };
 };
 
 } // namespace IDBServer
index e93a17d..fca4988 100644 (file)
@@ -78,6 +78,8 @@ public:
     bool supportsSimultaneousTransactions() final { return true; }
     bool isEphemeral() final { return true; }
 
+    void setQuota(uint64_t quota) final { UNUSED_PARAM(quota); };
+
     void removeObjectStoreForVersionChangeAbort(MemoryObjectStore&);
     void restoreObjectStoreForVersionChangeAbort(Ref<MemoryObjectStore>&&);
 
index a1db7a2..582d3ca 100644 (file)
@@ -226,9 +226,10 @@ static const String& blobFilesTableSchemaAlternate()
     return blobFilesTableSchemaString;
 }
 
-SQLiteIDBBackingStore::SQLiteIDBBackingStore(const IDBDatabaseIdentifier& identifier, const String& databaseRootDirectory, IDBBackingStoreTemporaryFileHandler& fileHandler)
+SQLiteIDBBackingStore::SQLiteIDBBackingStore(const IDBDatabaseIdentifier& identifier, const String& databaseRootDirectory, IDBBackingStoreTemporaryFileHandler& fileHandler, uint64_t quota)
     : m_identifier(identifier)
     , m_temporaryFileHandler(fileHandler)
+    , m_quota(quota)
 {
     m_absoluteDatabaseDirectory = identifier.databaseDirectoryRelativeToRoot(databaseRootDirectory);
 }
@@ -837,6 +838,36 @@ IDBError SQLiteIDBBackingStore::getOrEstablishDatabaseInfo(IDBDatabaseInfo& info
     return IDBError { };
 }
 
+uint64_t SQLiteIDBBackingStore::quotaForOrigin() const
+{
+    ASSERT(!isMainThread());
+    uint64_t diskFreeSpaceSize = 0;
+    FileSystem::getVolumeFreeSpace(m_absoluteDatabaseDirectory, diskFreeSpaceSize);
+    return std::min(diskFreeSpaceSize / 2, m_quota);
+}
+
+uint64_t SQLiteIDBBackingStore::maximumSize() const
+{
+    ASSERT(!isMainThread());
+
+    // The maximum size for one database file is the quota for its origin, minus size of all databases within that origin,
+    // and plus current size of the database file.
+    uint64_t databaseFileSize = SQLiteFileSystem::getDatabaseFileSize(fullDatabasePath());
+    uint64_t quota = quotaForOrigin();
+
+    uint64_t diskUsage = 0;
+    for (auto& directory : FileSystem::listDirectory(m_absoluteDatabaseDirectory, "*")) {
+        for (auto& file : FileSystem::listDirectory(directory, "*.sqlite3"_s))
+            diskUsage += SQLiteFileSystem::getDatabaseFileSize(file);
+    }
+    ASSERT(diskUsage >= databaseFileSize);
+
+    if (quota < diskUsage)
+        return databaseFileSize;
+
+    return quota - diskUsage + databaseFileSize;
+}
+
 IDBError SQLiteIDBBackingStore::beginTransaction(const IDBTransactionInfo& info)
 {
     LOG(IndexedDB, "SQLiteIDBBackingStore::beginTransaction - %s", info.identifier().loggingString().utf8().data());
@@ -845,6 +876,7 @@ IDBError SQLiteIDBBackingStore::beginTransaction(const IDBTransactionInfo& info)
     ASSERT(m_sqliteDB->isOpen());
     ASSERT(m_databaseInfo);
 
+    m_sqliteDB->setMaximumSize(maximumSize());
     auto addResult = m_transactions.add(info.identifier(), nullptr);
     if (!addResult.isNewEntry) {
         LOG_ERROR("Attempt to establish transaction identifier that already exists");
@@ -860,8 +892,12 @@ IDBError SQLiteIDBBackingStore::beginTransaction(const IDBTransactionInfo& info)
         SQLiteStatement sql(*m_sqliteDB, "UPDATE IDBDatabaseInfo SET value = ? where key = 'DatabaseVersion';"_s);
         if (sql.prepare() != SQLITE_OK
             || sql.bindText(1, String::number(info.newVersion())) != SQLITE_OK
-            || sql.step() != SQLITE_DONE)
-            error = IDBError { UnknownError, "Failed to store new database version in database"_s };
+            || sql.step() != SQLITE_DONE) {
+            if (m_sqliteDB->lastError() == SQLITE_FULL)
+                error = IDBError { QuotaExceededError, "Failed to store new database version in database because no enough space for domain"_s };
+            else
+                error = IDBError { UnknownError, "Failed to store new database version in database"_s };
+        }
     }
 
     return error;
@@ -944,6 +980,8 @@ IDBError SQLiteIDBBackingStore::createObjectStore(const IDBResourceIdentifier& t
             || sql->bindInt64(5, info.maxIndexID()) != SQLITE_OK
             || sql->step() != SQLITE_DONE) {
             LOG_ERROR("Could not add object store '%s' to ObjectStoreInfo table (%i) - %s", info.name().utf8().data(), m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+            if (m_sqliteDB->lastError() == SQLITE_FULL)
+                return IDBError { QuotaExceededError, "Could not create object store because no enough space for domain"_s };
             return IDBError { UnknownError, "Could not create object store"_s };
         }
     }
@@ -954,6 +992,8 @@ IDBError SQLiteIDBBackingStore::createObjectStore(const IDBResourceIdentifier& t
             || sql->bindInt64(1, info.identifier()) != SQLITE_OK
             || sql->step() != SQLITE_DONE) {
             LOG_ERROR("Could not seed initial key generator value for ObjectStoreInfo table (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+            if (m_sqliteDB->lastError() == SQLITE_FULL)
+                return IDBError { QuotaExceededError, "Could not seed initial key generator value for object store because no enough space for domain"_s };
             return IDBError { UnknownError, "Could not seed initial key generator value for object store"_s };
         }
     }
@@ -1079,6 +1119,8 @@ IDBError SQLiteIDBBackingStore::renameObjectStore(const IDBResourceIdentifier& t
             || sql->bindInt64(2, objectStoreIdentifier) != SQLITE_OK
             || sql->step() != SQLITE_DONE) {
             LOG_ERROR("Could not update name for object store id %" PRIi64 " in ObjectStoreInfo table (%i) - %s", objectStoreIdentifier, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+            if (m_sqliteDB->lastError() == SQLITE_FULL)
+                return IDBError { QuotaExceededError, "Could not rename object store because no enough space for domain"_s };
             return IDBError { UnknownError, "Could not rename object store"_s };
         }
     }
@@ -1162,6 +1204,8 @@ IDBError SQLiteIDBBackingStore::createIndex(const IDBResourceIdentifier& transac
         || sql->bindInt(6, info.multiEntry()) != SQLITE_OK
         || sql->step() != SQLITE_DONE) {
         LOG_ERROR("Could not add index '%s' to IndexInfo table (%i) - %s", info.name().utf8().data(), m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+        if (m_sqliteDB->lastError() == SQLITE_FULL)
+            return IDBError { QuotaExceededError, "Unable to create index in database because no enough space for domain"_s };
         return IDBError { UnknownError, "Unable to create index in database"_s };
     }
 
@@ -1304,6 +1348,8 @@ IDBError SQLiteIDBBackingStore::uncheckedPutIndexRecord(int64_t objectStoreID, i
             || sql->bindInt64(5, recordID) != SQLITE_OK
             || sql->step() != SQLITE_DONE) {
             LOG_ERROR("Could not put index record for index %" PRIi64 " in object store %" PRIi64 " in Records table (%i) - %s", indexID, objectStoreID, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+            if (m_sqliteDB->lastError() == SQLITE_FULL)
+                return IDBError { QuotaExceededError, "Error putting index record into database because no enough space for domain"_s };
             return IDBError { UnknownError, "Error putting index record into database"_s };
         }
     }
@@ -1393,6 +1439,8 @@ IDBError SQLiteIDBBackingStore::renameIndex(const IDBResourceIdentifier& transac
             || sql->bindInt64(3, indexIdentifier) != SQLITE_OK
             || sql->step() != SQLITE_DONE) {
             LOG_ERROR("Could not update name for index id (%" PRIi64 ", %" PRIi64 ") in IndexInfo table (%i) - %s", objectStoreIdentifier, indexIdentifier, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+            if (m_sqliteDB->lastError() == SQLITE_FULL)
+                return IDBError { QuotaExceededError, "Could not rename index because no enough space for domain"_s };
             return IDBError { UnknownError, "Could not rename index"_s };
         }
     }
@@ -1732,6 +1780,8 @@ IDBError SQLiteIDBBackingStore::addRecord(const IDBResourceIdentifier& transacti
             || sql->bindBlob(3, value.data().data()->data(), value.data().data()->size()) != SQLITE_OK
             || sql->step() != SQLITE_DONE) {
             LOG_ERROR("Could not put record for object store %" PRIi64 " in Records table (%i) - %s", objectStoreInfo.identifier(), m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+            if (m_sqliteDB->lastError() == SQLITE_FULL)
+                return IDBError { QuotaExceededError, "Unable to store record in object store because no enough space for domain"_s };
             return IDBError { UnknownError, "Unable to store record in object store"_s };
         }
 
@@ -1764,6 +1814,8 @@ IDBError SQLiteIDBBackingStore::addRecord(const IDBResourceIdentifier& transacti
                 || sql->bindText(2, url) != SQLITE_OK
                 || sql->step() != SQLITE_DONE) {
                 LOG_ERROR("Unable to record Blob record in database");
+                if (m_sqliteDB->lastError() == SQLITE_FULL)
+                    return IDBError { QuotaExceededError, "Unable to record Blob record in database because no enough space for domain"_s };
                 return IDBError { UnknownError, "Unable to record Blob record in database"_s };
             }
         }
@@ -1797,6 +1849,8 @@ IDBError SQLiteIDBBackingStore::addRecord(const IDBResourceIdentifier& transacti
                 || sql->bindText(2, storedFilename) != SQLITE_OK
                 || sql->step() != SQLITE_DONE) {
                 LOG_ERROR("Unable to record Blob file record in database");
+                if (m_sqliteDB->lastError() == SQLITE_FULL)
+                    return IDBError { QuotaExceededError, "Unable to record Blob file in database because no enough space for domain"_s };
                 return IDBError { UnknownError, "Unable to record Blob file record in database"_s };
             }
         }
@@ -2330,6 +2384,8 @@ IDBError SQLiteIDBBackingStore::uncheckedSetKeyGeneratorValue(int64_t objectStor
         || sql->bindInt64(2, value) != SQLITE_OK
         || sql->step() != SQLITE_DONE) {
         LOG_ERROR("Could not update key generator value (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+        if (m_sqliteDB->lastError() == SQLITE_FULL)
+            return IDBError { QuotaExceededError, "Error storing new key generator value in database because no enough space for domain"_s };
         return IDBError { ConstraintError, "Error storing new key generator value in database" };
     }
 
index 257efe8..4b8b086 100644 (file)
@@ -47,7 +47,7 @@ class SQLiteIDBCursor;
 
 class SQLiteIDBBackingStore : public IDBBackingStore {
 public:
-    SQLiteIDBBackingStore(const IDBDatabaseIdentifier&, const String& databaseRootDirectory, IDBBackingStoreTemporaryFileHandler&);
+    SQLiteIDBBackingStore(const IDBDatabaseIdentifier&, const String& databaseRootDirectory, IDBBackingStoreTemporaryFileHandler&, uint64_t quota);
     
     ~SQLiteIDBBackingStore() final;
 
@@ -80,6 +80,8 @@ public:
     IDBObjectStoreInfo* infoForObjectStore(uint64_t objectStoreIdentifier) final;
     void deleteBackingStore() final;
 
+    void setQuota(uint64_t quota) final { m_quota = quota; }
+
     bool supportsSimultaneousTransactions() final { return false; }
     bool isEphemeral() final { return false; }
 
@@ -97,6 +99,9 @@ private:
     String filenameForDatabaseName() const;
     String fullDatabasePath() const;
 
+    uint64_t quotaForOrigin() const;
+    uint64_t maximumSize() const;
+
     bool ensureValidRecordsTable();
     bool ensureValidIndexRecordsTable();
     bool ensureValidIndexRecordsIndex();
@@ -194,6 +199,8 @@ private:
     JSC::Strong<JSC::JSGlobalObject> m_globalObject;
 
     IDBBackingStoreTemporaryFileHandler& m_temporaryFileHandler;
+    
+    uint64_t m_quota;
 };
 
 } // namespace IDBServer
index a9871da..f6c4034 100644 (file)
@@ -1907,6 +1907,12 @@ void UniqueIDBDatabase::forgetErrorCallback(uint64_t callbackIdentifier)
     m_errorCallbacks.remove(callbackIdentifier);
 }
 
+void UniqueIDBDatabase::setQuota(uint64_t quota)
+{
+    if (m_backingStore)
+        m_backingStore->setQuota(quota);
+}
+
 } // namespace IDBServer
 } // namespace WebCore
 
index f3b8788..d46dfa9 100644 (file)
@@ -118,6 +118,7 @@ public:
 
     bool hardClosedForUserDelete() const { return m_hardClosedForUserDelete; }
 
+    void setQuota(uint64_t);
 private:
     void handleDatabaseOperations();
     void handleCurrentOperation();
index 4eb77c5..7061147 100644 (file)
@@ -1,3 +1,26 @@
+2018-11-01  Sihui Liu  <sihui_liu@apple.com>
+
+        Add a storage limit for IndexedDB
+        https://bugs.webkit.org/show_bug.cgi?id=190598
+        <rdar://problem/44654715>
+
+        Reviewed by Chris Dumez.
+
+        Add SPI for testing.
+
+        * NetworkProcess/NetworkProcess.cpp:
+        (WebKit::NetworkProcess::NetworkProcess):
+        (WebKit::NetworkProcess::idbServer):
+        (WebKit::NetworkProcess::setIDBPerOriginQuota):
+        * NetworkProcess/NetworkProcess.h:
+        * NetworkProcess/NetworkProcess.messages.in:
+        * UIProcess/API/C/WKContext.cpp:
+        (WKContextSetIDBPerOriginQuota):
+        * UIProcess/API/C/WKContextPrivate.h:
+        * UIProcess/WebProcessPool.cpp:
+        (WebKit::WebProcessPool::setIDBPerOriginQuota):
+        * UIProcess/WebProcessPool.h:
+
 2018-11-01  Chris Dumez  <cdumez@apple.com>
 
         [PSON] WebPageProxy::receivedNavigationPolicyDecision() should not schedule the new load asynchronously when process-swapping
index 859b3f7..073f34f 100644 (file)
@@ -138,6 +138,9 @@ NetworkProcess::NetworkProcess()
     , m_clearCacheDispatchGroup(0)
 #endif
     , m_storageTaskQueue(WorkQueue::create("com.apple.WebKit.StorageTask"))
+#if ENABLE(INDEXED_DATABASE)
+    , m_idbPerOriginQuota(IDBServer::defaultPerOriginQuota)
+#endif
 {
     NetworkProcessPlatformStrategies::initialize();
 
@@ -1105,6 +1108,7 @@ IDBServer::IDBServer& NetworkProcess::idbServer(PAL::SessionID sessionID)
     ASSERT(!path.isEmpty());
     
     addResult.iterator->value = IDBServer::IDBServer::create(path, NetworkProcess::singleton());
+    addResult.iterator->value->setPerOriginQuota(m_idbPerOriginQuota);
     return *addResult.iterator->value;
 }
 
@@ -1194,6 +1198,13 @@ void NetworkProcess::addIndexedDatabaseSession(PAL::SessionID sessionID, String&
     }
 }
 
+void NetworkProcess::setIDBPerOriginQuota(uint64_t quota)
+{
+    m_idbPerOriginQuota = quota;
+    
+    for (auto& server : m_idbServers.values())
+        server->setPerOriginQuota(quota);
+}
 #endif // ENABLE(INDEXED_DATABASE)
 
 #if ENABLE(SANDBOX_EXTENSIONS)
index 3a28d5c..bb0ce83 100644 (file)
@@ -191,6 +191,7 @@ public:
     // WebCore::IDBServer::IDBBackingStoreFileHandler.
     void prepareForAccessToTemporaryFile(const String& path) final;
     void accessToTemporaryFileComplete(const String& path) final;
+    void setIDBPerOriginQuota(uint64_t);
 #endif
 
 #if ENABLE(SANDBOX_EXTENSIONS)
@@ -392,6 +393,7 @@ private:
 #if ENABLE(INDEXED_DATABASE)
     HashMap<PAL::SessionID, String> m_idbDatabasePaths;
     HashMap<PAL::SessionID, RefPtr<WebCore::IDBServer::IDBServer>> m_idbServers;
+    uint64_t m_idbPerOriginQuota;
 #endif
 
     HashMap<String, RefPtr<SandboxExtension>> m_blobTemporaryFileSandboxExtensions;
index 2c797e0..7fbb074 100644 (file)
@@ -114,4 +114,8 @@ messages -> NetworkProcess LegacyReceiver {
 
     DisableServiceWorkerProcessTerminationDelay()
 #endif
+
+#if ENABLE(INDEXED_DATABASE)
+    SetIDBPerOriginQuota(uint64_t quota)
+#endif
 }
index 196528c..0401e72 100644 (file)
@@ -649,3 +649,8 @@ void WKContextClearSupportedPlugins(WKContextRef contextRef)
     toImpl(contextRef)->clearSupportedPlugins();
 #endif
 }
+
+void WKContextSetIDBPerOriginQuota(WKContextRef contextRef, uint64_t quota)
+{
+    toImpl(contextRef)->setIDBPerOriginQuota(quota);
+}
index ec6c863..46d37e1 100644 (file)
@@ -116,6 +116,8 @@ WK_EXPORT WKProcessID WKContextGetNetworkProcessIdentifier(WKContextRef context)
 WK_EXPORT void WKContextAddSupportedPlugin(WKContextRef context, WKStringRef domain, WKStringRef name, WKArrayRef mimeTypes, WKArrayRef extensions);
 WK_EXPORT void WKContextClearSupportedPlugins(WKContextRef context);
 
+WK_EXPORT void WKContextSetIDBPerOriginQuota(WKContextRef context, uint64_t quota);
+
 #ifdef __cplusplus
 }
 #endif
index 86335a2..3fcdd4e 100644 (file)
@@ -1616,6 +1616,13 @@ void WebProcessPool::syncNetworkProcessCookies()
     ensureNetworkProcess().syncAllCookies();
 }
 
+void WebProcessPool::setIDBPerOriginQuota(uint64_t quota)
+{
+#if ENABLE(INDEXED_DATABASE)
+    ensureNetworkProcess().send(Messages::NetworkProcess::SetIDBPerOriginQuota(quota), 0);
+#endif
+}
+
 void WebProcessPool::allowSpecificHTTPSCertificateForHost(const WebCertificateInfo* certificate, const String& host)
 {
     ensureNetworkProcess();
index 14071f0..dfacf81 100644 (file)
@@ -281,6 +281,8 @@ public:
 
     void syncNetworkProcessCookies();
 
+    void setIDBPerOriginQuota(uint64_t);
+
     void setShouldMakeNextWebProcessLaunchFailForTesting(bool value) { m_shouldMakeNextWebProcessLaunchFailForTesting = value; }
     bool shouldMakeNextWebProcessLaunchFailForTesting() const { return m_shouldMakeNextWebProcessLaunchFailForTesting; }
     void setShouldMakeNextNetworkProcessLaunchFailForTesting(bool value) { m_shouldMakeNextNetworkProcessLaunchFailForTesting = value; }
index 46a3cac..907e43c 100644 (file)
@@ -1,3 +1,16 @@
+2018-11-01  Sihui Liu  <sihui_liu@apple.com>
+
+        Add a storage limit for IndexedDB
+        https://bugs.webkit.org/show_bug.cgi?id=190598
+        <rdar://problem/44654715>
+
+        Reviewed by Chris Dumez.
+
+        * Storage/WebDatabaseProvider.cpp:
+        (WebDatabaseProvider::idbConnectionToServerForSession):
+        (WebDatabaseProvider::setIDBPerOriginQuota):
+        * Storage/WebDatabaseProvider.h:
+
 2018-10-30  Alexey Proskuryakov  <ap@apple.com>
 
         Enable InstallAPI for iOS unconditionally
index f5234dd..fb71661 100644 (file)
@@ -53,6 +53,8 @@ WebCore::IDBClient::IDBConnectionToServer& WebDatabaseProvider::idbConnectionToS
             result.iterator->value = WebCore::InProcessIDBServer::create(indexedDatabaseDirectoryPath());
     }
 
+    result.iterator->value->idbServer().setPerOriginQuota(m_idbPerOriginQuota);
+
     return result.iterator->value->connectionToServer();
 }
 
@@ -61,4 +63,13 @@ void WebDatabaseProvider::deleteAllDatabases()
     for (auto& server : m_idbServerMap.values())
         server->idbServer().closeAndDeleteDatabasesModifiedSince(-WallTime::infinity(), [] { });
 }
+
+void WebDatabaseProvider::setIDBPerOriginQuota(uint64_t quota)
+{
+    m_idbPerOriginQuota = quota;
+
+    for (auto& server : m_idbServerMap.values())
+        server->idbServer().setPerOriginQuota(quota);
+}
+
 #endif
index 3f506d7..5222330 100644 (file)
@@ -44,6 +44,8 @@ public:
     WebCore::IDBClient::IDBConnectionToServer& idbConnectionToServerForSession(const PAL::SessionID&) override;
 
     void deleteAllDatabases();
+
+    void setIDBPerOriginQuota(uint64_t);
 #endif
 
 private:
@@ -53,5 +55,6 @@ private:
 
 #if ENABLE(INDEXED_DATABASE)
     HashMap<uint64_t, RefPtr<WebCore::InProcessIDBServer>> m_idbServerMap;
+    uint64_t m_idbPerOriginQuota { WebCore::IDBServer::defaultPerOriginQuota };
 #endif
 };
index a9bddd6..c57d511 100644 (file)
@@ -1,3 +1,15 @@
+2018-11-01  Sihui Liu  <sihui_liu@apple.com>
+
+        Add a storage limit for IndexedDB
+        https://bugs.webkit.org/show_bug.cgi?id=190598
+        <rdar://problem/44654715>
+
+        Reviewed by Chris Dumez.
+
+        * Storage/WebDatabaseManager.mm:
+        (-[WebDatabaseManager setIDBPerOriginQuota:]):
+        * Storage/WebDatabaseManagerPrivate.h:
+
 2018-10-31  Antti Koivisto  <antti@apple.com>
 
         Remove LayerFlushScheduler
index 73e2331..6d62a98 100644 (file)
@@ -157,6 +157,13 @@ static NSString *databasesDirectoryPath();
 #endif
 }
 
+- (void)setIDBPerOriginQuota:(uint64_t)quota
+{
+#if ENABLE(INDEXED_DATABASE)
+    WebDatabaseProvider::singleton().setIDBPerOriginQuota(quota);
+#endif
+}
+
 #if PLATFORM(IOS_FAMILY)
 
 static bool isFileHidden(NSString *file)
index 939b4cf..2172416 100644 (file)
@@ -70,6 +70,7 @@ extern CFStringRef WebDatabaseOriginsDidChangeNotification;
 
 // For DumpRenderTree support only
 - (void)deleteAllIndexedDatabases;
+- (void)setIDBPerOriginQuota:(uint64_t)quota;
 
 #if TARGET_OS_IPHONE
 + (void)scheduleEmptyDatabaseRemoval;
index 6859a7e..e3b6c8d 100644 (file)
@@ -1,3 +1,16 @@
+2018-11-01  Sihui Liu  <sihui_liu@apple.com>
+
+        Add a storage limit for IndexedDB
+        https://bugs.webkit.org/show_bug.cgi?id=190598
+        <rdar://problem/44654715>
+
+        Reviewed by Chris Dumez.
+
+        * Interfaces/IWebDatabaseManager.idl:
+        * WebDatabaseManager.cpp:
+        (WebDatabaseManager::setIDBPerOriginQuota):
+        * WebDatabaseManager.h:
+
 2018-10-31  Fujii Hironori  <Hironori.Fujii@sony.com>
 
         [Win][WKL] DOMHTMLDocument::setNodeValue does infinite recursion
index 7aebcae..e81d493 100644 (file)
@@ -69,4 +69,5 @@ interface IWebDatabaseManager : IUnknown
 interface IWebDatabaseManager2 : IWebDatabaseManager
 {
     HRESULT deleteAllIndexedDatabases();
+    HRESULT setIDBPerOriginQuota([in] unsigned long long quota);
 }
index 3605623..a9693a1 100644 (file)
@@ -344,6 +344,14 @@ HRESULT WebDatabaseManager::deleteAllIndexedDatabases()
     return S_OK;
 }
 
+HRESULT WebDatabaseManager::setIDBPerOriginQuota(unsigned long long quota)
+{
+#if ENABLE(INDEXED_DATABASE)
+    WebDatabaseProvider::singleton().setIDBPerOriginQuota(quota);
+#endif
+    return S_OK;
+}
+
 class DidModifyOriginData {
     WTF_MAKE_NONCOPYABLE(DidModifyOriginData);
 public:
index f5f6869..4929219 100644 (file)
@@ -56,6 +56,7 @@ public:
 
     // IWebDatabaseManager2
     virtual HRESULT STDMETHODCALLTYPE deleteAllIndexedDatabases();
+    virtual HRESULT STDMETHODCALLTYPE setIDBPerOriginQuota(unsigned long long);
 
     // DatabaseManagerClient
     virtual void dispatchDidModifyOrigin(const WebCore::SecurityOriginData&);
index d93bb67..ba23204 100644 (file)
@@ -1,3 +1,31 @@
+2018-11-01  Sihui Liu  <sihui_liu@apple.com>
+
+        Add a storage limit for IndexedDB
+        https://bugs.webkit.org/show_bug.cgi?id=190598
+        <rdar://problem/44654715>
+
+        Reviewed by Chris Dumez.
+
+        Add API for testing.
+
+        * DumpRenderTree/TestRunner.cpp:
+        (setIDBPerOriginQuotaCallback):
+        (TestRunner::staticFunctions):
+        * DumpRenderTree/TestRunner.h:
+        * DumpRenderTree/mac/TestRunnerMac.mm:
+        (TestRunner::setIDBPerOriginQuota):
+        * DumpRenderTree/win/TestRunnerWin.cpp:
+        (TestRunner::setIDBPerOriginQuota):
+        * WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl:
+        * WebKitTestRunner/InjectedBundle/TestRunner.cpp:
+        (WTR::TestRunner::setIDBPerOriginQuota):
+        * WebKitTestRunner/InjectedBundle/TestRunner.h:
+        * WebKitTestRunner/TestController.cpp:
+        (WTR::TestController::setIDBPerOriginQuota):
+        * WebKitTestRunner/TestController.h:
+        * WebKitTestRunner/TestInvocation.cpp:
+        (WTR::TestInvocation::didReceiveSynchronousMessageFromInjectedBundle):
+
 2018-11-01  Chris Dumez  <cdumez@apple.com>
 
         [PSON] WebPageProxy::receivedNavigationPolicyDecision() should not schedule the new load asynchronously when process-swapping
index 6696334..50808bc 100644 (file)
@@ -923,6 +923,20 @@ static JSValueRef setDatabaseQuotaCallback(JSContextRef context, JSObjectRef fun
     return JSValueMakeUndefined(context);
 }
 
+static JSValueRef setIDBPerOriginQuotaCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+{
+    if (argumentCount < 1)
+        return JSValueMakeUndefined(context);
+    
+    auto* controller = static_cast<TestRunner*>(JSObjectGetPrivate(thisObject));
+    
+    double quota = JSValueToNumber(context, arguments[0], nullptr);
+    if (!std::isnan(quota))
+        controller->setIDBPerOriginQuota(static_cast<uint64_t>(quota));
+    
+    return JSValueMakeUndefined(context);
+}
+
 static JSValueRef setDefersLoadingCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
 {
     if (argumentCount < 1)
@@ -2199,6 +2213,7 @@ JSStaticFunction* TestRunner::staticFunctions()
         { "setRejectsProtectionSpaceAndContinueForAuthenticationChallenges", setRejectsProtectionSpaceAndContinueForAuthenticationChallengesCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "setHandlesAuthenticationChallenges", setHandlesAuthenticationChallengesCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "setIconDatabaseEnabled", setIconDatabaseEnabledCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
+        { "setIDBPerOriginQuota", setIDBPerOriginQuotaCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "setAutomaticLinkDetectionEnabled", setAutomaticLinkDetectionEnabledCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "setMainFrameIsFirstResponder", setMainFrameIsFirstResponderCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "setMockDeviceOrientation", setMockDeviceOrientationCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
index 3847926..1dc16f1 100644 (file)
@@ -106,6 +106,7 @@ public:
     void setDomainRelaxationForbiddenForURLScheme(bool forbidden, JSStringRef scheme);
     void setDefersLoading(bool);
     void setIconDatabaseEnabled(bool);
+    void setIDBPerOriginQuota(uint64_t);
     void setJavaScriptCanAccessClipboard(bool flag);
     void setAutomaticLinkDetectionEnabled(bool flag);
     void setMainFrameIsFirstResponder(bool flag);
index 773c109..ce8728e 100644 (file)
@@ -428,6 +428,11 @@ void TestRunner::setDatabaseQuota(unsigned long long quota)
     [origin release];
 }
 
+void TestRunner::setIDBPerOriginQuota(uint64_t quota)
+{
+    [[WebDatabaseManager sharedWebDatabaseManager] setIDBPerOriginQuota:quota];
+}
+
 void TestRunner::goBack()
 {
     [[mainFrame webView] goBack];
index 64fb190..974d4cc 100644 (file)
@@ -172,6 +172,22 @@ void TestRunner::clearAllDatabases()
     databaseManager2->deleteAllIndexedDatabases();
 }
 
+void TestRunner::setIDBPerOriginQuota(uint64_t quota)
+{
+    COMPtr<IWebDatabaseManager> databaseManager;
+    COMPtr<IWebDatabaseManager> tmpDatabaseManager;
+    if (FAILED(WebKitCreateInstance(CLSID_WebDatabaseManager, 0, IID_IWebDatabaseManager, (void**)&tmpDatabaseManager)))
+        return;
+    if (FAILED(tmpDatabaseManager->sharedWebDatabaseManager(&databaseManager)))
+        return;
+
+    COMPtr<IWebDatabaseManager2> databaseManager2;
+    if (FAILED(databaseManager->QueryInterface(&databaseManager2)))
+        return;
+
+    databaseManager2->setIDBPerOriginQuota(quota);
+}
+
 void TestRunner::setStorageDatabaseIdleInterval(double)
 {
     // FIXME: Implement. Requires non-existant (on Windows) WebStorageManager
index 1ed4ed4..d8659f3 100644 (file)
@@ -133,6 +133,9 @@ interface TestRunner {
     attribute double databaseDefaultQuota;
     attribute double databaseMaxQuota;
 
+    // IndexedDB API
+    void setIDBPerOriginQuota(unsigned long long quota);
+
     // Application Cache API
     void clearAllApplicationCaches();
     void setAppCacheMaximumSize(unsigned long long size);
index d93eae0..c952a9f 100644 (file)
@@ -378,6 +378,13 @@ void TestRunner::disallowIncreaseForApplicationCacheQuota()
     m_disallowIncreaseForApplicationCacheQuota = true;
 }
 
+void TestRunner::setIDBPerOriginQuota(uint64_t quota)
+{
+    WKRetainPtr<WKStringRef> messageName(AdoptWK, WKStringCreateWithUTF8CString("SetIDBPerOriginQuota"));
+    WKRetainPtr<WKUInt64Ref> messageBody(AdoptWK, WKUInt64Create(quota));
+    WKBundlePostSynchronousMessage(InjectedBundle::singleton().bundle(), messageName.get(), messageBody.get(), nullptr);
+}
+
 static inline JSValueRef stringArrayToJS(JSContextRef context, WKArrayRef strings)
 {
     const size_t count = WKArrayGetSize(strings);
index ba0d6a6..47e5972 100644 (file)
@@ -174,6 +174,9 @@ public:
     bool hasDOMCache(JSStringRef origin);
     uint64_t domCacheSize(JSStringRef origin);
 
+    // IndexedDB
+    void setIDBPerOriginQuota(uint64_t);
+
     // Failed load condition testing
     void forceImmediateCompletion();
 
index d39d2b1..817f0f4 100644 (file)
@@ -2730,6 +2730,11 @@ void TestController::clearDOMCaches()
     runUntil(context.done, noTimeout);
 }
 
+void TestController::setIDBPerOriginQuota(uint64_t quota)
+{
+    WKContextSetIDBPerOriginQuota(platformContext(), quota);
+}
+
 struct FetchCacheOriginsCallbackContext {
     FetchCacheOriginsCallbackContext(TestController& controller, WKStringRef origin)
         : testController(controller)
index 5c3fe53..93e8886 100644 (file)
@@ -241,6 +241,8 @@ public:
     bool hasDOMCache(WKStringRef origin);
     uint64_t domCacheSize(WKStringRef origin);
 
+    void setIDBPerOriginQuota(uint64_t);
+
     bool didReceiveServerRedirectForProvisionalNavigation() const { return m_didReceiveServerRedirectForProvisionalNavigation; }
     void clearDidReceiveServerRedirectForProvisionalNavigation() { m_didReceiveServerRedirectForProvisionalNavigation = false; }
 
index c2f9ad7..d2b8ca1 100644 (file)
@@ -1414,6 +1414,13 @@ WKRetainPtr<WKTypeRef> TestInvocation::didReceiveSynchronousMessageFromInjectedB
         return result;
     }
 
+    if (WKStringIsEqualToUTF8CString(messageName, "SetIDBPerOriginQuota")) {
+        ASSERT(WKGetTypeID(messageBody) == WKUInt64GetTypeID());
+        WKUInt64Ref quota = static_cast<WKUInt64Ref>(messageBody);
+        TestController::singleton().setIDBPerOriginQuota(WKUInt64GetValue(quota));
+        return nullptr;
+    }
+
     if (WKStringIsEqualToUTF8CString(messageName, "InjectUserScript")) {
         ASSERT(WKGetTypeID(messageBody) == WKStringGetTypeID());
         WKStringRef script = static_cast<WKStringRef>(messageBody);