Bug 559505 - Deprecate localstore.rdf and replace it with xulstore. r=enn, sr=bsmedberg
☠☠ backed out by dcd7b816b347 ☠ ☠
authorRoberto A. Vitillo <rvitillo@mozilla.com>
Tue, 19 Aug 2014 10:31:00 -0400
changeset 200579 143ae44587b25508b39f985aa96f549c88b1f3e1
parent 200578 38769be77c601a02fe9285462679f8894e920622
child 200580 73d3b14d6b01c0214ba81ff80a2c0cb2f7507509
push id47931
push userryanvm@gmail.com
push dateWed, 20 Aug 2014 14:23:31 +0000
treeherdermozilla-inbound@9f20a7ec2c1e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersenn, bsmedberg
bugs559505
milestone34.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 559505 - Deprecate localstore.rdf and replace it with xulstore. r=enn, sr=bsmedberg
b2g/installer/package-manifest.in
browser/components/nsBrowserGlue.js
browser/components/places/PlacesUIUtils.jsm
browser/components/places/content/treeView.js
browser/components/places/tests/browser/browser_toolbar_migration.js
browser/installer/package-manifest.in
content/xul/document/src/XULDocument.cpp
content/xul/document/src/XULDocument.h
content/xul/templates/src/nsXULContentUtils.cpp
content/xul/templates/src/nsXULContentUtils.h
content/xul/templates/src/nsXULTreeBuilder.cpp
mobile/android/installer/package-manifest.in
toolkit/components/moz.build
toolkit/components/places/nsNavHistory.cpp
toolkit/components/xulstore/XULStore.js
toolkit/components/xulstore/XULStore.manifest
toolkit/components/xulstore/moz.build
toolkit/components/xulstore/nsIXULStore.idl
toolkit/components/xulstore/tests/chrome/animals.rdf
toolkit/components/xulstore/tests/chrome/chrome.ini
toolkit/components/xulstore/tests/chrome/test_persistence.xul
toolkit/components/xulstore/tests/chrome/window_persistence.xul
toolkit/components/xulstore/tests/moz.build
toolkit/components/xulstore/tests/xpcshell/localstore.rdf
toolkit/components/xulstore/tests/xpcshell/test_XULStore.js
toolkit/components/xulstore/tests/xpcshell/xpcshell.ini
toolkit/mozapps/extensions/test/browser/browser_bug562797.js
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -291,16 +291,17 @@
 @BINPATH@/components/shellservice.xpt
 @BINPATH@/components/shistory.xpt
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
 @BINPATH@/components/telemetry.xpt
 @BINPATH@/components/toolkit_finalizationwitness.xpt
 @BINPATH@/components/toolkit_formautofill.xpt
 @BINPATH@/components/toolkit_osfile.xpt
+@BINPATH@/components/toolkit_xulstore.xpt
 @BINPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @BINPATH@/components/toolkitremote.xpt
 #endif
 @BINPATH@/components/txtsvc.xpt
 @BINPATH@/components/txmgr.xpt
 #ifdef MOZ_USE_NATIVE_UCONV
 @BINPATH@/components/ucnative.xpt
@@ -535,16 +536,18 @@
 @BINPATH@/components/HealthReportService.js
 #endif
 #ifdef MOZ_CAPTIVEDETECT
 @BINPATH@/components/CaptivePortalDetectComponents.manifest
 @BINPATH@/components/captivedetect.js
 #endif
 @BINPATH@/components/TelemetryStartup.js
 @BINPATH@/components/TelemetryStartup.manifest
+@BINPATH@/components/XULStore.js
+@BINPATH@/components/XULStore.manifest
 @BINPATH@/components/Webapps.js
 @BINPATH@/components/Webapps.manifest
 @BINPATH@/components/AppsService.js
 @BINPATH@/components/AppsService.manifest
 @BINPATH@/components/Push.js
 @BINPATH@/components/Push.manifest
 @BINPATH@/components/PushServiceLauncher.js
 
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1315,118 +1315,105 @@ BrowserGlue.prototype = {
     var notification = notifyBox.appendNotification(text, title, null,
                                                     notifyBox.PRIORITY_CRITICAL_MEDIUM,
                                                     buttons);
     notification.persistence = -1; // Until user closes it
   },
 
   _migrateUI: function BG__migrateUI() {
     const UI_VERSION = 23;
-    const BROWSER_DOCURL = "chrome://browser/content/browser.xul#";
+    const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
     let currentUIVersion = 0;
     try {
       currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
     } catch(ex) {}
     if (currentUIVersion >= UI_VERSION)
       return;
 
-    this._rdf = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
-    this._dataSource = this._rdf.GetDataSource("rdf:local-store");
-    this._dirty = false;
+    let xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
 
     if (currentUIVersion < 2) {
       // This code adds the customizable bookmarks button.
-      let currentsetResource = this._rdf.GetResource("currentset");
-      let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
-      let currentset = this._getPersist(toolbarResource, currentsetResource);
+      let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
       // Need to migrate only if toolbar is customized and the element is not found.
       if (currentset &&
           currentset.indexOf("bookmarks-menu-button-container") == -1) {
         currentset += ",bookmarks-menu-button-container";
-        this._setPersist(toolbarResource, currentsetResource, currentset);
+        xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
       }
     }
 
     if (currentUIVersion < 3) {
       // This code merges the reload/stop/go button into the url bar.
-      let currentsetResource = this._rdf.GetResource("currentset");
-      let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
-      let currentset = this._getPersist(toolbarResource, currentsetResource);
+      let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
       // Need to migrate only if toolbar is customized and all 3 elements are found.
       if (currentset &&
           currentset.indexOf("reload-button") != -1 &&
           currentset.indexOf("stop-button") != -1 &&
           currentset.indexOf("urlbar-container") != -1 &&
           currentset.indexOf("urlbar-container,reload-button,stop-button") == -1) {
         currentset = currentset.replace(/(^|,)reload-button($|,)/, "$1$2")
                                .replace(/(^|,)stop-button($|,)/, "$1$2")
                                .replace(/(^|,)urlbar-container($|,)/,
                                         "$1urlbar-container,reload-button,stop-button$2");
-        this._setPersist(toolbarResource, currentsetResource, currentset);
+        xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
       }
     }
 
     if (currentUIVersion < 4) {
       // This code moves the home button to the immediate left of the bookmarks menu button.
-      let currentsetResource = this._rdf.GetResource("currentset");
-      let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
-      let currentset = this._getPersist(toolbarResource, currentsetResource);
+      let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
       // Need to migrate only if toolbar is customized and the elements are found.
       if (currentset &&
           currentset.indexOf("home-button") != -1 &&
           currentset.indexOf("bookmarks-menu-button-container") != -1) {
         currentset = currentset.replace(/(^|,)home-button($|,)/, "$1$2")
                                .replace(/(^|,)bookmarks-menu-button-container($|,)/,
                                         "$1home-button,bookmarks-menu-button-container$2");
-        this._setPersist(toolbarResource, currentsetResource, currentset);
+        xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
       }
     }
 
     if (currentUIVersion < 5) {
       // This code uncollapses PersonalToolbar if its collapsed status is not
       // persisted, and user customized it or changed default bookmarks.
-      let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "PersonalToolbar");
-      let collapsedResource = this._rdf.GetResource("collapsed");
-      let collapsed = this._getPersist(toolbarResource, collapsedResource);
+      //
       // If the user does not have a persisted value for the toolbar's
       // "collapsed" attribute, try to determine whether it's customized.
-      if (collapsed === null) {
+      if (!xulStore.hasValue(BROWSER_DOCURL, "PersonalToolbar", "collapsed")) {
         // We consider the toolbar customized if it has more than
         // 3 children, or if it has a persisted currentset value.
-        let currentsetResource = this._rdf.GetResource("currentset");
-        let toolbarIsCustomized = !!this._getPersist(toolbarResource,
-                                                     currentsetResource);
+        let toolbarIsCustomized = xulStore.hasValue(BROWSER_DOCURL,
+                                                    "PersonalToolbar", "currentset");
         let getToolbarFolderCount = function () {
           let toolbarFolder =
             PlacesUtils.getFolderContents(PlacesUtils.toolbarFolderId).root;
           let toolbarChildCount = toolbarFolder.childCount;
           toolbarFolder.containerOpen = false;
           return toolbarChildCount;
         };
 
         if (toolbarIsCustomized || getToolbarFolderCount() > 3) {
-          this._setPersist(toolbarResource, collapsedResource, "false");
+          xulStore.setValue(BROWSER_DOCURL, "PersonalToolbar", "collapsed", "false");
         }
       }
     }
 
     if (currentUIVersion < 8) {
       // Reset homepage pref for users who have it set to google.com/firefox
       let uri = Services.prefs.getComplexValue("browser.startup.homepage",
                                                Ci.nsIPrefLocalizedString).data;
       if (uri && /^https?:\/\/(www\.)?google(\.\w{2,3}){1,2}\/firefox\/?$/.test(uri)) {
         Services.prefs.clearUserPref("browser.startup.homepage");
       }
     }
 
     if (currentUIVersion < 9) {
       // This code adds the customizable downloads buttons.
-      let currentsetResource = this._rdf.GetResource("currentset");
-      let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
-      let currentset = this._getPersist(toolbarResource, currentsetResource);
+      let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
 
       // Since the Downloads button is located in the navigation bar by default,
       // migration needs to happen only if the toolbar was customized using a
       // previous UI version, and the button was not already placed on the
       // toolbar manually.
       if (currentset &&
           currentset.indexOf("downloads-button") == -1) {
         // The element is added either after the search bar or before the home
@@ -1437,17 +1424,17 @@ BrowserGlue.prototype = {
                                           "$1search-container,downloads-button$2")
         } else if (currentset.indexOf("home-button") != -1) {
           currentset = currentset.replace(/(^|,)home-button($|,)/,
                                           "$1downloads-button,home-button$2")
         } else {
           currentset = currentset.replace(/(^|,)window-controls($|,)/,
                                           "$1downloads-button,window-controls$2")
         }
-        this._setPersist(toolbarResource, currentsetResource, currentset);
+        xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
       }
     }
 
 #ifdef XP_WIN
     if (currentUIVersion < 10) {
       // For Windows systems with display set to > 96dpi (i.e. systemDefaultScale
       // will return a value > 1.0), we want to discard any saved full-zoom settings,
       // as we'll now be scaling the content according to the system resolution
@@ -1467,25 +1454,23 @@ BrowserGlue.prototype = {
       Services.prefs.clearUserPref("dom.event.contextmenu.enabled");
       Services.prefs.clearUserPref("javascript.enabled");
       Services.prefs.clearUserPref("permissions.default.image");
     }
 
     if (currentUIVersion < 12) {
       // Remove bookmarks-menu-button-container, then place
       // bookmarks-menu-button into its position.
-      let currentsetResource = this._rdf.GetResource("currentset");
-      let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
-      let currentset = this._getPersist(toolbarResource, currentsetResource);
+      let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
       // Need to migrate only if toolbar is customized.
       if (currentset) {
         if (currentset.contains("bookmarks-menu-button-container")) {
           currentset = currentset.replace(/(^|,)bookmarks-menu-button-container($|,)/,
                                           "$1bookmarks-menu-button$2");
-          this._setPersist(toolbarResource, currentsetResource, currentset);
+          xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
         }
       }
     }
 
     if (currentUIVersion < 13) {
       try {
         if (Services.prefs.getBoolPref("plugins.hide_infobar_for_missing_plugin"))
           Services.prefs.setBoolPref("plugins.notifyMissingFlash", false);
@@ -1496,62 +1481,54 @@ BrowserGlue.prototype = {
     if (currentUIVersion < 14) {
       // DOM Storage doesn't specially handle about: pages anymore.
       let path = OS.Path.join(OS.Constants.Path.profileDir,
                               "chromeappsstore.sqlite");
       OS.File.remove(path);
     }
 
     if (currentUIVersion < 16) {
-      let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
-      let collapsedResource = this._rdf.GetResource("collapsed");
-      let isCollapsed = this._getPersist(toolbarResource, collapsedResource);
+      let isCollapsed = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "collapsed");
       if (isCollapsed == "true") {
-        this._setPersist(toolbarResource, collapsedResource, "false");
+        xulStore.setValue(BROWSER_DOCURL, "nav-bar", "collapsed", "false");
       }
     }
 
     // Insert the bookmarks-menu-button into the nav-bar if it isn't already
     // there.
     if (currentUIVersion < 17) {
-      let currentsetResource = this._rdf.GetResource("currentset");
-      let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
-      let currentset = this._getPersist(toolbarResource, currentsetResource);
+      let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
       // Need to migrate only if toolbar is customized.
       if (currentset) {
         if (!currentset.contains("bookmarks-menu-button")) {
           // The button isn't in the nav-bar, so let's look for an appropriate
           // place to put it.
           if (currentset.contains("downloads-button")) {
             currentset = currentset.replace(/(^|,)downloads-button($|,)/,
                                             "$1bookmarks-menu-button,downloads-button$2");
           } else if (currentset.contains("home-button")) {
             currentset = currentset.replace(/(^|,)home-button($|,)/,
                                             "$1bookmarks-menu-button,home-button$2");
           } else {
             // Just append.
             currentset = currentset.replace(/(^|,)window-controls($|,)/,
                                             "$1bookmarks-menu-button,window-controls$2")
           }
-          this._setPersist(toolbarResource, currentsetResource, currentset);
+          xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
         }
       }
     }
 
     if (currentUIVersion < 18) {
       // Remove iconsize and mode from all the toolbars
       let toolbars = ["navigator-toolbox", "nav-bar", "PersonalToolbar",
                       "addon-bar", "TabsToolbar", "toolbar-menubar"];
       for (let resourceName of ["mode", "iconsize"]) {
-        let resource = this._rdf.GetResource(resourceName);
         for (let toolbarId of toolbars) {
-          let toolbar = this._rdf.GetResource(BROWSER_DOCURL + toolbarId);
-          if (this._getPersist(toolbar, resource)) {
-            this._setPersist(toolbar, resource);
-          }
+          xulStore.removeValue(BROWSER_DOCURL, toolbarId, resourceName);
         }
       }
     }
 
     if (currentUIVersion < 19) {
       let detector = null;    
       try {
         detector = Services.prefs.getComplexValue("intl.charset.detector",
@@ -1564,31 +1541,23 @@ BrowserGlue.prototype = {
         // If the encoding detector pref value is not reachable from the UI,
         // reset to default (varies by localization).
         Services.prefs.clearUserPref("intl.charset.detector");
       }
     }
 
     if (currentUIVersion < 20) {
       // Remove persisted collapsed state from TabsToolbar.
-      let resource = this._rdf.GetResource("collapsed");
-      let toolbar = this._rdf.GetResource(BROWSER_DOCURL + "TabsToolbar");
-      if (this._getPersist(toolbar, resource)) {
-        this._setPersist(toolbar, resource);
-      }
+      xulStore.removeValue(BROWSER_DOCURL, "TabsToolbar", "collapsed");
     }
 
     if (currentUIVersion < 21) {
       // Make sure the 'toolbarbutton-1' class will always be present from here
       // on out.
-      let button = this._rdf.GetResource(BROWSER_DOCURL + "bookmarks-menu-button");
-      let classResource = this._rdf.GetResource("class");
-      if (this._getPersist(button, classResource)) {
-        this._setPersist(button, classResource);
-      }
+      xulStore.removeValue(BROWSER_DOCURL, "bookmarks-menu-button", "class");
     }
 
     if (currentUIVersion < 22) {
       // Reset the Sync promobox count to promote the new FxAccount-based Sync.
       Services.prefs.clearUserPref("browser.syncPromoViewsLeft");
       Services.prefs.clearUserPref("browser.syncPromoViewsLeftMap");
     }
 
@@ -1598,59 +1567,20 @@ BrowserGlue.prototype = {
         try {
           let name = Services.prefs.getComplexValue(kSelectedEnginePref,
                                                     Ci.nsIPrefLocalizedString).data;
           Services.search.currentEngine = Services.search.getEngineByName(name);
         } catch (ex) {}
       }
     }
 
-    if (this._dirty)
-      this._dataSource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();
-
-    delete this._rdf;
-    delete this._dataSource;
-
     // Update the migration version.
     Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
   },
 
-  _getPersist: function BG__getPersist(aSource, aProperty) {
-    var target = this._dataSource.GetTarget(aSource, aProperty, true);
-    if (target instanceof Ci.nsIRDFLiteral)
-      return target.Value;
-    return null;
-  },
-
-  _setPersist: function BG__setPersist(aSource, aProperty, aTarget) {
-    this._dirty = true;
-    try {
-      var oldTarget = this._dataSource.GetTarget(aSource, aProperty, true);
-      if (oldTarget) {
-        if (aTarget)
-          this._dataSource.Change(aSource, aProperty, oldTarget, this._rdf.GetLiteral(aTarget));
-        else
-          this._dataSource.Unassert(aSource, aProperty, oldTarget);
-      }
-      else {
-        this._dataSource.Assert(aSource, aProperty, this._rdf.GetLiteral(aTarget), true);
-      }
-
-      // Add the entry to the persisted set for this document if it's not there.
-      // This code is mostly borrowed from XULDocument::Persist.
-      let docURL = aSource.ValueUTF8.split("#")[0];
-      let docResource = this._rdf.GetResource(docURL);
-      let persistResource = this._rdf.GetResource("http://home.netscape.com/NC-rdf#persist");
-      if (!this._dataSource.HasAssertion(docResource, persistResource, aSource, true)) {
-        this._dataSource.Assert(docResource, persistResource, aSource, true);
-      }
-    }
-    catch(ex) {}
-  },
-
   // ------------------------------
   // public nsIBrowserGlue members
   // ------------------------------
 
   sanitize: function BG_sanitize(aParentWindow) {
     this._sanitizer.sanitize(aParentWindow);
   },
 
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -1015,20 +1015,16 @@ this.PlacesUIUtils = {
            Weave.Service.engineManager.get("tabs").enabled;
   },
 };
 
 XPCOMUtils.defineLazyServiceGetter(PlacesUIUtils, "RDF",
                                    "@mozilla.org/rdf/rdf-service;1",
                                    "nsIRDFService");
 
-XPCOMUtils.defineLazyGetter(PlacesUIUtils, "localStore", function() {
-  return PlacesUIUtils.RDF.GetDataSource("rdf:local-store");
-});
-
 XPCOMUtils.defineLazyGetter(PlacesUIUtils, "ellipsis", function() {
   return Services.prefs.getComplexValue("intl.ellipsis",
                                         Ci.nsIPrefLocalizedString).data;
 });
 
 XPCOMUtils.defineLazyGetter(PlacesUIUtils, "useAsyncTransactions", function() {
   try {
     return Services.prefs.getBoolPref("browser.places.useAsyncTransactions");
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -18,16 +18,24 @@ function PlacesTreeView(aFlatList, aOnOp
   this._flatList = aFlatList;
   this._openContainerCallback = aOnOpenFlatContainer;
   this._controller = aController;
 }
 
 PlacesTreeView.prototype = {
   get wrappedJSObject() this,
 
+  __xulStore: null,
+  get _xulStore() {
+    if (!this.__xulStore) {
+      this.__xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
+    }
+    return this.__xulStore;
+  },
+
   __dateService: null,
   get _dateService() {
     if (!this.__dateService) {
       this.__dateService = Cc["@mozilla.org/intl/scriptabledateformat;1"].
                            getService(Ci.nsIScriptableDateFormat);
     }
     return this.__dateService;
   },
@@ -302,21 +310,25 @@ PlacesTreeView.prototype = {
 
       this._rows[row] = curChild;
       rowsInserted++;
 
       // Recursively do containers.
       if (!this._flatList &&
           curChild instanceof Ci.nsINavHistoryContainerResultNode &&
           !this._controller.hasCachedLivemarkInfo(curChild)) {
-        let resource = this._getResourceForNode(curChild);
-        let isopen = resource != null &&
-                     PlacesUIUtils.localStore.HasAssertion(resource,
-                                                           openLiteral,
-                                                           trueLiteral, true);
+        let uri = curChild.uri;
+        let isopen = false;
+
+        if (uri) {
+          let docURI = this._getDocumentURI();
+          let val = this._xulStore.getValue(docURI, uri, "open");
+          isopen = (val == "true");
+        }
+
         if (isopen != curChild.containerOpen)
           aToOpen.push(curChild);
         else if (curChild.containerOpen && curChild.childCount > 0)
           rowsInserted += this._buildVisibleSection(curChild, row + 1, aToOpen);
       }
     }
 
     return rowsInserted;
@@ -1104,21 +1116,26 @@ PlacesTreeView.prototype = {
     try {
       return this._getRowForNode(aNode, true);
     }
     catch(ex) { }
 
     return Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE;
   },
 
-  _getResourceForNode: function PTV_getResourceForNode(aNode)
+  // Retrieves an nsIURI for the document
+  _documentURI: null,
+  _getDocumentURI: function()
   {
-    let uri = aNode.uri;
-    NS_ASSERT(uri, "if there is no uri, we can't persist the open state");
-    return uri ? PlacesUIUtils.RDF.GetResource(uri) : null;
+    if (!this._documentURI) {
+      let ioService = Cc["@mozilla.org/network/io-service;1"].
+                      getService(Ci.nsIIOService);
+      this._documentURI = ioService.newURI(document.URL, null, null);
+    }
+    return this._documentURI;
   },
 
   // nsITreeView
   get rowCount() this._rows.length,
   get selection() this._selection,
   set selection(val) this._selection = val,
 
   getRowProperties: function() { return ""; },
@@ -1492,25 +1509,26 @@ PlacesTreeView.prototype = {
     let node = this._rows[aRow];
     if (this._flatList && this._openContainerCallback) {
       this._openContainerCallback(node);
       return;
     }
 
     // Persist containers open status, but never persist livemarks.
     if (!this._controller.hasCachedLivemarkInfo(node)) {
-      let resource = this._getResourceForNode(node);
-      if (resource) {
-        const openLiteral = PlacesUIUtils.RDF.GetResource("http://home.netscape.com/NC-rdf#open");
-        const trueLiteral = PlacesUIUtils.RDF.GetLiteral("true");
+      let uri = node.uri;
+
+      if (uri) {
+        let docURI = this._getDocumentURI();
 
-        if (node.containerOpen)
-          PlacesUIUtils.localStore.Unassert(resource, openLiteral, trueLiteral);
-        else
-          PlacesUIUtils.localStore.Assert(resource, openLiteral, trueLiteral, true);
+        if (node.containerOpen) {
+          this._xulStore.removeValue(docURI, uri, "open");
+        } else {
+          this._xulStore.setValue(docURI, uri, "open", "true");
+        }
       }
     }
 
     node.containerOpen = !node.containerOpen;
   },
 
   cycleHeader: function PTV_cycleHeader(aColumn) {
     if (!this._result)
--- a/browser/components/places/tests/browser/browser_toolbar_migration.js
+++ b/browser/components/places/tests/browser/browser_toolbar_migration.js
@@ -1,90 +1,53 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 /**
  * Tests PersonalToolbar migration path.
  */
-
 let bg = Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver);
 let gOriginalMigrationVersion;
 const BROWSER_URL = getBrowserURL();
 
 let localStore = {
-  get RDF() Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService),
-  get store() this.RDF.GetDataSource("rdf:local-store"),
+  get xulStore() Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore),
 
-  get toolbar()
+  getValue: function getValue(aProperty)
   {
-    delete this.toolbar;
-    let toolbar = this.RDF.GetResource(BROWSER_URL + "#PersonalToolbar");
-    // Add the entry to the persisted set for this document if it's not there.
-    // See XULDocument::Persist.
-    let doc = this.RDF.GetResource(BROWSER_URL);
-    let persist = this.RDF.GetResource("http://home.netscape.com/NC-rdf#persist");
-    if (!this.store.HasAssertion(doc, persist, toolbar, true)) {
-      this.store.Assert(doc, persist, toolbar, true);
-    }
-    return this.toolbar = toolbar;
+    return this.xulStore.getValue(BROWSER_URL, "PersonalToolbar", aProperty);
   },
 
-  getPersist: function getPersist(aProperty)
-  {
-    let property = this.RDF.GetResource(aProperty);
-    let target = this.store.GetTarget(this.toolbar, property, true);
-    if (target instanceof Ci.nsIRDFLiteral)
-      return target.Value;
-    return null;
-  },
-
-  setPersist: function setPersist(aProperty, aValue)
+  setValue: function setValue(aProperty, aValue)
   {
-    let property = this.RDF.GetResource(aProperty);
-    let value = aValue ? this.RDF.GetLiteral(aValue) : null;
-
-    try {
-      let oldTarget = this.store.GetTarget(this.toolbar, property, true);
-      if (oldTarget && value) {
-        this.store.Change(this.toolbar, property, oldTarget, value);
-      }
-      else if (value) {
-        this.store.Assert(this.toolbar, property, value, true);  
-      }
-      else if (oldTarget) {
-        this.store.Unassert(this.toolbar, property, oldTarget);
-      }
-      else {
-        return;
-      }
+    if (aValue) {
+      this.xulStore.setValue(BROWSER_URL, "PersonalToolbar", aProperty, aValue);
+    } else {
+      this.xulStore.removeValue(BROWSER_URL, "PersonalToolbar", aProperty);
     }
-    catch(ex) {
-      return;
-    }
-    this.store.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();
   }
 };
 
 let gTests = [
 
 function test_explicitly_collapsed_toolbar()
 {
   info("An explicitly collapsed toolbar should not be uncollapsed.");
-  localStore.setPersist("collapsed", "true");
+  localStore.setValue("collapsed", "true");
   bg.observe(null, "browser-glue-test", "force-ui-migration");
-  is(localStore.getPersist("collapsed"), "true", "Toolbar is collapsed");
+  is(localStore.getValue("collapsed"), "true", "Toolbar is collapsed");
 },
 
 function test_customized_toolbar()
 {
   info("A customized toolbar should be uncollapsed.");
-  localStore.setPersist("currentset", "splitter");
+  localStore.setValue("currentset", "splitter");
   bg.observe(null, "browser-glue-test", "force-ui-migration");
-  is(localStore.getPersist("collapsed"), "false", "Toolbar has been uncollapsed");
+  is(localStore.getValue("collapsed"), "false", "Toolbar has been uncollapsed");
 },
 
 function test_many_bookmarks_toolbar()
 {
   info("A toolbar with added bookmarks should be uncollapsed.");
   let ids = [];
   ids.push(
     PlacesUtils.bookmarks.insertSeparator(PlacesUtils.toolbarFolderId,
@@ -93,43 +56,47 @@ function test_many_bookmarks_toolbar()
   ids.push(
     PlacesUtils.bookmarks.insertSeparator(PlacesUtils.toolbarFolderId,
                                           PlacesUtils.bookmarks.DEFAULT_INDEX)
   );
   ids.push(
     PlacesUtils.bookmarks.insertSeparator(PlacesUtils.toolbarFolderId,
                                           PlacesUtils.bookmarks.DEFAULT_INDEX)
   );
+  ids.push(
+    PlacesUtils.bookmarks.insertSeparator(PlacesUtils.toolbarFolderId,
+                                          PlacesUtils.bookmarks.DEFAULT_INDEX)
+  );
   bg.observe(null, "browser-glue-test", "force-ui-migration");
-  is(localStore.getPersist("collapsed"), "false", "Toolbar has been uncollapsed");
+  is(localStore.getValue("collapsed"), "false", "Toolbar has been uncollapsed");
 },
 
 ];
 
 function test()
 {
   gOriginalMigrationVersion = Services.prefs.getIntPref("browser.migration.version");
   registerCleanupFunction(clean);
 
-  if (localStore.getPersist("currentset") !== null) {
+  if (localStore.getValue("currentset") !== null) {
     info("Toolbar currentset was persisted by a previous test, fixing it.");
-    localStore.setPersist("currentset", null);
+    localStore.setValue("currentset", null);
   }
 
-  if (localStore.getPersist("collapsed") !== null) {
+  if (localStore.getValue("collapsed") !== null) {
     info("Toolbar collapsed status was persisted by a previous test, fixing it.");
-    localStore.setPersist("collapsed", null);
+    localStore.setValue("collapsed", null);
   }
 
   while (gTests.length) {
     clean();
     Services.prefs.setIntPref("browser.migration.version", 4);
     gTests.shift().call();
   }
 }
 
 function clean()
 {
   Services.prefs.setIntPref("browser.migration.version", gOriginalMigrationVersion);
-  localStore.setPersist("currentset", null);
-  localStore.setPersist("collapsed", null);
+  localStore.setValue("currentset", null);
+  localStore.setValue("collapsed", null);
 }
 
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -298,16 +298,17 @@
 #endif
 @BINPATH@/browser/components/shellservice.xpt
 @BINPATH@/components/shistory.xpt
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
 @BINPATH@/components/toolkit_finalizationwitness.xpt
 @BINPATH@/components/toolkit_formautofill.xpt
 @BINPATH@/components/toolkit_osfile.xpt
+@BINPATH@/components/toolkit_xulstore.xpt
 @BINPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @BINPATH@/components/toolkitremote.xpt
 #endif
 @BINPATH@/components/txtsvc.xpt
 @BINPATH@/components/txmgr.xpt
 @BINPATH@/components/uconv.xpt
 @BINPATH@/components/unicharutil.xpt
@@ -498,16 +499,18 @@
 #ifdef MOZ_CAPTIVEDETECT
 @BINPATH@/components/CaptivePortalDetectComponents.manifest
 @BINPATH@/components/captivedetect.js
 #endif
 @BINPATH@/components/servicesComponents.manifest
 @BINPATH@/components/cryptoComponents.manifest
 @BINPATH@/components/TelemetryStartup.js
 @BINPATH@/components/TelemetryStartup.manifest
+@BINPATH@/components/XULStore.js
+@BINPATH@/components/XULStore.manifest
 @BINPATH@/components/messageWakeupService.js
 @BINPATH@/components/messageWakeupService.manifest
 @BINPATH@/components/SettingsManager.js
 @BINPATH@/components/SettingsManager.manifest
 @BINPATH@/components/Webapps.js
 @BINPATH@/components/Webapps.manifest
 @BINPATH@/components/AppsService.js
 @BINPATH@/components/AppsService.manifest
--- a/content/xul/document/src/XULDocument.cpp
+++ b/content/xul/document/src/XULDocument.cpp
@@ -28,32 +28,28 @@
 
 #include "nsError.h"
 #include "nsIBoxObject.h"
 #include "nsIChromeRegistry.h"
 #include "nsView.h"
 #include "nsViewManager.h"
 #include "nsIContentViewer.h"
 #include "nsIDOMXULElement.h"
-#include "nsIRDFNode.h"
-#include "nsIRDFRemoteDataSource.h"
-#include "nsIRDFService.h"
 #include "nsIStreamListener.h"
 #include "nsITimer.h"
 #include "nsDocShell.h"
 #include "nsGkAtoms.h"
 #include "nsXMLContentSink.h"
 #include "nsXULContentSink.h"
 #include "nsXULContentUtils.h"
 #include "nsIXULOverlayProvider.h"
+#include "nsIStringEnumerator.h"
 #include "nsNetUtil.h"
 #include "nsParserCIID.h"
 #include "nsPIBoxObject.h"
-#include "nsRDFCID.h"
-#include "nsILocalStore.h"
 #include "nsXPIDLString.h"
 #include "nsPIDOMWindow.h"
 #include "nsPIWindowRoot.h"
 #include "nsXULCommandDispatcher.h"
 #include "nsXULElement.h"
 #include "prlog.h"
 #include "rdf.h"
 #include "nsIFrame.h"
@@ -132,21 +128,16 @@ const uint32_t kMaxAttributeLength = 409
 
 //----------------------------------------------------------------------
 //
 // Statics
 //
 
 int32_t XULDocument::gRefCnt = 0;
 
-nsIRDFService* XULDocument::gRDFService;
-nsIRDFResource* XULDocument::kNC_persist;
-nsIRDFResource* XULDocument::kNC_attribute;
-nsIRDFResource* XULDocument::kNC_value;
-
 PRLogModuleInfo* XULDocument::gXULLog;
 
 //----------------------------------------------------------------------
 
 struct BroadcasterMapEntry : public PLDHashEntryHdr {
     Element*         mBroadcaster; // [WEAK]
     nsSmallVoidArray mListeners;   // [OWNING] of BroadcastListener objects
 };
@@ -225,36 +216,21 @@ XULDocument::~XULDocument()
     // look for persisted data:
     mPersistenceIds.Clear();
 
     // Destroy our broadcaster map.
     if (mBroadcasterMap) {
         PL_DHashTableDestroy(mBroadcasterMap);
     }
 
-    if (mLocalStore) {
-        nsCOMPtr<nsIRDFRemoteDataSource> remote =
-            do_QueryInterface(mLocalStore);
-        if (remote)
-            remote->Flush();
-    }
-
     delete mTemplateBuilderTable;
 
     Preferences::UnregisterCallback(XULDocument::DirectionChanged,
                                     "intl.uidirection.", this);
 
-    if (--gRefCnt == 0) {
-        NS_IF_RELEASE(gRDFService);
-
-        NS_IF_RELEASE(kNC_persist);
-        NS_IF_RELEASE(kNC_attribute);
-        NS_IF_RELEASE(kNC_value);
-    }
-
     if (mOffThreadCompileStringBuf) {
       js_free(mOffThreadCompileStringBuf);
     }
 }
 
 } // namespace dom
 } // namespace mozilla
 
@@ -344,16 +320,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
     }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XULDocument, XMLDocument)
     delete tmp->mTemplateBuilderTable;
     tmp->mTemplateBuilderTable = nullptr;
 
     NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
+    NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStore)
     //XXX We should probably unlink all the objects we traverse.
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_ADDREF_INHERITED(XULDocument, XMLDocument)
 NS_IMPL_RELEASE_INHERITED(XULDocument, XMLDocument)
 
 
 // QueryInterface implementation for XULDocument
@@ -1326,126 +1303,60 @@ XULDocument::Persist(const nsAString& aI
         }
 
         tag = do_GetAtom(aAttr);
         NS_ENSURE_TRUE(tag, NS_ERROR_OUT_OF_MEMORY);
 
         nameSpaceID = kNameSpaceID_None;
     }
 
-    rv = Persist(element, nameSpaceID, tag);
-    if (NS_FAILED(rv)) return rv;
-
-    return NS_OK;
+    return Persist(element, nameSpaceID, tag);
 }
 
 nsresult
 XULDocument::Persist(nsIContent* aElement, int32_t aNameSpaceID,
                      nsIAtom* aAttribute)
 {
     // For non-chrome documents, persistance is simply broken
     if (!nsContentUtils::IsSystemPrincipal(NodePrincipal()))
         return NS_ERROR_NOT_AVAILABLE;
 
-    // First make sure we _have_ a local store to stuff the persisted
-    // information into. (We might not have one if profile information
-    // hasn't been loaded yet...)
-    if (!mLocalStore)
-        return NS_OK;
-
-    nsresult rv;
-
-    nsCOMPtr<nsIRDFResource> element;
-    rv = nsXULContentUtils::GetElementResource(aElement, getter_AddRefs(element));
-    if (NS_FAILED(rv)) return rv;
-
-    // No ID, so nothing to persist.
-    if (! element)
-        return NS_OK;
-
-    // Ick. Construct a property from the attribute. Punt on
-    // namespaces for now.
-    // Don't bother with unreasonable attributes. We clamp long values,
-    // but truncating attribute names turns it into a different attribute
-    // so there's no point in persisting anything at all
-    nsAtomCString attrstr(aAttribute);
-    if (attrstr.Length() > kMaxAttrNameLength) {
-        NS_WARNING("Can't persist, Attribute name too long");
-        return NS_ERROR_ILLEGAL_VALUE;
+    if (!mLocalStore) {
+        mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
+        if (NS_WARN_IF(!mLocalStore)) {
+            return NS_ERROR_NOT_INITIALIZED;
+        }
     }
 
-    nsCOMPtr<nsIRDFResource> attr;
-    rv = gRDFService->GetResource(attrstr,
-                                  getter_AddRefs(attr));
-    if (NS_FAILED(rv)) return rv;
-
-    // Turn the value into a literal
+    nsAutoString id;
+
+    aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
+    nsAtomString attrstr(aAttribute);
+
     nsAutoString valuestr;
     aElement->GetAttr(kNameSpaceID_None, aAttribute, valuestr);
 
-    // prevent over-long attributes that choke the parser (bug 319846)
-    // (can't simply Truncate without testing, it's implemented
-    // using SetLength and will grow a short string)
-    if (valuestr.Length() > kMaxAttributeLength) {
-        NS_WARNING("Truncating persisted attribute value");
-        valuestr.Truncate(kMaxAttributeLength);
-    }
-
-    // See if there was an old value...
-    nsCOMPtr<nsIRDFNode> oldvalue;
-    rv = mLocalStore->GetTarget(element, attr, true, getter_AddRefs(oldvalue));
-    if (NS_FAILED(rv)) return rv;
-
-    if (oldvalue && valuestr.IsEmpty()) {
-        // ...there was an oldvalue, and they've removed it. XXXThis
-        // handling isn't quite right...
-        rv = mLocalStore->Unassert(element, attr, oldvalue);
+    nsAutoCString utf8uri;
+    nsresult rv = mDocumentURI->GetSpec(utf8uri);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
     }
-    else {
-        // Now either 'change' or 'assert' based on whether there was
-        // an old value.
-        nsCOMPtr<nsIRDFLiteral> newvalue;
-        rv = gRDFService->GetLiteral(valuestr.get(), getter_AddRefs(newvalue));
-        if (NS_FAILED(rv)) return rv;
-
-        if (oldvalue) {
-            if (oldvalue != newvalue)
-                rv = mLocalStore->Change(element, attr, oldvalue, newvalue);
-            else
-                rv = NS_OK;
-        }
-        else {
-            rv = mLocalStore->Assert(element, attr, newvalue, true);
-        }
+    NS_ConvertUTF8toUTF16 uri(utf8uri);
+
+    bool hasAttr;
+    rv = mLocalStore->HasValue(uri, id, attrstr, &hasAttr);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
     }
 
-    if (NS_FAILED(rv)) return rv;
-
-    // Add it to the persisted set for this document (if it's not
-    // there already).
-    {
-        nsAutoCString docurl;
-        rv = mDocumentURI->GetSpec(docurl);
-        if (NS_FAILED(rv)) return rv;
-
-        nsCOMPtr<nsIRDFResource> doc;
-        rv = gRDFService->GetResource(docurl, getter_AddRefs(doc));
-        if (NS_FAILED(rv)) return rv;
-
-        bool hasAssertion;
-        rv = mLocalStore->HasAssertion(doc, kNC_persist, element, true, &hasAssertion);
-        if (NS_FAILED(rv)) return rv;
-
-        if (! hasAssertion) {
-            rv = mLocalStore->Assert(doc, kNC_persist, element, true);
-            if (NS_FAILED(rv)) return rv;
-        }
+    if (hasAttr && valuestr.IsEmpty()) {
+        return mLocalStore->RemoveValue(uri, id, attrstr);
+    } else {
+        return mLocalStore->SetValue(uri, id, attrstr, valuestr);
     }
-
-    return NS_OK;
 }
 
 
 nsresult
 XULDocument::GetViewportSize(int32_t* aWidth,
                              int32_t* aHeight)
 {
     *aWidth = *aHeight = 0;
@@ -1988,35 +1899,17 @@ XULDocument::Init()
 {
     nsresult rv = XMLDocument::Init();
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Create our command dispatcher and hook it up.
     mCommandDispatcher = new nsXULCommandDispatcher(this);
     NS_ENSURE_TRUE(mCommandDispatcher, NS_ERROR_OUT_OF_MEMORY);
 
-    // this _could_ fail; e.g., if we've tried to grab the local store
-    // before profiles have initialized. If so, no big deal; nothing
-    // will persist.
-    mLocalStore = do_GetService(NS_LOCALSTORE_CONTRACTID);
-
     if (gRefCnt++ == 0) {
-        // Keep the RDF service cached in a member variable to make using
-        // it a bit less painful
-        rv = CallGetService("@mozilla.org/rdf/rdf-service;1", &gRDFService);
-        NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF Service");
-        if (NS_FAILED(rv)) return rv;
-
-        gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "persist"),
-                                 &kNC_persist);
-        gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "attribute"),
-                                 &kNC_attribute);
-        gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "value"),
-                                 &kNC_value);
-
         // ensure that the XUL prototype cache is instantiated successfully,
         // so that we can use nsXULPrototypeCache::GetInstance() without
         // null-checks in the rest of the class.
         nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
         if (!cache) {
           NS_ERROR("Could not instantiate nsXULPrototypeCache");
           return NS_ERROR_FAILURE;
         }
@@ -2170,154 +2063,133 @@ nsresult
 XULDocument::ApplyPersistentAttributes()
 {
     // For non-chrome documents, persistance is simply broken
     if (!nsContentUtils::IsSystemPrincipal(NodePrincipal()))
         return NS_ERROR_NOT_AVAILABLE;
 
     // Add all of the 'persisted' attributes into the content
     // model.
-    if (!mLocalStore)
-        return NS_OK;
+    if (!mLocalStore) {
+        mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
+        if (NS_WARN_IF(!mLocalStore)) {
+            return NS_ERROR_NOT_INITIALIZED;
+        }
+    }
 
     mApplyingPersistedAttrs = true;
     ApplyPersistentAttributesInternal();
     mApplyingPersistedAttrs = false;
 
     // After we've applied persistence once, we should only reapply
     // it to nodes created by overlays
     mRestrictPersistence = true;
     mPersistenceIds.Clear();
 
     return NS_OK;
 }
 
 
-nsresult 
+nsresult
 XULDocument::ApplyPersistentAttributesInternal()
 {
     nsCOMArray<nsIContent> elements;
 
-    nsAutoCString docurl;
-    mDocumentURI->GetSpec(docurl);
-
-    nsCOMPtr<nsIRDFResource> doc;
-    gRDFService->GetResource(docurl, getter_AddRefs(doc));
-
-    nsCOMPtr<nsISimpleEnumerator> persisted;
-    mLocalStore->GetTargets(doc, kNC_persist, true, getter_AddRefs(persisted));
+    nsAutoCString utf8uri;
+    nsresult rv = mDocumentURI->GetSpec(utf8uri);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+    }
+    NS_ConvertUTF8toUTF16 uri(utf8uri);
+
+    // Get a list of element IDs for which persisted values are available
+    nsCOMPtr<nsIStringEnumerator> ids;
+    rv = mLocalStore->GetIDsEnumerator(uri, getter_AddRefs(ids));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+    }
 
     while (1) {
         bool hasmore = false;
-        persisted->HasMoreElements(&hasmore);
-        if (! hasmore)
+        ids->HasMore(&hasmore);
+        if (!hasmore) {
             break;
-
-        nsCOMPtr<nsISupports> isupports;
-        persisted->GetNext(getter_AddRefs(isupports));
-
-        nsCOMPtr<nsIRDFResource> resource = do_QueryInterface(isupports);
-        if (! resource) {
-            NS_WARNING("expected element to be a resource");
+        }
+
+        nsAutoString id;
+        ids->GetNext(id);
+
+        if (mRestrictPersistence && !mPersistenceIds.Contains(id)) {
             continue;
         }
 
-        const char *uri;
-        resource->GetValueConst(&uri);
-        if (! uri)
-            continue;
-
-        nsAutoString id;
-        nsXULContentUtils::MakeElementID(this, nsDependentCString(uri), id);
-
-        if (id.IsEmpty())
-            continue;
-
-        if (mRestrictPersistence && !mPersistenceIds.Contains(id))
-            continue;
-
         // This will clear the array if there are no elements.
         GetElementsForID(id, elements);
-
-        if (!elements.Count())
+        if (!elements.Count()) {
             continue;
-
-        ApplyPersistentAttributesToElements(resource, elements);
+        }
+
+        rv = ApplyPersistentAttributesToElements(id, elements);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+            return rv;
+        }
     }
 
     return NS_OK;
 }
 
 
 nsresult
-XULDocument::ApplyPersistentAttributesToElements(nsIRDFResource* aResource,
+XULDocument::ApplyPersistentAttributesToElements(const nsAString &aID,
                                                  nsCOMArray<nsIContent>& aElements)
 {
-    nsresult rv;
-
-    nsCOMPtr<nsISimpleEnumerator> attrs;
-    rv = mLocalStore->ArcLabelsOut(aResource, getter_AddRefs(attrs));
-    if (NS_FAILED(rv)) return rv;
+    nsAutoCString utf8uri;
+    nsresult rv = mDocumentURI->GetSpec(utf8uri);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+    }
+    NS_ConvertUTF8toUTF16 uri(utf8uri);
+
+    // Get a list of attributes for which persisted values are available
+    nsCOMPtr<nsIStringEnumerator> attrs;
+    rv = mLocalStore->GetAttributeEnumerator(uri, aID, getter_AddRefs(attrs));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+    }
 
     while (1) {
-        bool hasmore;
-        rv = attrs->HasMoreElements(&hasmore);
-        if (NS_FAILED(rv)) return rv;
-
-        if (! hasmore)
+        bool hasmore = PR_FALSE;
+        attrs->HasMore(&hasmore);
+        if (!hasmore) {
             break;
-
-        nsCOMPtr<nsISupports> isupports;
-        rv = attrs->GetNext(getter_AddRefs(isupports));
-        if (NS_FAILED(rv)) return rv;
-
-        nsCOMPtr<nsIRDFResource> property = do_QueryInterface(isupports);
-        if (! property) {
-            NS_WARNING("expected a resource");
-            continue;
         }
 
-        const char* attrname;
-        rv = property->GetValueConst(&attrname);
-        if (NS_FAILED(rv)) return rv;
-
-        nsCOMPtr<nsIAtom> attr = do_GetAtom(attrname);
-        if (! attr)
+        nsAutoString attrstr;
+        attrs->GetNext(attrstr);
+
+        nsAutoString value;
+        rv = mLocalStore->GetValue(uri, aID, attrstr, value);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+            return rv;
+        }
+
+        nsCOMPtr<nsIAtom> attr = do_GetAtom(attrstr);
+        if (NS_WARN_IF(!attr)) {
             return NS_ERROR_OUT_OF_MEMORY;
-
-        // XXX could hang namespace off here, as well...
-
-        nsCOMPtr<nsIRDFNode> node;
-        rv = mLocalStore->GetTarget(aResource, property, true,
-                                    getter_AddRefs(node));
-        if (NS_FAILED(rv)) return rv;
-
-        nsCOMPtr<nsIRDFLiteral> literal = do_QueryInterface(node);
-        if (! literal) {
-            NS_WARNING("expected a literal");
-            continue;
         }
 
-        const char16_t* value;
-        rv = literal->GetValueConst(&value);
-        if (NS_FAILED(rv)) return rv;
-
-        nsDependentString wrapper(value);
-
         uint32_t cnt = aElements.Count();
 
         for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) {
             nsCOMPtr<nsIContent> element = aElements.SafeObjectAt(i);
-            if (!element)
-                continue;
-
-            rv = element->SetAttr(/* XXX */ kNameSpaceID_None,
-                                  attr,
-                                  wrapper,
-                                  true);
+            if (!element) {
+                 continue;
+            }
+
+            rv = element->SetAttr(kNameSpaceID_None, attr, value, PR_TRUE);
         }
     }
 
     return NS_OK;
 }
 
 void
 XULDocument::TraceProtos(JSTracer* aTrc, uint32_t aGCNumber)
--- a/content/xul/document/src/XULDocument.h
+++ b/content/xul/document/src/XULDocument.h
@@ -17,16 +17,17 @@
 #include "nsIDOMXULCommandDispatcher.h"
 #include "nsIDOMXULDocument.h"
 #include "nsCOMArray.h"
 #include "nsIURI.h"
 #include "nsIXULDocument.h"
 #include "nsScriptLoader.h"
 #include "nsIStreamListener.h"
 #include "nsICSSLoaderObserver.h"
+#include "nsIXULStore.h"
 
 #include "mozilla/Attributes.h"
 
 #include "js/TracingAPI.h"
 #include "js/TypeDecls.h"
 
 class nsIRDFResource;
 class nsIRDFService;
@@ -254,17 +255,17 @@ protected:
                            nsIParser** aResult);
 
     nsresult 
     LoadOverlayInternal(nsIURI* aURI, bool aIsDynamic, bool* aShouldReturn,
                         bool* aFailureFromContent);
 
     nsresult ApplyPersistentAttributes();
     nsresult ApplyPersistentAttributesInternal();
-    nsresult ApplyPersistentAttributesToElements(nsIRDFResource* aResource,
+    nsresult ApplyPersistentAttributesToElements(const nsAString &aID,
                                                  nsCOMArray<nsIContent>& aElements);
 
     nsresult
     AddElementToDocumentPre(Element* aElement);
 
     nsresult
     AddElementToDocumentPost(Element* aElement);
 
@@ -309,20 +310,20 @@ protected:
     // NOTE, THIS IS STILL IN PROGRESS, TALK TO PINK OR SCC BEFORE
     // CHANGING
 
     XULDocument*             mNextSrcLoadWaiter;  // [OWNER] but not COMPtr
 
     // Tracks elements with a 'ref' attribute, or an 'id' attribute where
     // the element's namespace has no registered ID attribute name.
     nsTHashtable<nsRefMapEntry> mRefMap;
-    nsCOMPtr<nsIRDFDataSource> mLocalStore;
-    bool                       mApplyingPersistedAttrs;
-    bool                       mIsWritingFastLoad;
-    bool                       mDocumentLoaded;
+    nsCOMPtr<nsIXULStore>       mLocalStore;
+    bool                        mApplyingPersistedAttrs;
+    bool                        mIsWritingFastLoad;
+    bool                        mDocumentLoaded;
     /**
      * Since ResumeWalk is interruptible, it's possible that last
      * stylesheet finishes loading while the PD walk is still in
      * progress (waiting for an overlay to finish loading).
      * mStillWalking prevents DoneLoading (and StartLayout) from being
      * called in this situation.
      */
     bool                       mStillWalking;
--- a/content/xul/templates/src/nsXULContentUtils.cpp
+++ b/content/xul/templates/src/nsXULContentUtils.cpp
@@ -181,46 +181,16 @@ nsXULContentUtils::FindChildByTag(nsICon
         }
     }
 
     *aResult = nullptr;
     return NS_RDF_NO_VALUE; // not found
 }
 
 
-nsresult
-nsXULContentUtils::GetElementResource(nsIContent* aElement, nsIRDFResource** aResult)
-{
-    // Perform a reverse mapping from an element in the content model
-    // to an RDF resource.
-    nsresult rv;
-
-    char16_t buf[128];
-    nsFixedString id(buf, ArrayLength(buf), 0);
-
-    // Whoa.  Why the "id" attribute?  What if it's not even a XUL
-    // element?  This is totally bogus!
-    aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
-    if (id.IsEmpty())
-        return NS_ERROR_FAILURE;
-
-    // Since the element will store its ID attribute as a document-relative value,
-    // we may need to qualify it first...
-    nsCOMPtr<nsIDocument> doc = aElement->GetDocument();
-    NS_ASSERTION(doc, "element is not in any document");
-    if (! doc)
-        return NS_ERROR_FAILURE;
-
-    rv = nsXULContentUtils::MakeElementResource(doc, id, aResult);
-    if (NS_FAILED(rv)) return rv;
-
-    return NS_OK;
-}
-
-
 /*
 	Note: this routine is similar, yet distinctly different from, nsBookmarksService::GetTextForNode
 */
 
 nsresult
 nsXULContentUtils::GetTextForNode(nsIRDFNode* aNode, nsAString& aResult)
 {
     if (! aNode) {
@@ -283,88 +253,16 @@ nsXULContentUtils::GetTextForNode(nsIRDF
         return NS_OK;
     }
 
     NS_ERROR("not a resource or a literal");
     return NS_ERROR_UNEXPECTED;
 }
 
 nsresult
-nsXULContentUtils::MakeElementURI(nsIDocument* aDocument,
-                                  const nsAString& aElementID,
-                                  nsCString& aURI)
-{
-    // Convert an element's ID to a URI that can be used to refer to
-    // the element in the XUL graph.
-
-    nsIURI *docURI = aDocument->GetDocumentURI();
-    NS_ENSURE_TRUE(docURI, NS_ERROR_UNEXPECTED);
-
-    nsRefPtr<nsIURI> docURIClone;
-    nsresult rv = docURI->Clone(getter_AddRefs(docURIClone));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = docURIClone->SetRef(NS_ConvertUTF16toUTF8(aElementID));
-    if (NS_SUCCEEDED(rv)) {
-        return docURIClone->GetSpec(aURI);
-    }
-
-    // docURIClone is apparently immutable. Fine - we can append ref manually.
-    rv = docURI->GetSpec(aURI);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    nsAutoCString ref;
-    NS_EscapeURL(NS_ConvertUTF16toUTF8(aElementID), esc_FilePath | esc_AlwaysCopy, ref);
-
-    aURI.Append('#');
-    aURI.Append(ref);
-
-    return NS_OK;
-}
-
-
-nsresult
-nsXULContentUtils::MakeElementResource(nsIDocument* aDocument, const nsAString& aID, nsIRDFResource** aResult)
-{
-    nsresult rv;
-
-    char buf[256];
-    nsFixedCString uri(buf, sizeof(buf), 0);
-    rv = MakeElementURI(aDocument, aID, uri);
-    if (NS_FAILED(rv)) return rv;
-
-    rv = gRDF->GetResource(uri, aResult);
-    NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create resource");
-    if (NS_FAILED(rv)) return rv;
-
-    return NS_OK;
-}
-
-
-
-nsresult
-nsXULContentUtils::MakeElementID(nsIDocument* aDocument,
-                                 const nsACString& aURI,
-                                 nsAString& aElementID)
-{
-    // Convert a URI into an element ID that can be accessed from the
-    // DOM APIs.
-    nsCOMPtr<nsIURI> uri;
-    nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI,
-                            aDocument->GetDocumentCharacterSet().get());
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    nsAutoCString ref;
-    uri->GetRef(ref);
-    CopyUTF8toUTF16(ref, aElementID);
-
-    return NS_OK;
-}
-
-nsresult
 nsXULContentUtils::GetResource(int32_t aNameSpaceID, nsIAtom* aAttribute, nsIRDFResource** aResult)
 {
     // construct a fully-qualified URI from the namespace/tag pair.
     NS_PRECONDITION(aAttribute != nullptr, "null ptr");
     if (! aAttribute)
         return NS_ERROR_NULL_POINTER;
 
     return GetResource(aNameSpaceID, nsDependentAtomString(aAttribute),
--- a/content/xul/templates/src/nsXULContentUtils.h
+++ b/content/xul/templates/src/nsXULContentUtils.h
@@ -111,40 +111,18 @@ public:
                    nsIContent **aResult);
 
     static nsresult
     FindChildByResource(nsIContent* aElement,
                         nsIRDFResource* aResource,
                         nsIContent** aResult);
 
     static nsresult
-    GetElementResource(nsIContent* aElement, nsIRDFResource** aResult);
-
-    static nsresult
     GetTextForNode(nsIRDFNode* aNode, nsAString& aResult);
 
-    /**
-     * Construct a URI from the element ID given.  This uses aElement as the
-     * ref and aDocument's document URI as the base.  If aDocument's document
-     * URI does not support refs, this will throw NS_ERROR_NOT_AVAILABLE.
-     */
-    static nsresult
-    MakeElementURI(nsIDocument* aDocument, const nsAString& aElementID, nsCString& aURI);
-
-    static nsresult
-    MakeElementResource(nsIDocument* aDocument, const nsAString& aElementID, nsIRDFResource** aResult);
-
-    /**
-     * Extract the element ID from aURI.  Note that aURI must be an absolute
-     * URI string in UTF8; the element ID is the ref from the URI.  If the
-     * scheme does not support refs, then the ID will be empty.
-     */
-    static nsresult
-    MakeElementID(nsIDocument* aDocument, const nsACString& aURI, nsAString& aElementID);
-
     static nsresult
     GetResource(int32_t aNameSpaceID, nsIAtom* aAttribute, nsIRDFResource** aResult);
 
     static nsresult
     GetResource(int32_t aNameSpaceID, const nsAString& aAttribute, nsIRDFResource** aResult);
 
     static nsresult
     SetCommandUpdater(nsIDocument* aDocument, nsIContent* aElement);
--- a/content/xul/templates/src/nsXULTreeBuilder.cpp
+++ b/content/xul/templates/src/nsXULTreeBuilder.cpp
@@ -3,17 +3,16 @@
  * 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 "nscore.h"
 #include "nsError.h"
 #include "nsIContent.h"
 #include "mozilla/dom/NodeInfo.h"
 #include "nsIDOMElement.h"
-#include "nsILocalStore.h"
 #include "nsIBoxObject.h"
 #include "nsITreeBoxObject.h"
 #include "nsITreeSelection.h"
 #include "nsITreeColumns.h"
 #include "nsITreeView.h"
 #include "nsTreeUtils.h"
 #include "nsIServiceManager.h"
 #include "nsReadableUtils.h"
@@ -26,16 +25,17 @@
 #include "nsXULTemplateBuilder.h"
 #include "nsIXULSortService.h"
 #include "nsTArray.h"
 #include "nsUnicharUtils.h"
 #include "nsNameSpaceManager.h"
 #include "nsDOMClassInfoID.h"
 #include "nsWhitespaceTokenizer.h"
 #include "nsTreeContentView.h"
+#include "nsIXULStore.h"
 
 // For security check
 #include "nsIDocument.h"
 
 /**
  * A XUL template builder that serves as an tree view, allowing
  * (pretty much) arbitrary RDF to be presented in an tree.
  */
@@ -134,23 +134,20 @@ protected:
 
     /**
      * Remove the matches for the rows in a subtree
      */
     nsresult
     RemoveMatchesFor(nsTreeRows::Subtree& subtree);
 
     /**
-     * Helper methods that determine if the specified container is open.
+     * Helper method that determines if the specified container is open.
      */
-    nsresult
-    IsContainerOpen(nsIXULTemplateResult *aResult, bool* aOpen);
-
-    nsresult
-    IsContainerOpen(nsIRDFResource* aResource, bool* aOpen);
+    bool
+    IsContainerOpen(nsIXULTemplateResult* aResource);
 
     /**
      * A sorting callback for NS_QuickSort().
      */
     static int
     Compare(const void* aLeft, const void* aRight, void* aClosure);
 
     /**
@@ -237,16 +234,21 @@ protected:
      * Sort hints (compare case, etc)
      */
     uint32_t mSortHints;
 
     /** 
      * The builder observers.
      */
     nsCOMArray<nsIXULTreeBuilderObserver> mObservers;
+
+    /*
+     * XUL store for holding open container state
+     */
+    nsCOMPtr<nsIXULStore> mLocalStore;
 };
 
 //----------------------------------------------------------------------
 
 nsresult
 NS_NewXULTreeBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult)
 {
     *aResult = nullptr;
@@ -273,16 +275,17 @@ NS_NewXULTreeBuilder(nsISupports* aOuter
 
 NS_IMPL_ADDREF_INHERITED(nsXULTreeBuilder, nsXULTemplateBuilder)
 NS_IMPL_RELEASE_INHERITED(nsXULTreeBuilder, nsXULTemplateBuilder)
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsXULTreeBuilder, nsXULTemplateBuilder,
                                    mBoxObject,
                                    mSelection,
                                    mPersistStateStore,
+                                   mLocalStore,
                                    mObservers)
 
 DOMCI_DATA(XULTreeBuilder, nsXULTreeBuilder)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsXULTreeBuilder)
     NS_INTERFACE_MAP_ENTRY(nsIXULTreeBuilder)
     NS_INTERFACE_MAP_ENTRY(nsITreeView)
     NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(XULTreeBuilder)
@@ -523,18 +526,17 @@ nsXULTreeBuilder::IsContainerOpen(int32_
 {
     NS_PRECONDITION(aIndex >= 0 && aIndex < mRows.Count(), "bad row");
     if (aIndex < 0 || aIndex >= mRows.Count())
         return NS_ERROR_INVALID_ARG;
 
     nsTreeRows::iterator iter = mRows[aIndex];
 
     if (iter->mContainerState == nsTreeRows::eContainerState_Unknown) {
-        bool isOpen;
-        IsContainerOpen(iter->mMatch->mResult, &isOpen);
+        bool isOpen = IsContainerOpen(iter->mMatch->mResult);
 
         iter->mContainerState = isOpen
             ? nsTreeRows::eContainerState_Open
             : nsTreeRows::eContainerState_Closed;
     }
 
     *aOpen = (iter->mContainerState == nsTreeRows::eContainerState_Open);
     return NS_OK;
@@ -752,52 +754,26 @@ nsXULTreeBuilder::SetTree(nsITreeBoxObje
 
     // If this is teardown time, then we're done.
     if (!mBoxObject) {
         Uninit(false);
         return NS_OK;
     }
     NS_ENSURE_TRUE(mRoot, NS_ERROR_NOT_INITIALIZED);
 
-    // Is our root's principal trusted?
+    // Only use the XUL store if the root's principal is trusted.
     bool isTrusted = false;
     nsresult rv = IsSystemPrincipal(mRoot->NodePrincipal(), &isTrusted);
     if (NS_SUCCEEDED(rv) && isTrusted) {
-        // Get the datasource we intend to use to remember open state.
-        nsAutoString datasourceStr;
-        mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::statedatasource, datasourceStr);
-
-        // since we are trusted, use the user specified datasource
-        // if non specified, use localstore, which gives us
-        // persistence across sessions
-        if (! datasourceStr.IsEmpty()) {
-            gRDFService->GetDataSource(NS_ConvertUTF16toUTF8(datasourceStr).get(),
-                                       getter_AddRefs(mPersistStateStore));
-        }
-        else {
-            gRDFService->GetDataSource("rdf:local-store",
-                                       getter_AddRefs(mPersistStateStore));
+        mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
+        if(NS_WARN_IF(!mLocalStore)){
+            return NS_ERROR_NOT_INITIALIZED;
         }
     }
 
-    // Either no specific datasource was specified, or we failed
-    // to get one because we are not trusted.
-    //
-    // XXX if it were possible to ``write an arbitrary datasource
-    // back'', then we could also allow an untrusted document to
-    // use a statedatasource from the same codebase.
-    if (! mPersistStateStore) {
-        mPersistStateStore =
-            do_CreateInstance("@mozilla.org/rdf/datasource;1?name=in-memory-datasource");
-    }
-
-    NS_ASSERTION(mPersistStateStore, "failed to get a persistent state store");
-    if (! mPersistStateStore)
-        return NS_ERROR_FAILURE;
-
     Rebuild();
 
     EnsureSortVariables();
     if (mSortVariable)
         SortSubtree(mRows.GetRoot());
 
     return NS_OK;
 }
@@ -825,44 +801,46 @@ nsXULTreeBuilder::ToggleOpenState(int32_
 
     uint32_t count = mObservers.Count();
     for (uint32_t i = 0; i < count; ++i) {
         nsCOMPtr<nsIXULTreeBuilderObserver> observer = mObservers.SafeObjectAt(i);
         if (observer)
             observer->OnToggleOpenState(aIndex);
     }
 
-    if (mPersistStateStore) {
+    if (mLocalStore && mRoot) {
         bool isOpen;
         IsContainerOpen(aIndex, &isOpen);
 
-        nsCOMPtr<nsIRDFResource> container;
-        GetResourceFor(aIndex, getter_AddRefs(container));
-        if (! container)
+        nsIDocument* doc = mRoot->GetDocument();
+        if (!doc) {
             return NS_ERROR_FAILURE;
+        }
 
-        bool hasProperty;
-        IsContainerOpen(container, &hasProperty);
+        nsIURI* docURI = doc->GetDocumentURI();
+        nsTreeRows::Row& row = *(mRows[aIndex]);
+        nsAutoString nodeid;
+        nsresult rv = row.mMatch->mResult->GetId(nodeid);
+        if (NS_FAILED(rv)) {
+            return rv;
+        }
+
+        nsAutoCString utf8uri;
+        rv = docURI->GetSpec(utf8uri);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+            return rv;
+        }
+        NS_ConvertUTF8toUTF16 uri(utf8uri);
 
         if (isOpen) {
-            if (hasProperty) {
-                mPersistStateStore->Unassert(container,
-                                             nsXULContentUtils::NC_open,
-                                             nsXULContentUtils::true_);
-            }
-
+            mLocalStore->RemoveValue(uri, nodeid, NS_LITERAL_STRING("open"));
             CloseContainer(aIndex);
-        }
-        else {
-            if (! hasProperty) {
-                mPersistStateStore->Assert(container,
-                                           nsXULContentUtils::NC_open,
-                                           nsXULContentUtils::true_,
-                                           true);
-            }
+        } else {
+            mLocalStore->SetValue(uri, nodeid, NS_LITERAL_STRING("open"),
+                NS_LITERAL_STRING("true"));
 
             OpenContainer(aIndex, result);
         }
     }
 
     return NS_OK;
 }
 
@@ -1220,20 +1198,19 @@ nsXULTreeBuilder::ReplaceMatch(nsIXULTem
 
             if (result != mRootResult) {
                 // don't open containers if child processing isn't allowed
                 bool mayProcessChildren;
                 nsresult rv = result->GetMayProcessChildren(&mayProcessChildren);
                 if (NS_FAILED(rv) || ! mayProcessChildren) return NS_OK;
             }
 
-            bool open;
-            IsContainerOpen(result, &open);
-            if (open)
+            if (IsContainerOpen(result)) {
                 OpenContainer(iter.GetRowIndex(), result);
+            }
         }
     }
 
     return NS_OK;
 }
 
 nsresult
 nsXULTreeBuilder::SynchronizeResult(nsIXULTemplateResult* aResult)
@@ -1631,19 +1608,17 @@ nsXULTreeBuilder::OpenSubtreeForQuerySet
                     return rv;
                 }
 
                 // Remember that this match applied to this row
                 mRows.InsertRowAt(newmatch, aSubtree, count);
 
                 // If this is open, then remember it so we can recursively add
                 // *its* rows to the tree.
-                bool isOpen = false;
-                IsContainerOpen(nextresult, &isOpen);
-                if (isOpen) {
+                if (IsContainerOpen(nextresult)) {
                     if (open.AppendElement(count) == nullptr)
                         return NS_ERROR_OUT_OF_MEMORY;
                 }
 
                 ++count;
             }
 
             if (mFlags & eLoggingEnabled)
@@ -1717,46 +1692,52 @@ nsXULTreeBuilder::RemoveMatchesFor(nsTre
 
         if ((row.mContainerState == nsTreeRows::eContainerState_Open) && row.mSubtree)
             RemoveMatchesFor(*(row.mSubtree));
     }
 
     return NS_OK;
 }
 
-nsresult
-nsXULTreeBuilder::IsContainerOpen(nsIXULTemplateResult *aResult, bool* aOpen)
+
+bool
+nsXULTreeBuilder::IsContainerOpen(nsIXULTemplateResult *aResult)
 {
-    // items are never open if recursion is disabled
-    if ((mFlags & eDontRecurse) && aResult != mRootResult) {
-        *aOpen = false;
-        return NS_OK;
-    }
+  // items are never open if recursion is disabled
+  if ((mFlags & eDontRecurse) && aResult != mRootResult) {
+    return false;
+  }
 
-    nsCOMPtr<nsIRDFResource> id;
-    nsresult rv = GetResultResource(aResult, getter_AddRefs(id));
-    if (NS_FAILED(rv))
-        return rv;
+  if (!mLocalStore) {
+    return false;
+  }
+
+  nsIDocument* doc = mRoot->GetDocument();
+  if (!doc) {
+    return false;
+  }
 
-    return IsContainerOpen(id, aOpen);
-}
+  nsIURI* docURI = doc->GetDocumentURI();
+
+  nsAutoString nodeid;
+  nsresult rv = aResult->GetId(nodeid);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
 
-nsresult
-nsXULTreeBuilder::IsContainerOpen(nsIRDFResource* aResource, bool* aOpen)
-{
-    if (mPersistStateStore)
-        mPersistStateStore->HasAssertion(aResource,
-                                         nsXULContentUtils::NC_open,
-                                         nsXULContentUtils::true_,
-                                         true,
-                                         aOpen);
-    else
-        *aOpen = false;
+  nsAutoCString utf8uri;
+  rv = docURI->GetSpec(utf8uri);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+      return false;
+  }
+  NS_ConvertUTF8toUTF16 uri(utf8uri);
 
-    return NS_OK;
+  nsAutoString val;
+  mLocalStore->GetValue(uri, nodeid, NS_LITERAL_STRING("open"), val);
+  return val.EqualsLiteral("true");
 }
 
 int
 nsXULTreeBuilder::Compare(const void* aLeft, const void* aRight, void* aClosure)
 {
     nsXULTreeBuilder* self = static_cast<nsXULTreeBuilder*>(aClosure);
 
     nsTreeRows::Row* left = static_cast<nsTreeRows::Row*>
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -244,16 +244,17 @@
 @BINPATH@/components/shellservice.xpt
 @BINPATH@/components/shistory.xpt
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
 @BINPATH@/components/telemetry.xpt
 @BINPATH@/components/toolkit_finalizationwitness.xpt
 @BINPATH@/components/toolkit_formautofill.xpt
 @BINPATH@/components/toolkit_osfile.xpt
+@BINPATH@/components/toolkit_xulstore.xpt
 @BINPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @BINPATH@/components/toolkitremote.xpt
 #endif
 @BINPATH@/components/txtsvc.xpt
 @BINPATH@/components/txmgr.xpt
 @BINPATH@/components/uconv.xpt
 @BINPATH@/components/unicharutil.xpt
@@ -390,16 +391,18 @@
 #endif
 @BINPATH@/components/nsINIProcessor.manifest
 @BINPATH@/components/nsINIProcessor.js
 @BINPATH@/components/nsPrompter.manifest
 @BINPATH@/components/nsPrompter.js
 @BINPATH@/components/servicesComponents.manifest
 @BINPATH@/components/TelemetryStartup.js
 @BINPATH@/components/TelemetryStartup.manifest
+@BINPATH@/components/XULStore.js
+@BINPATH@/components/XULStore.manifest
 @BINPATH@/components/Webapps.js
 @BINPATH@/components/Webapps.manifest
 @BINPATH@/components/AppsService.js
 @BINPATH@/components/AppsService.manifest
 
 @BINPATH@/components/Push.js
 @BINPATH@/components/Push.manifest
 
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -44,16 +44,17 @@ DIRS += [
     'telemetry',
     'thumbnails',
     'typeaheadfind',
     'urlformatter',
     'viewconfig',
     'viewsource',
     'workerloader',
     'workerlz4',
+    'xulstore'
 ]
 
 if CONFIG['MOZ_CRASHREPORTER']:
     DIRS += ['crashes']
 
 if CONFIG['MOZ_SOCIAL']:
     DIRS += ['social']
 
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -3579,17 +3579,17 @@ nsNavHistory::ResultsAsList(mozIStorageS
   }
   return NS_OK;
 }
 
 const int64_t UNDEFINED_URN_VALUE = -1;
 
 // Create a urn (like
 // urn:places-persist:place:group=0&group=1&sort=1&type=1,,%28local%20files%29)
-// to be used to persist the open state of this container in localstore.rdf
+// to be used to persist the open state of this container
 nsresult
 CreatePlacesPersistURN(nsNavHistoryQueryResultNode *aResultNode, 
                        int64_t aValue, const nsCString& aTitle, nsCString& aURN)
 {
   nsAutoCString uri;
   nsresult rv = aResultNode->GetUri(uri);
   NS_ENSURE_SUCCESS(rv, rv);
 
new file mode 100644
--- /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() {
+  this.init();
+}
+
+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),
+  _writeTimerInitialized: false,
+
+  init: 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) {
+    if (topic == "profile-before-change") {
+      this.writeFile();
+      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  = 0600;
+
+    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() {
+    this._needsSaving = true;
+    if (this._writeTimerInitialized)
+      return;
+
+    let callback = () => {
+      this._writeTimerInitialized = false;
+      this.writeFile();
+    };
+
+    // Don't write the file more than once every 30 seconds.
+    this._writeTimerInitialized = true;
+    this._writeTimer.initWithCallback(callback, 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 > 1024 || attr.length > 1024 || value.length > 1024)
+      throw Components.Exception("id, attribute, or value too long", Cr.NS_ERROR_ILLEGAL_VALUE);
+
+    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]);
new file mode 100644
--- /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}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/xulstore/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; c-basic-offset: 4; 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/.
+
+TEST_DIRS += ['tests']
+
+XPIDL_SOURCES += [
+    'nsIXULStore.idl',
+]
+
+XPIDL_MODULE = 'toolkit_xulstore'
+
+EXTRA_COMPONENTS += [
+    'XULStore.js',
+    'XULStore.manifest',
+]
new file mode 100644
--- /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);
+};
new file mode 100644
--- /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>
new file mode 100644
--- /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]
new file mode 100644
--- /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>
new file mode 100644
--- /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[
+
+const Cc = Components.classes;
+const 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>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/xulstore/tests/moz.build
@@ -0,0 +1,6 @@
+# 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 += ['chrome/chrome.ini']
+XPCSHELL_TESTS_MANIFESTS += ['xpcshell/xpcshell.ini']
new file mode 100644
--- /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>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/xulstore/tests/xpcshell/test_XULStore.js
@@ -0,0 +1,184 @@
+/* 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")
+
+let XULStore = null;
+let browserURI = "chrome://browser/content/browser.xul";
+let 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* 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));
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/xulstore/tests/xpcshell/xpcshell.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+
+support-files =
+  localstore.rdf
+
+[test_XULStore.js]
--- a/toolkit/mozapps/extensions/test/browser/browser_bug562797.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug562797.js
@@ -471,27 +471,18 @@ add_test(function() {
   });
 });
 
 // Tests that going back to search results works
 add_test(function() {
   // Before we open the add-ons manager, we should make sure that no filter
   // has been set. If one is set, we remove it.
   // This is for the check below, from bug 611459.
-  let RDF = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
-  let store = RDF.GetDataSource("rdf:local-store");
-  let filterResource = RDF.GetResource("about:addons#search-filter-radiogroup");
-  let filterProperty = RDF.GetResource("value");
-  let filterTarget = store.GetTarget(filterResource, filterProperty, true);
-
-  if (filterTarget) {
-    is(filterTarget instanceof Ci.nsIRDFLiteral, true,
-       "Filter should be a value");
-    store.Unassert(filterResource, filterProperty, filterTarget);
-  }
+  let store = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
+  store.removeValue("about:addons", "search-filter-radiogroup", "value");
 
   open_manager("addons://list/extension", function(aManager) {
     info("Part 1");
     is_in_list(aManager, "addons://list/extension", false, false);
 
     var search = aManager.document.getElementById("header-search");
     search.focus();
     search.value = "bar";