Bug 1777973: Remove MOZ_NEW_XULSTORE implementation. r=NeilDeakin

Differential Revision: https://phabricator.services.mozilla.com/D166786
This commit is contained in:
Dave Townsend 2023-01-16 19:21:23 +00:00
parent ead5ce5973
commit 1135edff9e
31 changed files with 21 additions and 1807 deletions

20
Cargo.lock generated
View file

@ -2271,7 +2271,6 @@ dependencies = [
"wgpu_bindings",
"wpf-gpu-raster",
"xpcom",
"xulstore",
]
[[package]]
@ -6585,25 +6584,6 @@ dependencies = [
"syn",
]
[[package]]
name = "xulstore"
version = "0.1.0"
dependencies = [
"crossbeam-utils 0.8.14",
"cstr",
"libc",
"log",
"moz_task",
"nserror",
"nsstring",
"once_cell",
"rkv",
"serde_json",
"tempfile",
"thiserror",
"xpcom",
]
[[package]]
name = "yaml-rust"
version = "0.4.5"

View file

@ -6,13 +6,9 @@
#include "XULPersist.h"
#ifdef MOZ_NEW_XULSTORE
# include "mozilla/XULStore.h"
#else
# include "nsIXULStore.h"
# include "nsIStringEnumerator.h"
# include "nsServiceManagerUtils.h"
#endif
#include "nsIXULStore.h"
#include "nsIStringEnumerator.h"
#include "nsServiceManagerUtils.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Element.h"
@ -98,14 +94,12 @@ void XULPersist::Persist(Element* aElement, nsAtom* aAttribute) {
return;
}
#ifndef MOZ_NEW_XULSTORE
if (!mLocalStore) {
mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
if (NS_WARN_IF(!mLocalStore)) {
return;
}
}
#endif
nsAutoString id;
@ -132,11 +126,7 @@ void XULPersist::Persist(Element* aElement, nsAtom* aAttribute) {
valuestr = kMissingAttributeToken;
}
#ifdef MOZ_NEW_XULSTORE
rv = XULStore::SetValue(uri, id, attrstr, valuestr);
#else
mLocalStore->SetValue(uri, id, attrstr, valuestr);
#endif
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "value set");
}
@ -151,14 +141,12 @@ nsresult XULPersist::ApplyPersistentAttributes() {
// Add all of the 'persisted' attributes into the content
// model.
#ifndef MOZ_NEW_XULSTORE
if (!mLocalStore) {
mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
if (NS_WARN_IF(!mLocalStore)) {
return NS_ERROR_NOT_INITIALIZED;
}
}
#endif
nsCOMArray<Element> elements;
@ -170,35 +158,16 @@ nsresult XULPersist::ApplyPersistentAttributes() {
NS_ConvertUTF8toUTF16 uri(utf8uri);
// Get a list of element IDs for which persisted values are available
#ifdef MOZ_NEW_XULSTORE
UniquePtr<XULStoreIterator> ids;
rv = XULStore::GetIDs(uri, ids);
#else
nsCOMPtr<nsIStringEnumerator> ids;
rv = mLocalStore->GetIDsEnumerator(uri, getter_AddRefs(ids));
#endif
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
#ifdef MOZ_NEW_XULSTORE
while (ids->HasMore()) {
nsAutoString id;
rv = ids->GetNext(&id);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
#else
while (1) {
bool hasmore = false;
ids->HasMore(&hasmore);
if (!hasmore) {
break;
}
bool hasmore;
while (NS_SUCCEEDED(ids->HasMore(&hasmore)) && hasmore) {
nsAutoString id;
ids->GetNext(id);
#endif
// We want to hold strong refs to the elements while applying
// persistent attributes, just in case.
@ -226,41 +195,19 @@ nsresult XULPersist::ApplyPersistentAttributesToElements(
nsCOMArray<Element>& aElements) {
nsresult rv = NS_OK;
// Get a list of attributes for which persisted values are available
#ifdef MOZ_NEW_XULSTORE
UniquePtr<XULStoreIterator> attrs;
rv = XULStore::GetAttrs(aDocURI, aID, attrs);
#else
nsCOMPtr<nsIStringEnumerator> attrs;
rv = mLocalStore->GetAttributeEnumerator(aDocURI, aID, getter_AddRefs(attrs));
#endif
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
#ifdef MOZ_NEW_XULSTORE
while (attrs->HasMore()) {
nsAutoString attrstr;
rv = attrs->GetNext(&attrstr);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsAutoString value;
rv = XULStore::GetValue(aDocURI, aID, attrstr, value);
#else
while (1) {
bool hasmore = PR_FALSE;
attrs->HasMore(&hasmore);
if (!hasmore) {
break;
}
bool hasmore;
while (NS_SUCCEEDED(attrs->HasMore(&hasmore)) && hasmore) {
nsAutoString attrstr;
attrs->GetNext(attrstr);
nsAutoString value;
rv = mLocalStore->GetValue(aDocURI, aID, attrstr, value);
#endif
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}

View file

@ -39,9 +39,7 @@ class XULPersist final : public nsStubDocumentObserver {
const nsAString& aDocURI,
nsCOMArray<Element>& aElements);
#ifndef MOZ_NEW_XULSTORE
nsCOMPtr<nsIXULStore> mLocalStore;
#endif
// A weak pointer to our document. Nulled out by DropDocumentReference.
Document* MOZ_NON_OWNING_REF mDocument;

View file

@ -1,20 +0,0 @@
[package]
name = "xulstore"
version = "0.1.0"
authors = ["The Mozilla Project Developers"]
license = "MPL-2.0"
[dependencies]
crossbeam-utils = "0.8"
cstr = "0.2"
libc = "0.2"
log = "0.4"
moz_task = { path = "../../../xpcom/rust/moz_task" }
nsstring = { path = "../../../xpcom/rust/nsstring" }
nserror = { path = "../../../xpcom/rust/nserror" }
once_cell = "1"
rkv = { version = "0.18", default-features = false, features=["no-canonicalize-path"] }
serde_json = "1"
tempfile = "3"
thiserror = "1"
xpcom = { path = "../../../xpcom/rust/xpcom" }

View file

@ -4,32 +4,13 @@
# 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/.
if defined('MOZ_NEW_XULSTORE'):
Classes = [
{
'cid': '{be70bf11-0c28-4a02-a38c-0148538d42cf}',
'contract_ids': ['@mozilla.org/xul/xulstore;1'],
'type': 'nsIXULStore',
'headers': ['mozilla/XULStore.h'],
'singleton': True,
'constructor': 'mozilla::XULStore::GetService',
},
{
'js_name': 'xulStore',
'cid': '{e8e12dba-b942-4c0d-aa21-2843cfc64529}',
'contract_ids': ['@mozilla.org/xul/js-xulstore;1'],
'jsm': 'resource://gre/modules/XULStore.jsm',
'constructor': 'getXULStore',
},
]
else:
Classes = [
{
'js_name': 'xulStore',
'cid': '{6f46b6f4-c8b1-4bd4-a4fa-9ebbed0753ea}',
'contract_ids': ['@mozilla.org/xul/xulstore;1'],
'interfaces': ['nsIXULStore'],
'jsm': 'resource://gre/modules/XULStore.jsm',
'constructor': 'XULStore',
},
]
Classes = [
{
'js_name': 'xulStore',
'cid': '{6f46b6f4-c8b1-4bd4-a4fa-9ebbed0753ea}',
'contract_ids': ['@mozilla.org/xul/xulstore;1'],
'interfaces': ['nsIXULStore'],
'jsm': 'resource://gre/modules/XULStore.jsm',
'constructor': 'XULStore',
},
]

View file

@ -20,25 +20,6 @@ XPCOM_MANIFESTS += [
"components.conf",
]
if CONFIG["MOZ_NEW_XULSTORE"]:
EXTRA_JS_MODULES += [
"new/XULStore.jsm",
]
TEST_DIRS += [
"tests/gtest",
]
EXPORTS.mozilla += [
"XULStore.h",
]
UNIFIED_SOURCES += [
"XULStore.cpp",
]
FINAL_LIBRARY = "xul"
else:
EXTRA_JS_MODULES += [
"old/XULStore.jsm",
]
EXTRA_JS_MODULES += [
"XULStore.jsm",
]

View file

@ -1,107 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// This JS module wraps the nsIXULStore XPCOM service with useful abstractions.
// In particular, it wraps the enumerators returned by getIDsEnumerator()
// and getAttributeEnumerator() in JS objects that implement the iterable
// protocol. It also implements the persist() method. JS consumers should use
// this module rather than accessing nsIXULStore directly.
const EXPORTED_SYMBOLS = ["XULStore", "getXULStore"];
// Services.xulStore loads this module and returns its `XULStore` symbol
// when this implementation of XULStore is enabled, so using it here
// would loop infinitely. But the mozilla/use-services rule is a good
// requiremnt for every other consumer of XULStore.
// eslint-disable-next-line mozilla/use-services
const xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
// Enables logging.
const debugMode = false;
// Internal function for logging debug messages to the Error Console window
function log(message) {
if (!debugMode) {
return;
}
console.log("XULStore: " + message);
}
const XULStore = {
setValue: xulStore.setValue,
hasValue: xulStore.hasValue,
getValue: xulStore.getValue,
removeValue: xulStore.removeValue,
removeDocument: xulStore.removeDocument,
/**
* Sets a value for a specified node's attribute, except in
* the case below:
* If the value is empty and if calling `hasValue` with the node's
* document and ID and `attr` would return true, then the
* value instead gets removed from the store (see Bug 1476680).
*
* @param node - DOM node
* @param attr - attribute to store
*/
persist(node, attr) {
if (!node.id) {
throw new Error("Node without ID passed into persist()");
}
const uri = node.ownerDocument.documentURI;
const value = node.getAttribute(attr);
if (node.localName == "window") {
log("Persisting attributes to windows is handled by AppWindow.");
return;
}
// See Bug 1476680 - we could drop the `hasValue` check so that
// any time there's an empty attribute it gets removed from the
// store. Since this is copying behavior from document.persist,
// callers would need to be updated with that change.
if (!value && xulStore.hasValue(uri, node.id, attr)) {
xulStore.removeValue(uri, node.id, attr);
} else {
xulStore.setValue(uri, node.id, attr, value);
}
},
getIDsEnumerator(docURI) {
return new XULStoreEnumerator(xulStore.getIDsEnumerator(docURI));
},
getAttributeEnumerator(docURI, id) {
return new XULStoreEnumerator(xulStore.getAttributeEnumerator(docURI, id));
},
};
class XULStoreEnumerator {
constructor(enumerator) {
this.enumerator = enumerator;
}
hasMore() {
return this.enumerator.hasMore();
}
getNext() {
return this.enumerator.getNext();
}
*[Symbol.iterator]() {
while (this.enumerator.hasMore()) {
yield this.enumerator.getNext();
}
}
}
// Only here for the sake of component registration, which requires a
// callable function.
function getXULStore() {
return XULStore;
}

View file

@ -12,12 +12,6 @@ webidl Node;
* 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.
*
* If MOZ_NEW_XULSTORE is enabled:
* 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.
*
* If MOZ_NEW_XULSTORE is disabled:
* The data is serialized to [profile directory]/xulstore.json
*/
[scriptable, uuid(987c4b35-c426-4dd7-ad49-3c9fa4c65d20)]

View file

@ -1,81 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use nserror::{
nsresult, NS_ERROR_FAILURE, NS_ERROR_ILLEGAL_VALUE, NS_ERROR_NOT_AVAILABLE, NS_ERROR_UNEXPECTED,
};
use rkv::{MigrateError as RkvMigrateError, StoreError as RkvStoreError};
use serde_json::Error as SerdeJsonError;
use std::{io::Error as IoError, str::Utf8Error, string::FromUtf16Error, sync::PoisonError};
use thiserror::Error;
pub(crate) type XULStoreResult<T> = Result<T, XULStoreError>;
#[derive(Debug, Error)]
pub(crate) enum XULStoreError {
#[error("error converting bytes: {0:?}")]
ConvertBytes(#[from] Utf8Error),
#[error("error converting string: {0:?}")]
ConvertString(#[from] FromUtf16Error),
#[error("I/O error: {0:?}")]
IoError(#[from] IoError),
#[error("iteration is finished")]
IterationFinished,
#[error("JSON error: {0}")]
JsonError(#[from] SerdeJsonError),
#[error("error result {0}")]
NsResult(#[from] nsresult),
#[error("poison error getting read/write lock")]
PoisonError,
#[error("migrate error: {0:?}")]
RkvMigrateError(#[from] RkvMigrateError),
#[error("store error: {0:?}")]
RkvStoreError(#[from] RkvStoreError),
#[error("id or attribute name too long")]
IdAttrNameTooLong,
#[error("unavailable")]
Unavailable,
#[error("unexpected key: {0:?}")]
UnexpectedKey(String),
#[error("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::RkvMigrateError(_) => NS_ERROR_FAILURE,
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<T> From<PoisonError<T>> for XULStoreError {
fn from(_: PoisonError<T>) -> XULStoreError {
XULStoreError::PoisonError
}
}

View file

@ -1,325 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate as XULStore;
use crate::{iter::XULStoreIterator, statics::update_profile_dir};
use libc::{c_char, c_void};
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);
}
#[xpcom(implement(nsIXULStore), atomic)]
pub struct XULStoreService {}
impl XULStoreService {
fn new() -> RefPtr<XULStoreService> {
XULStoreService::allocate(InitXULStoreService {})
}
#[allow(non_snake_case)]
fn Persist(&self, _node: *const c_void, _attr: *const nsAString) -> nsresult {
NS_ERROR_NOT_IMPLEMENTED
}
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()),
}
}
}
#[xpcom(implement(nsIStringEnumerator), nonatomic)]
pub(crate) struct StringEnumerator {
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()),
}
}
}
#[xpcom(implement(nsIObserver), nonatomic)]
pub(crate) struct ProfileChangeObserver {}
impl ProfileChangeObserver {
#[allow(non_snake_case)]
unsafe fn Observe(
&self,
_subject: *const nsISupports,
_topic: *const c_char,
_data: *const u16,
) -> nsresult {
update_profile_dir();
NS_OK
}
pub(crate) fn new() -> RefPtr<ProfileChangeObserver> {
ProfileChangeObserver::allocate(InitProfileChangeObserver {})
}
}
#[no_mangle]
pub unsafe extern "C" fn xulstore_set_value(
doc: &nsAString,
id: &nsAString,
attr: &nsAString,
value: &nsAString,
) -> nsresult {
XULStore::set_value(doc, id, attr, value).into()
}
#[no_mangle]
pub unsafe extern "C" fn xulstore_has_value(
doc: &nsAString,
id: &nsAString,
attr: &nsAString,
has_value: *mut bool,
) -> nsresult {
match XULStore::has_value(doc, id, attr) {
Ok(val) => {
*has_value = val;
NS_OK
}
Err(err) => err.into(),
}
}
#[no_mangle]
pub unsafe extern "C" fn xulstore_get_value(
doc: &nsAString,
id: &nsAString,
attr: &nsAString,
value: *mut nsAString,
) -> nsresult {
match XULStore::get_value(doc, id, attr) {
Ok(val) => {
(*value).assign(&nsString::from(&val));
NS_OK
}
Err(err) => err.into(),
}
}
#[no_mangle]
pub unsafe extern "C" fn xulstore_remove_value(
doc: &nsAString,
id: &nsAString,
attr: &nsAString,
) -> nsresult {
XULStore::remove_value(doc, id, attr).into()
}
#[no_mangle]
pub unsafe extern "C" fn xulstore_get_ids(
doc: &nsAString,
result: *mut nsresult,
) -> *mut XULStoreIterator {
match XULStore::get_ids(doc) {
Ok(iter) => {
*result = NS_OK;
Box::into_raw(Box::new(iter))
}
Err(err) => {
*result = err.into();
ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn xulstore_get_attrs(
doc: &nsAString,
id: &nsAString,
result: *mut nsresult,
) -> *mut XULStoreIterator {
match XULStore::get_attrs(doc, id) {
Ok(iter) => {
*result = NS_OK;
Box::into_raw(Box::new(iter))
}
Err(err) => {
*result = err.into();
ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn xulstore_iter_has_more(iter: &XULStoreIterator) -> bool {
iter.has_more()
}
#[no_mangle]
pub unsafe extern "C" fn xulstore_iter_get_next(
iter: &mut XULStoreIterator,
value: *mut nsAString,
) -> nsresult {
match iter.get_next() {
Ok(val) => {
(*value).assign(&nsString::from(&val));
NS_OK
}
Err(err) => err.into(),
}
}
#[no_mangle]
pub unsafe extern "C" fn xulstore_iter_free(iter: *mut XULStoreIterator) {
drop(Box::from_raw(iter));
}
#[no_mangle]
pub unsafe extern "C" fn xulstore_shutdown() -> nsresult {
match XULStore::shutdown() {
Ok(()) => NS_OK,
Err(err) => err.into(),
}
}

View file

@ -1,24 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::error::{XULStoreError, XULStoreResult};
use std::vec::IntoIter;
pub struct XULStoreIterator {
values: IntoIter<String>,
}
impl XULStoreIterator {
pub(crate) fn new(values: IntoIter<String>) -> Self {
Self { values }
}
pub(crate) fn has_more(&self) -> bool {
!self.values.as_slice().is_empty()
}
pub(crate) fn get_next(&mut self) -> XULStoreResult<String> {
Ok(self.values.next().ok_or(XULStoreError::IterationFinished)?)
}
}

View file

@ -1,223 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
extern crate crossbeam_utils;
#[macro_use]
extern crate cstr;
extern crate libc;
#[macro_use]
extern crate log;
extern crate moz_task;
extern crate nserror;
extern crate nsstring;
extern crate once_cell;
extern crate rkv;
extern crate serde_json;
extern crate tempfile;
extern crate thiserror;
#[macro_use]
extern crate xpcom;
mod error;
mod ffi;
mod iter;
mod persist;
mod statics;
use crate::{
error::{XULStoreError, XULStoreResult},
iter::XULStoreIterator,
persist::{flush_writes, 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 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 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())),
}
}
pub(crate) fn shutdown() -> XULStoreResult<()> {
flush_writes()
}

View file

@ -1,179 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! 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.
use crate::{
error::{XULStoreError, XULStoreResult},
statics::get_database,
};
use crossbeam_utils::atomic::AtomicCell;
use moz_task::{DispatchOptions, Task, TaskRunnable};
use nserror::nsresult;
use once_cell::sync::Lazy;
use rkv::{StoreError as RkvStoreError, Value};
use std::{collections::HashMap, sync::Mutex, thread::sleep, time::Duration};
/// 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 CHANGES: Lazy<Mutex<Option<HashMap<String, Option<String>>>>> =
Lazy::new(|| 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 PERSIST: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
/// Synchronously persists changes recorded in memory to disk. Typically
/// called from a background thread, however this can be called from the main
/// thread in Gecko during shutdown (via flush_writes).
fn sync_persist() -> XULStoreResult<()> {
// 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();
// Return an error early if there's nothing to actually write
let writes = writes.ok_or(XULStoreError::Unavailable)?;
let db = get_database()?;
let env = db.rkv.read()?;
let mut writer = env.write()?;
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::KeyValuePairNotFound) => {
warn!("tried to remove key that isn't in the store");
}
Err(err) => return Err(err.into()),
}
}
}
}
writer.commit()?;
Ok(())
}
pub(crate) fn flush_writes() -> XULStoreResult<()> {
// One of three things will happen here (barring unexpected errors):
// - There are no writes queued and the background thread is idle. In which
// case, we will get the lock, see that there's nothing to write, and
// return (with data in memory and on disk in sync).
// - There are no writes queued because the background thread is writing
// them. In this case, we will block waiting for the lock held by the
// writing thread (which will ensure that the changes are flushed), then
// discover there are no more to write, and return.
// - The background thread is busy writing changes, and another thread has
// in the mean time added some. In this case, we will block waiting for
// the lock held by the writing thread, discover that there are more
// changes left, flush them ourselves, and return.
//
// This is not airtight, if changes are being added on a different thread
// than the one calling this. However it should be a reasonably strong
// guarantee even so.
let _lock = PERSIST.lock()?;
match sync_persist() {
Ok(_) => (),
// It's no problem (in fact it's generally expected) that there's just
// nothing to write.
Err(XULStoreError::Unavailable) => {
info!("Unable to persist xulstore");
}
Err(err) => return Err(err.into()),
}
Ok(())
}
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());
TaskRunnable::new("XULStore::Persist", task)?
.dispatch_background_task_with_options(DispatchOptions::default().may_block(true))?;
}
// 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()?;
sync_persist()
}()));
}
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(())
}
}

View file

@ -1,255 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::{
error::{XULStoreError, XULStoreResult},
ffi::ProfileChangeObserver,
make_key, SEPARATOR,
};
use moz_task::is_main_thread;
use nsstring::nsString;
use once_cell::sync::Lazy;
use rkv::backend::{SafeMode, SafeModeDatabase, SafeModeEnvironment};
use rkv::{StoreOptions, Value};
use std::{
collections::BTreeMap,
fs::{create_dir_all, remove_file, File},
path::PathBuf,
str,
sync::{Arc, Mutex, RwLock},
};
use xpcom::{
interfaces::{nsIFile, nsIObserverService, nsIProperties, nsIXULRuntime},
RefPtr, XpCom,
};
type Manager = rkv::Manager<SafeModeEnvironment>;
type Rkv = rkv::Rkv<SafeModeEnvironment>;
type SingleStore = rkv::SingleStore<SafeModeDatabase>;
type XULStoreCache = BTreeMap<String, BTreeMap<String, BTreeMap<String, String>>>;
pub struct Database {
pub rkv: Arc<RwLock<Rkv>>,
pub store: SingleStore,
}
impl Database {
fn new(rkv: Arc<RwLock<Rkv>>, store: SingleStore) -> Database {
Database { rkv, store }
}
}
static PROFILE_DIR: Lazy<Mutex<Option<PathBuf>>> = Lazy::new(|| {
observe_profile_change();
Mutex::new(get_profile_dir().ok())
});
pub(crate) static DATA_CACHE: Lazy<Mutex<Option<XULStoreCache>>> =
Lazy::new(|| Mutex::new(cache_data().ok()));
pub(crate) fn get_database() -> XULStoreResult<Database> {
let mut manager = Manager::singleton().write()?;
let xulstore_dir = get_xulstore_dir()?;
let xulstore_path = xulstore_dir.as_path();
let rkv = manager.get_or_create(xulstore_path, Rkv::new::<SafeMode>)?;
let store = rkv.read()?.open_single("db", StoreOptions::create())?;
Ok(Database::new(rkv, 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: RefPtr<nsIProperties> =
xpcom::components::Directory::service().map_err(|_| XULStoreError::Unavailable)?;
let mut profile_dir = xpcom::GetterAddrefs::<nsIFile>::new();
unsafe {
dir_svc
.Get(
cstr!("ProfD").as_ptr(),
&nsIFile::IID,
profile_dir.void_ptr(),
)
.to_result()
.or_else(|_| {
dir_svc
.Get(
cstr!("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: RefPtr<nsIObserverService> =
xpcom::components::Observer::service().map_err(|_| XULStoreError::Unavailable)?;
let observer = ProfileChangeObserver::new();
unsafe {
obs_svc
.AddObserver(
observer.coerce(),
cstr!("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 xul_runtime: RefPtr<nsIXULRuntime> =
xpcom::components::XULRuntime::service().map_err(|_| XULStoreError::Unavailable)?;
let mut in_safe_mode = false;
unsafe {
xul_runtime.GetInSafeMode(&mut in_safe_mode).to_result()?;
}
Ok(in_safe_mode)
}
fn cache_data() -> XULStoreResult<XULStoreCache> {
let db = get_database()?;
maybe_migrate_data(&db, db.store);
let mut all = XULStoreCache::default();
if in_safe_mode()? {
return Ok(all);
}
let env = db.rkv.read()?;
let reader = env.read()?;
let iterator = db.store.iter_start(&reader)?;
for result in iterator {
let (key, value): (&str, String) = match result {
Ok((key, 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(db: &Database, 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 env = db.rkv.read()?;
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: &Value) -> XULStoreResult<String> {
match value {
Value::Str(val) => Ok(val.to_string()),
// 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).
_ => Err(XULStoreError::UnexpectedValue),
}
}

View file

@ -1,7 +0,0 @@
[package]
name = "xulstore-gtest"
version = "0.1.0"
authors = ["The Mozilla Project Developers"]
[lib]
path = "test.rs"

View file

@ -1,141 +0,0 @@
#include <stdint.h>
#include "gtest/gtest.h"
#include "mozilla/XULStore.h"
#include "nsCOMPtr.h"
#include "nsString.h"
using mozilla::XULStoreIterator;
using mozilla::XULStore::GetAttrs;
using mozilla::XULStore::GetIDs;
using mozilla::XULStore::GetValue;
using mozilla::XULStore::HasValue;
using mozilla::XULStore::RemoveValue;
using mozilla::XULStore::SetValue;
TEST(XULStore, SetGetValue)
{
nsAutoString doc(u"SetGetValue"_ns);
nsAutoString id(u"foo"_ns);
nsAutoString attr(u"bar"_ns);
nsAutoString value;
EXPECT_EQ(GetValue(doc, id, attr, value), NS_OK);
EXPECT_TRUE(value.EqualsASCII(""));
{
nsAutoString value(u"baz"_ns);
EXPECT_EQ(SetValue(doc, id, attr, value), NS_OK);
}
EXPECT_EQ(GetValue(doc, id, attr, value), NS_OK);
EXPECT_TRUE(value.EqualsASCII("baz"));
}
TEST(XULStore, HasValue)
{
nsAutoString doc(u"HasValue"_ns);
nsAutoString id(u"foo"_ns);
nsAutoString attr(u"bar"_ns);
bool hasValue = true;
EXPECT_EQ(HasValue(doc, id, attr, hasValue), NS_OK);
EXPECT_FALSE(hasValue);
nsAutoString value(u"baz"_ns);
EXPECT_EQ(SetValue(doc, id, attr, value), NS_OK);
EXPECT_EQ(HasValue(doc, id, attr, hasValue), NS_OK);
EXPECT_TRUE(hasValue);
}
TEST(XULStore, RemoveValue)
{
nsAutoString doc(u"RemoveValue"_ns);
nsAutoString id(u"foo"_ns);
nsAutoString attr(u"bar"_ns);
nsAutoString value(u"baz"_ns);
EXPECT_EQ(SetValue(doc, id, attr, value), NS_OK);
EXPECT_EQ(GetValue(doc, id, attr, value), NS_OK);
EXPECT_TRUE(value.EqualsASCII("baz"));
EXPECT_EQ(RemoveValue(doc, id, attr), NS_OK);
EXPECT_EQ(GetValue(doc, id, attr, value), NS_OK);
EXPECT_TRUE(value.EqualsASCII(""));
}
TEST(XULStore, GetIDsIterator)
{
nsAutoString doc(u"idIterDoc"_ns);
nsAutoString id1(u"id1"_ns);
nsAutoString id2(u"id2"_ns);
nsAutoString id3(u"id3"_ns);
nsAutoString attr(u"attr"_ns);
nsAutoString value(u"value"_ns);
nsAutoString id;
// Confirm that the store doesn't have any IDs yet.
mozilla::UniquePtr<XULStoreIterator> iter;
EXPECT_EQ(GetIDs(doc, iter), NS_OK);
EXPECT_FALSE(iter->HasMore());
// EXPECT_EQ(iter->GetNext(&id), NS_ERROR_FAILURE);
// Insert with IDs in non-alphanumeric order to confirm
// that store will order them when iterating them.
EXPECT_EQ(SetValue(doc, id3, attr, value), NS_OK);
EXPECT_EQ(SetValue(doc, id1, attr, value), NS_OK);
EXPECT_EQ(SetValue(doc, id2, attr, value), NS_OK);
// Insert different ID for another doc to confirm that store
// won't return it when iterating IDs for our doc.
nsAutoString otherDoc(u"otherDoc"_ns);
nsAutoString otherID(u"otherID"_ns);
EXPECT_EQ(SetValue(otherDoc, otherID, attr, value), NS_OK);
EXPECT_EQ(GetIDs(doc, iter), NS_OK);
EXPECT_TRUE(iter->HasMore());
EXPECT_EQ(iter->GetNext(&id), NS_OK);
EXPECT_TRUE(id.EqualsASCII("id1"));
EXPECT_TRUE(iter->HasMore());
EXPECT_EQ(iter->GetNext(&id), NS_OK);
EXPECT_TRUE(id.EqualsASCII("id2"));
EXPECT_TRUE(iter->HasMore());
EXPECT_EQ(iter->GetNext(&id), NS_OK);
EXPECT_TRUE(id.EqualsASCII("id3"));
EXPECT_FALSE(iter->HasMore());
}
TEST(XULStore, GetAttributeIterator)
{
nsAutoString doc(u"attrIterDoc"_ns);
nsAutoString id(u"id"_ns);
nsAutoString attr1(u"attr1"_ns);
nsAutoString attr2(u"attr2"_ns);
nsAutoString attr3(u"attr3"_ns);
nsAutoString value(u"value"_ns);
nsAutoString attr;
mozilla::UniquePtr<XULStoreIterator> iter;
EXPECT_EQ(GetAttrs(doc, id, iter), NS_OK);
EXPECT_FALSE(iter->HasMore());
// EXPECT_EQ(iter->GetNext(&attr), NS_ERROR_FAILURE);
// Insert with attributes in non-alphanumeric order to confirm
// that store will order them when iterating them.
EXPECT_EQ(SetValue(doc, id, attr3, value), NS_OK);
EXPECT_EQ(SetValue(doc, id, attr1, value), NS_OK);
EXPECT_EQ(SetValue(doc, id, attr2, value), NS_OK);
// Insert different attribute for another ID to confirm that store
// won't return it when iterating attributes for our ID.
nsAutoString otherID(u"otherID"_ns);
nsAutoString otherAttr(u"otherAttr"_ns);
EXPECT_EQ(SetValue(doc, otherID, otherAttr, value), NS_OK);
EXPECT_EQ(GetAttrs(doc, id, iter), NS_OK);
EXPECT_TRUE(iter->HasMore());
EXPECT_EQ(iter->GetNext(&attr), NS_OK);
EXPECT_TRUE(attr.EqualsASCII("attr1"));
EXPECT_TRUE(iter->HasMore());
EXPECT_EQ(iter->GetNext(&attr), NS_OK);
EXPECT_TRUE(attr.EqualsASCII("attr2"));
EXPECT_TRUE(iter->HasMore());
EXPECT_EQ(iter->GetNext(&attr), NS_OK);
EXPECT_TRUE(attr.EqualsASCII("attr3"));
EXPECT_FALSE(iter->HasMore());
}

View file

@ -1,11 +0,0 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
UNIFIED_SOURCES += [
"TestXULStore.cpp",
]
FINAL_LIBRARY = "xul-gtest"

View file

@ -1,85 +0,0 @@
"use strict";
const { AppConstants } = ChromeUtils.importESModule(
"resource://gre/modules/AppConstants.sys.mjs"
);
function run_test() {
do_get_profile();
run_next_test();
}
add_task(
{
skip_if: () => !AppConstants.MOZ_NEW_XULSTORE,
},
async function test_create_old_datastore() {
const path = PathUtils.join(PathUtils.profileDir, "xulstore.json");
const xulstoreJSON = {
doc1: {
id1: {
attr1: "value1",
},
},
doc2: {
id1: {
attr2: "value2",
},
id2: {
attr1: "value1",
attr2: "value2",
attr3: "value3",
},
id3: {},
},
doc3: {},
};
await IOUtils.writeJSON(path, xulstoreJSON);
}
);
add_task(
{
skip_if: () => !AppConstants.MOZ_NEW_XULSTORE,
},
async function test_get_values() {
// We wait until now to import XULStore to ensure we've created
// the old datastore, as importing that module will initiate the attempt
// to migrate the old datastore to the new one.
const { XULStore } = ChromeUtils.import(
"resource://gre/modules/XULStore.jsm"
);
Assert.equal(await XULStore.getValue("doc1", "id1", "attr1"), "value1");
Assert.equal(await XULStore.getValue("doc1", "id1", "attr2"), "");
Assert.equal(await XULStore.getValue("doc1", "id1", "attr3"), "");
Assert.equal(await XULStore.getValue("doc1", "id2", "attr1"), "");
Assert.equal(await XULStore.getValue("doc1", "id2", "attr2"), "");
Assert.equal(await XULStore.getValue("doc1", "id2", "attr3"), "");
Assert.equal(await XULStore.getValue("doc1", "id3", "attr1"), "");
Assert.equal(await XULStore.getValue("doc1", "id3", "attr2"), "");
Assert.equal(await XULStore.getValue("doc1", "id3", "attr3"), "");
Assert.equal(await XULStore.getValue("doc2", "id1", "attr1"), "");
Assert.equal(await XULStore.getValue("doc2", "id1", "attr2"), "value2");
Assert.equal(await XULStore.getValue("doc2", "id1", "attr3"), "");
Assert.equal(await XULStore.getValue("doc2", "id2", "attr1"), "value1");
Assert.equal(await XULStore.getValue("doc2", "id2", "attr2"), "value2");
Assert.equal(await XULStore.getValue("doc2", "id2", "attr3"), "value3");
Assert.equal(await XULStore.getValue("doc2", "id3", "attr1"), "");
Assert.equal(await XULStore.getValue("doc2", "id3", "attr2"), "");
Assert.equal(await XULStore.getValue("doc2", "id3", "attr3"), "");
Assert.equal(await XULStore.getValue("doc3", "id1", "attr1"), "");
Assert.equal(await XULStore.getValue("doc3", "id1", "attr2"), "");
Assert.equal(await XULStore.getValue("doc3", "id1", "attr3"), "");
Assert.equal(await XULStore.getValue("doc3", "id2", "attr1"), "");
Assert.equal(await XULStore.getValue("doc3", "id2", "attr2"), "");
Assert.equal(await XULStore.getValue("doc3", "id2", "attr3"), "");
Assert.equal(await XULStore.getValue("doc3", "id3", "attr1"), "");
Assert.equal(await XULStore.getValue("doc3", "id3", "attr2"), "");
Assert.equal(await XULStore.getValue("doc3", "id3", "attr3"), "");
}
);

View file

@ -1,57 +0,0 @@
"use strict";
const { AppConstants } = ChromeUtils.importESModule(
"resource://gre/modules/AppConstants.sys.mjs"
);
function run_test() {
do_get_profile();
run_next_test();
}
add_task(
{
skip_if: () => !AppConstants.MOZ_NEW_XULSTORE,
},
async function test_create_old_datastore() {
const path = PathUtils.join(PathUtils.profileDir, "xulstore.json");
// Valid JSON, but invalid data: attr1's value is a number, not a string.
const xulstoreJSON = {
doc1: {
id1: {
attr1: 1,
},
},
doc2: {
id2: {
attr2: "value2",
},
},
};
await IOUtils.writeJSON(path, xulstoreJSON);
}
);
add_task(
{
skip_if: () => !AppConstants.MOZ_NEW_XULSTORE,
},
async function test_get_values() {
// We wait until now to import XULStore to ensure we've created
// the old store, as importing that module will initiate the attempt
// to migrate the old store to the new one.
const { XULStore } = ChromeUtils.import(
"resource://gre/modules/XULStore.jsm"
);
// XULStore should *not* have migrated the values from the old store,
// so it should return empty strings when we try to retrieve them.
// That's true for both values, even though one of them is valid,
// because the migrator uses a typed parser that requires the entire
// JSON file to conform to the XULStore format.
Assert.equal(await XULStore.getValue("doc1", "id1", "attr1"), "");
Assert.equal(await XULStore.getValue("doc2", "id2", "attr2"), "");
}
);

View file

@ -1,42 +0,0 @@
"use strict";
const { AppConstants } = ChromeUtils.importESModule(
"resource://gre/modules/AppConstants.sys.mjs"
);
function run_test() {
do_get_profile();
run_next_test();
}
add_task(
{
skip_if: () => !AppConstants.MOZ_NEW_XULSTORE,
},
async function test_create_old_datastore() {
const path = PathUtils.join(PathUtils.profileDir, "xulstore.json");
// Invalid JSON: it's missing the final closing brace.
const xulstoreJSON = '{ doc: { id: { attr: "value" } }';
await IOUtils.writeUTF8(path, xulstoreJSON);
}
);
add_task(
{
skip_if: () => !AppConstants.MOZ_NEW_XULSTORE,
},
async function test_get_value() {
// We wait until now to import XULStore to ensure we've created
// the old store, as importing that module will initiate the attempt
// to migrate the old store to the new one.
const { XULStore } = ChromeUtils.import(
"resource://gre/modules/XULStore.jsm"
);
// XULStore should *not* have migrated the value from the old store,
// so it should return an empty string when we try to retrieve it.
Assert.equal(await XULStore.getValue("doc", "id", "attr"), "");
}
);

View file

@ -1,56 +0,0 @@
"use strict";
const { AppConstants } = ChromeUtils.importESModule(
"resource://gre/modules/AppConstants.sys.mjs"
);
const { FileUtils } = ChromeUtils.importESModule(
"resource://gre/modules/FileUtils.sys.mjs"
);
add_task(
{
skip_if: () => !AppConstants.MOZ_NEW_XULSTORE,
},
async function test_get_values() {
// Import XULStore before getting the profile to ensure that the new store
// is initialized, as the purpose of this test is to confirm that the old
// store data gets migrated if the profile change happens post-initialization.
const { XULStore } = ChromeUtils.import(
"resource://gre/modules/XULStore.jsm"
);
// We haven't migrated any data yet (nor even changed to a profile), so there
// shouldn't be a value in the store.
Assert.equal(XULStore.getValue("doc1", "id1", "attr1"), "");
// Register an observer before the XULStore service registers its observer,
// so we can observe the profile-after-change notification first and create
// an old store for it to migrate. We need to write synchronously to avoid
// racing XULStore, so we use FileUtils instead of IOUtils.
Services.obs.addObserver(
{
observe() {
const file = FileUtils.getFile("ProfD", ["xulstore.json"]);
const xulstoreJSON = JSON.stringify({
doc1: {
id1: {
attr1: "value1",
},
},
});
let stream = FileUtils.openAtomicFileOutputStream(file);
stream.write(xulstoreJSON, xulstoreJSON.length);
FileUtils.closeAtomicFileOutputStream(stream);
},
},
"profile-after-change"
);
// This creates a profile and changes to it, triggering first our
// profile-after-change observer above and then XULStore's equivalent.
do_get_profile(true);
// XULStore should now have migrated the value from the old store.
Assert.equal(XULStore.getValue("doc1", "id1", "attr1"), "value1");
}
);

View file

@ -2,12 +2,3 @@
skip-if = toolkit == 'android'
[test_XULStore.js]
# These tests only run on the new implementation of XULStore, since they
# test migration of data from the old implementation to the new one.
# But there isn't a skip-if condition we can add here to disable them,
# so we disable them within each test file using add_task() properties.
[test_XULStore_migration.js]
[test_XULStore_migration_fail_invalid_json.js]
[test_XULStore_migration_fail_invalid_data.js]
[test_XULStore_migration_profile_change.js]

View file

@ -50,9 +50,6 @@ if CONFIG['MOZ_GECKO_PROFILER_PARSE_ELF']:
if CONFIG['MOZ_BITS_DOWNLOAD']:
gkrust_features += ['bitsdownload']
if CONFIG['MOZ_NEW_XULSTORE']:
gkrust_features += ['new_xulstore']
if CONFIG['LIBFUZZER']:
gkrust_features += ['libfuzzer']

View file

@ -43,7 +43,6 @@ authenticator = { version = "0.4.0-alpha.6", features = ["gecko"] }
gkrust_utils = { path = "../../../../xpcom/rust/gkrust_utils" }
gecko_logger = { path = "../../../../xpcom/rust/gecko_logger" }
rsdparsa_capi = { path = "../../../../dom/media/webrtc/sdp/rsdparsa_capi" }
xulstore = { path = "../../../components/xulstore", optional = true }
# We have these to enforce common feature sets for said crates.
log = {version = "0.4", features = ["release_max_level_info"]}
cose-c = { version = "0.1.5" }
@ -136,7 +135,6 @@ spidermonkey_rust = []
smoosh = ["jsrust_shared/smoosh"]
gecko_profiler = ["gecko-profiler/enabled", "profiler_helper"]
gecko_profiler_parse_elf = ["profiler_helper/parse_elf"]
new_xulstore = ["xulstore"]
libfuzzer = ["neqo_glue/fuzzing"]
webrtc = ["mdns_service"]
glean_disable_upload = ["fog_control/disable_upload"]

View file

@ -49,8 +49,6 @@ extern crate static_prefs;
extern crate storage;
extern crate webrender_bindings;
extern crate xpcom;
#[cfg(feature = "new_xulstore")]
extern crate xulstore;
extern crate audio_thread_priority;

View file

@ -393,13 +393,6 @@ export var AppConstants = Object.freeze({
TELEMETRY_PING_FORMAT_VERSION: @TELEMETRY_PING_FORMAT_VERSION@,
MOZ_NEW_XULSTORE:
#ifdef MOZ_NEW_XULSTORE
true,
#else
false,
#endif
MOZ_NEW_NOTIFICATION_STORE:
#ifdef MOZ_NEW_NOTIFICATION_STORE
true,

View file

@ -35,7 +35,6 @@ clippy:
- third_party/rust/mp4parse_capi/
- toolkit/components/kvstore/
- toolkit/components/glean/
- toolkit/components/xulstore/tests/gtest/
- toolkit/library/rust/
- tools/fuzzing/rust/
- tools/profiler/rust-api/
@ -69,7 +68,6 @@ clippy:
- storage/rust/
- storage/variant/
# nsstring
- toolkit/components/xulstore/
- servo/ports/geckolib/tests/
- xpcom/rust/xpcom/
- xpcom/rust/nsstring/

View file

@ -37,10 +37,6 @@
#endif
#include "prenv.h"
#ifdef MOZ_NEW_XULSTORE
# include "mozilla/XULStore.h"
#endif
#ifdef MOZ_BACKGROUNDTASKS
# include "mozilla/BackgroundTasks.h"
#endif
@ -236,11 +232,6 @@ void AppShutdown::MaybeFastShutdown(ShutdownPhase aPhase) {
}
nsresult rv;
#ifdef MOZ_NEW_XULSTORE
rv = XULStore::Shutdown();
NS_ASSERTION(NS_SUCCEEDED(rv), "XULStore::Shutdown() failed.");
#endif
nsCOMPtr<nsICertStorage> certStorage =
do_GetService("@mozilla.org/security/certstorage;1", &rv);
if (NS_SUCCEEDED(rv)) {

View file

@ -76,10 +76,6 @@
# include "nsIWindowsUIUtils.h"
#endif
#ifdef MOZ_NEW_XULSTORE
# include "mozilla/XULStore.h"
#endif
#include "mozilla/dom/DocumentL10n.h"
#ifdef XP_MACOSX
@ -1710,10 +1706,6 @@ nsresult AppWindow::GetPersistentValue(const nsAtom* aAttr, nsAString& aValue) {
NS_ENSURE_SUCCESS(rv, rv);
NS_ConvertUTF8toUTF16 uri(utf8uri);
#ifdef MOZ_NEW_XULSTORE
nsDependentAtomString attrString(aAttr);
rv = XULStore::GetValue(uri, windowElementId, attrString, aValue);
#else
if (!mLocalStore) {
mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
if (NS_WARN_IF(!mLocalStore)) {
@ -1723,7 +1715,6 @@ nsresult AppWindow::GetPersistentValue(const nsAtom* aAttr, nsAString& aValue) {
rv = mLocalStore->GetValue(uri, windowElementId, nsDependentAtomString(aAttr),
aValue);
#endif
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
@ -1946,11 +1937,6 @@ nsresult AppWindow::SetPersistentValue(const nsAtom* aAttr,
maybeConvertedValue);
}
#ifdef MOZ_NEW_XULSTORE
nsDependentAtomString attrString(aAttr);
return XULStore::SetValue(uri, windowElementId, attrString,
maybeConvertedValue);
#else
if (!mLocalStore) {
mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
if (NS_WARN_IF(!mLocalStore)) {
@ -1960,7 +1946,6 @@ nsresult AppWindow::SetPersistentValue(const nsAtom* aAttr,
return mLocalStore->SetValue(
uri, windowElementId, nsDependentAtomString(aAttr), maybeConvertedValue);
#endif
}
void AppWindow::MaybeSavePersistentPositionAndSize(

View file

@ -35,10 +35,7 @@
#include "nsIRemoteTab.h"
#include "nsIWebProgressListener.h"
#include "nsITimer.h"
#ifndef MOZ_NEW_XULSTORE
# include "nsIXULStore.h"
#endif
#include "nsIXULStore.h"
namespace mozilla {
namespace dom {
@ -387,9 +384,7 @@ class AppWindow final : public nsIBaseWindow,
nsresult SetPrimaryRemoteTabSize(int32_t aWidth, int32_t aHeight);
void SizeShellToWithLimit(int32_t aDesiredWidth, int32_t aDesiredHeight,
int32_t shellItemWidth, int32_t shellItemHeight);
#ifndef MOZ_NEW_XULSTORE
nsCOMPtr<nsIXULStore> mLocalStore;
#endif
};
NS_DEFINE_STATIC_IID_ACCESSOR(AppWindow, NS_APPWINDOW_IMPL_CID)