diff options
Diffstat (limited to 'toolkit/components/xulstore')
-rw-r--r-- | toolkit/components/xulstore/XULStore.cpp | 108 | ||||
-rw-r--r-- | toolkit/components/xulstore/XULStore.h | 56 | ||||
-rw-r--r-- | toolkit/components/xulstore/XULStore.sys.mjs | 329 | ||||
-rw-r--r-- | toolkit/components/xulstore/components.conf | 16 | ||||
-rw-r--r-- | toolkit/components/xulstore/moz.build | 25 | ||||
-rw-r--r-- | toolkit/components/xulstore/nsIXULStore.idl | 94 | ||||
-rw-r--r-- | toolkit/components/xulstore/tests/chrome/chrome.ini | 5 | ||||
-rw-r--r-- | toolkit/components/xulstore/tests/chrome/test_persistence.xhtml | 30 | ||||
-rw-r--r-- | toolkit/components/xulstore/tests/chrome/window_persistence.xhtml | 67 | ||||
-rw-r--r-- | toolkit/components/xulstore/tests/xpcshell/test_XULStore.js | 150 | ||||
-rw-r--r-- | toolkit/components/xulstore/tests/xpcshell/xpcshell.ini | 4 |
11 files changed, 884 insertions, 0 deletions
diff --git a/toolkit/components/xulstore/XULStore.cpp b/toolkit/components/xulstore/XULStore.cpp new file mode 100644 index 0000000000..a544746c6e --- /dev/null +++ b/toolkit/components/xulstore/XULStore.cpp @@ -0,0 +1,108 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/XULStore.h" +#include "nsCOMPtr.h" + +namespace mozilla { + +// The XULStore API is implemented in Rust and exposed to C++ via a set of +// C functions with the "xulstore_" prefix. We declare them in this anonymous +// namespace to prevent C++ code outside this file from accessing them, +// as they are an internal implementation detail, and C++ code should use +// the mozilla::XULStore::* functions and mozilla::XULStoreIterator class +// declared in XULStore.h. +namespace { +extern "C" { +void xulstore_new_service(nsIXULStore** result); +nsresult xulstore_set_value(const nsAString* doc, const nsAString* id, + const nsAString* attr, const nsAString* value); +nsresult xulstore_has_value(const nsAString* doc, const nsAString* id, + const nsAString* attr, bool* has_value); +nsresult xulstore_get_value(const nsAString* doc, const nsAString* id, + const nsAString* attr, nsAString* value); +nsresult xulstore_remove_value(const nsAString* doc, const nsAString* id, + const nsAString* attr); +XULStoreIterator* xulstore_get_ids(const nsAString* doc, nsresult* result); +XULStoreIterator* xulstore_get_attrs(const nsAString* doc, const nsAString* id, + nsresult* result); +bool xulstore_iter_has_more(const XULStoreIterator*); +nsresult xulstore_iter_get_next(XULStoreIterator*, nsAString* value); +void xulstore_iter_free(XULStoreIterator* iterator); +nsresult xulstore_shutdown(); +} + +// A static reference to the nsIXULStore singleton that JS uses to access +// the store. Retrieved via mozilla::XULStore::GetService(). +static StaticRefPtr<nsIXULStore> sXULStore; +} // namespace + +bool XULStoreIterator::HasMore() const { return xulstore_iter_has_more(this); } + +nsresult XULStoreIterator::GetNext(nsAString* item) { + return xulstore_iter_get_next(this, item); +} + +void DefaultDelete<XULStoreIterator>::operator()(XULStoreIterator* ptr) const { + xulstore_iter_free(ptr); +} + +namespace XULStore { +already_AddRefed<nsIXULStore> GetService() { + nsCOMPtr<nsIXULStore> xulStore; + + if (sXULStore) { + xulStore = sXULStore; + } else { + xulstore_new_service(getter_AddRefs(xulStore)); + sXULStore = xulStore; + mozilla::ClearOnShutdown(&sXULStore); + } + + return xulStore.forget(); +} + +nsresult SetValue(const nsAString& doc, const nsAString& id, + const nsAString& attr, const nsAString& value) { + return xulstore_set_value(&doc, &id, &attr, &value); +} +nsresult HasValue(const nsAString& doc, const nsAString& id, + const nsAString& attr, bool& has_value) { + return xulstore_has_value(&doc, &id, &attr, &has_value); +} +nsresult GetValue(const nsAString& doc, const nsAString& id, + const nsAString& attr, nsAString& value) { + return xulstore_get_value(&doc, &id, &attr, &value); +} +nsresult RemoveValue(const nsAString& doc, const nsAString& id, + const nsAString& attr) { + return xulstore_remove_value(&doc, &id, &attr); +} +nsresult GetIDs(const nsAString& doc, UniquePtr<XULStoreIterator>& iter) { + // We assign the value of the iter here in C++ via a return value + // rather than in the Rust function via an out parameter in order + // to ensure that any old value is deleted, since the UniquePtr's + // assignment operator won't delete the old value if the assignment + // happens in Rust. + nsresult result; + iter.reset(xulstore_get_ids(&doc, &result)); + return result; +} +nsresult GetAttrs(const nsAString& doc, const nsAString& id, + UniquePtr<XULStoreIterator>& iter) { + // We assign the value of the iter here in C++ via a return value + // rather than in the Rust function via an out parameter in order + // to ensure that any old value is deleted, since the UniquePtr's + // assignment operator won't delete the old value if the assignment + // happens in Rust. + nsresult result; + iter.reset(xulstore_get_attrs(&doc, &id, &result)); + return result; +} +nsresult Shutdown() { return xulstore_shutdown(); } + +}; // namespace XULStore +}; // namespace mozilla diff --git a/toolkit/components/xulstore/XULStore.h b/toolkit/components/xulstore/XULStore.h new file mode 100644 index 0000000000..25b69ed12e --- /dev/null +++ b/toolkit/components/xulstore/XULStore.h @@ -0,0 +1,56 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +/* + * This file declares the XULStore API for C++ via the mozilla::XULStore + * namespace and the mozilla::XULStoreIterator class. It also declares + * the mozilla::XULStore::GetService() function that the component manager + * uses to instantiate and retrieve the nsIXULStore singleton. + */ + +#ifndef mozilla_XULStore_h +#define mozilla_XULStore_h + +#include "nsIXULStore.h" + +namespace mozilla { +class XULStoreIterator final { + public: + bool HasMore() const; + nsresult GetNext(nsAString* item); + + private: + XULStoreIterator() = delete; + XULStoreIterator(const XULStoreIterator&) = delete; + XULStoreIterator& operator=(const XULStoreIterator&) = delete; + ~XULStoreIterator() = delete; +}; + +template <> +class DefaultDelete<XULStoreIterator> { + public: + void operator()(XULStoreIterator* ptr) const; +}; + +namespace XULStore { +// Instantiates and retrieves the nsIXULStore singleton that JS uses to access +// the store. C++ code should use the mozilla::XULStore::* functions instead. +already_AddRefed<nsIXULStore> GetService(); + +nsresult SetValue(const nsAString& doc, const nsAString& id, + const nsAString& attr, const nsAString& value); +nsresult HasValue(const nsAString& doc, const nsAString& id, + const nsAString& attr, bool& has_value); +nsresult GetValue(const nsAString& doc, const nsAString& id, + const nsAString& attr, nsAString& value); +nsresult RemoveValue(const nsAString& doc, const nsAString& id, + const nsAString& attr); +nsresult GetIDs(const nsAString& doc, UniquePtr<XULStoreIterator>& iter); +nsresult GetAttrs(const nsAString& doc, const nsAString& id, + UniquePtr<XULStoreIterator>& iter); +nsresult Shutdown(); +}; // namespace XULStore +}; // namespace mozilla + +#endif // mozilla_XULStore_h diff --git a/toolkit/components/xulstore/XULStore.sys.mjs b/toolkit/components/xulstore/XULStore.sys.mjs new file mode 100644 index 0000000000..1683358ad5 --- /dev/null +++ b/toolkit/components/xulstore/XULStore.sys.mjs @@ -0,0 +1,329 @@ +/* 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/. */ + +// Enables logging and shorter save intervals. +const debugMode = false; + +// Delay when a change is made to when the file is saved. +// 30 seconds normally, or 3 seconds for testing +const WRITE_DELAY_MS = (debugMode ? 3 : 30) * 1000; + +const XULSTORE_CID = Components.ID("{6f46b6f4-c8b1-4bd4-a4fa-9ebbed0753ea}"); +const STOREDB_FILENAME = "xulstore.json"; + +export function XULStore() { + if (!Services.appinfo.inSafeMode) { + this.load(); + } +} + +XULStore.prototype = { + classID: XULSTORE_CID, + name: "XULStore", + QueryInterface: ChromeUtils.generateQI([ + "nsINamed", + "nsIObserver", + "nsIXULStore", + "nsISupportsWeakReference", + ]), + + /* ---------- private members ---------- */ + + /* + * The format of _data is _data[docuri][elementid][attribute]. For example: + * { + * "chrome://blah/foo.xul" : { + * "main-window" : { aaa : 1, bbb : "c" }, + * "barColumn" : { ddd : 9, eee : "f" }, + * }, + * + * "chrome://foopy/b.xul" : { ... }, + * ... + * } + */ + _data: {}, + _storeFile: null, + _needsSaving: false, + _saveAllowed: true, + _writeTimer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer), + + load() { + Services.obs.addObserver(this, "profile-before-change", true); + + try { + this._storeFile = Services.dirsvc.get("ProfD", Ci.nsIFile); + } catch (ex) { + try { + this._storeFile = Services.dirsvc.get("ProfDS", Ci.nsIFile); + } catch (ex) { + throw new Error("Can't find profile directory."); + } + } + this._storeFile.append(STOREDB_FILENAME); + + this.readFile(); + }, + + observe(subject, topic, data) { + this.writeFile(); + if (topic == "profile-before-change") { + this._saveAllowed = false; + } + }, + + /* + * Internal function for logging debug messages to the Error Console window + */ + log(message) { + if (!debugMode) { + return; + } + console.log("XULStore: " + message); + }, + + readFile() { + try { + this._data = JSON.parse(Cu.readUTF8File(this._storeFile)); + } catch (e) { + this.log("Error reading JSON: " + e); + // This exception could mean that the file didn't exist. + // We'll just ignore the error and start with a blank slate. + } + }, + + async writeFile() { + if (!this._needsSaving) { + return; + } + + this._needsSaving = false; + + this.log("Writing to xulstore.json"); + + try { + await IOUtils.writeJSON(this._storeFile.path, this._data, { + tmpPath: this._storeFile.path + ".tmp", + }); + } catch (e) { + this.log("Failed to write xulstore.json: " + e); + throw e; + } + }, + + markAsChanged() { + if (this._needsSaving || !this._storeFile) { + return; + } + + // Don't write the file more than once every 30 seconds. + this._needsSaving = true; + this._writeTimer.init(this, WRITE_DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT); + }, + + /* ---------- interface implementation ---------- */ + + persist(node, attr) { + if (!node.id) { + throw new Error("Node without ID passed into persist()"); + } + + const uri = node.ownerDocument.documentURI; + const value = node.getAttribute(attr); + + if (node.localName == "window") { + this.log("Persisting attributes to windows is handled by AppWindow."); + return; + } + + // See Bug 1476680 - we could drop the `hasValue` check so that + // any time there's an empty attribute it gets removed from the + // store. Since this is copying behavior from document.persist, + // callers would need to be updated with that change. + if (!value && this.hasValue(uri, node.id, attr)) { + this.removeValue(uri, node.id, attr); + } else { + this.setValue(uri, node.id, attr, value); + } + }, + + setValue(docURI, id, attr, value) { + this.log( + "Saving " + attr + "=" + value + " for id=" + id + ", doc=" + docURI + ); + + if (!this._saveAllowed) { + Services.console.logStringMessage( + "XULStore: Changes after profile-before-change are ignored!" + ); + return; + } + + // bug 319846 -- don't save really long attributes or values. + if (id.length > 512 || attr.length > 512) { + throw Components.Exception( + "id or attribute name too long", + Cr.NS_ERROR_ILLEGAL_VALUE + ); + } + + if (value.length > 4096) { + Services.console.logStringMessage( + "XULStore: Warning, truncating long attribute value" + ); + value = value.substr(0, 4096); + } + + let obj = this._data; + if (!(docURI in obj)) { + obj[docURI] = {}; + } + obj = obj[docURI]; + if (!(id in obj)) { + obj[id] = {}; + } + obj = obj[id]; + + // Don't set the value if it is already set to avoid saving the file. + if (attr in obj && obj[attr] == value) { + return; + } + + obj[attr] = value; // IE, this._data[docURI][id][attr] = value; + + this.markAsChanged(); + }, + + hasValue(docURI, id, attr) { + this.log( + "has store value for id=" + id + ", attr=" + attr + ", doc=" + docURI + ); + + let ids = this._data[docURI]; + if (ids) { + let attrs = ids[id]; + if (attrs) { + return attr in attrs; + } + } + + return false; + }, + + getValue(docURI, id, attr) { + this.log( + "get store value for id=" + id + ", attr=" + attr + ", doc=" + docURI + ); + + let ids = this._data[docURI]; + if (ids) { + let attrs = ids[id]; + if (attrs) { + return attrs[attr] || ""; + } + } + + return ""; + }, + + removeValue(docURI, id, attr) { + this.log( + "remove store value for id=" + id + ", attr=" + attr + ", doc=" + docURI + ); + + if (!this._saveAllowed) { + Services.console.logStringMessage( + "XULStore: Changes after profile-before-change are ignored!" + ); + return; + } + + let ids = this._data[docURI]; + if (ids) { + let attrs = ids[id]; + if (attrs && attr in attrs) { + delete attrs[attr]; + + if (!Object.getOwnPropertyNames(attrs).length) { + delete ids[id]; + + if (!Object.getOwnPropertyNames(ids).length) { + delete this._data[docURI]; + } + } + + this.markAsChanged(); + } + } + }, + + removeDocument(docURI) { + this.log("remove store values for doc=" + docURI); + + if (!this._saveAllowed) { + Services.console.logStringMessage( + "XULStore: Changes after profile-before-change are ignored!" + ); + return; + } + + if (this._data[docURI]) { + delete this._data[docURI]; + this.markAsChanged(); + } + }, + + getIDsEnumerator(docURI) { + this.log("Getting ID enumerator for doc=" + docURI); + + if (!(docURI in this._data)) { + return new nsStringEnumerator([]); + } + + let result = []; + let ids = this._data[docURI]; + if (ids) { + for (let id in this._data[docURI]) { + result.push(id); + } + } + + return new nsStringEnumerator(result); + }, + + getAttributeEnumerator(docURI, id) { + this.log("Getting attribute enumerator for id=" + id + ", doc=" + docURI); + + if (!(docURI in this._data) || !(id in this._data[docURI])) { + return new nsStringEnumerator([]); + } + + let attrs = []; + for (let attr in this._data[docURI][id]) { + attrs.push(attr); + } + + return new nsStringEnumerator(attrs); + }, +}; + +function nsStringEnumerator(items) { + this._items = items; +} + +nsStringEnumerator.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIStringEnumerator"]), + _nextIndex: 0, + [Symbol.iterator]() { + return this._items.values(); + }, + hasMore() { + return this._nextIndex < this._items.length; + }, + getNext() { + if (!this.hasMore()) { + throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE); + } + return this._items[this._nextIndex++]; + }, +}; diff --git a/toolkit/components/xulstore/components.conf b/toolkit/components/xulstore/components.conf new file mode 100644 index 0000000000..533b5544b5 --- /dev/null +++ b/toolkit/components/xulstore/components.conf @@ -0,0 +1,16 @@ +# -*- 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/. + +Classes = [ + { + 'js_name': 'xulStore', + 'cid': '{6f46b6f4-c8b1-4bd4-a4fa-9ebbed0753ea}', + 'contract_ids': ['@mozilla.org/xul/xulstore;1'], + 'interfaces': ['nsIXULStore'], + 'esModule': 'resource://gre/modules/XULStore.sys.mjs', + 'constructor': 'XULStore', + }, +] diff --git a/toolkit/components/xulstore/moz.build b/toolkit/components/xulstore/moz.build new file mode 100644 index 0000000000..eebaef5baf --- /dev/null +++ b/toolkit/components/xulstore/moz.build @@ -0,0 +1,25 @@ +# -*- 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/. + +with Files("**"): + BUG_COMPONENT = ("Toolkit", "Startup and Profile System") + +MOCHITEST_CHROME_MANIFESTS += ["tests/chrome/chrome.ini"] +XPCSHELL_TESTS_MANIFESTS += ["tests/xpcshell/xpcshell.ini"] + +XPIDL_MODULE = "toolkit_xulstore" + +XPIDL_SOURCES += [ + "nsIXULStore.idl", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +EXTRA_JS_MODULES += [ + "XULStore.sys.mjs", +] diff --git a/toolkit/components/xulstore/nsIXULStore.idl b/toolkit/components/xulstore/nsIXULStore.idl new file mode 100644 index 0000000000..832c07dc90 --- /dev/null +++ b/toolkit/components/xulstore/nsIXULStore.idl @@ -0,0 +1,94 @@ +/* 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/. */ + +#include "nsISupports.idl" + +interface nsIStringEnumerator; +webidl Node; + +/** + * The XUL store is used to store information related to a XUL document/application. + * Typically it is used to store the persisted state for the document, such as + * window location, toolbars that are open and nodes that are open and closed in a tree. + * + * The data is serialized to [profile directory]/xulstore.json + */ +[scriptable, uuid(987c4b35-c426-4dd7-ad49-3c9fa4c65d20)] +interface nsIXULStore: nsISupports +{ + /** + * Sets a value for a specified node's attribute, except in + * the case below: + * If the value is empty and if calling `hasValue` with the node's + * document and ID and `attr` would return true, then the + * value instead gets removed from the store (see Bug 1476680). + * + * @param node - DOM node + * @param attr - attribute to store + */ + void persist(in Node aNode, in AString attr); + + /** + * Sets a value in the store. + * + * @param doc - document URI + * @param id - identifier of the node + * @param attr - attribute to store + * @param value - value of the attribute + */ + void setValue(in AString doc, in AString id, in AString attr, in AString value); + + /** + * Returns true if the store contains a value for attr. + * + * @param doc - URI of the document + * @param id - identifier of the node + * @param attr - attribute + */ + bool hasValue(in AString doc, in AString id, in AString attr); + + /** + * Retrieves a value in the store, or an empty string if it does not exist. + * + * @param doc - document URI + * @param id - identifier of the node + * @param attr - attribute to retrieve + * + * @returns the value of the attribute + */ + AString getValue(in AString doc, in AString id, in AString attr); + + /** + * Removes a value in the store. + * + * @param doc - document URI + * @param id - identifier of the node + * @param attr - attribute to remove + */ + void removeValue(in AString doc, in AString id, in AString attr); + + /** + * Removes all values related to the given document. + * + * @param doc - document URI + */ + void removeDocument(in AString doc); + + /** + * Iterates over all of the ids associated with a given document uri that + * have stored data. + * + * @param doc - document URI + */ + nsIStringEnumerator getIDsEnumerator(in AString doc); + + /** + * Iterates over all of the attributes associated with a given document uri + * and id that have stored data. + * + * @param doc - document URI + * @param id - identifier of the node + */ + nsIStringEnumerator getAttributeEnumerator(in AString doc, in AString id); +}; 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/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/xpcshell.ini b/toolkit/components/xulstore/tests/xpcshell/xpcshell.ini new file mode 100644 index 0000000000..accfb1c95b --- /dev/null +++ b/toolkit/components/xulstore/tests/xpcshell/xpcshell.ini @@ -0,0 +1,4 @@ +[DEFAULT] +skip-if = toolkit == 'android' + +[test_XULStore.js] |