summaryrefslogtreecommitdiffstats
path: root/dom/localstorage/test/unit/test_unicodeCharacters.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/localstorage/test/unit/test_unicodeCharacters.js')
-rw-r--r--dom/localstorage/test/unit/test_unicodeCharacters.js205
1 files changed, 205 insertions, 0 deletions
diff --git a/dom/localstorage/test/unit/test_unicodeCharacters.js b/dom/localstorage/test/unit/test_unicodeCharacters.js
new file mode 100644
index 0000000000..9e48274161
--- /dev/null
+++ b/dom/localstorage/test/unit/test_unicodeCharacters.js
@@ -0,0 +1,205 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const interpretChar = (chars, index) => {
+ return chars
+ .charCodeAt(index)
+ .toString(16)
+ .padStart(4, "0");
+};
+
+const hexEncode = str => {
+ let result = "";
+ const len = str.length;
+ for (let i = 0; i < len; ++i) {
+ result += interpretChar(str, i);
+ }
+ return result;
+};
+
+const collectCorrupted = (expected, actual) => {
+ const len = Math.min(expected.length, actual.length);
+ let notEquals = [];
+ for (let i = 0; i < len; ++i) {
+ if (expected[i] !== actual[i]) {
+ notEquals.push([hexEncode(expected[i]), hexEncode(actual[i])]);
+ }
+ }
+ return notEquals;
+};
+
+const sanitizeOutputWithSurrogates = (testValue, prefix = "") => {
+ let utf8What = prefix;
+ for (let i = 0; i < testValue.length; ++i) {
+ const valueChar = testValue.charCodeAt(i);
+ const isPlanar = 0xd800 <= valueChar && valueChar <= 0xdfff;
+ utf8What += isPlanar ? "\\u" + interpretChar(testValue, i) : testValue[i];
+ }
+ return utf8What;
+};
+
+const getEncodingSample = () => {
+ const expectedSample =
+ "3681207208613504e0a5028800b945551988c60050008027ebc2808c00d38e806e03d8210ac906722b85499be9d00000";
+
+ let result = "";
+ const len = expectedSample.length;
+ for (let i = 0; i < len; i += 4) {
+ result += String.fromCharCode(parseInt(expectedSample.slice(i, i + 4), 16));
+ }
+ return result;
+};
+
+const getSeparatedBasePlane = () => {
+ let result = "";
+ for (let i = 0xffff; i >= 0; --i) {
+ result += String.fromCharCode(i) + "\n";
+ }
+ return result;
+};
+
+const getJoinedBasePlane = () => {
+ let result = "";
+ for (let i = 0; i <= 0xffff; ++i) {
+ result += String.fromCharCode(i);
+ }
+ return result;
+};
+
+const getSurrogateCombinations = () => {
+ const upperLead = String.fromCharCode(0xdbff);
+ const lowerTrail = String.fromCharCode(0xdc00);
+
+ const regularSlot = ["w", "abcdefghijklmnopqrst", "aaaaaaaaaaaaaaaaaaaa", ""];
+ const surrogateSlot = [lowerTrail, upperLead];
+
+ let samples = [];
+ for (const leadSnippet of regularSlot) {
+ for (const firstSlot of surrogateSlot) {
+ for (const trailSnippet of regularSlot) {
+ for (const secondSlot of surrogateSlot) {
+ samples.push(leadSnippet + firstSlot + secondSlot + trailSnippet);
+ }
+ samples.push(leadSnippet + firstSlot + trailSnippet);
+ }
+ }
+ }
+
+ return samples;
+};
+
+const fetchFrom = async (itemKey, sample, meanwhile) => {
+ const principal = getPrincipal("http://example.com/", {});
+
+ let request = clearOrigin(principal);
+ await requestFinished(request);
+
+ const storage = getLocalStorage(principal);
+
+ await storage.setItem(itemKey, sample);
+
+ await meanwhile(principal);
+
+ return storage.getItem(itemKey);
+};
+
+/**
+ * Value fetched from existing snapshot based on
+ * existing in-memory datastore in the parent process
+ * without any communication between content/parent
+ */
+const fetchFromExistingSnapshotExistingDatastore = async (itemKey, sample) => {
+ return fetchFrom(itemKey, sample, async () => {});
+};
+
+/**
+ * Value fetched from newly created snapshot based on
+ * existing in-memory datastore in the parent process
+ */
+const fetchFromNewSnapshotExistingDatastore = async (itemKey, sample) => {
+ return fetchFrom(itemKey, sample, async () => {
+ await returnToEventLoop();
+ });
+};
+
+/**
+ * Value fetched from newly created snapshot based on newly created
+ * in-memory datastore based on database in the parent process
+ */
+const fetchFromNewSnapshotNewDatastore = async (itemKey, sample) => {
+ return fetchFrom(itemKey, sample, async principal => {
+ let request = resetOrigin(principal);
+ await requestFinished(request);
+ });
+};
+
+add_task(async function testSteps() {
+ /* This test is based on bug 1681300 */
+ Services.prefs.setBoolPref(
+ "dom.storage.enable_unsupported_legacy_implementation",
+ false
+ );
+ Services.prefs.setBoolPref("dom.storage.snapshot_reusing", false);
+
+ const reportWhat = (testKey, testValue) => {
+ if (testKey.length + testValue.length > 82) {
+ return testKey;
+ }
+ return sanitizeOutputWithSurrogates(testValue, /* prefix */ testKey + ":");
+ };
+
+ const testFetchMode = async (testType, storeAndLookup) => {
+ const testPairs = [
+ { testEmptyValue: [""] },
+ { testSampleKey: [getEncodingSample()] },
+ { testSeparatedKey: [getSeparatedBasePlane()] },
+ { testJoinedKey: [getJoinedBasePlane()] },
+ { testCombinations: getSurrogateCombinations() },
+ ];
+
+ for (const testPair of testPairs) {
+ for (const [testKey, expectedValues] of Object.entries(testPair)) {
+ for (const expected of expectedValues) {
+ const actual = await storeAndLookup(testKey, expected);
+ const testInfo = reportWhat(testKey, expected);
+ is(
+ null != actual,
+ true,
+ testType + ": Value not null for " + testInfo
+ );
+ is(
+ expected.length,
+ actual.length,
+ testType + ": Returned size for " + testInfo
+ );
+
+ const notEquals = collectCorrupted(expected, actual);
+ for (let i = 0; i < notEquals.length; ++i) {
+ is(
+ notEquals[i][0],
+ notEquals[i][1],
+ testType + ": Unequal character at " + i + " for " + testInfo
+ );
+ }
+ }
+ }
+ }
+ };
+
+ await testFetchMode(
+ "ExistingSnapshotExistingDatastore",
+ fetchFromExistingSnapshotExistingDatastore
+ );
+
+ await testFetchMode(
+ "NewSnapshotExistingDatastore",
+ fetchFromNewSnapshotExistingDatastore
+ );
+
+ await testFetchMode(
+ "NewSnapshotNewDatastore",
+ fetchFromNewSnapshotNewDatastore
+ );
+});