summaryrefslogtreecommitdiffstats
path: root/toolkit/components/xulstore/tests
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/xulstore/tests')
-rw-r--r--toolkit/components/xulstore/tests/chrome/chrome.ini5
-rw-r--r--toolkit/components/xulstore/tests/chrome/test_persistence.xhtml30
-rw-r--r--toolkit/components/xulstore/tests/chrome/window_persistence.xhtml67
-rw-r--r--toolkit/components/xulstore/tests/gtest/Cargo.toml7
-rw-r--r--toolkit/components/xulstore/tests/gtest/TestXULStore.cpp141
-rw-r--r--toolkit/components/xulstore/tests/gtest/moz.build11
-rw-r--r--toolkit/components/xulstore/tests/xpcshell/test_XULStore.js150
-rw-r--r--toolkit/components/xulstore/tests/xpcshell/test_XULStore_migration.js85
-rw-r--r--toolkit/components/xulstore/tests/xpcshell/test_XULStore_migration_fail_invalid_data.js57
-rw-r--r--toolkit/components/xulstore/tests/xpcshell/test_XULStore_migration_fail_invalid_json.js42
-rw-r--r--toolkit/components/xulstore/tests/xpcshell/test_XULStore_migration_profile_change.js56
-rw-r--r--toolkit/components/xulstore/tests/xpcshell/xpcshell.ini13
12 files changed, 664 insertions, 0 deletions
diff --git a/toolkit/components/xulstore/tests/chrome/chrome.ini b/toolkit/components/xulstore/tests/chrome/chrome.ini
new file mode 100644
index 0000000000..c8f4ddf073
--- /dev/null
+++ b/toolkit/components/xulstore/tests/chrome/chrome.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files =
+ window_persistence.xhtml
+
+[test_persistence.xhtml]
diff --git a/toolkit/components/xulstore/tests/chrome/test_persistence.xhtml b/toolkit/components/xulstore/tests/chrome/test_persistence.xhtml
new file mode 100644
index 0000000000..b3e65fb050
--- /dev/null
+++ b/toolkit/components/xulstore/tests/chrome/test_persistence.xhtml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Persistence Tests"
+ onload="runTest()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ function runTest() {
+ window.openDialog("window_persistence.xhtml", "_blank", "chrome,noopener", true, window);
+ }
+
+ function windowOpened() {
+ window.openDialog("window_persistence.xhtml", "_blank", "chrome,noopener", false, window);
+ }
+
+ function testDone() {
+ SimpleTest.finish();
+ }
+ </script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"/>
+</body>
+
+</window>
diff --git a/toolkit/components/xulstore/tests/chrome/window_persistence.xhtml b/toolkit/components/xulstore/tests/chrome/window_persistence.xhtml
new file mode 100644
index 0000000000..d474891cfc
--- /dev/null
+++ b/toolkit/components/xulstore/tests/chrome/window_persistence.xhtml
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Persistence Tests"
+ onload="opened()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ persist="screenX screenY width height">
+
+<button id="button1" label="Button1" persist="value"/>
+<button id="button2" label="Button2" value="Normal" persist="value"/>
+<button id="button3" label="Button3" value="Normal" persist="hidden" hidden="true"/>
+
+<script>
+<![CDATA[
+
+const XULStore = Services.xulStore;
+let URI = "chrome://mochitests/content/chrome/toolkit/components/xulstore/tests/chrome/window_persistence.xhtml";
+
+function opened()
+{
+ runTest();
+}
+
+function runTest()
+{
+ var firstRun = window.arguments[0];
+ var button1 = document.getElementById("button1");
+ var button2 = document.getElementById("button2");
+ var button3 = document.getElementById("button3");
+ if (firstRun) {
+ button1.setAttribute("value", "Pressed");
+ button2.removeAttribute("value");
+
+ button2.setAttribute("foo", "bar");
+ XULStore.persist(button2, "foo");
+ is(XULStore.getValue(URI, "button2", "foo"), "bar", "attribute persisted");
+ button2.removeAttribute("foo");
+ XULStore.persist(button2, "foo");
+ is(XULStore.hasValue(URI, "button2", "foo"), false, "attribute removed");
+
+ button3.removeAttribute("hidden");
+
+ window.close();
+ window.arguments[1].windowOpened();
+ }
+ else {
+ is(button1.getAttribute("value"), "Pressed",
+ "Attribute set");
+ is(button2.hasAttribute("value"), false,
+ "Attribute cleared");
+ is(button2.hasAttribute("foo"), false,
+ "Attribute cleared");
+
+ is(button3.hasAttribute("hidden"), false,
+ "Attribute cleared");
+
+ window.close();
+ window.arguments[1].testDone();
+ }
+}
+
+function is(l, r, n) { window.arguments[1].SimpleTest.is(l,r,n); }
+
+]]></script>
+
+</window>
diff --git a/toolkit/components/xulstore/tests/gtest/Cargo.toml b/toolkit/components/xulstore/tests/gtest/Cargo.toml
new file mode 100644
index 0000000000..53c75a2100
--- /dev/null
+++ b/toolkit/components/xulstore/tests/gtest/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "xulstore-gtest"
+version = "0.1.0"
+authors = ["The Mozilla Project Developers"]
+
+[lib]
+path = "test.rs"
diff --git a/toolkit/components/xulstore/tests/gtest/TestXULStore.cpp b/toolkit/components/xulstore/tests/gtest/TestXULStore.cpp
new file mode 100644
index 0000000000..50d7f54b80
--- /dev/null
+++ b/toolkit/components/xulstore/tests/gtest/TestXULStore.cpp
@@ -0,0 +1,141 @@
+#include <stdint.h>
+#include "gtest/gtest.h"
+#include "mozilla/XULStore.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+using mozilla::XULStoreIterator;
+using mozilla::XULStore::GetAttrs;
+using mozilla::XULStore::GetIDs;
+using mozilla::XULStore::GetValue;
+using mozilla::XULStore::HasValue;
+using mozilla::XULStore::RemoveValue;
+using mozilla::XULStore::SetValue;
+
+TEST(XULStore, SetGetValue)
+{
+ nsAutoString doc(u"SetGetValue"_ns);
+ nsAutoString id(u"foo"_ns);
+ nsAutoString attr(u"bar"_ns);
+ nsAutoString value;
+
+ EXPECT_EQ(GetValue(doc, id, attr, value), NS_OK);
+ EXPECT_TRUE(value.EqualsASCII(""));
+
+ {
+ nsAutoString value(u"baz"_ns);
+ EXPECT_EQ(SetValue(doc, id, attr, value), NS_OK);
+ }
+
+ EXPECT_EQ(GetValue(doc, id, attr, value), NS_OK);
+ EXPECT_TRUE(value.EqualsASCII("baz"));
+}
+
+TEST(XULStore, HasValue)
+{
+ nsAutoString doc(u"HasValue"_ns);
+ nsAutoString id(u"foo"_ns);
+ nsAutoString attr(u"bar"_ns);
+ bool hasValue = true;
+ EXPECT_EQ(HasValue(doc, id, attr, hasValue), NS_OK);
+ EXPECT_FALSE(hasValue);
+ nsAutoString value(u"baz"_ns);
+ EXPECT_EQ(SetValue(doc, id, attr, value), NS_OK);
+ EXPECT_EQ(HasValue(doc, id, attr, hasValue), NS_OK);
+ EXPECT_TRUE(hasValue);
+}
+
+TEST(XULStore, RemoveValue)
+{
+ nsAutoString doc(u"RemoveValue"_ns);
+ nsAutoString id(u"foo"_ns);
+ nsAutoString attr(u"bar"_ns);
+ nsAutoString value(u"baz"_ns);
+ EXPECT_EQ(SetValue(doc, id, attr, value), NS_OK);
+ EXPECT_EQ(GetValue(doc, id, attr, value), NS_OK);
+ EXPECT_TRUE(value.EqualsASCII("baz"));
+ EXPECT_EQ(RemoveValue(doc, id, attr), NS_OK);
+ EXPECT_EQ(GetValue(doc, id, attr, value), NS_OK);
+ EXPECT_TRUE(value.EqualsASCII(""));
+}
+
+TEST(XULStore, GetIDsIterator)
+{
+ nsAutoString doc(u"idIterDoc"_ns);
+ nsAutoString id1(u"id1"_ns);
+ nsAutoString id2(u"id2"_ns);
+ nsAutoString id3(u"id3"_ns);
+ nsAutoString attr(u"attr"_ns);
+ nsAutoString value(u"value"_ns);
+ nsAutoString id;
+
+ // Confirm that the store doesn't have any IDs yet.
+ mozilla::UniquePtr<XULStoreIterator> iter;
+ EXPECT_EQ(GetIDs(doc, iter), NS_OK);
+ EXPECT_FALSE(iter->HasMore());
+ // EXPECT_EQ(iter->GetNext(&id), NS_ERROR_FAILURE);
+
+ // Insert with IDs in non-alphanumeric order to confirm
+ // that store will order them when iterating them.
+ EXPECT_EQ(SetValue(doc, id3, attr, value), NS_OK);
+ EXPECT_EQ(SetValue(doc, id1, attr, value), NS_OK);
+ EXPECT_EQ(SetValue(doc, id2, attr, value), NS_OK);
+
+ // Insert different ID for another doc to confirm that store
+ // won't return it when iterating IDs for our doc.
+ nsAutoString otherDoc(u"otherDoc"_ns);
+ nsAutoString otherID(u"otherID"_ns);
+ EXPECT_EQ(SetValue(otherDoc, otherID, attr, value), NS_OK);
+
+ EXPECT_EQ(GetIDs(doc, iter), NS_OK);
+ EXPECT_TRUE(iter->HasMore());
+ EXPECT_EQ(iter->GetNext(&id), NS_OK);
+ EXPECT_TRUE(id.EqualsASCII("id1"));
+ EXPECT_TRUE(iter->HasMore());
+ EXPECT_EQ(iter->GetNext(&id), NS_OK);
+ EXPECT_TRUE(id.EqualsASCII("id2"));
+ EXPECT_TRUE(iter->HasMore());
+ EXPECT_EQ(iter->GetNext(&id), NS_OK);
+ EXPECT_TRUE(id.EqualsASCII("id3"));
+ EXPECT_FALSE(iter->HasMore());
+}
+
+TEST(XULStore, GetAttributeIterator)
+{
+ nsAutoString doc(u"attrIterDoc"_ns);
+ nsAutoString id(u"id"_ns);
+ nsAutoString attr1(u"attr1"_ns);
+ nsAutoString attr2(u"attr2"_ns);
+ nsAutoString attr3(u"attr3"_ns);
+ nsAutoString value(u"value"_ns);
+ nsAutoString attr;
+
+ mozilla::UniquePtr<XULStoreIterator> iter;
+ EXPECT_EQ(GetAttrs(doc, id, iter), NS_OK);
+ EXPECT_FALSE(iter->HasMore());
+ // EXPECT_EQ(iter->GetNext(&attr), NS_ERROR_FAILURE);
+
+ // Insert with attributes in non-alphanumeric order to confirm
+ // that store will order them when iterating them.
+ EXPECT_EQ(SetValue(doc, id, attr3, value), NS_OK);
+ EXPECT_EQ(SetValue(doc, id, attr1, value), NS_OK);
+ EXPECT_EQ(SetValue(doc, id, attr2, value), NS_OK);
+
+ // Insert different attribute for another ID to confirm that store
+ // won't return it when iterating attributes for our ID.
+ nsAutoString otherID(u"otherID"_ns);
+ nsAutoString otherAttr(u"otherAttr"_ns);
+ EXPECT_EQ(SetValue(doc, otherID, otherAttr, value), NS_OK);
+
+ EXPECT_EQ(GetAttrs(doc, id, iter), NS_OK);
+ EXPECT_TRUE(iter->HasMore());
+ EXPECT_EQ(iter->GetNext(&attr), NS_OK);
+ EXPECT_TRUE(attr.EqualsASCII("attr1"));
+ EXPECT_TRUE(iter->HasMore());
+ EXPECT_EQ(iter->GetNext(&attr), NS_OK);
+ EXPECT_TRUE(attr.EqualsASCII("attr2"));
+ EXPECT_TRUE(iter->HasMore());
+ EXPECT_EQ(iter->GetNext(&attr), NS_OK);
+ EXPECT_TRUE(attr.EqualsASCII("attr3"));
+ EXPECT_FALSE(iter->HasMore());
+}
diff --git a/toolkit/components/xulstore/tests/gtest/moz.build b/toolkit/components/xulstore/tests/gtest/moz.build
new file mode 100644
index 0000000000..81fd27a7e7
--- /dev/null
+++ b/toolkit/components/xulstore/tests/gtest/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+UNIFIED_SOURCES += [
+ "TestXULStore.cpp",
+]
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/toolkit/components/xulstore/tests/xpcshell/test_XULStore.js b/toolkit/components/xulstore/tests/xpcshell/test_XULStore.js
new file mode 100644
index 0000000000..d100592e81
--- /dev/null
+++ b/toolkit/components/xulstore/tests/xpcshell/test_XULStore.js
@@ -0,0 +1,150 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/◦
+*/
+
+"use strict";
+
+var XULStore = null;
+var browserURI = "chrome://browser/content/browser.xhtml";
+var aboutURI = "about:config";
+
+function run_test() {
+ do_get_profile();
+ run_next_test();
+}
+
+function checkValue(uri, id, attr, reference) {
+ let value = XULStore.getValue(uri, id, attr);
+ Assert.equal(value, reference);
+}
+
+function checkValueExists(uri, id, attr, exists) {
+ Assert.equal(XULStore.hasValue(uri, id, attr), exists);
+}
+
+function getIDs(uri) {
+ return Array.from(XULStore.getIDsEnumerator(uri)).sort();
+}
+
+function getAttributes(uri, id) {
+ return Array.from(XULStore.getAttributeEnumerator(uri, id)).sort();
+}
+
+function checkArrays(a, b) {
+ a.sort();
+ b.sort();
+ Assert.equal(a.toString(), b.toString());
+}
+
+add_task(async function setup() {
+ // Set a value that a future test depends on manually
+ XULStore = Services.xulStore;
+ XULStore.setValue(browserURI, "main-window", "width", "994");
+});
+
+add_task(async function testTruncation() {
+ let dos = Array(8192).join("~");
+ // Long id names should trigger an exception
+ Assert.throws(
+ () => XULStore.setValue(browserURI, dos, "foo", "foo"),
+ /NS_ERROR_ILLEGAL_VALUE/
+ );
+
+ // Long attr names should trigger an exception
+ Assert.throws(
+ () => XULStore.setValue(browserURI, "foo", dos, "foo"),
+ /NS_ERROR_ILLEGAL_VALUE/
+ );
+
+ // Long values should be truncated
+ XULStore.setValue(browserURI, "dos", "dos", dos);
+ dos = XULStore.getValue(browserURI, "dos", "dos");
+ Assert.ok(dos.length == 4096);
+ XULStore.removeValue(browserURI, "dos", "dos");
+});
+
+add_task(async function testGetValue() {
+ // Get non-existing property
+ checkValue(browserURI, "side-window", "height", "");
+
+ // Get existing property
+ checkValue(browserURI, "main-window", "width", "994");
+});
+
+add_task(async function testHasValue() {
+ // Check non-existing property
+ checkValueExists(browserURI, "side-window", "height", false);
+
+ // Check existing property
+ checkValueExists(browserURI, "main-window", "width", true);
+});
+
+add_task(async function testSetValue() {
+ // Set new attribute
+ checkValue(browserURI, "side-bar", "width", "");
+ XULStore.setValue(browserURI, "side-bar", "width", "1000");
+ checkValue(browserURI, "side-bar", "width", "1000");
+ checkArrays(["main-window", "side-bar"], getIDs(browserURI));
+ checkArrays(["width"], getAttributes(browserURI, "side-bar"));
+
+ // Modify existing property
+ checkValue(browserURI, "side-bar", "width", "1000");
+ XULStore.setValue(browserURI, "side-bar", "width", "1024");
+ checkValue(browserURI, "side-bar", "width", "1024");
+ checkArrays(["main-window", "side-bar"], getIDs(browserURI));
+ checkArrays(["width"], getAttributes(browserURI, "side-bar"));
+
+ // Add another attribute
+ checkValue(browserURI, "side-bar", "height", "");
+ XULStore.setValue(browserURI, "side-bar", "height", "1000");
+ checkValue(browserURI, "side-bar", "height", "1000");
+ checkArrays(["main-window", "side-bar"], getIDs(browserURI));
+ checkArrays(["width", "height"], getAttributes(browserURI, "side-bar"));
+});
+
+add_task(async function testRemoveValue() {
+ // Remove first attribute
+ checkValue(browserURI, "side-bar", "width", "1024");
+ XULStore.removeValue(browserURI, "side-bar", "width");
+ checkValue(browserURI, "side-bar", "width", "");
+ checkValueExists(browserURI, "side-bar", "width", false);
+ checkArrays(["main-window", "side-bar"], getIDs(browserURI));
+ checkArrays(["height"], getAttributes(browserURI, "side-bar"));
+
+ // Remove second attribute
+ checkValue(browserURI, "side-bar", "height", "1000");
+ XULStore.removeValue(browserURI, "side-bar", "height");
+ checkValue(browserURI, "side-bar", "height", "");
+ checkArrays(["main-window"], getIDs(browserURI));
+
+ // Removing an attribute that doesn't exists shouldn't fail
+ XULStore.removeValue(browserURI, "main-window", "bar");
+
+ // Removing from an id that doesn't exists shouldn't fail
+ XULStore.removeValue(browserURI, "foo", "bar");
+
+ // Removing from a document that doesn't exists shouldn't fail
+ let nonDocURI = "chrome://example/content/other.xul";
+ XULStore.removeValue(nonDocURI, "foo", "bar");
+
+ // Remove all attributes in browserURI
+ XULStore.removeValue(browserURI, "addon-bar", "collapsed");
+ checkArrays([], getAttributes(browserURI, "addon-bar"));
+ XULStore.removeValue(browserURI, "main-window", "width");
+ XULStore.removeValue(browserURI, "main-window", "height");
+ XULStore.removeValue(browserURI, "main-window", "screenX");
+ XULStore.removeValue(browserURI, "main-window", "screenY");
+ XULStore.removeValue(browserURI, "main-window", "sizemode");
+ checkArrays([], getAttributes(browserURI, "main-window"));
+ XULStore.removeValue(browserURI, "sidebar-title", "value");
+ checkArrays([], getAttributes(browserURI, "sidebar-title"));
+ checkArrays([], getIDs(browserURI));
+
+ // Remove all attributes in aboutURI
+ XULStore.removeValue(aboutURI, "prefCol", "ordinal");
+ XULStore.removeValue(aboutURI, "prefCol", "sortDirection");
+ checkArrays([], getAttributes(aboutURI, "prefCol"));
+ XULStore.removeValue(aboutURI, "lockCol", "ordinal");
+ checkArrays([], getAttributes(aboutURI, "lockCol"));
+ checkArrays([], getIDs(aboutURI));
+});
diff --git a/toolkit/components/xulstore/tests/xpcshell/test_XULStore_migration.js b/toolkit/components/xulstore/tests/xpcshell/test_XULStore_migration.js
new file mode 100644
index 0000000000..f054a45288
--- /dev/null
+++ b/toolkit/components/xulstore/tests/xpcshell/test_XULStore_migration.js
@@ -0,0 +1,85 @@
+"use strict";
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+function run_test() {
+ do_get_profile();
+ run_next_test();
+}
+
+add_task(
+ {
+ skip_if: () => !AppConstants.MOZ_NEW_XULSTORE,
+ },
+ async function test_create_old_datastore() {
+ const path = PathUtils.join(PathUtils.profileDir, "xulstore.json");
+
+ const xulstoreJSON = {
+ doc1: {
+ id1: {
+ attr1: "value1",
+ },
+ },
+ doc2: {
+ id1: {
+ attr2: "value2",
+ },
+ id2: {
+ attr1: "value1",
+ attr2: "value2",
+ attr3: "value3",
+ },
+ id3: {},
+ },
+ doc3: {},
+ };
+
+ await IOUtils.writeJSON(path, xulstoreJSON);
+ }
+);
+
+add_task(
+ {
+ skip_if: () => !AppConstants.MOZ_NEW_XULSTORE,
+ },
+ async function test_get_values() {
+ // We wait until now to import XULStore to ensure we've created
+ // the old datastore, as importing that module will initiate the attempt
+ // to migrate the old datastore to the new one.
+ const { XULStore } = ChromeUtils.import(
+ "resource://gre/modules/XULStore.jsm"
+ );
+
+ Assert.equal(await XULStore.getValue("doc1", "id1", "attr1"), "value1");
+ Assert.equal(await XULStore.getValue("doc1", "id1", "attr2"), "");
+ Assert.equal(await XULStore.getValue("doc1", "id1", "attr3"), "");
+ Assert.equal(await XULStore.getValue("doc1", "id2", "attr1"), "");
+ Assert.equal(await XULStore.getValue("doc1", "id2", "attr2"), "");
+ Assert.equal(await XULStore.getValue("doc1", "id2", "attr3"), "");
+ Assert.equal(await XULStore.getValue("doc1", "id3", "attr1"), "");
+ Assert.equal(await XULStore.getValue("doc1", "id3", "attr2"), "");
+ Assert.equal(await XULStore.getValue("doc1", "id3", "attr3"), "");
+
+ Assert.equal(await XULStore.getValue("doc2", "id1", "attr1"), "");
+ Assert.equal(await XULStore.getValue("doc2", "id1", "attr2"), "value2");
+ Assert.equal(await XULStore.getValue("doc2", "id1", "attr3"), "");
+ Assert.equal(await XULStore.getValue("doc2", "id2", "attr1"), "value1");
+ Assert.equal(await XULStore.getValue("doc2", "id2", "attr2"), "value2");
+ Assert.equal(await XULStore.getValue("doc2", "id2", "attr3"), "value3");
+ Assert.equal(await XULStore.getValue("doc2", "id3", "attr1"), "");
+ Assert.equal(await XULStore.getValue("doc2", "id3", "attr2"), "");
+ Assert.equal(await XULStore.getValue("doc2", "id3", "attr3"), "");
+
+ Assert.equal(await XULStore.getValue("doc3", "id1", "attr1"), "");
+ Assert.equal(await XULStore.getValue("doc3", "id1", "attr2"), "");
+ Assert.equal(await XULStore.getValue("doc3", "id1", "attr3"), "");
+ Assert.equal(await XULStore.getValue("doc3", "id2", "attr1"), "");
+ Assert.equal(await XULStore.getValue("doc3", "id2", "attr2"), "");
+ Assert.equal(await XULStore.getValue("doc3", "id2", "attr3"), "");
+ Assert.equal(await XULStore.getValue("doc3", "id3", "attr1"), "");
+ Assert.equal(await XULStore.getValue("doc3", "id3", "attr2"), "");
+ Assert.equal(await XULStore.getValue("doc3", "id3", "attr3"), "");
+ }
+);
diff --git a/toolkit/components/xulstore/tests/xpcshell/test_XULStore_migration_fail_invalid_data.js b/toolkit/components/xulstore/tests/xpcshell/test_XULStore_migration_fail_invalid_data.js
new file mode 100644
index 0000000000..b7b06bac2f
--- /dev/null
+++ b/toolkit/components/xulstore/tests/xpcshell/test_XULStore_migration_fail_invalid_data.js
@@ -0,0 +1,57 @@
+"use strict";
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+function run_test() {
+ do_get_profile();
+ run_next_test();
+}
+
+add_task(
+ {
+ skip_if: () => !AppConstants.MOZ_NEW_XULSTORE,
+ },
+ async function test_create_old_datastore() {
+ const path = PathUtils.join(PathUtils.profileDir, "xulstore.json");
+
+ // Valid JSON, but invalid data: attr1's value is a number, not a string.
+ const xulstoreJSON = {
+ doc1: {
+ id1: {
+ attr1: 1,
+ },
+ },
+ doc2: {
+ id2: {
+ attr2: "value2",
+ },
+ },
+ };
+
+ await IOUtils.writeJSON(path, xulstoreJSON);
+ }
+);
+
+add_task(
+ {
+ skip_if: () => !AppConstants.MOZ_NEW_XULSTORE,
+ },
+ async function test_get_values() {
+ // We wait until now to import XULStore to ensure we've created
+ // the old store, as importing that module will initiate the attempt
+ // to migrate the old store to the new one.
+ const { XULStore } = ChromeUtils.import(
+ "resource://gre/modules/XULStore.jsm"
+ );
+
+ // XULStore should *not* have migrated the values from the old store,
+ // so it should return empty strings when we try to retrieve them.
+ // That's true for both values, even though one of them is valid,
+ // because the migrator uses a typed parser that requires the entire
+ // JSON file to conform to the XULStore format.
+ Assert.equal(await XULStore.getValue("doc1", "id1", "attr1"), "");
+ Assert.equal(await XULStore.getValue("doc2", "id2", "attr2"), "");
+ }
+);
diff --git a/toolkit/components/xulstore/tests/xpcshell/test_XULStore_migration_fail_invalid_json.js b/toolkit/components/xulstore/tests/xpcshell/test_XULStore_migration_fail_invalid_json.js
new file mode 100644
index 0000000000..057e00c680
--- /dev/null
+++ b/toolkit/components/xulstore/tests/xpcshell/test_XULStore_migration_fail_invalid_json.js
@@ -0,0 +1,42 @@
+"use strict";
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+function run_test() {
+ do_get_profile();
+ run_next_test();
+}
+
+add_task(
+ {
+ skip_if: () => !AppConstants.MOZ_NEW_XULSTORE,
+ },
+ async function test_create_old_datastore() {
+ const path = PathUtils.join(PathUtils.profileDir, "xulstore.json");
+
+ // Invalid JSON: it's missing the final closing brace.
+ const xulstoreJSON = '{ doc: { id: { attr: "value" } }';
+
+ await IOUtils.writeUTF8(path, xulstoreJSON);
+ }
+);
+
+add_task(
+ {
+ skip_if: () => !AppConstants.MOZ_NEW_XULSTORE,
+ },
+ async function test_get_value() {
+ // We wait until now to import XULStore to ensure we've created
+ // the old store, as importing that module will initiate the attempt
+ // to migrate the old store to the new one.
+ const { XULStore } = ChromeUtils.import(
+ "resource://gre/modules/XULStore.jsm"
+ );
+
+ // XULStore should *not* have migrated the value from the old store,
+ // so it should return an empty string when we try to retrieve it.
+ Assert.equal(await XULStore.getValue("doc", "id", "attr"), "");
+ }
+);
diff --git a/toolkit/components/xulstore/tests/xpcshell/test_XULStore_migration_profile_change.js b/toolkit/components/xulstore/tests/xpcshell/test_XULStore_migration_profile_change.js
new file mode 100644
index 0000000000..d3db2af4f3
--- /dev/null
+++ b/toolkit/components/xulstore/tests/xpcshell/test_XULStore_migration_profile_change.js
@@ -0,0 +1,56 @@
+"use strict";
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+const { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+);
+
+add_task(
+ {
+ skip_if: () => !AppConstants.MOZ_NEW_XULSTORE,
+ },
+ async function test_get_values() {
+ // Import XULStore before getting the profile to ensure that the new store
+ // is initialized, as the purpose of this test is to confirm that the old
+ // store data gets migrated if the profile change happens post-initialization.
+ const { XULStore } = ChromeUtils.import(
+ "resource://gre/modules/XULStore.jsm"
+ );
+
+ // We haven't migrated any data yet (nor even changed to a profile), so there
+ // shouldn't be a value in the store.
+ Assert.equal(XULStore.getValue("doc1", "id1", "attr1"), "");
+
+ // Register an observer before the XULStore service registers its observer,
+ // so we can observe the profile-after-change notification first and create
+ // an old store for it to migrate. We need to write synchronously to avoid
+ // racing XULStore, so we use FileUtils instead of IOUtils.
+ Services.obs.addObserver(
+ {
+ observe() {
+ const file = FileUtils.getFile("ProfD", ["xulstore.json"]);
+ const xulstoreJSON = JSON.stringify({
+ doc1: {
+ id1: {
+ attr1: "value1",
+ },
+ },
+ });
+ let stream = FileUtils.openAtomicFileOutputStream(file);
+ stream.write(xulstoreJSON, xulstoreJSON.length);
+ FileUtils.closeAtomicFileOutputStream(stream);
+ },
+ },
+ "profile-after-change"
+ );
+
+ // This creates a profile and changes to it, triggering first our
+ // profile-after-change observer above and then XULStore's equivalent.
+ do_get_profile(true);
+
+ // XULStore should now have migrated the value from the old store.
+ Assert.equal(XULStore.getValue("doc1", "id1", "attr1"), "value1");
+ }
+);
diff --git a/toolkit/components/xulstore/tests/xpcshell/xpcshell.ini b/toolkit/components/xulstore/tests/xpcshell/xpcshell.ini
new file mode 100644
index 0000000000..04c579dab5
--- /dev/null
+++ b/toolkit/components/xulstore/tests/xpcshell/xpcshell.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+skip-if = toolkit == 'android'
+
+[test_XULStore.js]
+
+# These tests only run on the new implementation of XULStore, since they
+# test migration of data from the old implementation to the new one.
+# But there isn't a skip-if condition we can add here to disable them,
+# so we disable them within each test file using add_task() properties.
+[test_XULStore_migration.js]
+[test_XULStore_migration_fail_invalid_json.js]
+[test_XULStore_migration_fail_invalid_data.js]
+[test_XULStore_migration_profile_change.js]