diff options
Diffstat (limited to 'toolkit/components/xulstore')
13 files changed, 959 insertions, 0 deletions
diff --git a/toolkit/components/xulstore/XULStore.js b/toolkit/components/xulstore/XULStore.js new file mode 100644 index 000000000..c2721327c --- /dev/null +++ b/toolkit/components/xulstore/XULStore.js @@ -0,0 +1,336 @@ +/* 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/. */ + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; +const Cu = Components.utils; + +// Enables logging and shorter save intervals. +const debugMode = false; + +// Delay when a change is made to when the file is saved. +// 30 seconds normally, or 3 seconds for testing +const WRITE_DELAY_MS = (debugMode ? 3 : 30) * 1000; + +const XULSTORE_CONTRACTID = "@mozilla.org/xul/xulstore;1"; +const XULSTORE_CID = Components.ID("{6f46b6f4-c8b1-4bd4-a4fa-9ebbed0753ea}"); +const STOREDB_FILENAME = "xulstore.json"; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); + +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); + +function XULStore() { + if (!Services.appinfo.inSafeMode) + this.load(); +} + +XULStore.prototype = { + classID: XULSTORE_CID, + classInfo: XPCOMUtils.generateCI({classID: XULSTORE_CID, + contractID: XULSTORE_CONTRACTID, + classDescription: "XULStore", + interfaces: [Ci.nsIXULStore]}), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsIXULStore, + Ci.nsISupportsWeakReference]), + _xpcom_factory: XPCOMUtils.generateSingletonFactory(XULStore), + + /* ---------- private members ---------- */ + + /* + * The format of _data is _data[docuri][elementid][attribute]. For example: + * { + * "chrome://blah/foo.xul" : { + * "main-window" : { aaa : 1, bbb : "c" }, + * "barColumn" : { ddd : 9, eee : "f" }, + * }, + * + * "chrome://foopy/b.xul" : { ... }, + * ... + * } + */ + _data: {}, + _storeFile: null, + _needsSaving: false, + _saveAllowed: true, + _writeTimer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer), + + load: function () { + Services.obs.addObserver(this, "profile-before-change", true); + + this._storeFile = Services.dirsvc.get("ProfD", Ci.nsIFile); + this._storeFile.append(STOREDB_FILENAME); + + if (!this._storeFile.exists()) { + this.import(); + } else { + this.readFile(); + } + }, + + observe: function(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: function (message) { + if (!debugMode) + return; + dump("XULStore: " + message + "\n"); + Services.console.logStringMessage("XULStore: " + message); + }, + + import: function() { + let localStoreFile = Services.dirsvc.get("ProfD", Ci.nsIFile); + + localStoreFile.append("localstore.rdf"); + if (!localStoreFile.exists()) { + return; + } + + const RDF = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService); + const persistKey = RDF.GetResource("http://home.netscape.com/NC-rdf#persist"); + + this.log("Import localstore from " + localStoreFile.path); + + let localStoreURI = Services.io.newFileURI(localStoreFile).spec; + let localStore = RDF.GetDataSourceBlocking(localStoreURI); + let resources = localStore.GetAllResources(); + + while (resources.hasMoreElements()) { + let resource = resources.getNext().QueryInterface(Ci.nsIRDFResource); + let uri; + + try { + uri = NetUtil.newURI(resource.ValueUTF8); + } catch (ex) { + continue; // skip invalid uris + } + + // If this has a ref, then this is an attribute reference. Otherwise, + // this is a document reference. + if (!uri.hasRef) + continue; + + // Verify that there the persist key is connected up. + let docURI = uri.specIgnoringRef; + + if (!localStore.HasAssertion(RDF.GetResource(docURI), persistKey, resource, true)) + continue; + + let id = uri.ref; + let attrs = localStore.ArcLabelsOut(resource); + + while (attrs.hasMoreElements()) { + let attr = attrs.getNext().QueryInterface(Ci.nsIRDFResource); + let value = localStore.GetTarget(resource, attr, true); + + if (value instanceof Ci.nsIRDFLiteral) { + this.setValue(docURI, id, attr.ValueUTF8, value.Value); + } + } + } + }, + + readFile: function() { + const MODE_RDONLY = 0x01; + const FILE_PERMS = 0o600; + + let stream = Cc["@mozilla.org/network/file-input-stream;1"]. + createInstance(Ci.nsIFileInputStream); + let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON); + try { + stream.init(this._storeFile, MODE_RDONLY, FILE_PERMS, 0); + this._data = json.decodeFromStream(stream, stream.available()); + } catch (e) { + this.log("Error reading JSON: " + e); + // Ignore problem, we'll just continue on with an empty dataset. + } finally { + stream.close(); + } + }, + + writeFile: Task.async(function* () { + 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); + yield 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: function() { + 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 ---------- */ + + setValue: function (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: function (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: function (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: function (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(); + } + } + }, + + getIDsEnumerator: function (docURI) { + this.log("Getting ID enumerator for doc=" + docURI); + + if (!(docURI in this._data)) + return new nsStringEnumerator([]); + + let result = []; + let ids = this._data[docURI]; + if (ids) { + for (let id in this._data[docURI]) { + result.push(id); + } + } + + return new nsStringEnumerator(result); + }, + + getAttributeEnumerator: function (docURI, id) { + this.log("Getting attribute enumerator for id=" + id + ", doc=" + docURI); + + if (!(docURI in this._data) || !(id in this._data[docURI])) + return new nsStringEnumerator([]); + + let attrs = []; + for (let attr in this._data[docURI][id]) { + attrs.push(attr); + } + + return new nsStringEnumerator(attrs); + } +}; + +function nsStringEnumerator(items) { + this._items = items; +} + +nsStringEnumerator.prototype = { + QueryInterface : XPCOMUtils.generateQI([Ci.nsIStringEnumerator]), + _nextIndex : 0, + hasMore: function() { + return this._nextIndex < this._items.length; + }, + getNext : function() { + if (!this.hasMore()) + throw Cr.NS_ERROR_NOT_AVAILABLE; + return this._items[this._nextIndex++]; + }, +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([XULStore]); diff --git a/toolkit/components/xulstore/XULStore.manifest b/toolkit/components/xulstore/XULStore.manifest new file mode 100644 index 000000000..249f0c447 --- /dev/null +++ b/toolkit/components/xulstore/XULStore.manifest @@ -0,0 +1,2 @@ +component {6f46b6f4-c8b1-4bd4-a4fa-9ebbed0753ea} XULStore.js +contract @mozilla.org/xul/xulstore;1 {6f46b6f4-c8b1-4bd4-a4fa-9ebbed0753ea} diff --git a/toolkit/components/xulstore/moz.build b/toolkit/components/xulstore/moz.build new file mode 100644 index 000000000..30559fccb --- /dev/null +++ b/toolkit/components/xulstore/moz.build @@ -0,0 +1,19 @@ +# -*- 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/. + +MOCHITEST_CHROME_MANIFESTS += ['tests/chrome/chrome.ini'] +XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini'] + +XPIDL_SOURCES += [ + 'nsIXULStore.idl', +] + +XPIDL_MODULE = 'toolkit_xulstore' + +EXTRA_COMPONENTS += [ + 'XULStore.js', + 'XULStore.manifest', +] diff --git a/toolkit/components/xulstore/nsIXULStore.idl b/toolkit/components/xulstore/nsIXULStore.idl new file mode 100644 index 000000000..57050b31b --- /dev/null +++ b/toolkit/components/xulstore/nsIXULStore.idl @@ -0,0 +1,75 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIStringEnumerator; + +/** + * The XUL store is used to store information related to a XUL document/application. + * Typically it is used to store the persisted state for the document, such as + * window location, toolbars that are open and nodes that are open and closed in a tree. + * + * The data is serialized to [profile directory]/xulstore.json + */ +[scriptable, uuid(987c4b35-c426-4dd7-ad49-3c9fa4c65d20)] + +interface nsIXULStore: nsISupports +{ + /** + * Sets a value in the store. + * + * @param doc - document URI + * @param id - identifier of the node + * @param attr - attribute to store + * @param value - value of the attribute + */ + void setValue(in AString doc, in AString id, in AString attr, in AString value); + + /** + * Returns true if the store contains a value for attr. + * + * @param doc - URI of the document + * @param id - identifier of the node + * @param attr - attribute + */ + bool hasValue(in AString doc, in AString id, in AString attr); + + /** + * Retrieves a value in the store, or an empty string if it does not exist. + * + * @param doc - document URI + * @param id - identifier of the node + * @param attr - attribute to retrieve + * + * @returns the value of the attribute + */ + AString getValue(in AString doc, in AString id, in AString attr); + + /** + * Removes a value in the store. + * + * @param doc - document URI + * @param id - identifier of the node + * @param attr - attribute to remove + */ + void removeValue(in AString doc, in AString id, in AString attr); + + /** + * Iterates over all of the ids associated with a given document uri that + * have stored data. + * + * @param doc - document URI + */ + nsIStringEnumerator getIDsEnumerator(in AString doc); + + /** + * Iterates over all of the attributes associated with a given document uri + * and id that have stored data. + * + * @param doc - document URI + * @param id - identifier of the node + */ + nsIStringEnumerator getAttributeEnumerator(in AString doc, in AString id); +}; diff --git a/toolkit/components/xulstore/tests/chrome/.eslintrc.js b/toolkit/components/xulstore/tests/chrome/.eslintrc.js new file mode 100644 index 000000000..8c0f4f574 --- /dev/null +++ b/toolkit/components/xulstore/tests/chrome/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/chrome.eslintrc.js" + ] +}; diff --git a/toolkit/components/xulstore/tests/chrome/animals.rdf b/toolkit/components/xulstore/tests/chrome/animals.rdf new file mode 100644 index 000000000..c7319e641 --- /dev/null +++ b/toolkit/components/xulstore/tests/chrome/animals.rdf @@ -0,0 +1,142 @@ +<?xml version="1.0"?> + +<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:NC="http://home.netscape.com/NC-rdf#" + xmlns:ANIMALS="http://www.some-fictitious-zoo.com/rdf#"> + + <ANIMALS:Class RDF:about="http://www.some-fictitious-zoo.com/arachnids"> + <ANIMALS:name>Arachnids</ANIMALS:name> + </ANIMALS:Class> + + <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/arachnids/tarantula"> + <ANIMALS:name>Tarantula</ANIMALS:name> + </RDF:Description> + + <ANIMALS:Class RDF:about="http://www.some-fictitious-zoo.com/birds"> + <ANIMALS:name>Birds</ANIMALS:name> + </ANIMALS:Class> + + <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/birds/emu"> + <ANIMALS:name>Emu</ANIMALS:name> + </RDF:Description> + + <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/birds/barnowl"> + <ANIMALS:name>Barn Owl</ANIMALS:name> + </RDF:Description> + + <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/birds/raven"> + <ANIMALS:name>Raven</ANIMALS:name> + </RDF:Description> + + <ANIMALS:Class RDF:about="http://www.some-fictitious-zoo.com/crustaceans"> + <ANIMALS:name>Crustaceans</ANIMALS:name> + </ANIMALS:Class> + + <ANIMALS:Class RDF:about="http://www.some-fictitious-zoo.com/fish"> + <ANIMALS:name>Fish</ANIMALS:name> + </ANIMALS:Class> + + <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/fish/cod"> + <ANIMALS:name>Cod</ANIMALS:name> + </RDF:Description> + + <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/fish/swordfish"> + <ANIMALS:name>Swordfish</ANIMALS:name> + </RDF:Description> + + <ANIMALS:Class RDF:about="http://www.some-fictitious-zoo.com/mammals"> + <ANIMALS:name>Mammals</ANIMALS:name> + </ANIMALS:Class> + + <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/mammals/lion"> + <ANIMALS:name>Lion</ANIMALS:name> + </RDF:Description> + + <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/mammals/hippopotamus"> + <ANIMALS:name>HIPPOPOTAMUS</ANIMALS:name> + </RDF:Description> + + <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/mammals/africanelephant"> + <ANIMALS:name>African Elephant</ANIMALS:name> + </RDF:Description> + + <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/mammals/llama"> + <ANIMALS:name>LLAMA</ANIMALS:name> + </RDF:Description> + + <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/mammals/polarbear"> + <ANIMALS:name>Polar Bear</ANIMALS:name> + </RDF:Description> + + <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/mammals/aardvark"> + <ANIMALS:name>aardvark</ANIMALS:name> + </RDF:Description> + + <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo"> + <ANIMALS:name>Nine-banded Armadillo</ANIMALS:name> + </RDF:Description> + + <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/mammals/gorilla"> + <ANIMALS:name>Gorilla</ANIMALS:name> + </RDF:Description> + + <ANIMALS:Class RDF:about="http://www.some-fictitious-zoo.com/reptiles"> + <ANIMALS:name>Reptiles</ANIMALS:name> + </ANIMALS:Class> + + <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/reptiles/anaconda"> + <ANIMALS:name>Anaconda</ANIMALS:name> + </RDF:Description> + + <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/reptiles/chameleon"> + <ANIMALS:name>Chameleon</ANIMALS:name> + </RDF:Description> + + <RDF:Seq RDF:about="http://www.some-fictitious-zoo.com/some-animals" ANIMALS:name="Zoo Animals"> + <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/arachnids"/> + <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/birds"/> + </RDF:Seq> + + <RDF:Seq RDF:about="http://www.some-fictitious-zoo.com/all-animals" ANIMALS:name="Zoo Animals"> + <RDF:li> + <RDF:Seq RDF:about="http://www.some-fictitious-zoo.com/arachnids"> + <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/arachnids/tarantula"/> + </RDF:Seq> + </RDF:li> + <RDF:li> + <RDF:Seq RDF:about="http://www.some-fictitious-zoo.com/birds"> + <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/birds/emu"/> + <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/birds/barnowl"/> + <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/birds/raven"/> + </RDF:Seq> + </RDF:li> + <RDF:li> + <RDF:Seq RDF:about="http://www.some-fictitious-zoo.com/crustaceans"/> + </RDF:li> + <RDF:li> + <RDF:Seq RDF:about="http://www.some-fictitious-zoo.com/fish"> + <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/fish/cod"/> + <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/fish/swordfish"/> + </RDF:Seq> + </RDF:li> + <RDF:li> + <RDF:Seq RDF:about="http://www.some-fictitious-zoo.com/mammals"> + <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/mammals/lion"/> + <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/mammals/hippopotamus"/> + <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/mammals/africanelephant"/> + <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/mammals/llama"/> + <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/mammals/polarbear"/> + <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/mammals/aardvark"/> + <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo"/> + <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/mammals/gorilla"/> + </RDF:Seq> + </RDF:li> + <RDF:li> + <RDF:Seq RDF:about="http://www.some-fictitious-zoo.com/reptiles"> + <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/reptiles/anaconda"/> + <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/reptiles/chameleon"/> + </RDF:Seq> + </RDF:li> + </RDF:Seq> + +</RDF:RDF> diff --git a/toolkit/components/xulstore/tests/chrome/chrome.ini b/toolkit/components/xulstore/tests/chrome/chrome.ini new file mode 100644 index 000000000..91efd5455 --- /dev/null +++ b/toolkit/components/xulstore/tests/chrome/chrome.ini @@ -0,0 +1,6 @@ +[DEFAULT] +support-files = + window_persistence.xul + animals.rdf + +[test_persistence.xul] diff --git a/toolkit/components/xulstore/tests/chrome/test_persistence.xul b/toolkit/components/xulstore/tests/chrome/test_persistence.xul new file mode 100644 index 000000000..736a067ed --- /dev/null +++ b/toolkit/components/xulstore/tests/chrome/test_persistence.xul @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Persistence Tests" + onload="runTest()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <script> + SimpleTest.waitForExplicitFinish(); + function runTest() { + window.openDialog("window_persistence.xul", "_blank", "chrome", true); + } + + function windowOpened() { + window.openDialog("window_persistence.xul", "_blank", "chrome", false); + } + + function testDone() { + SimpleTest.finish(); + } + </script> + +<body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"/> +</body> + +</window> diff --git a/toolkit/components/xulstore/tests/chrome/window_persistence.xul b/toolkit/components/xulstore/tests/chrome/window_persistence.xul new file mode 100644 index 000000000..4d76fe11d --- /dev/null +++ b/toolkit/components/xulstore/tests/chrome/window_persistence.xul @@ -0,0 +1,98 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Persistence Tests" + onload="opened()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + persist="screenX screenY width height"> + +<button id="button1" label="Button1" persist="value"/> +<button id="button2" label="Button2" value="Normal" persist="value"/> + +<tree id="tree" datasources="animals.rdf" ref="http://www.some-fictitious-zoo.com/all-animals" + flags="dont-build-content" width="200" height="200"> + <treecols orient="horizontal" id="treecols"> + <treecol id="treecol" primary="true" label="Name" flex="1"/> + </treecols> + <template id="template"> + <treechildren> + <treeitem uri="rdf:*"> + <treerow> + <treecell label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/> + <treecell/> + </treerow> + </treeitem> + </treechildren> + </template> +</tree> + +<script> +<![CDATA[ + +var Cc = Components.classes; +var Ci = Components.interfaces; + +let XULStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore); +let URI = "chrome://mochitests/content/chrome/toolkit/components/xulstore/tests/chrome/window_persistence.xul"; + +function opened() +{ + // If the data in the tree has not been loaded yet, wait a bit and try again. + var treeView = document.getElementById("tree").view; + if (treeView.rowCount != 6 && treeView.rowCount != 17) { + setTimeout(opened, 50); + return; + } + + runTest(treeView); +} + +function runTest(treeView) +{ + var firstRun = window.arguments[0]; + if (firstRun) { + document.getElementById("button1").setAttribute("value", "Pressed"); + document.getElementById("button2").removeAttribute("value"); + + document.getElementById("button2").setAttribute("foo", "bar"); + document.persist("button2", "foo"); + is(XULStore.getValue(URI, "button2", "foo"), "bar", "attribute persisted") + document.getElementById("button2").removeAttribute("foo"); + document.persist("button2", "foo"); + is(XULStore.hasValue(URI, "button2", "foo"), false, "attribute removed") + + is(treeView.rowCount, 6, "tree rows are closed"); + treeView.toggleOpenState(1); + treeView.toggleOpenState(7); + + window.close(); + window.opener.windowOpened(); + } + else { + is(document.getElementById("button1").getAttribute("value"), "Pressed", + "Attribute set"); + is(document.getElementById("button2").hasAttribute("value"), true, + "Attribute cleared"); + is(document.getElementById("button2").getAttribute("value"), "", + "Attribute cleared"); + is(document.getElementById("button2").hasAttribute("foo"), false, + "Attribute cleared"); + is(document.getElementById("button2").getAttribute("foo"), "", + "Attribute cleared"); + + is(treeView.rowCount, 17, "tree rows are open"); + is(treeView.isContainerOpen(0), false, "first closed row"); + is(treeView.isContainerOpen(1), true, "first open row"); + is(treeView.isContainerOpen(7), true, "second open row"); + + window.close(); + window.opener.testDone(); + } +} + +function is(l, r, n) { window.opener.wrappedJSObject.SimpleTest.is(l,r,n); } + +]]></script> + +</window> diff --git a/toolkit/components/xulstore/tests/xpcshell/.eslintrc.js b/toolkit/components/xulstore/tests/xpcshell/.eslintrc.js new file mode 100644 index 000000000..d35787cd2 --- /dev/null +++ b/toolkit/components/xulstore/tests/xpcshell/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/xpcshell/xpcshell.eslintrc.js" + ] +}; diff --git a/toolkit/components/xulstore/tests/xpcshell/localstore.rdf b/toolkit/components/xulstore/tests/xpcshell/localstore.rdf new file mode 100644 index 000000000..458eb50ea --- /dev/null +++ b/toolkit/components/xulstore/tests/xpcshell/localstore.rdf @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<RDF:RDF xmlns:NC="http://home.netscape.com/NC-rdf#" + xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> + <RDF:Description RDF:about="chrome://browser/content/browser.xul#sidebar-title" + value="" /> + <RDF:Description RDF:about="about:config#prefCol" + ordinal="1" + sortDirection="ascending" /> + <RDF:Description RDF:about="chrome://browser/content/browser.xul#addon-bar" + collapsed="true" /> + <RDF:Description RDF:about="about:config"> + <NC:persist RDF:resource="about:config#prefCol"/> + <NC:persist RDF:resource="about:config#lockCol"/> + <NC:persist RDF:resource="about:config#typeCol"/> + <NC:persist RDF:resource="about:config#valueCol"/> + </RDF:Description> + <RDF:Description RDF:about="about:config#lockCol" + ordinal="3" /> + <RDF:Description RDF:about="chrome://browser/content/browser.xul"> + <NC:persist RDF:resource="chrome://browser/content/browser.xul#main-window"/> + <NC:persist RDF:resource="chrome://browser/content/browser.xul#addon-bar"/> + <NC:persist RDF:resource="chrome://browser/content/browser.xul#sidebar-box"/> + <NC:persist RDF:resource="chrome://browser/content/browser.xul#sidebar-title"/> + </RDF:Description> + <RDF:Description RDF:about="chrome://browser/content/browser.xul#main-window" + width="994" + height="768" + screenX="4" + screenY="22" + sizemode="normal" /> +</RDF:RDF> diff --git a/toolkit/components/xulstore/tests/xpcshell/test_XULStore.js b/toolkit/components/xulstore/tests/xpcshell/test_XULStore.js new file mode 100644 index 000000000..c3c96654b --- /dev/null +++ b/toolkit/components/xulstore/tests/xpcshell/test_XULStore.js @@ -0,0 +1,199 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/◦ +*/ + +"use strict" + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; +const Cr = Components.results; + +Cu.import("resource://gre/modules/osfile.jsm") + +var XULStore = null; +var browserURI = "chrome://browser/content/browser.xul"; +var aboutURI = "about:config"; + +function run_test() { + do_get_profile(); + run_next_test(); +} + +function checkValue(uri, id, attr, reference) { + let value = XULStore.getValue(uri, id, attr); + do_check_true(value === reference); +} + +function checkValueExists(uri, id, attr, exists) { + do_check_eq(XULStore.hasValue(uri, id, attr), exists); +} + +function getIDs(uri) { + let it = XULStore.getIDsEnumerator(uri); + let result = []; + + while (it.hasMore()) { + let value = it.getNext(); + result.push(value); + } + + result.sort(); + return result; +} + +function getAttributes(uri, id) { + let it = XULStore.getAttributeEnumerator(uri, id); + + let result = []; + + while (it.hasMore()) { + let value = it.getNext(); + result.push(value); + } + + result.sort(); + return result; +} + +function checkArrays(a, b) { + a.sort(); + b.sort(); + do_check_true(a.toString() == b.toString()); +} + +function checkOldStore() { + checkArrays(['addon-bar', 'main-window', 'sidebar-title'], getIDs(browserURI)); + checkArrays(['collapsed'], getAttributes(browserURI, 'addon-bar')); + checkArrays(['height', 'screenX', 'screenY', 'sizemode', 'width'], + getAttributes(browserURI, 'main-window')); + checkArrays(['value'], getAttributes(browserURI, 'sidebar-title')); + + checkValue(browserURI, "addon-bar", "collapsed", "true"); + checkValue(browserURI, "main-window", "width", "994"); + checkValue(browserURI, "main-window", "height", "768"); + checkValue(browserURI, "main-window", "screenX", "4"); + checkValue(browserURI, "main-window", "screenY", "22"); + checkValue(browserURI, "main-window", "sizemode", "normal"); + checkValue(browserURI, "sidebar-title", "value", ""); + + checkArrays(['lockCol', 'prefCol'], getIDs(aboutURI)); + checkArrays(['ordinal'], getAttributes(aboutURI, 'lockCol')); + checkArrays(['ordinal', 'sortDirection'], getAttributes(aboutURI, 'prefCol')); + + checkValue(aboutURI, "prefCol", "ordinal", "1"); + checkValue(aboutURI, "prefCol", "sortDirection", "ascending"); + checkValue(aboutURI, "lockCol", "ordinal", "3"); +} + +add_task(function* testImport() { + let src = "localstore.rdf"; + let dst = OS.Path.join(OS.Constants.Path.profileDir, src); + + yield OS.File.copy(src, dst); + + // Importing relies on XULStore not yet being loaded before this point. + XULStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore); + checkOldStore(); +}); + +add_task(function* testTruncation() { + let dos = Array(8192).join("~"); + // Long id names should trigger an exception + Assert.throws(() => XULStore.setValue(browserURI, dos, "foo", "foo"), /NS_ERROR_ILLEGAL_VALUE/); + + // Long attr names should trigger an exception + Assert.throws(() => XULStore.setValue(browserURI, "foo", dos, "foo"), /NS_ERROR_ILLEGAL_VALUE/); + + // Long values should be truncated + XULStore.setValue(browserURI, "dos", "dos", dos); + dos =XULStore.getValue(browserURI, "dos", "dos"); + do_check_true(dos.length == 4096) + XULStore.removeValue(browserURI, "dos", "dos") +}); + +add_task(function* testGetValue() { + // Get non-existing property + checkValue(browserURI, "side-window", "height", ""); + + // Get existing property + checkValue(browserURI, "main-window", "width", "994"); +}); + +add_task(function* testHasValue() { + // Check non-existing property + checkValueExists(browserURI, "side-window", "height", false); + + // Check existing property + checkValueExists(browserURI, "main-window", "width", true); +}); + +add_task(function* testSetValue() { + // Set new attribute + checkValue(browserURI, "side-bar", "width", ""); + XULStore.setValue(browserURI, "side-bar", "width", "1000"); + checkValue(browserURI, "side-bar", "width", "1000"); + checkArrays(["addon-bar", "main-window", "side-bar", "sidebar-title"], getIDs(browserURI)); + checkArrays(["width"], getAttributes(browserURI, 'side-bar')); + + // Modify existing property + checkValue(browserURI, "side-bar", "width", "1000"); + XULStore.setValue(browserURI, "side-bar", "width", "1024"); + checkValue(browserURI, "side-bar", "width", "1024"); + checkArrays(["addon-bar", "main-window", "side-bar", "sidebar-title"], getIDs(browserURI)); + checkArrays(["width"], getAttributes(browserURI, 'side-bar')); + + // Add another attribute + checkValue(browserURI, "side-bar", "height", ""); + XULStore.setValue(browserURI, "side-bar", "height", "1000"); + checkValue(browserURI, "side-bar", "height", "1000"); + checkArrays(["addon-bar", "main-window", "side-bar", "sidebar-title"], getIDs(browserURI)); + checkArrays(["width", "height"], getAttributes(browserURI, 'side-bar')); +}); + +add_task(function* testRemoveValue() { + // Remove first attribute + checkValue(browserURI, "side-bar", "width", "1024"); + XULStore.removeValue(browserURI, "side-bar", "width"); + checkValue(browserURI, "side-bar", "width", ""); + checkValueExists(browserURI, "side-bar", "width", false); + checkArrays(["addon-bar", "main-window", "side-bar", "sidebar-title"], getIDs(browserURI)); + checkArrays(["height"], getAttributes(browserURI, 'side-bar')); + + // Remove second attribute + checkValue(browserURI, "side-bar", "height", "1000"); + XULStore.removeValue(browserURI, "side-bar", "height"); + checkValue(browserURI, "side-bar", "height", ""); + checkArrays(["addon-bar", "main-window", "sidebar-title"], getIDs(browserURI)); + + // Removing an attribute that doesn't exists shouldn't fail + XULStore.removeValue(browserURI, "main-window", "bar"); + + // Removing from an id that doesn't exists shouldn't fail + XULStore.removeValue(browserURI, "foo", "bar"); + + // Removing from a document that doesn't exists shouldn't fail + let nonDocURI = "chrome://example/content/other.xul"; + XULStore.removeValue(nonDocURI, "foo", "bar"); + + // Remove all attributes in browserURI + XULStore.removeValue(browserURI, "addon-bar", "collapsed"); + checkArrays([], getAttributes(browserURI, "addon-bar")); + XULStore.removeValue(browserURI, "main-window", "width"); + XULStore.removeValue(browserURI, "main-window", "height"); + XULStore.removeValue(browserURI, "main-window", "screenX"); + XULStore.removeValue(browserURI, "main-window", "screenY"); + XULStore.removeValue(browserURI, "main-window", "sizemode"); + checkArrays([], getAttributes(browserURI, "main-window")); + XULStore.removeValue(browserURI, "sidebar-title", "value"); + checkArrays([], getAttributes(browserURI, "sidebar-title")); + checkArrays([], getIDs(browserURI)); + + // Remove all attributes in aboutURI + XULStore.removeValue(aboutURI, "prefCol", "ordinal"); + XULStore.removeValue(aboutURI, "prefCol", "sortDirection"); + checkArrays([], getAttributes(aboutURI, "prefCol")); + XULStore.removeValue(aboutURI, "lockCol", "ordinal"); + checkArrays([], getAttributes(aboutURI, "lockCol")); + checkArrays([], getIDs(aboutURI)); +}); diff --git a/toolkit/components/xulstore/tests/xpcshell/xpcshell.ini b/toolkit/components/xulstore/tests/xpcshell/xpcshell.ini new file mode 100644 index 000000000..d483dae02 --- /dev/null +++ b/toolkit/components/xulstore/tests/xpcshell/xpcshell.ini @@ -0,0 +1,6 @@ +[DEFAULT] +skip-if = toolkit == 'android' +support-files = + localstore.rdf + +[test_XULStore.js] |