From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- toolkit/components/xulstore/XULStore.cpp | 108 +++++++ toolkit/components/xulstore/XULStore.h | 56 ++++ toolkit/components/xulstore/XULStore.sys.mjs | 329 +++++++++++++++++++++ toolkit/components/xulstore/components.conf | 16 + toolkit/components/xulstore/moz.build | 25 ++ toolkit/components/xulstore/nsIXULStore.idl | 94 ++++++ .../components/xulstore/tests/chrome/chrome.ini | 5 + .../xulstore/tests/chrome/test_persistence.xhtml | 30 ++ .../xulstore/tests/chrome/window_persistence.xhtml | 67 +++++ .../xulstore/tests/xpcshell/test_XULStore.js | 150 ++++++++++ .../xulstore/tests/xpcshell/xpcshell.ini | 4 + 11 files changed, 884 insertions(+) create mode 100644 toolkit/components/xulstore/XULStore.cpp create mode 100644 toolkit/components/xulstore/XULStore.h create mode 100644 toolkit/components/xulstore/XULStore.sys.mjs create mode 100644 toolkit/components/xulstore/components.conf create mode 100644 toolkit/components/xulstore/moz.build create mode 100644 toolkit/components/xulstore/nsIXULStore.idl create mode 100644 toolkit/components/xulstore/tests/chrome/chrome.ini create mode 100644 toolkit/components/xulstore/tests/chrome/test_persistence.xhtml create mode 100644 toolkit/components/xulstore/tests/chrome/window_persistence.xhtml create mode 100644 toolkit/components/xulstore/tests/xpcshell/test_XULStore.js create mode 100644 toolkit/components/xulstore/tests/xpcshell/xpcshell.ini (limited to 'toolkit/components/xulstore') 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 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::operator()(XULStoreIterator* ptr) const { + xulstore_iter_free(ptr); +} + +namespace XULStore { +already_AddRefed GetService() { + nsCOMPtr 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& 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& 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 { + 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 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& iter); +nsresult GetAttrs(const nsAString& doc, const nsAString& id, + UniquePtr& 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 @@ + + + + + + + + + +

+ + + 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 @@ + + + + + + +