Bug 1556217 - LSNG: Add a test for large items; r=asuth, smaug

Differential Revision: https://phabricator.services.mozilla.com/D33417
This commit is contained in:
Jan Varga 2019-06-02 08:00:25 +02:00
parent 77fbcce9dc
commit 67c1136dd9
13 changed files with 237 additions and 1 deletions

View File

@ -45,6 +45,12 @@ class LSDatabase final {
mActor = nullptr;
}
bool HasActiveSnapshot() const {
AssertIsOnOwningThread();
return !!mSnapshot;
}
bool IsAllowedToClose() const {
AssertIsOnOwningThread();

View File

@ -766,6 +766,24 @@ void LSObject::EndExplicitSnapshot(nsIPrincipal& aSubjectPrincipal,
}
}
bool LSObject::GetHasActiveSnapshot(nsIPrincipal& aSubjectPrincipal,
ErrorResult& aError) {
AssertIsOnOwningThread();
if (!CanUseStorage(aSubjectPrincipal)) {
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return 0;
}
if (mDatabase && mDatabase->HasActiveSnapshot()) {
MOZ_ASSERT(!mDatabase->IsAllowedToClose());
return true;
}
return false;
}
NS_IMPL_ADDREF_INHERITED(LSObject, Storage)
NS_IMPL_RELEASE_INHERITED(LSObject, Storage)

View File

@ -173,6 +173,9 @@ class LSObject final : public Storage {
void EndExplicitSnapshot(nsIPrincipal& aSubjectPrincipal,
ErrorResult& aError) override;
bool GetHasActiveSnapshot(nsIPrincipal& aSubjectPrincipal,
ErrorResult& aError) override;
//////////////////////////////////////////////////////////////////////////////
NS_DECL_ISUPPORTS_INHERITED

View File

@ -7,6 +7,8 @@
with Files("**"):
BUG_COMPONENT = ("Core", "DOM: Web Storage")
MOCHITEST_MANIFESTS += ['test/mochitest.ini']
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
XPCSHELL_TESTS_MANIFESTS += [

View File

@ -2,6 +2,7 @@
module.exports = {
"extends": [
"plugin:mozilla/mochitest-test",
"plugin:mozilla/browser-test",
],
};

View File

@ -0,0 +1,74 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// testSteps is expected to be defined by the test using this file.
/* global testSteps:false */
function executeSoon(aFun) {
SpecialPowers.Services.tm.dispatchToMainThread({
run() {
aFun();
},
});
}
function clearAllDatabases() {
let qms = SpecialPowers.Services.qms;
let principal = SpecialPowers.wrap(document).nodePrincipal;
let request = qms.clearStoragesForPrincipal(principal);
return request;
}
if (!window.runTest) {
window.runTest = async function() {
SimpleTest.waitForExplicitFinish();
info("Pushing preferences");
await SpecialPowers.pushPrefEnv(
{
set: [
["dom.storage.testing", true],
["dom.quotaManager.testing", true],
],
});
info("Clearing old databases");
await requestFinished(clearAllDatabases());
ok(typeof testSteps === "function", "There should be a testSteps function");
ok(testSteps.constructor.name === "AsyncFunction",
"testSteps should be an async function");
SimpleTest.registerCleanupFunction(async function() {
await requestFinished(clearAllDatabases());
});
add_task(testSteps);
};
}
function returnToEventLoop() {
return new Promise(function(resolve) {
executeSoon(resolve);
});
}
function getLocalStorage() {
return localStorage;
}
function requestFinished(request) {
return new Promise(function(resolve, reject) {
request.callback = SpecialPowers.wrapCallback(function(requestInner) {
if (requestInner.resultCode === SpecialPowers.Cr.NS_OK) {
resolve(requestInner.result);
} else {
reject(requestInner.resultCode);
}
});
});
}

View File

@ -0,0 +1,10 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
[DEFAULT]
support-files =
helpers.js
unit/test_largeItems.js
[test_largeItems.html]

View File

@ -0,0 +1,19 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<html>
<head>
<title>Large Items Test</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="text/javascript" src="unit/test_largeItems.js"></script>
<script type="text/javascript" src="helpers.js"></script>
</head>
<body onload="runTest();"></body>
</html>

View File

@ -28,6 +28,8 @@ if (!this.runTest) {
enableTesting();
Cu.importGlobalProperties(["crypto"]);
Assert.ok(typeof testSteps === "function",
"There should be a testSteps function");
Assert.ok(testSteps.constructor.name === "AsyncFunction",
@ -227,9 +229,13 @@ function getCurrentPrincipal() {
return Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal);
}
function getDefaultPrincipal() {
return getPrincipal("http://example.com");
}
function getLocalStorage(principal) {
if (!principal) {
principal = getCurrentPrincipal();
principal = getDefaultPrincipal();
}
return Services.domStorageManager.createStorage(null, principal, principal, "");

View File

@ -0,0 +1,84 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
/**
* Test repeatedly setting values that are just under the LocalStorage quota
* limit without yielding control flow in order to verify that the write
* optimizer is present / works. If there was no write optimizer present, the
* IPC message size limit would be exceeded, resulting in a crash.
*/
async function testSteps() {
const globalLimitKB = 5 * 1024;
// 18 and more iterations would produce an IPC message with size greater than
// 256 MB if write optimizer was not present. This number was determined
// experimentally by running the test with disabled write optimizer.
const numberOfIterations = 18;
const randomStringBlockSize = 65536;
// We need to use a random string because LS internally tries to compress
// values.
function getRandomString(size) {
let crypto = this.window ? this.window.crypto : this.crypto;
let decoder = new TextDecoder("ISO-8859-2");
function getRandomStringBlock(array) {
crypto.getRandomValues(array);
return decoder.decode(array);
}
let string = "";
let quotient = size / randomStringBlockSize;
if (quotient) {
let array = new Uint8Array(randomStringBlockSize);
for (let i = 1; i <= quotient; i++) {
string += getRandomStringBlock(array);
}
}
let remainder = size % randomStringBlockSize;
if (remainder) {
let array = new Uint8Array(remainder);
string += getRandomStringBlock(array);
}
return string;
}
const data = {};
data.key = "foo";
data.value = getRandomString(globalLimitKB * 1024 - data.key.length -
numberOfIterations.toString().length);
info("Setting pref");
// By disabling snapshot reusing, we guarantee that the snapshot will be
// checkpointed once control returns to the event loop.
if (this.window) {
await SpecialPowers.pushPrefEnv(
{ set: [["dom.storage.snapshot_reusing", false]] });
} else {
Services.prefs.setBoolPref("dom.storage.snapshot_reusing", false);
}
info("Getting storage");
let storage = getLocalStorage();
info("Adding/updating item");
for (var i = 0; i < numberOfIterations; i++) {
storage.setItem(data.key, data.value + i);
}
info("Returning to event loop");
await returnToEventLoop();
ok(!storage.hasActiveSnapshot, "Snapshot successfully finished");
}

View File

@ -34,6 +34,7 @@ run-sequentially = this test depends on a file produced by test_databaseShadowin
[test_eviction.js]
[test_groupLimit.js]
[test_groupMismatch.js]
[test_largeItems.js]
[test_migration.js]
[test_orderingAfterRemoveAdd.js]
[test_originInit.js]

View File

@ -110,6 +110,11 @@ class Storage : public nsISupports, public nsWrapperCache {
virtual void EndExplicitSnapshot(nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv) {}
virtual bool GetHasActiveSnapshot(nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv) {
return false;
}
//////////////////////////////////////////////////////////////////////////////
// Dispatch storage notification events on all impacted pages in the current

View File

@ -70,4 +70,11 @@ partial interface Storage {
*/
[Throws, NeedsSubjectPrincipal, Pref="dom.storage.testing"]
void endExplicitSnapshot();
/**
* Returns true if the underlying database has been opened and it has an
* active snapshot (initialized implicitly or explicitly).
*/
[Throws, NeedsSubjectPrincipal, Pref="dom.storage.testing"]
readonly attribute boolean hasActiveSnapshot;
};