mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 03:15:11 +00:00
Backed out changeset 2f8f0e53a7db (bug 1460811) for leakcheck perma failures. CLOSED TREE
This commit is contained in:
parent
b8b5ed3ca4
commit
1f6c35708a
19
Cargo.lock
generated
19
Cargo.lock
generated
@ -1248,7 +1248,6 @@ dependencies = [
|
||||
"u2fhid 0.2.3",
|
||||
"webrender_bindings 0.1.0",
|
||||
"xpcom 0.1.0",
|
||||
"xulstore 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3433,24 +3432,6 @@ dependencies = [
|
||||
"syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xulstore"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"crossbeam-utils 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lmdb-rkv 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"moz_task 0.1.0",
|
||||
"nserror 0.1.0",
|
||||
"nsstring 0.1.0",
|
||||
"rkv 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"xpcom 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.2"
|
||||
|
@ -5,7 +5,8 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "XULPersist.h"
|
||||
#include "mozilla/XULStore.h"
|
||||
|
||||
#include "nsIXULStore.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
@ -79,6 +80,13 @@ void XULPersist::Persist(Element* aElement, int32_t aNameSpaceID,
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mLocalStore) {
|
||||
mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
|
||||
if (NS_WARN_IF(!mLocalStore)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
nsAutoString id;
|
||||
|
||||
aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
|
||||
@ -95,14 +103,13 @@ void XULPersist::Persist(Element* aElement, int32_t aNameSpaceID,
|
||||
NS_ConvertUTF8toUTF16 uri(utf8uri);
|
||||
|
||||
bool hasAttr;
|
||||
rv = XULStore::HasValue(uri, id, attrstr, hasAttr);
|
||||
rv = mLocalStore->HasValue(uri, id, attrstr, &hasAttr);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasAttr && valuestr.IsEmpty()) {
|
||||
rv = XULStore::RemoveValue(uri, id, attrstr);
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "value removed");
|
||||
mLocalStore->RemoveValue(uri, id, attrstr);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -114,8 +121,7 @@ void XULPersist::Persist(Element* aElement, int32_t aNameSpaceID,
|
||||
}
|
||||
}
|
||||
|
||||
rv = XULStore::SetValue(uri, id, attrstr, valuestr);
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "value set");
|
||||
mLocalStore->SetValue(uri, id, attrstr, valuestr);
|
||||
}
|
||||
|
||||
nsresult XULPersist::ApplyPersistentAttributes() {
|
||||
@ -129,6 +135,13 @@ nsresult XULPersist::ApplyPersistentAttributes() {
|
||||
|
||||
// Add all of the 'persisted' attributes into the content
|
||||
// model.
|
||||
if (!mLocalStore) {
|
||||
mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
|
||||
if (NS_WARN_IF(!mLocalStore)) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
}
|
||||
|
||||
ApplyPersistentAttributesInternal();
|
||||
|
||||
return NS_OK;
|
||||
@ -145,19 +158,22 @@ nsresult XULPersist::ApplyPersistentAttributesInternal() {
|
||||
NS_ConvertUTF8toUTF16 uri(utf8uri);
|
||||
|
||||
// Get a list of element IDs for which persisted values are available
|
||||
UniquePtr<XULStoreIterator> ids;
|
||||
rv = XULStore::GetIDs(uri, ids);
|
||||
nsCOMPtr<nsIStringEnumerator> ids;
|
||||
rv = mLocalStore->GetIDsEnumerator(uri, getter_AddRefs(ids));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
while (ids->HasMore()) {
|
||||
nsAutoString id;
|
||||
rv = ids->GetNext(&id);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
while (1) {
|
||||
bool hasmore = false;
|
||||
ids->HasMore(&hasmore);
|
||||
if (!hasmore) {
|
||||
break;
|
||||
}
|
||||
|
||||
nsAutoString id;
|
||||
ids->GetNext(id);
|
||||
|
||||
// We want to hold strong refs to the elements while applying
|
||||
// persistent attributes, just in case.
|
||||
const nsTArray<Element*>* allElements = mDocument->GetAllElementsForId(id);
|
||||
@ -189,21 +205,24 @@ nsresult XULPersist::ApplyPersistentAttributesToElements(
|
||||
NS_ConvertUTF8toUTF16 uri(utf8uri);
|
||||
|
||||
// Get a list of attributes for which persisted values are available
|
||||
UniquePtr<XULStoreIterator> attrs;
|
||||
rv = XULStore::GetAttrs(uri, aID, attrs);
|
||||
nsCOMPtr<nsIStringEnumerator> attrs;
|
||||
rv = mLocalStore->GetAttributeEnumerator(uri, aID, getter_AddRefs(attrs));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
while (attrs->HasMore()) {
|
||||
nsAutoString attrstr;
|
||||
rv = attrs->GetNext(&attrstr);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
while (1) {
|
||||
bool hasmore = PR_FALSE;
|
||||
attrs->HasMore(&hasmore);
|
||||
if (!hasmore) {
|
||||
break;
|
||||
}
|
||||
|
||||
nsAutoString attrstr;
|
||||
attrs->GetNext(attrstr);
|
||||
|
||||
nsAutoString value;
|
||||
rv = XULStore::GetValue(uri, aID, attrstr, value);
|
||||
rv = mLocalStore->GetValue(uri, aID, attrstr, value);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
@ -7,6 +7,8 @@
|
||||
#ifndef mozilla_dom_XULPersist_h
|
||||
#define mozilla_dom_XULPersist_h
|
||||
|
||||
class nsIXULStore;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
@ -31,6 +33,7 @@ class XULPersist final : public nsStubDocumentObserver {
|
||||
nsresult ApplyPersistentAttributesToElements(const nsAString& aID,
|
||||
nsCOMArray<Element>& aElements);
|
||||
|
||||
nsCOMPtr<nsIXULStore> mLocalStore;
|
||||
// A weak pointer to our document. Nulled out by DropDocumentReference.
|
||||
Document* MOZ_NON_OWNING_REF mDocument;
|
||||
};
|
||||
|
@ -566,16 +566,16 @@
|
||||
"minbytes": 6000,
|
||||
"maxbytes": 6000
|
||||
},
|
||||
"{profile}\\xulstore.json": {
|
||||
"mincount": 0,
|
||||
"maxcount": 0,
|
||||
"minbytes": 0,
|
||||
"maxbytes": 702
|
||||
},
|
||||
"{talos}\\talos\\tests\\{tp5n_files}": {
|
||||
"mincount": 0,
|
||||
"maxcount": 2,
|
||||
"minbytes": 0,
|
||||
"maxbytes": 16384
|
||||
},
|
||||
"{profile}\\xulstore\\data.mdb": {
|
||||
"mincount": 0,
|
||||
"maxcount": 4,
|
||||
"minbytes": 0,
|
||||
"maxbytes": 608
|
||||
}
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ DIRS += [
|
||||
'windowcreator',
|
||||
'windowwatcher',
|
||||
'workerloader',
|
||||
'xulstore',
|
||||
'xulstore'
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_BUILD_APP'] != 'mobile/android':
|
||||
|
@ -1,25 +0,0 @@
|
||||
[package]
|
||||
name = "xulstore"
|
||||
version = "0.1.0"
|
||||
authors = ["nobody@mozilla.org"]
|
||||
license = "MPL-2.0"
|
||||
|
||||
[dependencies]
|
||||
crossbeam-utils = "0.6.3"
|
||||
lazy_static = "1.0"
|
||||
libc = "0.2"
|
||||
lmdb-rkv = "0.11.2"
|
||||
log = "0.4"
|
||||
moz_task = { path = "../../../xpcom/rust/moz_task" }
|
||||
nsstring = { path = "../../../xpcom/rust/nsstring" }
|
||||
nserror = { path = "../../../xpcom/rust/nserror" }
|
||||
rkv = "0.9.3"
|
||||
serde_json = "1"
|
||||
xpcom = { path = "../../../xpcom/rust/xpcom" }
|
||||
|
||||
# Get rid of failure's dependency on backtrace. Eventually
|
||||
# backtrace will move into Rust core, but we don't need it here.
|
||||
[dependencies.failure]
|
||||
version = "0.1"
|
||||
default_features = false
|
||||
features = ["derive"]
|
@ -1,107 +0,0 @@
|
||||
/* 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"
|
||||
#include "nsIXULStore.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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
}; // namespace XULStore
|
||||
}; // namespace mozilla
|
@ -1,55 +0,0 @@
|
||||
/* 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);
|
||||
}; // namespace XULStore
|
||||
}; // namespace mozilla
|
||||
|
||||
#endif // mozilla_XULStore_h
|
@ -2,45 +2,127 @@
|
||||
* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
// This JS module wraps the nsIXULStore XPCOM service with useful abstractions.
|
||||
// In particular, it wraps the enumerators returned by getIDsEnumerator()
|
||||
// and getAttributeEnumerator() in JS objects that implement the iterable
|
||||
// protocol. It also implements the persist() method. JS consumers should use
|
||||
// this module rather than accessing nsIXULStore directly.
|
||||
|
||||
const EXPORTED_SYMBOLS = ["XULStore"];
|
||||
|
||||
const xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
|
||||
|
||||
// Enables logging.
|
||||
// Enables logging and shorter save intervals.
|
||||
const debugMode = false;
|
||||
|
||||
// Internal function for logging debug messages to the Error Console window
|
||||
function log(message) {
|
||||
if (!debugMode)
|
||||
return;
|
||||
console.log("XULStore: " + message);
|
||||
// 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";
|
||||
|
||||
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
|
||||
|
||||
function XULStore() {
|
||||
if (!Services.appinfo.inSafeMode)
|
||||
this.load();
|
||||
}
|
||||
|
||||
const XULStore = {
|
||||
setValue: xulStore.setValue,
|
||||
hasValue: xulStore.hasValue,
|
||||
getValue: xulStore.getValue,
|
||||
removeValue: xulStore.removeValue,
|
||||
removeDocument: xulStore.removeDocument,
|
||||
XULStore.prototype = {
|
||||
classID: XULSTORE_CID,
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver, Ci.nsIXULStore,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
_xpcom_factory: XPCOMUtils.generateSingletonFactory(XULStore),
|
||||
|
||||
/**
|
||||
* Sets a value for a specified node's attribute, except in
|
||||
* the case below (following the original XULDocument::persist):
|
||||
* 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).
|
||||
/* ---------- 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" },
|
||||
* },
|
||||
*
|
||||
* @param node - DOM node
|
||||
* @param attr - attribute to store
|
||||
* "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 {
|
||||
let data = JSON.stringify(this._data);
|
||||
let encoder = new TextEncoder();
|
||||
|
||||
data = encoder.encode(data);
|
||||
await OS.File.writeAtomic(this._storeFile.path, 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()");
|
||||
@ -50,7 +132,7 @@ const XULStore = {
|
||||
const value = node.getAttribute(attr);
|
||||
|
||||
if (node.localName == "window") {
|
||||
log("Persisting attributes to windows is handled by nsXULWindow.");
|
||||
this.log("Persisting attributes to windows is handled by nsXULWindow.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -58,38 +140,169 @@ const XULStore = {
|
||||
// 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 && xulStore.hasValue(uri, node.id, attr)) {
|
||||
xulStore.removeValue(uri, node.id, attr);
|
||||
if (!value && this.hasValue(uri, node.id, attr)) {
|
||||
this.removeValue(uri, node.id, attr);
|
||||
} else {
|
||||
xulStore.setValue(uri, node.id, attr, value);
|
||||
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 == 0) {
|
||||
delete ids[id];
|
||||
|
||||
if (Object.getOwnPropertyNames(ids).length == 0) {
|
||||
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) {
|
||||
return new XULStoreEnumerator(xulStore.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) {
|
||||
return new XULStoreEnumerator(xulStore.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);
|
||||
},
|
||||
};
|
||||
|
||||
class XULStoreEnumerator {
|
||||
constructor(enumerator) {
|
||||
this.enumerator = enumerator;
|
||||
}
|
||||
|
||||
hasMore() {
|
||||
return this.enumerator.hasMore();
|
||||
}
|
||||
|
||||
getNext() {
|
||||
return this.enumerator.getNext();
|
||||
}
|
||||
|
||||
* [Symbol.iterator]() {
|
||||
while (this.enumerator.hasMore()) {
|
||||
yield (this.enumerator.getNext());
|
||||
}
|
||||
}
|
||||
function nsStringEnumerator(items) {
|
||||
this._items = items;
|
||||
}
|
||||
|
||||
nsStringEnumerator.prototype = {
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIStringEnumerator]),
|
||||
_nextIndex: 0,
|
||||
[Symbol.iterator]() {
|
||||
return this._items.values();
|
||||
},
|
||||
hasMore() {
|
||||
return this._nextIndex < this._items.length;
|
||||
},
|
||||
getNext() {
|
||||
if (!this.hasMore())
|
||||
throw Cr.NS_ERROR_NOT_AVAILABLE;
|
||||
return this._items[this._nextIndex++];
|
||||
},
|
||||
};
|
||||
|
||||
var EXPORTED_SYMBOLS = ["XULStore"];
|
||||
|
@ -6,11 +6,9 @@
|
||||
|
||||
Classes = [
|
||||
{
|
||||
'cid': '{be70bf11-0c28-4a02-a38c-0148538d42cf}',
|
||||
'cid': '{6f46b6f4-c8b1-4bd4-a4fa-9ebbed0753ea}',
|
||||
'contract_ids': ['@mozilla.org/xul/xulstore;1'],
|
||||
'type': 'nsIXULStore',
|
||||
'headers': ['mozilla/XULStore.h'],
|
||||
'singleton': True,
|
||||
'constructor': 'mozilla::XULStore::GetService',
|
||||
'jsm': 'resource://gre/modules/XULStore.jsm',
|
||||
'constructor': 'XULStore',
|
||||
},
|
||||
]
|
||||
|
@ -14,26 +14,12 @@ XPIDL_SOURCES += [
|
||||
'nsIXULStore.idl',
|
||||
]
|
||||
|
||||
TEST_DIRS += [
|
||||
'tests/gtest',
|
||||
]
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
'XULStore.h',
|
||||
]
|
||||
XPIDL_MODULE = 'toolkit_xulstore'
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'XULStore.jsm',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'xulstore'
|
||||
|
||||
XPCOM_MANIFESTS += [
|
||||
'components.conf',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'XULStore.cpp',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
@ -5,19 +5,31 @@
|
||||
#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.
|
||||
*
|
||||
* XULStore.jsm wraps this API in useful abstractions for JS consumers.
|
||||
* XULStore.h provides a more idiomatic API for C++ consumers.
|
||||
* You should use those APIs unless you have good reasons to use this one.
|
||||
* 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 (following the original XULDocument::persist):
|
||||
* 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.
|
||||
*
|
||||
|
@ -1,114 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
use nserror::{
|
||||
nsresult, NS_ERROR_FAILURE, NS_ERROR_ILLEGAL_VALUE, NS_ERROR_NOT_AVAILABLE, NS_ERROR_UNEXPECTED,
|
||||
};
|
||||
use rkv::StoreError as RkvStoreError;
|
||||
use serde_json::Error as SerdeJsonError;
|
||||
use std::{
|
||||
io::Error as IoError, str::Utf8Error, string::FromUtf16Error, sync::PoisonError,
|
||||
};
|
||||
|
||||
pub(crate) type XULStoreResult<T> = Result<T, XULStoreError>;
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
pub(crate) enum XULStoreError {
|
||||
#[fail(display = "error converting bytes: {:?}", _0)]
|
||||
ConvertBytes(Utf8Error),
|
||||
|
||||
#[fail(display = "error converting string: {:?}", _0)]
|
||||
ConvertString(FromUtf16Error),
|
||||
|
||||
#[fail(display = "I/O error: {:?}", _0)]
|
||||
IoError(IoError),
|
||||
|
||||
#[fail(display = "iteration is finished")]
|
||||
IterationFinished,
|
||||
|
||||
#[fail(display = "JSON error: {}", _0)]
|
||||
JsonError(SerdeJsonError),
|
||||
|
||||
#[fail(display = "error result {}", _0)]
|
||||
NsResult(nsresult),
|
||||
|
||||
#[fail(display = "poison error getting read/write lock")]
|
||||
PoisonError,
|
||||
|
||||
#[fail(display = "store error: {:?}", _0)]
|
||||
RkvStoreError(RkvStoreError),
|
||||
|
||||
#[fail(display = "id or attribute name too long")]
|
||||
IdAttrNameTooLong,
|
||||
|
||||
#[fail(display = "unavailable")]
|
||||
Unavailable,
|
||||
|
||||
#[fail(display = "unexpected key: {:?}", _0)]
|
||||
UnexpectedKey(String),
|
||||
|
||||
#[fail(display = "unexpected value")]
|
||||
UnexpectedValue,
|
||||
}
|
||||
|
||||
impl From<XULStoreError> for nsresult {
|
||||
fn from(err: XULStoreError) -> nsresult {
|
||||
match err {
|
||||
XULStoreError::ConvertBytes(_) => NS_ERROR_FAILURE,
|
||||
XULStoreError::ConvertString(_) => NS_ERROR_FAILURE,
|
||||
XULStoreError::IoError(_) => NS_ERROR_FAILURE,
|
||||
XULStoreError::IterationFinished => NS_ERROR_FAILURE,
|
||||
XULStoreError::JsonError(_) => NS_ERROR_FAILURE,
|
||||
XULStoreError::NsResult(result) => result,
|
||||
XULStoreError::PoisonError => NS_ERROR_UNEXPECTED,
|
||||
XULStoreError::RkvStoreError(_) => NS_ERROR_FAILURE,
|
||||
XULStoreError::IdAttrNameTooLong => NS_ERROR_ILLEGAL_VALUE,
|
||||
XULStoreError::Unavailable => NS_ERROR_NOT_AVAILABLE,
|
||||
XULStoreError::UnexpectedKey(_) => NS_ERROR_UNEXPECTED,
|
||||
XULStoreError::UnexpectedValue => NS_ERROR_UNEXPECTED,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FromUtf16Error> for XULStoreError {
|
||||
fn from(err: FromUtf16Error) -> XULStoreError {
|
||||
XULStoreError::ConvertString(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nsresult> for XULStoreError {
|
||||
fn from(result: nsresult) -> XULStoreError {
|
||||
XULStoreError::NsResult(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<PoisonError<T>> for XULStoreError {
|
||||
fn from(_: PoisonError<T>) -> XULStoreError {
|
||||
XULStoreError::PoisonError
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RkvStoreError> for XULStoreError {
|
||||
fn from(err: RkvStoreError) -> XULStoreError {
|
||||
XULStoreError::RkvStoreError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Utf8Error> for XULStoreError {
|
||||
fn from(err: Utf8Error) -> XULStoreError {
|
||||
XULStoreError::ConvertBytes(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IoError> for XULStoreError {
|
||||
fn from(err: IoError) -> XULStoreError {
|
||||
XULStoreError::IoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SerdeJsonError> for XULStoreError {
|
||||
fn from(err: SerdeJsonError) -> XULStoreError {
|
||||
XULStoreError::JsonError(err)
|
||||
}
|
||||
}
|
@ -1,318 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
use crate as XULStore;
|
||||
use crate::{iter::XULStoreIterator, statics::update_profile_dir};
|
||||
use libc::c_char;
|
||||
use nserror::{nsresult, NS_ERROR_NOT_IMPLEMENTED, NS_OK};
|
||||
use nsstring::{nsAString, nsString};
|
||||
use std::cell::RefCell;
|
||||
use std::ptr;
|
||||
use xpcom::{
|
||||
interfaces::{nsIJSEnumerator, nsIStringEnumerator, nsISupports, nsIXULStore},
|
||||
RefPtr,
|
||||
};
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn xulstore_new_service(result: *mut *const nsIXULStore) {
|
||||
let xul_store_service = XULStoreService::new();
|
||||
RefPtr::new(xul_store_service.coerce::<nsIXULStore>()).forget(&mut *result);
|
||||
}
|
||||
|
||||
#[derive(xpcom)]
|
||||
#[xpimplements(nsIXULStore)]
|
||||
#[refcnt = "atomic"]
|
||||
pub struct InitXULStoreService {}
|
||||
|
||||
impl XULStoreService {
|
||||
fn new() -> RefPtr<XULStoreService> {
|
||||
XULStoreService::allocate(InitXULStoreService {})
|
||||
}
|
||||
|
||||
xpcom_method!(
|
||||
set_value => SetValue(
|
||||
doc: *const nsAString,
|
||||
id: *const nsAString,
|
||||
attr: *const nsAString,
|
||||
value: *const nsAString
|
||||
)
|
||||
);
|
||||
|
||||
fn set_value(
|
||||
&self,
|
||||
doc: &nsAString,
|
||||
id: &nsAString,
|
||||
attr: &nsAString,
|
||||
value: &nsAString,
|
||||
) -> Result<(), nsresult> {
|
||||
XULStore::set_value(doc, id, attr, value).map_err(|err| err.into())
|
||||
}
|
||||
|
||||
xpcom_method!(
|
||||
has_value => HasValue(
|
||||
doc: *const nsAString,
|
||||
id: *const nsAString,
|
||||
attr: *const nsAString
|
||||
) -> bool
|
||||
);
|
||||
|
||||
fn has_value(
|
||||
&self,
|
||||
doc: &nsAString,
|
||||
id: &nsAString,
|
||||
attr: &nsAString,
|
||||
) -> Result<bool, nsresult> {
|
||||
XULStore::has_value(doc, id, attr).map_err(|err| err.into())
|
||||
}
|
||||
|
||||
xpcom_method!(
|
||||
get_value => GetValue(
|
||||
doc: *const nsAString,
|
||||
id: *const nsAString,
|
||||
attr: *const nsAString
|
||||
) -> nsAString
|
||||
);
|
||||
|
||||
fn get_value(
|
||||
&self,
|
||||
doc: &nsAString,
|
||||
id: &nsAString,
|
||||
attr: &nsAString,
|
||||
) -> Result<nsString, nsresult> {
|
||||
match XULStore::get_value(doc, id, attr) {
|
||||
Ok(val) => Ok(nsString::from(&val)),
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
xpcom_method!(
|
||||
remove_value => RemoveValue(
|
||||
doc: *const nsAString,
|
||||
id: *const nsAString,
|
||||
attr: *const nsAString
|
||||
)
|
||||
);
|
||||
|
||||
fn remove_value(
|
||||
&self,
|
||||
doc: &nsAString,
|
||||
id: &nsAString,
|
||||
attr: &nsAString,
|
||||
) -> Result<(), nsresult> {
|
||||
XULStore::remove_value(doc, id, attr).map_err(|err| err.into())
|
||||
}
|
||||
|
||||
xpcom_method!(
|
||||
remove_document => RemoveDocument(doc: *const nsAString)
|
||||
);
|
||||
|
||||
fn remove_document(&self, doc: &nsAString) -> Result<(), nsresult> {
|
||||
XULStore::remove_document(doc).map_err(|err| err.into())
|
||||
}
|
||||
|
||||
xpcom_method!(
|
||||
get_ids_enumerator => GetIDsEnumerator(
|
||||
doc: *const nsAString
|
||||
) -> * const nsIStringEnumerator
|
||||
);
|
||||
|
||||
fn get_ids_enumerator(&self, doc: &nsAString) -> Result<RefPtr<nsIStringEnumerator>, nsresult> {
|
||||
match XULStore::get_ids(doc) {
|
||||
Ok(val) => {
|
||||
let enumerator = StringEnumerator::new(val);
|
||||
Ok(RefPtr::new(enumerator.coerce::<nsIStringEnumerator>()))
|
||||
}
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
xpcom_method!(
|
||||
get_attribute_enumerator => GetAttributeEnumerator(
|
||||
doc: *const nsAString,
|
||||
id: *const nsAString
|
||||
) -> * const nsIStringEnumerator
|
||||
);
|
||||
|
||||
fn get_attribute_enumerator(
|
||||
&self,
|
||||
doc: &nsAString,
|
||||
id: &nsAString,
|
||||
) -> Result<RefPtr<nsIStringEnumerator>, nsresult> {
|
||||
match XULStore::get_attrs(doc, id) {
|
||||
Ok(val) => {
|
||||
let enumerator = StringEnumerator::new(val);
|
||||
Ok(RefPtr::new(enumerator.coerce::<nsIStringEnumerator>()))
|
||||
}
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(xpcom)]
|
||||
#[xpimplements(nsIStringEnumerator)]
|
||||
#[refcnt = "nonatomic"]
|
||||
pub(crate) struct InitStringEnumerator {
|
||||
iter: RefCell<XULStoreIterator>,
|
||||
}
|
||||
impl StringEnumerator {
|
||||
pub(crate) fn new(iter: XULStoreIterator) -> RefPtr<StringEnumerator> {
|
||||
StringEnumerator::allocate(InitStringEnumerator {
|
||||
iter: RefCell::new(iter),
|
||||
})
|
||||
}
|
||||
|
||||
xpcom_method!(string_iterator => StringIterator() -> *const nsIJSEnumerator);
|
||||
|
||||
fn string_iterator(&self) -> Result<RefPtr<nsIJSEnumerator>, nsresult> {
|
||||
Err(NS_ERROR_NOT_IMPLEMENTED)
|
||||
}
|
||||
|
||||
xpcom_method!(has_more => HasMore() -> bool);
|
||||
|
||||
fn has_more(&self) -> Result<bool, nsresult> {
|
||||
let iter = self.iter.borrow();
|
||||
Ok(iter.has_more())
|
||||
}
|
||||
|
||||
xpcom_method!(get_next => GetNext() -> nsAString);
|
||||
|
||||
fn get_next(&self) -> Result<nsString, nsresult> {
|
||||
let mut iter = self.iter.borrow_mut();
|
||||
match iter.get_next() {
|
||||
Ok(value) => Ok(nsString::from(&value)),
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(xpcom)]
|
||||
#[xpimplements(nsIObserver)]
|
||||
#[refcnt = "nonatomic"]
|
||||
pub(crate) struct InitProfileChangeObserver {}
|
||||
impl ProfileChangeObserver {
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn Observe(
|
||||
&self,
|
||||
_subject: *const nsISupports,
|
||||
_topic: *const c_char,
|
||||
_data: *const i16,
|
||||
) -> nsresult {
|
||||
update_profile_dir();
|
||||
NS_OK
|
||||
}
|
||||
|
||||
pub(crate) fn new() -> RefPtr<ProfileChangeObserver> {
|
||||
ProfileChangeObserver::allocate(InitProfileChangeObserver {})
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn xulstore_set_value(
|
||||
doc: &nsAString,
|
||||
id: &nsAString,
|
||||
attr: &nsAString,
|
||||
value: &nsAString,
|
||||
) -> nsresult {
|
||||
XULStore::set_value(doc, id, attr, value).into()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn xulstore_has_value(
|
||||
doc: &nsAString,
|
||||
id: &nsAString,
|
||||
attr: &nsAString,
|
||||
has_value: *mut bool,
|
||||
) -> nsresult {
|
||||
match XULStore::has_value(doc, id, attr) {
|
||||
Ok(val) => {
|
||||
*has_value = val;
|
||||
NS_OK
|
||||
}
|
||||
Err(err) => err.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn xulstore_get_value(
|
||||
doc: &nsAString,
|
||||
id: &nsAString,
|
||||
attr: &nsAString,
|
||||
value: *mut nsAString,
|
||||
) -> nsresult {
|
||||
match XULStore::get_value(doc, id, attr) {
|
||||
Ok(val) => {
|
||||
(*value).assign(&nsString::from(&val));
|
||||
NS_OK
|
||||
}
|
||||
Err(err) => err.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn xulstore_remove_value(
|
||||
doc: &nsAString,
|
||||
id: &nsAString,
|
||||
attr: &nsAString,
|
||||
) -> nsresult {
|
||||
XULStore::remove_value(doc, id, attr).into()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn xulstore_get_ids(
|
||||
doc: &nsAString,
|
||||
result: *mut nsresult,
|
||||
) -> *mut XULStoreIterator {
|
||||
match XULStore::get_ids(doc) {
|
||||
Ok(iter) => {
|
||||
*result = NS_OK;
|
||||
Box::into_raw(Box::new(iter))
|
||||
}
|
||||
Err(err) => {
|
||||
*result = err.into();
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn xulstore_get_attrs(
|
||||
doc: &nsAString,
|
||||
id: &nsAString,
|
||||
result: *mut nsresult,
|
||||
) -> *mut XULStoreIterator {
|
||||
match XULStore::get_attrs(doc, id) {
|
||||
Ok(iter) => {
|
||||
*result = NS_OK;
|
||||
Box::into_raw(Box::new(iter))
|
||||
}
|
||||
Err(err) => {
|
||||
*result = err.into();
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn xulstore_iter_has_more(iter: &XULStoreIterator) -> bool {
|
||||
iter.has_more()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn xulstore_iter_get_next(
|
||||
iter: &mut XULStoreIterator,
|
||||
value: *mut nsAString,
|
||||
) -> nsresult {
|
||||
match iter.get_next() {
|
||||
Ok(val) => {
|
||||
(*value).assign(&nsString::from(&val));
|
||||
NS_OK
|
||||
}
|
||||
Err(err) => err.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn xulstore_iter_free(iter: *mut XULStoreIterator) {
|
||||
drop(Box::from_raw(iter));
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
use crate::error::{XULStoreError, XULStoreResult};
|
||||
use std::vec::IntoIter;
|
||||
|
||||
pub struct XULStoreIterator {
|
||||
values: IntoIter<String>,
|
||||
}
|
||||
|
||||
impl XULStoreIterator {
|
||||
pub(crate) fn new(values: IntoIter<String>) -> Self {
|
||||
Self { values }
|
||||
}
|
||||
|
||||
pub(crate) fn has_more(&self) -> bool {
|
||||
!self.values.as_slice().is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn get_next(&mut self) -> XULStoreResult<String> {
|
||||
Ok(self.values.next().ok_or(XULStoreError::IterationFinished)?)
|
||||
}
|
||||
}
|
@ -1,219 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
extern crate crossbeam_utils;
|
||||
#[macro_use]
|
||||
extern crate failure;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate libc;
|
||||
extern crate lmdb;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate moz_task;
|
||||
extern crate nserror;
|
||||
extern crate nsstring;
|
||||
extern crate rkv;
|
||||
extern crate serde_json;
|
||||
#[macro_use]
|
||||
extern crate xpcom;
|
||||
|
||||
mod error;
|
||||
mod ffi;
|
||||
mod iter;
|
||||
mod persist;
|
||||
mod statics;
|
||||
|
||||
use crate::{
|
||||
error::{XULStoreError, XULStoreResult},
|
||||
iter::XULStoreIterator,
|
||||
persist::persist,
|
||||
statics::DATA_CACHE,
|
||||
};
|
||||
use nsstring::nsAString;
|
||||
use std::collections::btree_map::Entry;
|
||||
use std::fmt::Display;
|
||||
|
||||
const SEPARATOR: char = '\u{0009}';
|
||||
|
||||
pub(crate) fn make_key(doc: &impl Display, id: &impl Display, attr: &impl Display) -> String {
|
||||
format!("{}{}{}{}{}", doc, SEPARATOR, id, SEPARATOR, attr)
|
||||
}
|
||||
|
||||
pub(crate) fn set_value(
|
||||
doc: &nsAString,
|
||||
id: &nsAString,
|
||||
attr: &nsAString,
|
||||
value: &nsAString,
|
||||
) -> XULStoreResult<()> {
|
||||
debug!("XULStore set value: {} {} {} {}", doc, id, attr, value);
|
||||
|
||||
// bug 319846 -- don't save really long attributes or values.
|
||||
if id.len() > 512 || attr.len() > 512 {
|
||||
return Err(XULStoreError::IdAttrNameTooLong);
|
||||
}
|
||||
|
||||
let value = if value.len() > 4096 {
|
||||
warn!("XULStore: truncating long attribute value");
|
||||
String::from_utf16(&value[0..4096])?
|
||||
} else {
|
||||
String::from_utf16(value)?
|
||||
};
|
||||
|
||||
let mut cache_guard = DATA_CACHE.lock()?;
|
||||
let data = match cache_guard.as_mut() {
|
||||
Some(data) => data,
|
||||
None => return Ok(()),
|
||||
};
|
||||
data.entry(doc.to_string())
|
||||
.or_default()
|
||||
.entry(id.to_string())
|
||||
.or_default()
|
||||
.insert(attr.to_string(), value.clone());
|
||||
|
||||
persist(make_key(doc, id, attr), Some(value))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn has_value(doc: &nsAString, id: &nsAString, attr: &nsAString) -> XULStoreResult<bool> {
|
||||
debug!("XULStore has value: {} {} {}", doc, id, attr);
|
||||
|
||||
let cache_guard = DATA_CACHE.lock()?;
|
||||
let data = match cache_guard.as_ref() {
|
||||
Some(data) => data,
|
||||
None => return Ok(false),
|
||||
};
|
||||
|
||||
match data.get(&doc.to_string()) {
|
||||
Some(ids) => match ids.get(&id.to_string()) {
|
||||
Some(attrs) => Ok(attrs.contains_key(&attr.to_string())),
|
||||
None => Ok(false),
|
||||
},
|
||||
None => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_value(
|
||||
doc: &nsAString,
|
||||
id: &nsAString,
|
||||
attr: &nsAString,
|
||||
) -> XULStoreResult<String> {
|
||||
debug!("XULStore get value {} {} {}", doc, id, attr);
|
||||
|
||||
let cache_guard = DATA_CACHE.lock()?;
|
||||
let data = match cache_guard.as_ref() {
|
||||
Some(data) => data,
|
||||
None => return Ok(String::new()),
|
||||
};
|
||||
|
||||
match data.get(&doc.to_string()) {
|
||||
Some(ids) => match ids.get(&id.to_string()) {
|
||||
Some(attrs) => match attrs.get(&attr.to_string()) {
|
||||
Some(value) => Ok(value.clone()),
|
||||
None => Ok(String::new()),
|
||||
},
|
||||
None => Ok(String::new()),
|
||||
},
|
||||
None => Ok(String::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn remove_value(
|
||||
doc: &nsAString,
|
||||
id: &nsAString,
|
||||
attr: &nsAString,
|
||||
) -> XULStoreResult<()> {
|
||||
debug!("XULStore remove value {} {} {}", doc, id, attr);
|
||||
|
||||
let mut cache_guard = DATA_CACHE.lock()?;
|
||||
let data = match cache_guard.as_mut() {
|
||||
Some(data) => data,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
let mut ids_empty = false;
|
||||
if let Some(ids) = data.get_mut(&doc.to_string()) {
|
||||
let mut attrs_empty = false;
|
||||
if let Some(attrs) = ids.get_mut(&id.to_string()) {
|
||||
attrs.remove(&attr.to_string());
|
||||
if attrs.is_empty() {
|
||||
attrs_empty = true;
|
||||
}
|
||||
}
|
||||
if attrs_empty {
|
||||
ids.remove(&id.to_string());
|
||||
if ids.is_empty() {
|
||||
ids_empty = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
if ids_empty {
|
||||
data.remove(&doc.to_string());
|
||||
}
|
||||
|
||||
persist(make_key(doc, id, attr), None)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn remove_document(doc: &nsAString) -> XULStoreResult<()> {
|
||||
debug!("XULStore remove document {}", doc);
|
||||
|
||||
let mut cache_guard = DATA_CACHE.lock()?;
|
||||
let data = match cache_guard.as_mut() {
|
||||
Some(data) => data,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
if let Entry::Occupied(entry) = data.entry(doc.to_string()) {
|
||||
for (id, attrs) in entry.get() {
|
||||
for attr in attrs.keys() {
|
||||
persist(make_key(entry.key(), id, attr), None)?;
|
||||
}
|
||||
}
|
||||
entry.remove_entry();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn get_ids(doc: &nsAString) -> XULStoreResult<XULStoreIterator> {
|
||||
debug!("XULStore get IDs for {}", doc);
|
||||
|
||||
let cache_guard = DATA_CACHE.lock()?;
|
||||
let data = match cache_guard.as_ref() {
|
||||
Some(data) => data,
|
||||
None => return Ok(XULStoreIterator::new(vec![].into_iter())),
|
||||
};
|
||||
|
||||
match data.get(&doc.to_string()) {
|
||||
Some(ids) => {
|
||||
let mut ids: Vec<String> = ids.keys().map(|id| id.clone()).collect();
|
||||
Ok(XULStoreIterator::new(ids.into_iter()))
|
||||
}
|
||||
None => Ok(XULStoreIterator::new(vec![].into_iter())),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_attrs(doc: &nsAString, id: &nsAString) -> XULStoreResult<XULStoreIterator> {
|
||||
debug!("XULStore get attrs for doc, ID: {} {}", doc, id);
|
||||
|
||||
let cache_guard = DATA_CACHE.lock()?;
|
||||
let data = match cache_guard.as_ref() {
|
||||
Some(data) => data,
|
||||
None => return Ok(XULStoreIterator::new(vec![].into_iter())),
|
||||
};
|
||||
|
||||
match data.get(&doc.to_string()) {
|
||||
Some(ids) => match ids.get(&id.to_string()) {
|
||||
Some(attrs) => {
|
||||
let mut attrs: Vec<String> = attrs.keys().map(|attr| attr.clone()).collect();
|
||||
Ok(XULStoreIterator::new(attrs.into_iter()))
|
||||
}
|
||||
None => Ok(XULStoreIterator::new(vec![].into_iter())),
|
||||
},
|
||||
None => Ok(XULStoreIterator::new(vec![].into_iter())),
|
||||
}
|
||||
}
|
@ -1,160 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
use crate::{
|
||||
error::{XULStoreError, XULStoreResult},
|
||||
statics::get_database,
|
||||
};
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use lmdb::Error as LmdbError;
|
||||
use moz_task::{create_thread, Task, TaskRunnable};
|
||||
use nserror::nsresult;
|
||||
use rkv::{StoreError as RkvStoreError, Value};
|
||||
use std::{collections::HashMap, sync::Mutex, thread::sleep, time::Duration};
|
||||
use xpcom::{interfaces::nsIThread, RefPtr, ThreadBoundRefPtr};
|
||||
|
||||
/// The XULStore API is synchronous for both C++ and JS consumers and accessed
|
||||
/// on the main thread, so we persist its data to disk on a background thread
|
||||
/// to avoid janking the UI.
|
||||
///
|
||||
/// We also re-open the database each time we write to it in order to conserve
|
||||
/// heap memory, since holding a database connection open would consume at least
|
||||
/// 3MB of heap memory in perpetuity.
|
||||
///
|
||||
/// Since re-opening the database repeatedly to write individual changes can be
|
||||
/// expensive when there are many of them in quick succession, we batch changes
|
||||
/// and write them in batches.
|
||||
|
||||
lazy_static! {
|
||||
/// A map of key/value pairs to persist. Values are Options so we can
|
||||
/// use the same structure for both puts and deletes, with a `None` value
|
||||
/// identifying a key that should be deleted from the database.
|
||||
///
|
||||
/// This is a map rather than a sequence in order to merge consecutive
|
||||
/// changes to the same key, i.e. when a consumer sets *foo* to `bar`
|
||||
/// and then sets it again to `baz` before we persist the first change.
|
||||
///
|
||||
/// In that case, there's no point in setting *foo* to `bar` before we set
|
||||
/// it to `baz`, and the map ensures we only ever persist the latest value
|
||||
/// for any given key.
|
||||
static ref CHANGES: Mutex<Option<HashMap<String, Option<String>>>> = { Mutex::new(None) };
|
||||
|
||||
/// A Mutex that prevents two PersistTasks from running at the same time,
|
||||
/// since each task opens the database, and we need to ensure there is only
|
||||
/// one open database handle for the database at any given time.
|
||||
static ref PERSIST: Mutex<()> = { Mutex::new(()) };
|
||||
|
||||
static ref THREAD: Option<ThreadBoundRefPtr<nsIThread>> = {
|
||||
let thread: RefPtr<nsIThread> = match create_thread("XULStore") {
|
||||
Ok(thread) => thread,
|
||||
Err(err) => {
|
||||
error!("error creating XULStore thread: {}", err);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
Some(ThreadBoundRefPtr::new(thread))
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn persist(key: String, value: Option<String>) -> XULStoreResult<()> {
|
||||
let mut changes = CHANGES.lock()?;
|
||||
|
||||
if changes.is_none() {
|
||||
*changes = Some(HashMap::new());
|
||||
|
||||
// If *changes* was `None`, then this is the first change since
|
||||
// the last time we persisted, so dispatch a new PersistTask.
|
||||
let task = Box::new(PersistTask::new());
|
||||
let thread = THREAD
|
||||
.as_ref()
|
||||
.ok_or(XULStoreError::Unavailable)?
|
||||
.get_ref()
|
||||
.ok_or(XULStoreError::Unavailable)?;
|
||||
TaskRunnable::new("XULStore::Persist", task)?.dispatch(thread)?;
|
||||
}
|
||||
|
||||
// Now insert the key/value pair into the map. The unwrap() call here
|
||||
// should never panic, since the code above sets `writes` to a Some(HashMap)
|
||||
// if it's None.
|
||||
changes.as_mut().unwrap().insert(key, value);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct PersistTask {
|
||||
result: AtomicCell<Option<Result<(), XULStoreError>>>,
|
||||
}
|
||||
|
||||
impl PersistTask {
|
||||
pub fn new() -> PersistTask {
|
||||
PersistTask {
|
||||
result: AtomicCell::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Task for PersistTask {
|
||||
fn run(&self) {
|
||||
self.result.store(Some(|| -> Result<(), XULStoreError> {
|
||||
// Avoid persisting too often. We might want to adjust this value
|
||||
// in the future to trade durability for performance.
|
||||
sleep(Duration::from_millis(200));
|
||||
|
||||
// Prevent another PersistTask from running until this one finishes.
|
||||
// We do this before getting the database to ensure that there is
|
||||
// only ever one open database handle at a given time.
|
||||
let _lock = PERSIST.lock()?;
|
||||
|
||||
let db = get_database()?;
|
||||
let mut writer = db.env.write()?;
|
||||
|
||||
// Get the map of key/value pairs from the mutex, replacing it
|
||||
// with None. To avoid janking the main thread (if it decides
|
||||
// to makes more changes while we're persisting to disk), we only
|
||||
// lock the map long enough to move it out of the Mutex.
|
||||
let writes = CHANGES.lock()?.take();
|
||||
|
||||
// The Option should be a Some(HashMap) (otherwise the task
|
||||
// shouldn't have been scheduled in the first place). If it's None,
|
||||
// unexpectedly, then we return an error early.
|
||||
let writes = writes.ok_or(XULStoreError::Unavailable)?;
|
||||
|
||||
for (key, value) in writes.iter() {
|
||||
match value {
|
||||
Some(val) => db.store.put(&mut writer, &key, &Value::Str(val))?,
|
||||
None => {
|
||||
match db.store.delete(&mut writer, &key) {
|
||||
Ok(_) => (),
|
||||
|
||||
// The XULStore API doesn't care if a consumer tries
|
||||
// to remove a value that doesn't exist in the store,
|
||||
// so we ignore the error (although in this case the key
|
||||
// should exist, since it was in the cache!).
|
||||
Err(RkvStoreError::LmdbError(LmdbError::NotFound)) => {
|
||||
warn!("tried to remove key that isn't in the store");
|
||||
}
|
||||
|
||||
Err(err) => return Err(err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writer.commit()?;
|
||||
|
||||
Ok(())
|
||||
}()));
|
||||
}
|
||||
|
||||
fn done(&self) -> Result<(), nsresult> {
|
||||
match self.result.swap(None) {
|
||||
Some(Ok(())) => (),
|
||||
Some(Err(err)) => error!("removeDocument error: {}", err),
|
||||
None => error!("removeDocument error: unexpected result"),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,245 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
use crate::{
|
||||
error::{XULStoreError, XULStoreResult},
|
||||
ffi::ProfileChangeObserver,
|
||||
make_key, SEPARATOR,
|
||||
};
|
||||
use moz_task::is_main_thread;
|
||||
use nsstring::nsString;
|
||||
use rkv::{Rkv, SingleStore, StoreOptions, Value};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fs::{create_dir_all, remove_file, File},
|
||||
path::PathBuf,
|
||||
str,
|
||||
sync::Mutex,
|
||||
};
|
||||
use xpcom::{interfaces::nsIFile, XpCom};
|
||||
|
||||
type XULStoreCache = BTreeMap<String, BTreeMap<String, BTreeMap<String, String>>>;
|
||||
|
||||
pub struct Database {
|
||||
pub env: Rkv,
|
||||
pub store: SingleStore,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
fn new(env: Rkv, store: SingleStore) -> Database {
|
||||
Database { env, store }
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref PROFILE_DIR: Mutex<Option<PathBuf>> = {
|
||||
observe_profile_change();
|
||||
Mutex::new(get_profile_dir().ok())
|
||||
};
|
||||
pub(crate) static ref DATA_CACHE: Mutex<Option<XULStoreCache>> =
|
||||
{ Mutex::new(cache_data().ok()) };
|
||||
}
|
||||
|
||||
pub(crate) fn get_database() -> XULStoreResult<Database> {
|
||||
let xulstore_dir = get_xulstore_dir()?;
|
||||
let env = Rkv::new(xulstore_dir.as_path())?;
|
||||
let store = env.open_single("db", StoreOptions::create())?;
|
||||
|
||||
Ok(Database::new(env, store))
|
||||
}
|
||||
|
||||
pub(crate) fn update_profile_dir() {
|
||||
// Failure to update the dir isn't fatal (although it means that we won't
|
||||
// persist XULStore data for this session), so we don't return a result.
|
||||
// But we use a closure returning a result to enable use of the ? operator.
|
||||
(|| -> XULStoreResult<()> {
|
||||
{
|
||||
let mut profile_dir_guard = PROFILE_DIR.lock()?;
|
||||
*profile_dir_guard = get_profile_dir().ok();
|
||||
}
|
||||
|
||||
let mut cache_guard = DATA_CACHE.lock()?;
|
||||
*cache_guard = cache_data().ok();
|
||||
|
||||
Ok(())
|
||||
})()
|
||||
.unwrap_or_else(|err| error!("error updating profile dir: {}", err));
|
||||
}
|
||||
|
||||
fn get_profile_dir() -> XULStoreResult<PathBuf> {
|
||||
// We can't use getter_addrefs() here because get_DirectoryService()
|
||||
// returns its nsIProperties interface, and its Get() method returns
|
||||
// a directory via its nsQIResult out param, which gets translated to
|
||||
// a `*mut *mut libc::c_void` in Rust, whereas getter_addrefs() expects
|
||||
// a closure with a `*mut *const T` parameter.
|
||||
|
||||
let dir_svc = xpcom::services::get_DirectoryService().ok_or(XULStoreError::Unavailable)?;
|
||||
let mut profile_dir = xpcom::GetterAddrefs::<nsIFile>::new();
|
||||
unsafe {
|
||||
dir_svc
|
||||
.Get(
|
||||
c_str!("ProfD").as_ptr(),
|
||||
&nsIFile::IID,
|
||||
profile_dir.void_ptr(),
|
||||
)
|
||||
.to_result()
|
||||
.or_else(|_| {
|
||||
dir_svc
|
||||
.Get(
|
||||
c_str!("ProfDS").as_ptr(),
|
||||
&nsIFile::IID,
|
||||
profile_dir.void_ptr(),
|
||||
)
|
||||
.to_result()
|
||||
})?;
|
||||
}
|
||||
let profile_dir = profile_dir.refptr().ok_or(XULStoreError::Unavailable)?;
|
||||
|
||||
let mut profile_path = nsString::new();
|
||||
unsafe {
|
||||
profile_dir.GetPath(&mut *profile_path).to_result()?;
|
||||
}
|
||||
|
||||
let path = String::from_utf16(&profile_path[..])?;
|
||||
Ok(PathBuf::from(&path))
|
||||
}
|
||||
|
||||
fn get_xulstore_dir() -> XULStoreResult<PathBuf> {
|
||||
let mut xulstore_dir = PROFILE_DIR
|
||||
.lock()?
|
||||
.clone()
|
||||
.ok_or(XULStoreError::Unavailable)?;
|
||||
xulstore_dir.push("xulstore");
|
||||
|
||||
create_dir_all(xulstore_dir.clone())?;
|
||||
|
||||
Ok(xulstore_dir)
|
||||
}
|
||||
|
||||
fn observe_profile_change() {
|
||||
assert!(is_main_thread());
|
||||
|
||||
// Failure to observe the change isn't fatal (although it means we won't
|
||||
// persist XULStore data for this session), so we don't return a result.
|
||||
// But we use a closure returning a result to enable use of the ? operator.
|
||||
(|| -> XULStoreResult<()> {
|
||||
// Observe profile changes so we can update this directory accordingly.
|
||||
let obs_svc = xpcom::services::get_ObserverService().ok_or(XULStoreError::Unavailable)?;
|
||||
let observer = ProfileChangeObserver::new();
|
||||
unsafe {
|
||||
obs_svc
|
||||
.AddObserver(observer.coerce(), c_str!("profile-after-change").as_ptr(), false)
|
||||
.to_result()?
|
||||
};
|
||||
Ok(())
|
||||
})()
|
||||
.unwrap_or_else(|err| error!("error observing profile change: {}", err));
|
||||
}
|
||||
|
||||
fn in_safe_mode() -> XULStoreResult<bool> {
|
||||
let app_info_svc = xpcom::services::get_AppInfoService().ok_or(XULStoreError::Unavailable)?;
|
||||
let mut in_safe_mode = false;
|
||||
unsafe {
|
||||
app_info_svc.GetInSafeMode(&mut in_safe_mode).to_result()?;
|
||||
}
|
||||
Ok(in_safe_mode)
|
||||
}
|
||||
|
||||
fn cache_data() -> XULStoreResult<XULStoreCache> {
|
||||
if in_safe_mode()? {
|
||||
return Ok(BTreeMap::new());
|
||||
}
|
||||
|
||||
let db = get_database()?;
|
||||
maybe_migrate_data(&db.env, db.store);
|
||||
|
||||
let reader = db.env.read()?;
|
||||
let mut all = XULStoreCache::default();
|
||||
let iterator = db.store.iter_start(&reader)?;
|
||||
|
||||
for result in iterator {
|
||||
let (key, value): (&str, String) = match result {
|
||||
Ok((key, value)) => {
|
||||
assert!(value.is_some(), "iterated key has value");
|
||||
match (str::from_utf8(&key), unwrap_value(&value)) {
|
||||
(Ok(key), Ok(value)) => (key, value),
|
||||
(Err(err), _) => return Err(err.into()),
|
||||
(_, Err(err)) => return Err(err),
|
||||
}
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
let parts = key.split(SEPARATOR).collect::<Vec<&str>>();
|
||||
if parts.len() != 3 {
|
||||
return Err(XULStoreError::UnexpectedKey(key.to_string()));
|
||||
}
|
||||
let (doc, id, attr) = (
|
||||
parts[0].to_string(),
|
||||
parts[1].to_string(),
|
||||
parts[2].to_string(),
|
||||
);
|
||||
|
||||
all.entry(doc)
|
||||
.or_default()
|
||||
.entry(id)
|
||||
.or_default()
|
||||
.entry(attr)
|
||||
.or_insert(value);
|
||||
}
|
||||
|
||||
Ok(all)
|
||||
}
|
||||
|
||||
fn maybe_migrate_data(env: &Rkv, store: SingleStore) {
|
||||
// Failure to migrate data isn't fatal, so we don't return a result.
|
||||
// But we use a closure returning a result to enable use of the ? operator.
|
||||
(|| -> XULStoreResult<()> {
|
||||
let mut old_datastore = PROFILE_DIR
|
||||
.lock()?
|
||||
.clone()
|
||||
.ok_or(XULStoreError::Unavailable)?;
|
||||
old_datastore.push("xulstore.json");
|
||||
if !old_datastore.exists() {
|
||||
debug!("old datastore doesn't exist: {:?}", old_datastore);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let file = File::open(old_datastore.clone())?;
|
||||
let json: XULStoreCache = serde_json::from_reader(file)?;
|
||||
|
||||
let mut writer = env.write()?;
|
||||
|
||||
for (doc, ids) in json {
|
||||
for (id, attrs) in ids {
|
||||
for (attr, value) in attrs {
|
||||
let key = make_key(&doc, &id, &attr);
|
||||
store.put(&mut writer, &key, &Value::Str(&value))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writer.commit()?;
|
||||
|
||||
remove_file(old_datastore)?;
|
||||
|
||||
Ok(())
|
||||
})()
|
||||
.unwrap_or_else(|err| error!("error migrating data: {}", err));
|
||||
}
|
||||
|
||||
fn unwrap_value(value: &Option<Value>) -> XULStoreResult<String> {
|
||||
match value {
|
||||
Some(Value::Str(val)) => Ok(val.to_string()),
|
||||
|
||||
// Per the XULStore API, return an empty string if the value
|
||||
// isn't found.
|
||||
None => Ok(String::new()),
|
||||
|
||||
// This should never happen, but it could happen in theory
|
||||
// if someone writes a different kind of value into the store
|
||||
// using a more general API (kvstore, rkv, LMDB).
|
||||
Some(_) => Err(XULStoreError::UnexpectedValue),
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@
|
||||
<script>
|
||||
<![CDATA[
|
||||
|
||||
const {XULStore} = ChromeUtils.import("resource://gre/modules/XULStore.jsm");
|
||||
let XULStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
|
||||
let URI = "chrome://mochitests/content/chrome/toolkit/components/xulstore/tests/chrome/window_persistence.xul";
|
||||
|
||||
function opened()
|
||||
|
@ -1,7 +0,0 @@
|
||||
[package]
|
||||
name = "xulstore-gtest"
|
||||
version = "0.1.0"
|
||||
authors = ["nobody@mozilla.org"]
|
||||
|
||||
[lib]
|
||||
path = "test.rs"
|
@ -1,141 +0,0 @@
|
||||
#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(NS_LITERAL_STRING("SetGetValue"));
|
||||
nsAutoString id(NS_LITERAL_STRING("foo"));
|
||||
nsAutoString attr(NS_LITERAL_STRING("bar"));
|
||||
nsAutoString value;
|
||||
|
||||
EXPECT_EQ(GetValue(doc, id, attr, value), NS_OK);
|
||||
EXPECT_TRUE(value.EqualsASCII(""));
|
||||
|
||||
{
|
||||
nsAutoString value(NS_LITERAL_STRING("baz"));
|
||||
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(NS_LITERAL_STRING("HasValue"));
|
||||
nsAutoString id(NS_LITERAL_STRING("foo"));
|
||||
nsAutoString attr(NS_LITERAL_STRING("bar"));
|
||||
bool hasValue = true;
|
||||
EXPECT_EQ(HasValue(doc, id, attr, hasValue), NS_OK);
|
||||
EXPECT_FALSE(hasValue);
|
||||
nsAutoString value(NS_LITERAL_STRING("baz"));
|
||||
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(NS_LITERAL_STRING("RemoveValue"));
|
||||
nsAutoString id(NS_LITERAL_STRING("foo"));
|
||||
nsAutoString attr(NS_LITERAL_STRING("bar"));
|
||||
nsAutoString value(NS_LITERAL_STRING("baz"));
|
||||
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(NS_LITERAL_STRING("idIterDoc"));
|
||||
nsAutoString id1(NS_LITERAL_STRING("id1"));
|
||||
nsAutoString id2(NS_LITERAL_STRING("id2"));
|
||||
nsAutoString id3(NS_LITERAL_STRING("id3"));
|
||||
nsAutoString attr(NS_LITERAL_STRING("attr"));
|
||||
nsAutoString value(NS_LITERAL_STRING("value"));
|
||||
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(NS_LITERAL_STRING("otherDoc"));
|
||||
nsAutoString otherID(NS_LITERAL_STRING("otherID"));
|
||||
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(NS_LITERAL_STRING("attrIterDoc"));
|
||||
nsAutoString id(NS_LITERAL_STRING("id"));
|
||||
nsAutoString attr1(NS_LITERAL_STRING("attr1"));
|
||||
nsAutoString attr2(NS_LITERAL_STRING("attr2"));
|
||||
nsAutoString attr3(NS_LITERAL_STRING("attr3"));
|
||||
nsAutoString value(NS_LITERAL_STRING("value"));
|
||||
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(NS_LITERAL_STRING("otherID"));
|
||||
nsAutoString otherAttr(NS_LITERAL_STRING("otherAttr"));
|
||||
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());
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
# -*- 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'
|
||||
|
||||
if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
|
||||
CXXFLAGS += ['-Wno-error=shadow']
|
@ -1,71 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
function run_test() {
|
||||
do_get_profile();
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(async function test_create_old_datastore() {
|
||||
const path = OS.Path.join(OS.Constants.Path.profileDir, "xulstore.json");
|
||||
|
||||
const xulstoreJSON = {
|
||||
doc1: {
|
||||
id1: {
|
||||
attr1: "value1",
|
||||
},
|
||||
},
|
||||
doc2: {
|
||||
id1: {
|
||||
attr2: "value2",
|
||||
},
|
||||
id2: {
|
||||
attr1: "value1",
|
||||
attr2: "value2",
|
||||
attr3: "value3",
|
||||
},
|
||||
id3: {},
|
||||
},
|
||||
doc3: {},
|
||||
};
|
||||
|
||||
await OS.File.writeAtomic(path, JSON.stringify(xulstoreJSON));
|
||||
});
|
||||
|
||||
add_task(async function test_get_values() {
|
||||
// We wait until now to import XULStore.jsm 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"), "");
|
||||
});
|
@ -1,43 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
function run_test() {
|
||||
do_get_profile();
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(async function test_create_old_datastore() {
|
||||
const path = OS.Path.join(OS.Constants.Path.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 OS.File.writeAtomic(path, JSON.stringify(xulstoreJSON));
|
||||
});
|
||||
|
||||
add_task(async function test_get_values() {
|
||||
// We wait until now to import XULStore.jsm 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"), "");
|
||||
});
|
@ -1,28 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
function run_test() {
|
||||
do_get_profile();
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(async function test_create_old_datastore() {
|
||||
const path = OS.Path.join(OS.Constants.Path.profileDir, "xulstore.json");
|
||||
|
||||
// Invalid JSON: it's missing the final closing brace.
|
||||
const xulstoreJSON = '{ doc: { id: { attr: "value" } }';
|
||||
|
||||
await OS.File.writeAtomic(path, xulstoreJSON);
|
||||
});
|
||||
|
||||
add_task(async function test_get_value() {
|
||||
// We wait until now to import XULStore.jsm 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"), "");
|
||||
});
|
@ -1,43 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
|
||||
const {FileUtils} = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
|
||||
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
add_task(async function test_get_values() {
|
||||
// Import XULStore.jsm 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 OS.File.
|
||||
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");
|
||||
});
|
@ -2,7 +2,3 @@
|
||||
skip-if = toolkit == 'android'
|
||||
|
||||
[test_XULStore.js]
|
||||
[test_XULStore_migration.js]
|
||||
[test_XULStore_migration_fail_invalid_json.js]
|
||||
[test_XULStore_migration_fail_invalid_data.js]
|
||||
[test_XULStore_migration_profile_change.js]
|
||||
|
@ -27,7 +27,6 @@ audioipc-server = { path = "../../../../media/audioipc/server", optional = true
|
||||
u2fhid = { path = "../../../../dom/webauthn/u2f-hid-rs" }
|
||||
gkrust_utils = { path = "../../../../xpcom/rust/gkrust_utils" }
|
||||
rsdparsa_capi = { path = "../../../../media/webrtc/signaling/src/sdp/rsdparsa_capi" }
|
||||
xulstore = { path = "../../../components/xulstore" }
|
||||
# We have these to enforce common feature sets for said crates.
|
||||
log = {version = "0.4", features = ["release_max_level_info"]}
|
||||
env_logger = {version = "0.5", default-features = false} # disable `regex` to reduce code size
|
||||
|
@ -34,7 +34,6 @@ extern crate log;
|
||||
extern crate cert_storage;
|
||||
extern crate cosec;
|
||||
extern crate rsdparsa_capi;
|
||||
extern crate xulstore;
|
||||
#[cfg(feature = "spidermonkey_rust")]
|
||||
extern crate jsrust_shared;
|
||||
#[cfg(feature = "bitsdownload")]
|
||||
|
@ -51,11 +51,6 @@ if (AppConstants.MOZ_CRASHREPORTER) {
|
||||
});
|
||||
}
|
||||
|
||||
XPCOMUtils.defineLazyGetter(Services, "xulStore", () => {
|
||||
const {XULStore} = ChromeUtils.import("resource://gre/modules/XULStore.jsm");
|
||||
return XULStore;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(Services, "io", () => {
|
||||
return Cc["@mozilla.org/network/io-service;1"]
|
||||
.getService(Ci.nsIIOService)
|
||||
@ -104,6 +99,7 @@ var initTable = {
|
||||
netUtils: ["@mozilla.org/network/util;1", "nsINetUtil"],
|
||||
loadContextInfo: ["@mozilla.org/load-context-info-factory;1", "nsILoadContextInfoFactory"],
|
||||
qms: ["@mozilla.org/dom/quota-manager-service;1", "nsIQuotaManagerService"],
|
||||
xulStore: ["@mozilla.org/xul/xulstore;1", "nsIXULStore"],
|
||||
};
|
||||
|
||||
if (AppConstants.platform == "android") {
|
||||
|
@ -52,9 +52,6 @@ service('URIFixup', 'nsIURIFixup',
|
||||
"@mozilla.org/docshell/urifixup;1")
|
||||
service('Bits', 'nsIBits',
|
||||
"@mozilla.org/bits;1")
|
||||
# NB: this should also expose nsIXULAppInfo, as does Services.jsm.
|
||||
service('AppInfoService', 'nsIXULRuntime',
|
||||
"@mozilla.org/xre/app-info;1")
|
||||
|
||||
# The definition file needs access to the definitions of the particular
|
||||
# interfaces. If you add a new interface here, make sure the necessary includes
|
||||
@ -88,7 +85,6 @@ CPP_INCLUDES = """
|
||||
#include "nsIGfxInfo.h"
|
||||
#include "nsIURIFixup.h"
|
||||
#include "nsIBits.h"
|
||||
#include "nsIXULRuntime.h"
|
||||
"""
|
||||
|
||||
#####
|
||||
|
@ -52,18 +52,6 @@ impl fmt::Debug for nsresult {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> From<Result<T, E>> for nsresult
|
||||
where
|
||||
E: Into<nsresult>,
|
||||
{
|
||||
fn from(result: Result<T, E>) -> nsresult {
|
||||
match result {
|
||||
Ok(_) => NS_OK,
|
||||
Err(e) => e.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for nsresult {}
|
||||
|
||||
extern "C" {
|
||||
|
@ -55,7 +55,6 @@
|
||||
#include "mozilla/AutoRestore.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/XULStore.h"
|
||||
#include "mozilla/dom/BarProps.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "mozilla/dom/Event.h"
|
||||
@ -1604,8 +1603,15 @@ nsresult nsXULWindow::GetPersistentValue(const nsAtom* aAttr,
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ConvertUTF8toUTF16 uri(utf8uri);
|
||||
|
||||
nsDependentAtomString attrString(aAttr);
|
||||
rv = XULStore::GetValue(uri, windowElementId, attrString, aValue);
|
||||
if (!mLocalStore) {
|
||||
mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
|
||||
if (NS_WARN_IF(!mLocalStore)) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
}
|
||||
|
||||
rv = mLocalStore->GetValue(uri, windowElementId, nsDependentAtomString(aAttr),
|
||||
aValue);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
@ -1655,9 +1661,15 @@ nsresult nsXULWindow::SetPersistentValue(const nsAtom* aAttr,
|
||||
maybeConvertedValue);
|
||||
}
|
||||
|
||||
nsDependentAtomString attrString(aAttr);
|
||||
return XULStore::SetValue(uri, windowElementId, attrString,
|
||||
maybeConvertedValue);
|
||||
if (!mLocalStore) {
|
||||
mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
|
||||
if (NS_WARN_IF(!mLocalStore)) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
}
|
||||
|
||||
return mLocalStore->SetValue(
|
||||
uri, windowElementId, nsDependentAtomString(aAttr), maybeConvertedValue);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsXULWindow::SavePersistentAttributes() {
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include "nsIXULBrowserWindow.h"
|
||||
#include "nsIWidgetListener.h"
|
||||
#include "nsITabParent.h"
|
||||
#include "nsIXULStore.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
@ -194,6 +195,7 @@ class nsXULWindow : public nsIBaseWindow,
|
||||
GetPrimaryTabParentSize(int32_t* aWidth, int32_t* aHeight);
|
||||
nsresult GetPrimaryContentShellSize(int32_t* aWidth, int32_t* aHeight);
|
||||
nsresult SetPrimaryTabParentSize(int32_t aWidth, int32_t aHeight);
|
||||
nsCOMPtr<nsIXULStore> mLocalStore;
|
||||
};
|
||||
|
||||
NS_DEFINE_STATIC_IID_ACCESSOR(nsXULWindow, NS_XULWINDOW_IMPL_CID)
|
||||
|
Loading…
Reference in New Issue
Block a user