Bug 1460811 - migrate XULStore to rkv r=bgrins,lina
Differential Revision: https://phabricator.services.mozilla.com/D25355
This commit is contained in:
19
Cargo.lock
generated
19
Cargo.lock
generated
@@ -1248,6 +1248,7 @@ dependencies = [
|
|||||||
"u2fhid 0.2.3",
|
"u2fhid 0.2.3",
|
||||||
"webrender_bindings 0.1.0",
|
"webrender_bindings 0.1.0",
|
||||||
"xpcom 0.1.0",
|
"xpcom 0.1.0",
|
||||||
|
"xulstore 0.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3432,6 +3433,24 @@ dependencies = [
|
|||||||
"syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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]]
|
[[package]]
|
||||||
name = "yaml-rust"
|
name = "yaml-rust"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
|
|||||||
@@ -5,8 +5,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#include "XULPersist.h"
|
#include "XULPersist.h"
|
||||||
|
#include "mozilla/XULStore.h"
|
||||||
#include "nsIXULStore.h"
|
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
namespace dom {
|
namespace dom {
|
||||||
@@ -80,13 +79,6 @@ void XULPersist::Persist(Element* aElement, int32_t aNameSpaceID,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mLocalStore) {
|
|
||||||
mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
|
|
||||||
if (NS_WARN_IF(!mLocalStore)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nsAutoString id;
|
nsAutoString id;
|
||||||
|
|
||||||
aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
|
aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
|
||||||
@@ -103,13 +95,14 @@ void XULPersist::Persist(Element* aElement, int32_t aNameSpaceID,
|
|||||||
NS_ConvertUTF8toUTF16 uri(utf8uri);
|
NS_ConvertUTF8toUTF16 uri(utf8uri);
|
||||||
|
|
||||||
bool hasAttr;
|
bool hasAttr;
|
||||||
rv = mLocalStore->HasValue(uri, id, attrstr, &hasAttr);
|
rv = XULStore::HasValue(uri, id, attrstr, hasAttr);
|
||||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasAttr && valuestr.IsEmpty()) {
|
if (hasAttr && valuestr.IsEmpty()) {
|
||||||
mLocalStore->RemoveValue(uri, id, attrstr);
|
rv = XULStore::RemoveValue(uri, id, attrstr);
|
||||||
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "value removed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +114,8 @@ void XULPersist::Persist(Element* aElement, int32_t aNameSpaceID,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mLocalStore->SetValue(uri, id, attrstr, valuestr);
|
rv = XULStore::SetValue(uri, id, attrstr, valuestr);
|
||||||
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "value set");
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult XULPersist::ApplyPersistentAttributes() {
|
nsresult XULPersist::ApplyPersistentAttributes() {
|
||||||
@@ -135,13 +129,6 @@ nsresult XULPersist::ApplyPersistentAttributes() {
|
|||||||
|
|
||||||
// Add all of the 'persisted' attributes into the content
|
// Add all of the 'persisted' attributes into the content
|
||||||
// model.
|
// model.
|
||||||
if (!mLocalStore) {
|
|
||||||
mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
|
|
||||||
if (NS_WARN_IF(!mLocalStore)) {
|
|
||||||
return NS_ERROR_NOT_INITIALIZED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ApplyPersistentAttributesInternal();
|
ApplyPersistentAttributesInternal();
|
||||||
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
@@ -158,21 +145,18 @@ nsresult XULPersist::ApplyPersistentAttributesInternal() {
|
|||||||
NS_ConvertUTF8toUTF16 uri(utf8uri);
|
NS_ConvertUTF8toUTF16 uri(utf8uri);
|
||||||
|
|
||||||
// Get a list of element IDs for which persisted values are available
|
// Get a list of element IDs for which persisted values are available
|
||||||
nsCOMPtr<nsIStringEnumerator> ids;
|
UniquePtr<XULStoreIterator> ids;
|
||||||
rv = mLocalStore->GetIDsEnumerator(uri, getter_AddRefs(ids));
|
rv = XULStore::GetIDs(uri, ids);
|
||||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (1) {
|
while (ids->HasMore()) {
|
||||||
bool hasmore = false;
|
|
||||||
ids->HasMore(&hasmore);
|
|
||||||
if (!hasmore) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsAutoString id;
|
nsAutoString id;
|
||||||
ids->GetNext(id);
|
rv = ids->GetNext(&id);
|
||||||
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
// We want to hold strong refs to the elements while applying
|
// We want to hold strong refs to the elements while applying
|
||||||
// persistent attributes, just in case.
|
// persistent attributes, just in case.
|
||||||
@@ -205,24 +189,21 @@ nsresult XULPersist::ApplyPersistentAttributesToElements(
|
|||||||
NS_ConvertUTF8toUTF16 uri(utf8uri);
|
NS_ConvertUTF8toUTF16 uri(utf8uri);
|
||||||
|
|
||||||
// Get a list of attributes for which persisted values are available
|
// Get a list of attributes for which persisted values are available
|
||||||
nsCOMPtr<nsIStringEnumerator> attrs;
|
UniquePtr<XULStoreIterator> attrs;
|
||||||
rv = mLocalStore->GetAttributeEnumerator(uri, aID, getter_AddRefs(attrs));
|
rv = XULStore::GetAttrs(uri, aID, attrs);
|
||||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (1) {
|
while (attrs->HasMore()) {
|
||||||
bool hasmore = PR_FALSE;
|
nsAutoString attrstr;
|
||||||
attrs->HasMore(&hasmore);
|
rv = attrs->GetNext(&attrstr);
|
||||||
if (!hasmore) {
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
break;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsAutoString attrstr;
|
|
||||||
attrs->GetNext(attrstr);
|
|
||||||
|
|
||||||
nsAutoString value;
|
nsAutoString value;
|
||||||
rv = mLocalStore->GetValue(uri, aID, attrstr, value);
|
rv = XULStore::GetValue(uri, aID, attrstr, value);
|
||||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,6 @@
|
|||||||
#ifndef mozilla_dom_XULPersist_h
|
#ifndef mozilla_dom_XULPersist_h
|
||||||
#define mozilla_dom_XULPersist_h
|
#define mozilla_dom_XULPersist_h
|
||||||
|
|
||||||
class nsIXULStore;
|
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
namespace dom {
|
namespace dom {
|
||||||
|
|
||||||
@@ -33,7 +31,6 @@ class XULPersist final : public nsStubDocumentObserver {
|
|||||||
nsresult ApplyPersistentAttributesToElements(const nsAString& aID,
|
nsresult ApplyPersistentAttributesToElements(const nsAString& aID,
|
||||||
nsCOMArray<Element>& aElements);
|
nsCOMArray<Element>& aElements);
|
||||||
|
|
||||||
nsCOMPtr<nsIXULStore> mLocalStore;
|
|
||||||
// A weak pointer to our document. Nulled out by DropDocumentReference.
|
// A weak pointer to our document. Nulled out by DropDocumentReference.
|
||||||
Document* MOZ_NON_OWNING_REF mDocument;
|
Document* MOZ_NON_OWNING_REF mDocument;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -566,16 +566,16 @@
|
|||||||
"minbytes": 6000,
|
"minbytes": 6000,
|
||||||
"maxbytes": 6000
|
"maxbytes": 6000
|
||||||
},
|
},
|
||||||
"{profile}\\xulstore.json": {
|
|
||||||
"mincount": 0,
|
|
||||||
"maxcount": 0,
|
|
||||||
"minbytes": 0,
|
|
||||||
"maxbytes": 702
|
|
||||||
},
|
|
||||||
"{talos}\\talos\\tests\\{tp5n_files}": {
|
"{talos}\\talos\\tests\\{tp5n_files}": {
|
||||||
"mincount": 0,
|
"mincount": 0,
|
||||||
"maxcount": 2,
|
"maxcount": 2,
|
||||||
"minbytes": 0,
|
"minbytes": 0,
|
||||||
"maxbytes": 16384
|
"maxbytes": 16384
|
||||||
|
},
|
||||||
|
"{profile}\\xulstore\\data.mdb": {
|
||||||
|
"mincount": 0,
|
||||||
|
"maxcount": 4,
|
||||||
|
"minbytes": 0,
|
||||||
|
"maxbytes": 608
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ DIRS += [
|
|||||||
'windowcreator',
|
'windowcreator',
|
||||||
'windowwatcher',
|
'windowwatcher',
|
||||||
'workerloader',
|
'workerloader',
|
||||||
'xulstore'
|
'xulstore',
|
||||||
]
|
]
|
||||||
|
|
||||||
if CONFIG['MOZ_BUILD_APP'] != 'mobile/android':
|
if CONFIG['MOZ_BUILD_APP'] != 'mobile/android':
|
||||||
|
|||||||
25
toolkit/components/xulstore/Cargo.toml
Normal file
25
toolkit/components/xulstore/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
[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"]
|
||||||
107
toolkit/components/xulstore/XULStore.cpp
Normal file
107
toolkit/components/xulstore/XULStore.cpp
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/* 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
|
||||||
55
toolkit/components/xulstore/XULStore.h
Normal file
55
toolkit/components/xulstore/XULStore.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/* 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,127 +2,45 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
// Enables logging and shorter save intervals.
|
"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.
|
||||||
const debugMode = false;
|
const debugMode = false;
|
||||||
|
|
||||||
// Delay when a change is made to when the file is saved.
|
// Internal function for logging debug messages to the Error Console window
|
||||||
// 30 seconds normally, or 3 seconds for testing
|
function log(message) {
|
||||||
const WRITE_DELAY_MS = (debugMode ? 3 : 30) * 1000;
|
if (!debugMode)
|
||||||
|
return;
|
||||||
const XULSTORE_CID = Components.ID("{6f46b6f4-c8b1-4bd4-a4fa-9ebbed0753ea}");
|
console.log("XULStore: " + message);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
XULStore.prototype = {
|
const XULStore = {
|
||||||
classID: XULSTORE_CID,
|
setValue: xulStore.setValue,
|
||||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver, Ci.nsIXULStore,
|
hasValue: xulStore.hasValue,
|
||||||
Ci.nsISupportsWeakReference]),
|
getValue: xulStore.getValue,
|
||||||
_xpcom_factory: XPCOMUtils.generateSingletonFactory(XULStore),
|
removeValue: xulStore.removeValue,
|
||||||
|
removeDocument: xulStore.removeDocument,
|
||||||
|
|
||||||
/* ---------- private members ---------- */
|
/**
|
||||||
|
* Sets a value for a specified node's attribute, except in
|
||||||
/*
|
* the case below (following the original XULDocument::persist):
|
||||||
* The format of _data is _data[docuri][elementid][attribute]. For example:
|
* If the value is empty and if calling `hasValue` with the node's
|
||||||
* {
|
* document and ID and `attr` would return true, then the
|
||||||
* "chrome://blah/foo.xul" : {
|
* value instead gets removed from the store (see Bug 1476680).
|
||||||
* "main-window" : { aaa : 1, bbb : "c" },
|
|
||||||
* "barColumn" : { ddd : 9, eee : "f" },
|
|
||||||
* },
|
|
||||||
*
|
*
|
||||||
* "chrome://foopy/b.xul" : { ... },
|
* @param node - DOM node
|
||||||
* ...
|
* @param attr - attribute to store
|
||||||
* }
|
|
||||||
*/
|
*/
|
||||||
_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) {
|
persist(node, attr) {
|
||||||
if (!node.id) {
|
if (!node.id) {
|
||||||
throw new Error("Node without ID passed into persist()");
|
throw new Error("Node without ID passed into persist()");
|
||||||
@@ -132,7 +50,7 @@ XULStore.prototype = {
|
|||||||
const value = node.getAttribute(attr);
|
const value = node.getAttribute(attr);
|
||||||
|
|
||||||
if (node.localName == "window") {
|
if (node.localName == "window") {
|
||||||
this.log("Persisting attributes to windows is handled by nsXULWindow.");
|
log("Persisting attributes to windows is handled by nsXULWindow.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,169 +58,38 @@ XULStore.prototype = {
|
|||||||
// any time there's an empty attribute it gets removed from the
|
// any time there's an empty attribute it gets removed from the
|
||||||
// store. Since this is copying behavior from document.persist,
|
// store. Since this is copying behavior from document.persist,
|
||||||
// callers would need to be updated with that change.
|
// callers would need to be updated with that change.
|
||||||
if (!value && this.hasValue(uri, node.id, attr)) {
|
if (!value && xulStore.hasValue(uri, node.id, attr)) {
|
||||||
this.removeValue(uri, node.id, attr);
|
xulStore.removeValue(uri, node.id, attr);
|
||||||
} else {
|
} else {
|
||||||
this.setValue(uri, node.id, attr, value);
|
xulStore.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) {
|
getIDsEnumerator(docURI) {
|
||||||
this.log("Getting ID enumerator for doc=" + docURI);
|
return new XULStoreEnumerator(xulStore.getIDsEnumerator(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) {
|
getAttributeEnumerator(docURI, id) {
|
||||||
this.log("Getting attribute enumerator for id=" + id + ", doc=" + docURI);
|
return new XULStoreEnumerator(xulStore.getAttributeEnumerator(docURI, id));
|
||||||
|
|
||||||
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) {
|
class XULStoreEnumerator {
|
||||||
this._items = items;
|
constructor(enumerator) {
|
||||||
}
|
this.enumerator = enumerator;
|
||||||
|
}
|
||||||
|
|
||||||
nsStringEnumerator.prototype = {
|
|
||||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIStringEnumerator]),
|
|
||||||
_nextIndex: 0,
|
|
||||||
[Symbol.iterator]() {
|
|
||||||
return this._items.values();
|
|
||||||
},
|
|
||||||
hasMore() {
|
hasMore() {
|
||||||
return this._nextIndex < this._items.length;
|
return this.enumerator.hasMore();
|
||||||
},
|
}
|
||||||
getNext() {
|
|
||||||
if (!this.hasMore())
|
|
||||||
throw Cr.NS_ERROR_NOT_AVAILABLE;
|
|
||||||
return this._items[this._nextIndex++];
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var EXPORTED_SYMBOLS = ["XULStore"];
|
getNext() {
|
||||||
|
return this.enumerator.getNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
* [Symbol.iterator]() {
|
||||||
|
while (this.enumerator.hasMore()) {
|
||||||
|
yield (this.enumerator.getNext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,9 +6,11 @@
|
|||||||
|
|
||||||
Classes = [
|
Classes = [
|
||||||
{
|
{
|
||||||
'cid': '{6f46b6f4-c8b1-4bd4-a4fa-9ebbed0753ea}',
|
'cid': '{be70bf11-0c28-4a02-a38c-0148538d42cf}',
|
||||||
'contract_ids': ['@mozilla.org/xul/xulstore;1'],
|
'contract_ids': ['@mozilla.org/xul/xulstore;1'],
|
||||||
'jsm': 'resource://gre/modules/XULStore.jsm',
|
'type': 'nsIXULStore',
|
||||||
'constructor': 'XULStore',
|
'headers': ['mozilla/XULStore.h'],
|
||||||
|
'singleton': True,
|
||||||
|
'constructor': 'mozilla::XULStore::GetService',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -14,12 +14,26 @@ XPIDL_SOURCES += [
|
|||||||
'nsIXULStore.idl',
|
'nsIXULStore.idl',
|
||||||
]
|
]
|
||||||
|
|
||||||
XPIDL_MODULE = 'toolkit_xulstore'
|
TEST_DIRS += [
|
||||||
|
'tests/gtest',
|
||||||
|
]
|
||||||
|
|
||||||
|
EXPORTS.mozilla += [
|
||||||
|
'XULStore.h',
|
||||||
|
]
|
||||||
|
|
||||||
EXTRA_JS_MODULES += [
|
EXTRA_JS_MODULES += [
|
||||||
'XULStore.jsm',
|
'XULStore.jsm',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
XPIDL_MODULE = 'xulstore'
|
||||||
|
|
||||||
XPCOM_MANIFESTS += [
|
XPCOM_MANIFESTS += [
|
||||||
'components.conf',
|
'components.conf',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
UNIFIED_SOURCES += [
|
||||||
|
'XULStore.cpp',
|
||||||
|
]
|
||||||
|
|
||||||
|
FINAL_LIBRARY = 'xul'
|
||||||
|
|||||||
@@ -5,30 +5,19 @@
|
|||||||
#include "nsISupports.idl"
|
#include "nsISupports.idl"
|
||||||
|
|
||||||
interface nsIStringEnumerator;
|
interface nsIStringEnumerator;
|
||||||
webidl Node;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The XUL store is used to store information related to a XUL document/application.
|
* 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
|
* 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.
|
* 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
|
* 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.
|
||||||
*/
|
*/
|
||||||
[scriptable, uuid(987c4b35-c426-4dd7-ad49-3c9fa4c65d20)]
|
[scriptable, uuid(987c4b35-c426-4dd7-ad49-3c9fa4c65d20)]
|
||||||
interface nsIXULStore: nsISupports
|
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.
|
* Sets a value in the store.
|
||||||
*
|
*
|
||||||
|
|||||||
112
toolkit/components/xulstore/src/error.rs
Normal file
112
toolkit/components/xulstore/src/error.rs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
/* 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
339
toolkit/components/xulstore/src/ffi.rs
Normal file
339
toolkit/components/xulstore/src/ffi.rs
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
/* 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, persist::clear_on_shutdown, 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 {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(xpcom)]
|
||||||
|
#[xpimplements(nsIObserver)]
|
||||||
|
#[refcnt = "nonatomic"]
|
||||||
|
pub(crate) struct InitXpcomShutdownObserver {}
|
||||||
|
impl XpcomShutdownObserver {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
unsafe fn Observe(
|
||||||
|
&self,
|
||||||
|
_subject: *const nsISupports,
|
||||||
|
_topic: *const c_char,
|
||||||
|
_data: *const i16,
|
||||||
|
) -> nsresult {
|
||||||
|
clear_on_shutdown();
|
||||||
|
NS_OK
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn new() -> RefPtr<XpcomShutdownObserver> {
|
||||||
|
XpcomShutdownObserver::allocate(InitXpcomShutdownObserver {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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));
|
||||||
|
}
|
||||||
24
toolkit/components/xulstore/src/iter.rs
Normal file
24
toolkit/components/xulstore/src/iter.rs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/* 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)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
219
toolkit/components/xulstore/src/lib.rs
Normal file
219
toolkit/components/xulstore/src/lib.rs
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
/* 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().cloned().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().cloned().collect();
|
||||||
|
Ok(XULStoreIterator::new(attrs.into_iter()))
|
||||||
|
}
|
||||||
|
None => Ok(XULStoreIterator::new(vec![].into_iter())),
|
||||||
|
},
|
||||||
|
None => Ok(XULStoreIterator::new(vec![].into_iter())),
|
||||||
|
}
|
||||||
|
}
|
||||||
188
toolkit/components/xulstore/src/persist.rs
Normal file
188
toolkit/components/xulstore/src/persist.rs
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
/* 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::XpcomShutdownObserver,
|
||||||
|
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: Mutex<Option<ThreadBoundRefPtr<nsIThread>>> = {
|
||||||
|
let thread: RefPtr<nsIThread> = match create_thread("XULStore") {
|
||||||
|
Ok(thread) => thread,
|
||||||
|
Err(err) => {
|
||||||
|
error!("error creating XULStore thread: {}", err);
|
||||||
|
return Mutex::new(None);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Observe XPCOM shutdown so we can clear the thread and thus not
|
||||||
|
// "leak" it (from the perspective of the leak checker).
|
||||||
|
observe_xpcom_shutdown();
|
||||||
|
|
||||||
|
Mutex::new(Some(ThreadBoundRefPtr::new(thread)))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn observe_xpcom_shutdown() {
|
||||||
|
(|| -> XULStoreResult<()> {
|
||||||
|
let obs_svc = xpcom::services::get_ObserverService().ok_or(XULStoreError::Unavailable)?;
|
||||||
|
let observer = XpcomShutdownObserver::new();
|
||||||
|
unsafe {
|
||||||
|
obs_svc
|
||||||
|
.AddObserver(observer.coerce(), c_str!("xpcom-shutdown").as_ptr(), false)
|
||||||
|
.to_result()?
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
})()
|
||||||
|
.unwrap_or_else(|err| error!("error observing XPCOM shutdown: {}", err));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clear_on_shutdown() {
|
||||||
|
(|| -> XULStoreResult<()> {
|
||||||
|
THREAD.lock()?.take();
|
||||||
|
Ok(())
|
||||||
|
})()
|
||||||
|
.unwrap_or_else(|err| error!("error clearing thread: {}", err));
|
||||||
|
}
|
||||||
|
|
||||||
|
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_guard = THREAD.lock()?;
|
||||||
|
let thread = thread_guard
|
||||||
|
.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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
249
toolkit/components/xulstore/src/statics.rs
Normal file
249
toolkit/components/xulstore/src/statics.rs
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
/* 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> {
|
||||||
|
let db = get_database()?;
|
||||||
|
maybe_migrate_data(&db.env, db.store);
|
||||||
|
|
||||||
|
let mut all = XULStoreCache::default();
|
||||||
|
if in_safe_mode()? {
|
||||||
|
return Ok(all);
|
||||||
|
}
|
||||||
|
|
||||||
|
let reader = db.env.read()?;
|
||||||
|
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>
|
<script>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
|
|
||||||
let XULStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
|
const {XULStore} = ChromeUtils.import("resource://gre/modules/XULStore.jsm");
|
||||||
let URI = "chrome://mochitests/content/chrome/toolkit/components/xulstore/tests/chrome/window_persistence.xul";
|
let URI = "chrome://mochitests/content/chrome/toolkit/components/xulstore/tests/chrome/window_persistence.xul";
|
||||||
|
|
||||||
function opened()
|
function opened()
|
||||||
|
|||||||
7
toolkit/components/xulstore/tests/gtest/Cargo.toml
Normal file
7
toolkit/components/xulstore/tests/gtest/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "xulstore-gtest"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["nobody@mozilla.org"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "test.rs"
|
||||||
141
toolkit/components/xulstore/tests/gtest/TestXULStore.cpp
Normal file
141
toolkit/components/xulstore/tests/gtest/TestXULStore.cpp
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "mozilla/XULStore.h"
|
||||||
|
#include "nsCOMPtr.h"
|
||||||
|
#include "nsString.h"
|
||||||
|
|
||||||
|
using mozilla::XULStoreIterator;
|
||||||
|
using mozilla::XULStore::GetAttrs;
|
||||||
|
using mozilla::XULStore::GetIDs;
|
||||||
|
using mozilla::XULStore::GetValue;
|
||||||
|
using mozilla::XULStore::HasValue;
|
||||||
|
using mozilla::XULStore::RemoveValue;
|
||||||
|
using mozilla::XULStore::SetValue;
|
||||||
|
|
||||||
|
TEST(XULStore, SetGetValue)
|
||||||
|
{
|
||||||
|
nsAutoString doc(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());
|
||||||
|
}
|
||||||
14
toolkit/components/xulstore/tests/gtest/moz.build
Normal file
14
toolkit/components/xulstore/tests/gtest/moz.build
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# -*- 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']
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
"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"), "");
|
||||||
|
});
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
"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"), "");
|
||||||
|
});
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
"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"), "");
|
||||||
|
});
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
"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,3 +2,7 @@
|
|||||||
skip-if = toolkit == 'android'
|
skip-if = toolkit == 'android'
|
||||||
|
|
||||||
[test_XULStore.js]
|
[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,6 +27,7 @@ audioipc-server = { path = "../../../../media/audioipc/server", optional = true
|
|||||||
u2fhid = { path = "../../../../dom/webauthn/u2f-hid-rs" }
|
u2fhid = { path = "../../../../dom/webauthn/u2f-hid-rs" }
|
||||||
gkrust_utils = { path = "../../../../xpcom/rust/gkrust_utils" }
|
gkrust_utils = { path = "../../../../xpcom/rust/gkrust_utils" }
|
||||||
rsdparsa_capi = { path = "../../../../media/webrtc/signaling/src/sdp/rsdparsa_capi" }
|
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.
|
# We have these to enforce common feature sets for said crates.
|
||||||
log = {version = "0.4", features = ["release_max_level_info"]}
|
log = {version = "0.4", features = ["release_max_level_info"]}
|
||||||
env_logger = {version = "0.5", default-features = false} # disable `regex` to reduce code size
|
env_logger = {version = "0.5", default-features = false} # disable `regex` to reduce code size
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ extern crate log;
|
|||||||
extern crate cert_storage;
|
extern crate cert_storage;
|
||||||
extern crate cosec;
|
extern crate cosec;
|
||||||
extern crate rsdparsa_capi;
|
extern crate rsdparsa_capi;
|
||||||
|
extern crate xulstore;
|
||||||
#[cfg(feature = "spidermonkey_rust")]
|
#[cfg(feature = "spidermonkey_rust")]
|
||||||
extern crate jsrust_shared;
|
extern crate jsrust_shared;
|
||||||
#[cfg(feature = "bitsdownload")]
|
#[cfg(feature = "bitsdownload")]
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ if (AppConstants.MOZ_CRASHREPORTER) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(Services, "xulStore", () => {
|
||||||
|
const {XULStore} = ChromeUtils.import("resource://gre/modules/XULStore.jsm");
|
||||||
|
return XULStore;
|
||||||
|
});
|
||||||
|
|
||||||
XPCOMUtils.defineLazyGetter(Services, "io", () => {
|
XPCOMUtils.defineLazyGetter(Services, "io", () => {
|
||||||
return Cc["@mozilla.org/network/io-service;1"]
|
return Cc["@mozilla.org/network/io-service;1"]
|
||||||
.getService(Ci.nsIIOService)
|
.getService(Ci.nsIIOService)
|
||||||
@@ -99,7 +104,6 @@ var initTable = {
|
|||||||
netUtils: ["@mozilla.org/network/util;1", "nsINetUtil"],
|
netUtils: ["@mozilla.org/network/util;1", "nsINetUtil"],
|
||||||
loadContextInfo: ["@mozilla.org/load-context-info-factory;1", "nsILoadContextInfoFactory"],
|
loadContextInfo: ["@mozilla.org/load-context-info-factory;1", "nsILoadContextInfoFactory"],
|
||||||
qms: ["@mozilla.org/dom/quota-manager-service;1", "nsIQuotaManagerService"],
|
qms: ["@mozilla.org/dom/quota-manager-service;1", "nsIQuotaManagerService"],
|
||||||
xulStore: ["@mozilla.org/xul/xulstore;1", "nsIXULStore"],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (AppConstants.platform == "android") {
|
if (AppConstants.platform == "android") {
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ service('URIFixup', 'nsIURIFixup',
|
|||||||
"@mozilla.org/docshell/urifixup;1")
|
"@mozilla.org/docshell/urifixup;1")
|
||||||
service('Bits', 'nsIBits',
|
service('Bits', 'nsIBits',
|
||||||
"@mozilla.org/bits;1")
|
"@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
|
# The definition file needs access to the definitions of the particular
|
||||||
# interfaces. If you add a new interface here, make sure the necessary includes
|
# interfaces. If you add a new interface here, make sure the necessary includes
|
||||||
@@ -85,6 +88,7 @@ CPP_INCLUDES = """
|
|||||||
#include "nsIGfxInfo.h"
|
#include "nsIGfxInfo.h"
|
||||||
#include "nsIURIFixup.h"
|
#include "nsIURIFixup.h"
|
||||||
#include "nsIBits.h"
|
#include "nsIBits.h"
|
||||||
|
#include "nsIXULRuntime.h"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#####
|
#####
|
||||||
|
|||||||
@@ -52,6 +52,18 @@ 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 {}
|
impl Error for nsresult {}
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|||||||
@@ -55,6 +55,7 @@
|
|||||||
#include "mozilla/AutoRestore.h"
|
#include "mozilla/AutoRestore.h"
|
||||||
#include "mozilla/Preferences.h"
|
#include "mozilla/Preferences.h"
|
||||||
#include "mozilla/Services.h"
|
#include "mozilla/Services.h"
|
||||||
|
#include "mozilla/XULStore.h"
|
||||||
#include "mozilla/dom/BarProps.h"
|
#include "mozilla/dom/BarProps.h"
|
||||||
#include "mozilla/dom/Element.h"
|
#include "mozilla/dom/Element.h"
|
||||||
#include "mozilla/dom/Event.h"
|
#include "mozilla/dom/Event.h"
|
||||||
@@ -1603,15 +1604,8 @@ nsresult nsXULWindow::GetPersistentValue(const nsAtom* aAttr,
|
|||||||
NS_ENSURE_SUCCESS(rv, rv);
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
NS_ConvertUTF8toUTF16 uri(utf8uri);
|
NS_ConvertUTF8toUTF16 uri(utf8uri);
|
||||||
|
|
||||||
if (!mLocalStore) {
|
nsDependentAtomString attrString(aAttr);
|
||||||
mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
|
rv = XULStore::GetValue(uri, windowElementId, attrString, aValue);
|
||||||
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))) {
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
@@ -1661,15 +1655,9 @@ nsresult nsXULWindow::SetPersistentValue(const nsAtom* aAttr,
|
|||||||
maybeConvertedValue);
|
maybeConvertedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mLocalStore) {
|
nsDependentAtomString attrString(aAttr);
|
||||||
mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
|
return XULStore::SetValue(uri, windowElementId, attrString,
|
||||||
if (NS_WARN_IF(!mLocalStore)) {
|
maybeConvertedValue);
|
||||||
return NS_ERROR_NOT_INITIALIZED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mLocalStore->SetValue(
|
|
||||||
uri, windowElementId, nsDependentAtomString(aAttr), maybeConvertedValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP nsXULWindow::SavePersistentAttributes() {
|
NS_IMETHODIMP nsXULWindow::SavePersistentAttributes() {
|
||||||
|
|||||||
@@ -33,7 +33,6 @@
|
|||||||
#include "nsIXULBrowserWindow.h"
|
#include "nsIXULBrowserWindow.h"
|
||||||
#include "nsIWidgetListener.h"
|
#include "nsIWidgetListener.h"
|
||||||
#include "nsITabParent.h"
|
#include "nsITabParent.h"
|
||||||
#include "nsIXULStore.h"
|
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
namespace dom {
|
namespace dom {
|
||||||
@@ -195,7 +194,6 @@ class nsXULWindow : public nsIBaseWindow,
|
|||||||
GetPrimaryTabParentSize(int32_t* aWidth, int32_t* aHeight);
|
GetPrimaryTabParentSize(int32_t* aWidth, int32_t* aHeight);
|
||||||
nsresult GetPrimaryContentShellSize(int32_t* aWidth, int32_t* aHeight);
|
nsresult GetPrimaryContentShellSize(int32_t* aWidth, int32_t* aHeight);
|
||||||
nsresult SetPrimaryTabParentSize(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)
|
NS_DEFINE_STATIC_IID_ACCESSOR(nsXULWindow, NS_XULWINDOW_IMPL_CID)
|
||||||
|
|||||||
Reference in New Issue
Block a user