Merge from mozilla-central.
authorDavid Anderson <danderson@mozilla.com>
Mon, 27 Feb 2012 12:40:46 -0800
changeset 105903 c757b4a747a5d92e54403998abe229cbff299a78
parent 105902 c6f122f35328c5fbdca218e7c613666bfef48207 (current diff)
parent 87821 8ea5c983743fbb7d6bcac41acea8f124eaf15e73 (diff)
child 105904 1714eb4edf547a0099c63a1f7e4c145ca7a35526
push id1075
push uservporof@mozilla.com
push dateThu, 13 Sep 2012 10:46:49 +0000
treeherderfx-team@f39786e8364d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone13.0a1
Merge from mozilla-central.
accessible/src/base/Statistics.h
accessible/src/base/nsARIAMap.cpp
accessible/src/base/nsAccessible.cpp
browser/app/profile/firefox.js
browser/base/content/browser.js
browser/base/content/browser.xul
browser/base/content/highlighter.css
browser/base/content/tabbrowser.xml
browser/components/nsBrowserGlue.js
browser/components/tabview/test/head.js
browser/devtools/highlighter/test/Makefile.in
browser/devtools/highlighter/test/browser_inspector_editor.js
browser/devtools/highlighter/test/browser_inspector_registertools.js
browser/devtools/highlighter/test/browser_inspector_tab_switch.js
browser/devtools/styleinspector/StyleInspector.jsm
browser/devtools/webconsole/HUDService.jsm
browser/devtools/webconsole/test/browser_gcli_web.js
browser/locales/en-US/chrome/browser/browser.dtd
browser/locales/en-US/chrome/browser/devtools/inspector.properties
browser/themes/pinstripe/livemark-item.png
browser/themes/winstripe/livemark-item-aero.png
browser/themes/winstripe/livemark-item.png
config/autoconf.mk.in
configure.in
content/base/public/nsINode.h
content/base/src/nsDOMTokenList.cpp
content/base/src/nsDocument.cpp
content/base/src/nsDocument.h
content/base/src/nsEventSource.cpp
content/base/src/nsGenericElement.cpp
content/base/src/nsNodeUtils.cpp
content/base/test/Makefile.in
content/canvas/src/CustomQS_Canvas2D.h
content/canvas/src/WebGLContext.cpp
content/html/content/src/nsGenericHTMLElement.cpp
content/html/content/src/nsHTMLCanvasElement.cpp
content/html/content/src/nsHTMLMediaElement.cpp
content/html/content/src/nsHTMLOptionElement.cpp
content/html/content/src/nsHTMLSelectElement.cpp
content/html/content/src/nsHTMLSelectElement.h
content/media/MediaResource.cpp
content/smil/nsSMILTimedElement.cpp
content/xul/templates/src/nsXULTemplateBuilder.cpp
docshell/base/nsDocShell.cpp
dom/base/nsDOMClassInfo.cpp
dom/base/nsJSEnvironment.cpp
dom/indexedDB/IDBIndex.cpp
dom/indexedDB/IDBObjectStore.cpp
dom/interfaces/canvas/nsIDOMWebGLRenderingContext.idl
dom/interfaces/css/nsIDOMCSS2Properties.idl
dom/plugins/base/nsJSNPRuntime.cpp
dom/workers/Events.cpp
dom/workers/File.cpp
dom/workers/WorkerPrivate.cpp
gfx/angle/README.mozilla
gfx/angle/src/libGLESv2/Context.cpp
gfx/angle/src/libGLESv2/Context.h
gfx/angle/src/libGLESv2/libGLESv2.cpp
gfx/thebes/gfxPlatform.h
gfx/thebes/gfxPlatformFontList.cpp
gfx/thebes/gfxPlatformFontList.h
gfx/thebes/gfxRect.h
js/jsd/jsd_val.c
js/src/Makefile.in
js/src/MemoryMetrics.cpp
js/src/configure.in
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/FoldConstants.cpp
js/src/frontend/ParseNode.h
js/src/gc/Barrier.h
js/src/ion/CompileInfo.h
js/src/jit-test/tests/jaeger/bug563000/eif-trap-newvar.js
js/src/jit-test/tests/jaeger/bug563000/eif-trap.js
js/src/jit-test/tests/jaeger/bug563000/trap-from-add-ool.js
js/src/jit-test/tests/jaeger/bug563000/trap-parent.js
js/src/jit-test/tests/jaeger/bug563000/trap-self-as-parent.js
js/src/jit-test/tests/jaeger/bug563000/trap-self-from-trap.js
js/src/jit-test/tests/jaeger/bug563000/trap-self.js
js/src/jsanalyze.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsarray.cpp
js/src/jsarray.h
js/src/jsbool.cpp
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jscompartment.cpp
js/src/jsdate.cpp
js/src/jsdate.h
js/src/jsgc.cpp
js/src/jsgc.h
js/src/jsinfer.cpp
js/src/jsinterp.cpp
js/src/jsiter.cpp
js/src/jsnum.cpp
js/src/jsnum.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/json.cpp
js/src/jsopcode.cpp
js/src/jsopcode.h
js/src/jsopcode.tbl
js/src/jspubtd.h
js/src/jsscript.cpp
js/src/jsstr.cpp
js/src/jstypedarray.cpp
js/src/jsweakmap.cpp
js/src/jsxdrapi.h
js/src/jsxml.cpp
js/src/methodjit/Compiler.cpp
js/src/methodjit/Compiler.h
js/src/methodjit/FastOps.cpp
js/src/methodjit/InvokeHelpers.cpp
js/src/methodjit/LoopState.cpp
js/src/methodjit/MonoIC.cpp
js/src/methodjit/StubCalls.cpp
js/src/methodjit/TypedArrayIC.h
js/src/shell/js.cpp
js/src/vm/Debugger.cpp
js/xpconnect/src/XPCComponents.cpp
js/xpconnect/src/XPCConvert.cpp
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/XPCQuickStubs.h
js/xpconnect/src/qsgen.py
js/xpconnect/wrappers/XrayWrapper.cpp
layout/base/FrameLayerBuilder.cpp
layout/base/nsBidiPresUtils.cpp
layout/base/nsCSSRendering.cpp
layout/generic/nsFloatManager.cpp
layout/reftests/canvas/reftest.list
layout/reftests/transform-3d/scale3d-2-ref.html
layout/style/Declaration.cpp
layout/style/nsCSSParser.cpp
layout/style/nsCSSProps.cpp
layout/style/nsCSSProps.h
layout/style/nsComputedDOMStyle.cpp
layout/style/nsDOMCSSDeclaration.cpp
layout/style/nsRuleNode.cpp
layout/style/nsStyleCoord.cpp
layout/style/nsStyleCoord.h
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/test/property_database.js
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoAppShell.java
mobile/android/base/GeckoDirProvider.java
mobile/android/base/LauncherShortcuts.java.in
mobile/android/base/Makefile.in
mobile/android/base/resources/layout/gecko_menu.xml
mobile/xul/app/mobile.js
modules/libpref/src/init/all.js
mozglue/android/APKOpen.cpp
netwerk/base/src/nsSocketTransportService2.cpp
netwerk/cache/nsDiskCacheDeviceSQL.cpp
netwerk/cache/nsDiskCacheDeviceSQL.h
netwerk/protocol/http/SpdySession.cpp
netwerk/protocol/http/SpdySession.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpConnectionInfo.cpp
netwerk/protocol/http/nsHttpConnectionInfo.h
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/http/nsHttpConnectionMgr.h
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/protocol/http/nsHttpHandler.h
parser/html/nsHtml5TreeOpExecutor.cpp
parser/html/nsHtml5TreeOpExecutor.h
security/manager/ssl/src/nsNSSIOLayer.cpp
security/manager/ssl/src/nsNSSIOLayer.h
services/sync/tests/unit/test_bookmark_engine.js
storage/src/mozStorageConnection.cpp
testing/mochitest/runtests.py
testing/xpcshell/xpcshell.ini
toolkit/components/downloads/nsDownloadScanner.h
toolkit/components/places/History.cpp
toolkit/components/places/SQLFunctions.cpp
toolkit/components/places/nsNavBookmarks.cpp
toolkit/components/places/nsNavBookmarks.h
toolkit/components/places/nsNavHistory.cpp
toolkit/components/places/nsNavHistoryQuery.cpp
toolkit/components/places/nsNavHistoryResult.cpp
toolkit/components/places/nsPlacesImportExportService.cpp
toolkit/components/places/nsPlacesImportExportService.h
toolkit/components/places/tests/autocomplete/test_livemarks.js
toolkit/components/places/tests/chrome/test_329534.xul
toolkit/components/places/tests/head_common.js
toolkit/components/places/tests/unit/test_exclude_livemarks.js
toolkit/components/places/tests/unit/test_livemarkService_getLivemarkIdForFeedURI.js
toolkit/components/telemetry/TelemetryHistograms.h
toolkit/components/telemetry/TelemetryPing.js
toolkit/components/telemetry/tests/unit/test_TelemetryPing.js
toolkit/content/widgets/videocontrols.xml
toolkit/mozapps/update/nsUpdateService.js
toolkit/mozapps/update/test/unit/head_update.js.in
toolkit/themes/pinstripe/global/media/videocontrols.css
toolkit/themes/winstripe/global/media/videocontrols.css
uriloader/exthandler/win/nsMIMEInfoWin.cpp
widget/android/AndroidBridge.cpp
widget/android/AndroidBridge.h
widget/android/nsWindow.cpp
widget/xpwidgets/GfxInfoX11.cpp
widget/xpwidgets/GfxInfoX11.h
xpcom/base/nsCycleCollector.cpp
xpcom/ds/nsCheapSets.cpp
xpcom/ds/nsCheapSets.h
xpcom/glue/nsTHashtable.h
xpcom/io/nsILocalFile.idl
xpcom/io/nsLocalFileWin.cpp
--- a/accessible/src/base/Statistics.h
+++ b/accessible/src/base/Statistics.h
@@ -55,31 +55,31 @@ namespace statistics {
 
   /**
    * Report that ISimpleDOM* has been used.
    */
   inline void ISimpleDOMUsed()
   {
     static bool firstTime = true;
     if (firstTime) {
-      Telemetry::Accumulate(Telemetry::ISIMPLE_DOM_USAGE, 1);
+      Telemetry::Accumulate(Telemetry::A11Y_ISIMPLEDOM_USAGE, 1);
       firstTime = false;
     }
   }
 
   /**
    * Report that IAccessibleTable has been used.
    */
   inline void IAccessibleTableUsed()
-    { Telemetry::Accumulate(Telemetry::IACCESSIBLE_TABLE_USAGE, 1); }
+    { Telemetry::Accumulate(Telemetry::A11Y_IATABLE_USAGE, 1); }
 
   /**
    * Report that XForms accessibility has been instantiated.
    */
   inline void XFormsAccessibleUsed()
-    { Telemetry::Accumulate(Telemetry::XFORMS_ACCESSIBLE_USED, 1); }
+    { Telemetry::Accumulate(Telemetry::A11Y_XFORMS_USAGE, 1); }
 
 } // namespace statistics
 } // namespace a11y
 } // namespace mozilla
 
 #endif
 
--- a/accessible/src/base/nsARIAMap.cpp
+++ b/accessible/src/base/nsARIAMap.cpp
@@ -131,17 +131,17 @@ nsRoleMapEntry nsARIAMap::gWAIRoleMap[] 
     kNoReqStates,
     eARIASelectable,
     eARIAReadonly
   },
   {
     "combobox",
     roles::COMBOBOX,
     kUseMapRole,
-    eHasValueMinMax,
+    eNoValue,
     eOpenCloseAction,
     eNoLiveAttr,
     states::COLLAPSED | states::HASPOPUP,
     eARIAAutoComplete,
     eARIAReadonly
   },
   {
     "dialog",
--- a/accessible/src/base/nsAccessible.cpp
+++ b/accessible/src/base/nsAccessible.cpp
@@ -849,19 +849,20 @@ nsAccessible::ChildAtPoint(PRInt32 aX, P
   if (!foundFrame || !(content = foundFrame->GetContent()))
     return fallbackAnswer;
 
   // Get accessible for the node with the point or the first accessible in
   // the DOM parent chain.
   nsDocAccessible* contentDocAcc = GetAccService()->
     GetDocAccessible(content->OwnerDoc());
 
-  // contentDocAcc in some circumstances can be NULL
-  // See https://bugzilla.mozilla.org/show_bug.cgi?id=729861
-  NS_ENSURE_TRUE(contentDocAcc, fallbackAnswer);
+  // contentDocAcc in some circumstances can be NULL. See bug 729861
+  NS_ASSERTION(contentDocAcc, "could not get the document accessible");
+  if (!contentDocAcc)
+    return fallbackAnswer;
 
   nsAccessible* accessible = contentDocAcc->GetAccessibleOrContainer(content);
   if (!accessible)
     return fallbackAnswer;
 
   if (accessible == this) {
     // Manually walk through accessible children and see if the are within this
     // point. Skip offscreen or invisible accessibles. This takes care of cases
--- a/accessible/src/msaa/Compatibility.cpp
+++ b/accessible/src/msaa/Compatibility.cpp
@@ -112,16 +112,26 @@ Compatibility::Init()
     statistics::A11yConsumers(SEROTEK);
 
   if (::GetModuleHandleW(L"nvdaHelperRemote"))
     statistics::A11yConsumers(NVDA);
 
   if (::GetModuleHandleW(L"OsmHooks"))
     statistics::A11yConsumers(COBRA);
 
+  if (::GetModuleHandleW(L"WebFinderRemote"))
+    statistics::A11yConsumers(ZOOMTEXT);
+
+  if (::GetModuleHandleW(L"Kazahook"))
+    statistics::A11yConsumers(KAZAGURU);
+
+  if (::GetModuleHandleW(L"TextExtractorImpl32") ||
+      ::GetModuleHandleW(L"TextExtractorImpl64"))
+    statistics::A11yConsumers(YOUDAO);
+
   // Turn off new tab switching for Jaws and WE.
   if (sMode & JAWSMode || sMode & WEMode) {
     // Check to see if the pref for disallowing CtrlTab is already set. If so,
     // bail out (respect the user settings). If not, set it.
     if (!Preferences::HasUserValue("browser.ctrlTab.disallowForScreenReaders"))
       Preferences::SetBool("browser.ctrlTab.disallowForScreenReaders", true);
   }
 }
--- a/accessible/src/msaa/Compatibility.h
+++ b/accessible/src/msaa/Compatibility.h
@@ -102,17 +102,20 @@ private:
    */
   enum {
     NVDA = 0,
     JAWS = 1,
     OLDJAWS = 2,
     WE = 3,
     DOLPHIN = 4,
     SEROTEK = 5,
-    COBRA = 6
+    COBRA = 6,
+    ZOOMTEXT = 7,
+    KAZAGURU = 8,
+    YOUDAO = 9
   };
 
 private:
   static PRUint32 sMode;
 };
 
 } // a11y namespace
 } // mozilla namespace
--- a/accessible/tests/mochitest/events/test_focus_general.html
+++ b/accessible/tests/mochitest/events/test_focus_general.html
@@ -101,20 +101,24 @@
         // other platforms requires setting a ui.key.menuAccessKeyFocuses
         // preference.
         gQueue.push(new toggleTopMenu(editableDoc, new topMenuChecker()));
         gQueue.push(new toggleTopMenu(editableDoc, new focusChecker(editableDoc)));
       }
       gQueue.push(new synthContextMenu(editableDoc, new contextMenuChecker()));
       gQueue.push(new synthDownKey(editableDoc, new focusContextMenuItemChecker()));
       gQueue.push(new synthEscapeKey(editableDoc, new focusChecker(editableDoc)));
+      if (SEAMONKEY) {
+        todo(false, "shift tab from editable document fails on (Windows) SeaMonkey! (Bug 718235)");
+      } else {
       if (LINUX)
-        todo(false, "shift tab from editable document Fails on linux!");
+        todo(false, "shift tab from editable document fails on linux!");
       else
         gQueue.push(new synthShiftTab("link", new focusChecker("link")));
+      } // ! SEAMONKEY
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 </head>
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -50,18 +50,19 @@ pref("dom.telephony.app.phone.url", "htt
 
 // Device pixel to CSS px ratio, in percent. Set to -1 to calculate based on display density.
 pref("browser.viewport.scaleRatio", -1);
 
 /* disable text selection */
 pref("browser.ignoreNativeFrameTextSelection", true);
 
 /* cache prefs */
-pref("browser.cache.disk.enable", false);
-pref("browser.cache.disk.capacity", 0); // kilobytes
+pref("browser.cache.disk.enable", true);
+pref("browser.cache.disk.capacity", 55000); // kilobytes
+pref("browser.cache.disk.parent_directory", "/cache");
 pref("browser.cache.disk.smart_size.enabled", false);
 pref("browser.cache.disk.smart_size.first_run", false);
 
 pref("browser.cache.memory.enable", true);
 pref("browser.cache.memory.capacity", 1024); // kilobytes
 
 /* image cache prefs */
 pref("image.cache.size", 1048576); // bytes
--- a/b2g/chrome/content/shell.xul
+++ b/b2g/chrome/content/shell.xul
@@ -27,11 +27,11 @@
   <commandset id="mainCommandSet">
     <command id="cmd_close" oncommand="CommandUpdater.doCommand(this.id);"/>
   </commandset>
 
   <browser id="homescreen"
            type="content-primary"
            flex="1"
            style="overflow: hidden;"
-           src="data:text/html;base64,PCFET0NUWVBFIGh0bWw+PGh0bWw+PGJvZHkgc3R5bGU9ImJhY2tncm91bmQ6YmxhY2s7Ij48L2JvZHk+PC9odG1sPgo="/>
+           src="data:text/html,%3C!DOCTYPE html>%3Cbody style='background:black;'>"/>
 </window>
 
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0"?>
-<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1329176667000">
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1330033499000">
   <emItems>
       <emItem  blockID="i58" id="webmaster@buzzzzvideos.info">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i41" id="{99079a25-328f-4bd4-be04-00955acaa0a7}">
                         <versionRange  minVersion="0.1" maxVersion="4.3.1.00" severity="1">
                     </versionRange>
@@ -106,27 +106,33 @@
       <emItem  blockID="i53" id="{a3a5c777-f583-4fef-9380-ab4add1bc2a8}">
                         <versionRange  minVersion="2.0.3" maxVersion="2.0.3">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i59" id="ghostviewer@youtube2.com">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
-      <emItem  blockID="i51" id="admin@youtubeplayer.com">
-                        <versionRange  minVersion="0" maxVersion="*">
+      <emItem  blockID="i70" id="psid-vhvxQHMZBOzUZA@jetpack">
+                        <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                   </emItem>
+      <emItem  blockID="i7" id="{2224e955-00e9-4613-a844-ce69fccaae91}">
+                        </emItem>
       <emItem  blockID="i46" id="{841468a1-d7f4-4bd3-84e6-bb0f13a06c64}">
                         <versionRange  minVersion="0.1" maxVersion="*">
                       <targetApplication  id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
                               <versionRange  minVersion="9.0a1" maxVersion="9.0" />
                           </targetApplication>
                     </versionRange>
                   </emItem>
+      <emItem  blockID="i67" id="youtube2@youtube2.com">
+                        <versionRange  minVersion="0" maxVersion="*">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i60" id="youtb3@youtb3.com">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i23" id="firefox@bandoo.com">
                         <versionRange  minVersion="5.0" maxVersion="5.0" severity="1">
                       <targetApplication  id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
                               <versionRange  minVersion="3.7a1pre" maxVersion="*" />
@@ -156,30 +162,38 @@
                     </versionRange>
                   </emItem>
       <emItem  blockID="i44" id="sigma@labs.mozilla">
                         </emItem>
       <emItem  blockID="i5" id="support@daemon-tools.cc">
                         <versionRange  minVersion=" " maxVersion="1.0.0.5">
                     </versionRange>
                   </emItem>
+      <emItem  blockID="i69" id="{977f3b97-5461-4346-92c8-a14c749b77c9}">
+                        <versionRange  minVersion="0" maxVersion="*" severity="1">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i6" id="{3f963a5b-e555-4543-90e2-c3908898db71}">
                         <versionRange  minVersion=" " maxVersion="8.5">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i12" id="masterfiler@gmail.com">
                         <versionRange  severity="3">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i48" id="admin@youtubespeedup.com">
                         </emItem>
       <emItem  blockID="i20" id="{AB2CE124-6272-4b12-94A9-7303C7397BD1}">
                         <versionRange  minVersion="0.1" maxVersion="5.2.0.7164" severity="1">
                     </versionRange>
                   </emItem>
+      <emItem  blockID="i68" id="flashupdate@adobe.com">
+                        <versionRange  minVersion="0" maxVersion="*">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i47" id="youtube@youtube2.com">
                         </emItem>
       <emItem  blockID="i62" id="jid0-EcdqvFOgWLKHNJPuqAnawlykCGZ@jetpack">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i17" id="{3252b9ae-c69a-4eaf-9502-dc9c1f6c009e}">
                         <versionRange  minVersion="2.2" maxVersion="2.2">
@@ -199,18 +213,20 @@
       <emItem  blockID="i19" id="{46551EC9-40F0-4e47-8E18-8E5CF550CFB8}">
                         <versionRange  minVersion="1.1b1" maxVersion="1.1b1">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i3" id="langpack-vi-VN@firefox.mozilla.org">
                         <versionRange  minVersion="2.0" maxVersion="2.0">
                     </versionRange>
                   </emItem>
-      <emItem  blockID="i7" id="{2224e955-00e9-4613-a844-ce69fccaae91}">
-                        </emItem>
+      <emItem  blockID="i51" id="admin@youtubeplayer.com">
+                        <versionRange  minVersion="0" maxVersion="*">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i52" id="ff-ext@youtube">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i24" id="{6E19037A-12E3-4295-8915-ED48BC341614}">
                         <versionRange  minVersion="0.1" maxVersion="1.3.328.4" severity="1">
                       <targetApplication  id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
                               <versionRange  minVersion="3.7a1pre" maxVersion="*" />
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -794,17 +794,17 @@ pref("browser.sessionstore.max_windows_u
 // number of crashes that can occur before the about:sessionrestore page is displayed
 // (this pref has no effect if more than 6 hours have passed since the last crash)
 pref("browser.sessionstore.max_resumed_crashes", 1);
 // restore_on_demand overrides MAX_CONCURRENT_TAB_RESTORES (sessionstore constant)
 // and restore_hidden_tabs. When true, tabs will not be restored until they are
 // focused (also applies to tabs that aren't visible). When false, the values
 // for MAX_CONCURRENT_TAB_RESTORES and restore_hidden_tabs are respected.
 // Selected tabs are always restored regardless of this pref.
-pref("browser.sessionstore.restore_on_demand", false);
+pref("browser.sessionstore.restore_on_demand", true);
 // Whether to automatically restore hidden tabs (i.e., tabs in other tab groups) or not
 pref("browser.sessionstore.restore_hidden_tabs", false);
 // If restore_on_demand is set, pinned tabs are restored on startup by default.
 // When set to true, this pref overrides that behavior, and pinned tabs will only
 // be restored when they are focused.
 pref("browser.sessionstore.restore_pinned_tabs_on_demand", false);
 
 // allow META refresh by default
@@ -1023,16 +1023,19 @@ pref("services.sync.prefs.sync.xpinstall
 #endif
 
 // Disable the error console
 pref("devtools.errorconsole.enabled", false);
 
 // Enable the Inspector
 pref("devtools.inspector.enabled", true);
 pref("devtools.inspector.htmlHeight", 112);
+pref("devtools.inspector.htmlPanelOpen", false);
+pref("devtools.inspector.sidebarOpen", false);
+pref("devtools.inspector.activeSidebar", "ruleview");
 
 // Enable the Debugger
 pref("devtools.debugger.enabled", false);
 
 // The default Debugger UI height
 pref("devtools.debugger.ui.height", 250);
 
 // Enable the style inspector
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -20,16 +20,17 @@
 # the Initial Developer. All Rights Reserved.
 #
 # Contributor(s):
 #   Ben Goodger <ben@bengoodger.com> (v2.0)
 #   Blake Ross <blakeross@telocity.com>
 #   Shawn Wilsher <me@shawnwilsher.com>
 #   Ehsan Akhgari <ehsan.akhgari@gmail.com>
 #   Rob Campbell <rcampbell@mozilla.com>
+#   Paul Rouget <paul@mozilla.com>
 #
 # Alternatively, the contents of this file may be used under the terms of
 # either the GNU General Public License Version 2 or later (the "GPL"), or
 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 # in which case the provisions of the GPL or the LGPL are applicable instead
 # of those above. If you wish to allow use of your version of this file only
 # under the terms of either the GPL or the LGPL, and not to allow others to
 # use your version of this file under the terms of the MPL, indicate your
@@ -148,31 +149,39 @@
 
   <commandset id="inspectorCommands">
     <command id="Inspector:Inspect"
              oncommand="InspectorUI.toggleInspection();"/>
     <command id="Inspector:Sidebar"
              oncommand="InspectorUI.toggleSidebar();"/>
     <command id="Inspector:Tilt"
              oncommand="Tilt.initialize();"/>
+    <command id="Inspector:HTMLPanel"
+             oncommand="InspectorUI.toggleHTMLPanel();"/>
+    <command id="Inspector:CopyInner"
+             oncommand="InspectorUI.copyInnerHTML();"/>
+    <command id="Inspector:CopyOuter"
+             oncommand="InspectorUI.copyOuterHTML();"/>
+    <command id="Inspector:DeleteNode"
+             oncommand="InspectorUI.deleteNode();"/>
   </commandset>
 
   <broadcasterset id="mainBroadcasterSet">
     <broadcaster id="viewBookmarksSidebar" autoCheck="false" label="&bookmarksButton.label;"
                  type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/bookmarks/bookmarksPanel.xul"
                  oncommand="toggleSidebar('viewBookmarksSidebar');"/>
 
     <!-- for both places and non-places, the sidebar lives at
          chrome://browser/content/history/history-panel.xul so there are no
          problems when switching between versions -->
     <broadcaster id="viewHistorySidebar" autoCheck="false" sidebartitle="&historyButton.label;"
                  type="checkbox" group="sidebar"
                  sidebarurl="chrome://browser/content/history/history-panel.xul"
                  oncommand="toggleSidebar('viewHistorySidebar');"/>
-                 
+
     <broadcaster id="viewWebPanelsSidebar" autoCheck="false"
                  type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/web-panels.xul"
                  oncommand="toggleSidebar('viewWebPanelsSidebar');"/>
 
     <!-- popup blocking menu items -->
     <broadcaster id="blockedPopupAllowSite"
                  accesskey="&allowPopups.accesskey;"
                  oncommand="gPopupBlockerObserver.toggleAllowPopupsForSite(event);"/>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1648,37 +1648,33 @@ function delayedStartup(isLoadingBlank, 
   }
 
   PlacesToolbarHelper.init();
 
   ctrlTab.readPref();
   gPrefService.addObserver(ctrlTab.prefName, ctrlTab, false);
   gPrefService.addObserver(allTabs.prefName, allTabs, false);
 
-  // Delayed initialization of the livemarks update timer.
-  // Livemark updates don't need to start until after bookmark UI
-  // such as the toolbar has initialized. Starting 5 seconds after
-  // delayedStartup in order to stagger this before the download manager starts.
-  setTimeout(function() PlacesUtils.livemarks.start(), 5000);
-
   // Initialize the download manager some time after the app starts so that
   // auto-resume downloads begin (such as after crashing or quitting with
   // active downloads) and speeds up the first-load of the download manager UI.
   // If the user manually opens the download manager before the timeout, the
   // downloads will start right away, and getting the service again won't hurt.
   setTimeout(function() {
     gDownloadMgr = Cc["@mozilla.org/download-manager;1"].
                    getService(Ci.nsIDownloadManager);
 
+#ifdef XP_WIN
     if (Win7Features) {
       let tempScope = {};
       Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm",
                 tempScope);
       tempScope.DownloadTaskbarProgress.onBrowserWindowLoad(window);
     }
+#endif
   }, 10000);
 
 #ifndef XP_MACOSX
   updateEditUIVisibility();
   let placesContext = document.getElementById("placesContext");
   placesContext.addEventListener("popupshowing", updateEditUIVisibility, false);
   placesContext.addEventListener("popuphiding", updateEditUIVisibility, false);
 #endif
@@ -3625,22 +3621,22 @@ function FillHistoryMenu(aParent) {
     let entry = sessionHistory.getEntryAtIndex(j, false);
     let uri = entry.URI.spec;
 
     item.setAttribute("uri", uri);
     item.setAttribute("label", entry.title || uri);
     item.setAttribute("index", j);
 
     if (j != index) {
-      try {
-        let iconURL = Cc["@mozilla.org/browser/favicon-service;1"]
-                         .getService(Ci.nsIFaviconService)
-                         .getFaviconForPage(entry.URI).spec;
+      function FHM_getFaviconURLCallback(aURI) {
+        let iconURL = PlacesUtils.favicons.getFaviconLinkForIcon(aURI).spec;
         item.style.listStyleImage = "url(" + iconURL + ")";
-      } catch (ex) {}
+      }
+      PlacesUtils.favicons.getFaviconURLForPage(entry.URI,
+                                                FHM_getFaviconURLCallback);
     }
 
     if (j < index) {
       item.className = "unified-nav-back menuitem-iconic menuitem-with-favicon";
       item.setAttribute("tooltiptext", tooltipBack);
     } else if (j == index) {
       item.setAttribute("type", "radio");
       item.setAttribute("checked", "true");
@@ -6993,21 +6989,23 @@ function getPluginInfo(pluginElement)
     }
   }
 
   return {mimetype: tagMimetype, pluginsPage: pluginsPage};
 }
 
 var gPluginHandler = {
 
+#ifdef MOZ_CRASHREPORTER
   get CrashSubmit() {
     delete this.CrashSubmit;
     Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
     return this.CrashSubmit;
   },
+#endif
 
   // Map the plugin's name to a filtered version more suitable for user UI.
   makeNicePluginName : function (aName, aFilename) {
     if (aName == "Shockwave Flash")
       return "Adobe Flash";
 
     // Clean up the plugin name by stripping off any trailing version numbers
     // or "plugin". EG, "Foo Bar Plugin 1.23_02" --> "Foo Bar"
@@ -7137,24 +7135,26 @@ var gPluginHandler = {
                {plugins: missingPluginsArray, browser: gBrowser.selectedBrowser});
   },
 
   // Callback for user clicking on a disabled plugin
   managePlugins: function (aEvent) {
     BrowserOpenAddonsMgr("addons://list/plugin");
   },
 
+#ifdef MOZ_CRASHREPORTER
   // Callback for user clicking "submit a report" link
   submitReport : function(pluginDumpID, browserDumpID) {
     // The crash reporter wants a DOM element it can append an IFRAME to,
     // which it uses to submit a form. Let's just give it gBrowser.
     this.CrashSubmit.submit(pluginDumpID);
     if (browserDumpID)
       this.CrashSubmit.submit(browserDumpID);
   },
+#endif
 
   // Callback for user clicking a "reload page" link
   reloadPage: function (browser) {
     browser.reload();
   },
 
   // Callback for user clicking the help icon
   openHelpPage: function () {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -33,16 +33,17 @@
 #   Dão Gottwald <dao@mozilla.com>
 #   Ehsan Akhgari <ehsan.akhgari@gmail.com>
 #   Robert Strong <robert.bugzilla@gmail.com>
 #   Rob Campbell <rcampbell@mozilla.com>
 #   Patrick Walton <pcwalton@mozilla.com>
 #   David Dahl <ddahl@mozilla.com>
 #   Frank Yan <fyan@mozilla.com>
 #   Victor Porof <vporof@mozilla.com>
+#   Paul Rouget <paul@mozilla.com>
 #
 # Alternatively, the contents of this file may be used under the terms of
 # either the GNU General Public License Version 2 or later (the "GPL"), or
 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 # in which case the provisions of the GPL or the LGPL are applicable instead
 # of those above. If you wish to allow use of your version of this file only
 # under the terms of either the GPL or the LGPL, and not to allow others to
 # use your version of this file under the terms of the MPL, indicate your
@@ -222,30 +223,31 @@
                 class="editBookmarkPanelBottomButton"
                 label="&editBookmark.done.label;"
                 default="true"
                 oncommand="StarUI.panel.hidePopup();"/>
 #endif
       </hbox>
     </panel>
 
-    <panel id="inspector-tree-panel"
-           orient="vertical"
-           hidden="true"
-           ignorekeys="true"
-           noautofocus="true"
-           noautohide="true"
-           titlebar="normal"
-           close="true"
-           label="&inspectPanelTitle.label;">
-      <hbox id="tree-panel-resizer-box" align="end">
-        <spacer flex="1" />
-        <resizer dir="bottomend" />
-      </hbox>
-    </panel>
+    <menupopup id="inspector-node-popup">
+      <menuitem id="inspectorHTMLCopyInner"
+                label="&inspectorHTMLCopyInner.label;"
+                accesskey="&inspectorHTMLCopyInner.accesskey;"
+                command="Inspector:CopyInner"/>
+      <menuitem id="inspectorHTMLCopyOuter"
+                label="&inspectorHTMLCopyOuter.label;"
+                accesskey="&inspectorHTMLCopyOuter.accesskey;"
+                command="Inspector:CopyOuter"/>
+      <menuseparator/>
+      <menuitem id="inspectorHTMLDelete"
+                label="&inspectorHTMLDelete.label;"
+                accesskey="&inspectorHTMLDelete.accesskey;"
+                command="Inspector:DeleteNode"/>
+    </menupopup>
 
     <menupopup id="toolbar-context-menu"
                onpopupshowing="onViewToolbarsPopupShowing(event);">
       <menuseparator/>
       <menuitem command="cmd_ToggleTabsOnTop"
                 type="checkbox"
                 label="&viewTabsOnTop.label;"
                 accesskey="&viewTabsOnTop.accesskey;"/>
@@ -989,55 +991,54 @@
     </hbox>
   </hbox>
 
   <vbox id="browser-bottombox" layer="true">
     <toolbar id="inspector-toolbar"
              class="devtools-toolbar"
              nowindowdrag="true"
              hidden="true">
-      <vbox flex="1">
-        <resizer id="inspector-top-resizer" flex="1" 
-                 dir="top" disabled="true"
-                 element="inspector-tree-box"/>
-        <hbox>
 #ifdef XP_MACOSX
-          <toolbarbutton id="highlighter-closebutton"
-                         oncommand="InspectorUI.closeInspectorUI(false);"
-                         tooltiptext="&inspectCloseButton.tooltiptext;"/>
+      <toolbarbutton id="highlighter-closebutton"
+                     oncommand="InspectorUI.closeInspectorUI(false);"
+                     tooltiptext="&inspectCloseButton.tooltiptext;"/>
 #endif
-          <toolbarbutton id="inspector-inspect-toolbutton"
-                         class="devtools-toolbarbutton"
-                         label="&inspectButton.label;"
-                         accesskey="&inspectButton.accesskey;"
-                         command="Inspector:Inspect"/>
-          <arrowscrollbox id="inspector-breadcrumbs"
-                          flex="1" orient="horizontal"
-                          clicktoscroll="true"/>
-          <hbox id="inspector-tools">
-            <toolbarbutton id="inspector-3D-button"
-                           class="devtools-toolbarbutton"
-                           hidden="true"
-                           label="&inspect3DViewButton.label;"
-                           accesskey="&inspect3DViewButton.accesskey;"
-                           command="Inspector:Tilt"/>
-            <toolbarbutton id="inspector-style-button"
-                           class="devtools-toolbarbutton"
-                           label="&inspectStyleButton.label;"
-                           accesskey="&inspectStyleButton.accesskey;"
-                           command="Inspector:Sidebar"/>
-            <!-- registered tools go here -->
-          </hbox>
+      <toolbarbutton id="inspector-inspect-toolbutton"
+                     class="devtools-toolbarbutton"
+                     label="&inspectButton.label;"
+                     accesskey="&inspectButton.accesskey;"
+                     command="Inspector:Inspect"/>
+      <toolbarbutton id="inspector-treepanel-toolbutton"
+                     class="devtools-toolbarbutton"
+                     label="&htmlPanel.label;"
+                     accesskey="&htmlPanel.accesskey;"
+                     tooltiptext="&htmlPanel.tooltiptext;"
+                     command="Inspector:HTMLPanel"/>
+      <arrowscrollbox id="inspector-breadcrumbs"
+                      flex="1" orient="horizontal"
+                      clicktoscroll="true"/>
+      <hbox id="inspector-tools">
+        <toolbarbutton id="inspector-3D-button"
+                       class="devtools-toolbarbutton"
+                       hidden="true"
+                       label="&inspect3DViewButton.label;"
+                       accesskey="&inspect3DViewButton.accesskey;"
+                       command="Inspector:Tilt"/>
+        <toolbarbutton id="inspector-style-button"
+                       class="devtools-toolbarbutton"
+                       label="&inspectStyleButton.label;"
+                       accesskey="&inspectStyleButton.accesskey;"
+                       command="Inspector:Sidebar"/>
+        <!-- registered tools go here -->
+      </hbox>
 #ifndef XP_MACOSX
-          <toolbarbutton id="highlighter-closebutton"
-                         oncommand="InspectorUI.closeInspectorUI(false);"
-                         tooltiptext="&inspectCloseButton.tooltiptext;"/>
+      <toolbarbutton id="highlighter-closebutton"
+                     oncommand="InspectorUI.closeInspectorUI(false);"
+                     tooltiptext="&inspectCloseButton.tooltiptext;"/>
 #endif
-        </hbox>
-      </vbox>
     </toolbar>
     <toolbar id="addon-bar"
              toolbarname="&addonBarCmd.label;" accesskey="&addonBarCmd.accesskey;"
              collapsed="true"
              class="toolbar-primary chromeclass-toolbar"
              context="toolbar-context-menu" toolboxid="navigator-toolbox"
              mode="icons" iconsize="small" defaulticonsize="small"
              lockiconsize="true"
--- a/browser/base/content/highlighter.css
+++ b/browser/base/content/highlighter.css
@@ -29,24 +29,16 @@
 #highlighter-veil-middlebox:-moz-locale-dir(rtl) {
   -moz-box-direction: reverse;
 }
 
 .inspector-breadcrumbs-button {
   direction: ltr;
 }
 
-#inspector-top-resizer {
-  display: none;
-}
-
-#inspector-toolbar[treepanel-open] > vbox > #inspector-top-resizer {
-  display: -moz-box;
-}
-
 /*
  * Node Infobar
  */
 
 #highlighter-nodeinfobar-container {
   position: absolute;
   max-width: 95%;
 }
--- a/browser/base/content/newtab/grid.js
+++ b/browser/base/content/newtab/grid.js
@@ -18,18 +18,20 @@ let gGrid = {
    * The cached DOM fragment for sites.
    */
   _siteFragment: null,
 
   /**
    * All cells contained in the grid.
    */
   get cells() {
+    let cells = [];
     let children = this.node.querySelectorAll("li");
-    let cells = [new Cell(this, child) for each (child in children)];
+    for (let i = 0; i < children.length; i++)
+      cells.push(new Cell(this, children[i]));
 
     // Replace the getter with our cached value.
     Object.defineProperty(this, "cells", {value: cells, enumerable: true});
 
     return cells;
   },
 
   /**
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -688,18 +688,18 @@
         <body>
           <![CDATA[
             var browser = this.getBrowserForTab(aTab);
             browser.mIconURL = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
 
             if (aURI && this.mFaviconService) {
               if (!(aURI instanceof Ci.nsIURI))
                 aURI = makeURI(aURI);
-              this.mFaviconService.setAndLoadFaviconForPage(browser.currentURI,
-                                                            aURI, false);
+              this.mFaviconService.setAndFetchFaviconForPage(browser.currentURI,
+                                                             aURI, false);
             }
 
             if ((browser.mIconURL || "") != aTab.getAttribute("image")) {
               if (browser.mIconURL)
                 aTab.setAttribute("image", browser.mIconURL);
               else
                 aTab.removeAttribute("image");
               this._tabAttrModified(aTab);
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -220,16 +220,17 @@ endif
                  browser_tab_dragdrop.js \
                  browser_tab_dragdrop2.js \
                  browser_tab_dragdrop2_frame1.xul \
                  browser_tabfocus.js \
                  browser_tabs_isActive.js \
                  browser_tabs_owner.js \
                  browser_urlbarCopying.js \
                  browser_urlbarEnter.js \
+                 browser_urlbarRevert.js \
                  browser_urlbarTrimURLs.js \
                  browser_urlHighlight.js \
                  browser_visibleFindSelection.js \
                  browser_visibleTabs.js \
                  browser_visibleTabs_contextMenu.js \
                  browser_visibleTabs_bookmarkAllPages.js \
                  browser_visibleTabs_bookmarkAllTabs.js \
                  browser_visibleTabs_tabPreview.js \
--- a/browser/base/content/test/browser_bug422590.js
+++ b/browser/base/content/test/browser_bug422590.js
@@ -3,18 +3,18 @@ function test() {
   ignoreAllUncaughtExceptions();
   
   // test the main (normal) browser window
   testCustomize(window, testChromeless);
 }
 
 function testChromeless() {
   // test a chromeless window
-  var newWin = openDialog("chrome://browser/content/", "_blank",
-                      "chrome,dialog=no,toolbar=no", "about:blank");
+  var newWin = openDialog(getBrowserURL(), "_blank",
+                          "chrome,dialog=no,toolbar=no", "about:blank");
   ok(newWin, "got new window");
 
   function runWindowTest() {
     // Check that the search bar is hidden
     var searchBar = newWin.BrowserSearch.searchBar;
     ok(searchBar, "got search bar");
 
     var searchBarBO = searchBar.boxObject;
--- a/browser/base/content/test/browser_tabMatchesInAwesomebar.js
+++ b/browser/base/content/test/browser_tabMatchesInAwesomebar.js
@@ -72,34 +72,35 @@ var gTestSteps = [
       loadTab(gBrowser.tabs[i], TEST_URL_BASES[0] + gTabCounter);
   },
   function() {
     info("Running step 4");
     let ps = Services.prefs;
     ps.setBoolPref("browser.privatebrowsing.keep_current_session", true);
     ps.setBoolPref("browser.tabs.warnOnClose", false);
 
+    // Make sure that all restored tabs are loaded without waiting for the user
+    // to bring them to the foreground. We ensure this by resetting the
+    // related preference (see the "firefox.js" defaults file for details).
+    ps.setBoolPref("browser.sessionstore.restore_on_demand", false);
+
     gPrivateBrowsing.privateBrowsingEnabled = true;
 
     executeSoon(function() {
       ensure_opentabs_match_db(nextStep);
     });
   },
   function() {
     info("Running step 5");
     gPrivateBrowsing.privateBrowsingEnabled = false;
 
     executeSoon(function() {
       let ps = Services.prefs;
-      try {
-        ps.clearUserPref("browser.privatebrowsing.keep_current_session");
-      } catch (ex) {}
-      try {
-        ps.clearUserPref("browser.tabs.warnOnClose");
-      } catch (ex) {}
+      ps.clearUserPref("browser.privatebrowsing.keep_current_session");
+      ps.clearUserPref("browser.tabs.warnOnClose");
 
       ensure_opentabs_match_db(nextStep);
     });
   },
   function() {
     info("Running step 6 - ensure we don't register subframes as open pages");
     let tab = gBrowser.addTab();
     tab.linkedBrowser.addEventListener("load", function () {
@@ -164,32 +165,31 @@ var gTestSteps = [
   },
   function() {
     info("Running step 12 - leave private browsing mode");
 
     Services.obs.addObserver(function(aSubject, aTopic, aData) {
       Services.obs.removeObserver(arguments.callee, "private-browsing-transition-complete");
 
       let ps = Services.prefs;
-      try {
-        ps.clearUserPref("browser.privatebrowsing.keep_current_session");
-      } catch (ex) {}
-      try {
-        ps.clearUserPref("browser.tabs.warnOnClose");
-      } catch (ex) {}
+      ps.clearUserPref("browser.privatebrowsing.keep_current_session");
+      ps.clearUserPref("browser.tabs.warnOnClose");
 
       for (let i = 1; i < gBrowser.tabs.length; i++)
         waitForRestoredTab(gBrowser.tabs[i]);
 
     }, "private-browsing-transition-complete", false);
 
     gPrivateBrowsing.privateBrowsingEnabled = false;
   },
   function() {
     info("Running step 13 - close all tabs");
+
+    Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
+
     gBrowser.addTab("about:blank", {skipAnimation: true});
     while (gBrowser.tabs.length > 1) {
       info("Removing tab: " + gBrowser.tabs[0].linkedBrowser.currentURI.spec);
       gBrowser.selectTabAtIndex(0);
       gBrowser.removeCurrentTab();
     }
     ensure_opentabs_match_db(nextStep);
   }
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_urlbarRevert.js
@@ -0,0 +1,29 @@
+function test() {
+  waitForExplicitFinish();
+
+  let tab = gBrowser.addTab("http://example.com");
+  gBrowser.selectedTab = tab;
+
+  onLoad(function () {
+    let originalValue = gURLBar.value;
+
+    gBrowser.userTypedValue = "foobar";
+    gBrowser.selectedTab = gBrowser.tabs[0];
+    gBrowser.selectedTab = tab;
+    is(gURLBar.value, "foobar", "location bar displays typed value");
+
+    gURLBar.focus();
+    EventUtils.synthesizeKey("VK_ESCAPE", {});
+    is(gURLBar.value, originalValue, "ESC reverted the location bar value");
+
+    gBrowser.removeTab(tab);
+    finish();
+  });
+}
+
+function onLoad(callback) {
+  gBrowser.selectedBrowser.addEventListener("pageshow", function loadListener() {
+    gBrowser.selectedBrowser.removeEventListener("pageshow", loadListener, false);
+    executeSoon(callback);
+  });
+}
--- a/browser/components/distribution.js
+++ b/browser/components/distribution.js
@@ -40,16 +40,20 @@ EXPORTED_SYMBOLS = [ "DistributionCustom
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 const DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC =
   "distribution-customization-complete";
 
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+                                  "resource://gre/modules/PlacesUtils.jsm");
+
 function DistributionCustomizer() {
   let dirSvc = Cc["@mozilla.org/file/directory_service;1"].
                getService(Ci.nsIProperties);
   let iniFile = dirSvc.get("XCurProcD", Ci.nsIFile);
   iniFile.append("distribution");
   iniFile.append("distribution.ini");
   if (iniFile.exists())
     this._iniFile = iniFile;
@@ -73,37 +77,16 @@ DistributionCustomizer.prototype = {
     }
     catch (e) {
       locale = "en-US";
     }
     this.__defineGetter__("_locale", function() locale);
     return this._locale;
   },
 
-  get _bmSvc() {
-    let svc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
-              getService(Ci.nsINavBookmarksService);
-    this.__defineGetter__("_bmSvc", function() svc);
-    return this._bmSvc;
-  },
-
-  get _annoSvc() {
-    let svc = Cc["@mozilla.org/browser/annotation-service;1"].
-              getService(Ci.nsIAnnotationService);
-    this.__defineGetter__("_annoSvc", function() svc);
-    return this._annoSvc;
-  },
-
-  get _livemarkSvc() {
-    let svc = Cc["@mozilla.org/browser/livemark-service;2"].
-              getService(Ci.nsILivemarkService);
-    this.__defineGetter__("_livemarkSvc", function() svc);
-    return this._livemarkSvc;
-  },
-
   get _prefSvc() {
     let svc = Cc["@mozilla.org/preferences-service;1"].
               getService(Ci.nsIPrefService);
     this.__defineGetter__("_prefSvc", function() svc);
     return this._prefSvc;
   },
 
   get _prefs() {
@@ -162,73 +145,75 @@ DistributionCustomizer.prototype = {
       }
     }
 
     let prependIndex = 0;
     for (let iid = 0; iid <= maxItemId; iid++) {
       if (!items[iid])
         continue;
 
-      let index = this._bmSvc.DEFAULT_INDEX;
+      let index = PlacesUtils.bookmarks.DEFAULT_INDEX;
       let newId;
 
       switch (items[iid]["type"]) {
       case "default":
         break;
 
       case "folder":
         if (iid < defaultItemId)
           index = prependIndex++;
 
-        newId = this._bmSvc.createFolder(parentId, items[iid]["title"], index);
+        newId = PlacesUtils.bookmarks.createFolder(parentId,
+                                                   items[iid]["title"],
+                                                   index);
 
         this._parseBookmarksSection(newId, "BookmarksFolder-" +
                                     items[iid]["folderId"]);
 
         if (items[iid]["description"])
-          this._annoSvc.setItemAnnotation(newId,
-                                          "bookmarkProperties/description",
-                                          items[iid]["description"], 0,
-                                          this._annoSvc.EXPIRE_NEVER);
+          PlacesUtils.annotations.setItemAnnotation(newId,
+                                                    "bookmarkProperties/description",
+                                                    items[iid]["description"], 0,
+                                                    PlacesUtils.annotations.EXPIRE_NEVER);
 
         break;
 
       case "separator":
         if (iid < defaultItemId)
           index = prependIndex++;
-        this._bmSvc.insertSeparator(parentId, index);
+        PlacesUtils.bookmarks.insertSeparator(parentId, index);
         break;
 
       case "livemark":
         if (iid < defaultItemId)
           index = prependIndex++;
 
         // Don't bother updating the livemark contents on creation.
-        newId = this._livemarkSvc.
-          createLivemarkFolderOnly(parentId,
-                                   items[iid]["title"],
-                                   this._makeURI(items[iid]["siteLink"]),
-                                   this._makeURI(items[iid]["feedLink"]),
-                                   index);
+        PlacesUtils.livemarks.addLivemark({ title: items[iid]["title"]
+                                          , parentId: parentId
+                                          , index: index
+                                          , feedURI: this._makeURI(items[iid]["feedLink"])
+                                          , siteURI: this._makeURI(items[iid]["siteLink"])
+                                          });
         break;
 
       case "bookmark":
       default:
         if (iid < defaultItemId)
           index = prependIndex++;
 
-        newId = this._bmSvc.insertBookmark(parentId,
-                                           this._makeURI(items[iid]["link"]),
-                                           index, items[iid]["title"]);
+        newId = PlacesUtils.bookmarks.insertBookmark(parentId,
+                                                     this._makeURI(items[iid]["link"]),
+                                                     index, items[iid]["title"]);
 
         if (items[iid]["description"])
-          this._annoSvc.setItemAnnotation(newId,
-                                          "bookmarkProperties/description",
-                                          items[iid]["description"], 0,
-                                          this._annoSvc.EXPIRE_NEVER);
+          PlacesUtils.annotations.setItemAnnotation(newId,
+                                                    "bookmarkProperties/description",
+                                                    items[iid]["description"], 0,
+                                                    PlacesUtils.annotations.EXPIRE_NEVER);
 
         break;
       }
     }
   },
 
   _customizationsApplied: false,
   applyCustomizations: function DIST_applyCustomizations() {
@@ -272,20 +257,20 @@ DistributionCustomizer.prototype = {
     let bmProcessed = false;
     try {
       bmProcessed = this._prefs.getBoolPref(bmProcessedPref);
     }
     catch (e) {}
 
     if (!bmProcessed) {
       if (sections["BookmarksMenu"])
-        this._parseBookmarksSection(this._bmSvc.bookmarksMenuFolder,
+        this._parseBookmarksSection(PlacesUtils.bookmarksMenuFolderId,
                                     "BookmarksMenu");
       if (sections["BookmarksToolbar"])
-        this._parseBookmarksSection(this._bmSvc.toolbarFolder,
+        this._parseBookmarksSection(PlacesUtils.toolbarFolderId,
                                     "BookmarksToolbar");
       this._prefs.setBoolPref(bmProcessedPref, true);
     }
     return this._checkCustomizationComplete();
   },
 
   _prefDefaultsApplied: false,
   applyPrefDefaults: function DIST_applyPrefDefaults() {
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -772,16 +772,25 @@ BrowserGlue.prototype = {
     const PREF_TELEMETRY_PROMPTED = "toolkit.telemetry.prompted";
     const PREF_TELEMETRY_ENABLED  = "toolkit.telemetry.enabled";
     const PREF_TELEMETRY_REJECTED  = "toolkit.telemetry.rejected";
     const PREF_TELEMETRY_INFOURL  = "toolkit.telemetry.infoURL";
     const PREF_TELEMETRY_SERVER_OWNER = "toolkit.telemetry.server_owner";
     // This is used to reprompt users when privacy message changes
     const TELEMETRY_PROMPT_REV = 2;
 
+    function appendTelemetryNotification(notifyBox, message, buttons, hideclose) {
+      let notification = notifyBox.appendNotification(message, "telemetry", null,
+						      notifyBox.PRIORITY_INFO_LOW,
+						      buttons);
+      notification.setAttribute("hideclose", hideclose);
+      notification.persistence = -1;  // Until user closes it
+      return notification;
+    }
+
     var telemetryPrompted = null;
     try {
       telemetryPrompted = Services.prefs.getIntPref(PREF_TELEMETRY_PROMPTED);
     } catch(e) {}
     // If the user has seen the latest telemetry prompt, do not prompt again
     // else clear old prefs and reprompt
     if (telemetryPrompted === TELEMETRY_PROMPT_REV)
       return;
@@ -818,33 +827,30 @@ BrowserGlue.prototype = {
                         Services.prefs.setBoolPref(PREF_TELEMETRY_REJECTED, true);
                       }
                     }
                   ];
 
     // Set pref to indicate we've shown the notification.
     Services.prefs.setIntPref(PREF_TELEMETRY_PROMPTED, TELEMETRY_PROMPT_REV);
 
-    var notification = notifyBox.appendNotification(telemetryPrompt, "telemetry", null, notifyBox.PRIORITY_INFO_LOW, buttons);
-    notification.setAttribute("hideclose", true);
-    notification.persistence = -1;  // Until user closes it
-
+    let notification = appendTelemetryNotification(notifyBox, telemetryPrompt,
+						   buttons, true);
     let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
     let link = notification.ownerDocument.createElementNS(XULNS, "label");
     link.className = "text-link telemetry-text-link";
     link.setAttribute("value", browserBundle.GetStringFromName("telemetryLinkLabel"));
     link.addEventListener('click', function() {
       // Open the learn more url in a new tab
       browser.selectedTab = browser.addTab(Services.prefs.getCharPref(PREF_TELEMETRY_INFOURL));
       // Remove the notification on which the user clicked
       notification.parentNode.removeNotification(notification, true);
       // Add a new notification to that tab, with no "Learn more" link
       notifyBox = browser.getNotificationBox();
-      notification = notifyBox.appendNotification(telemetryPrompt, "telemetry", null, notifyBox.PRIORITY_INFO_LOW, buttons);
-      notification.persistence = -1; // Until user closes it
+      appendTelemetryNotification(notifyBox, telemetryPrompt, buttons, true);
     }, false);
     let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
     description.appendChild(link);
   },
 #endif
 
   _showPluginUpdatePage: function BG__showPluginUpdatePage() {
     Services.prefs.setBoolPref(PREF_PLUGINS_NOTIFYUSER, false);
@@ -1295,17 +1301,17 @@ BrowserGlue.prototype = {
   ensurePlacesDefaultQueriesInitialized:
   function BG_ensurePlacesDefaultQueriesInitialized() {
     // This is actual version of the smart bookmarks, must be increased every
     // time smart bookmarks change.
     // When adding a new smart bookmark below, its newInVersion property must
     // be set to the version it has been added in, we will compare its value
     // to users' smartBookmarksVersion and add new smart bookmarks without
     // recreating old deleted ones.
-    const SMART_BOOKMARKS_VERSION = 2;
+    const SMART_BOOKMARKS_VERSION = 3;
     const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
     const SMART_BOOKMARKS_PREF = "browser.places.smartBookmarksVersion";
 
     // TODO bug 399268: should this be a pref?
     const MAX_RESULTS = 10;
 
     // Get current smart bookmarks version.  If not set, create them.
     let smartBookmarksCurrentVersion = 0;
@@ -1341,17 +1347,16 @@ BrowserGlue.prototype = {
             title: bundle.GetStringFromName("recentlyBookmarkedTitle"),
             uri: NetUtil.newURI("place:folder=BOOKMARKS_MENU" +
                                 "&folder=UNFILED_BOOKMARKS" +
                                 "&folder=TOOLBAR" +
                                 "&queryType=" +
                                 Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS +
                                 "&sort=" +
                                 Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING +
-                                "&excludeItemIfParentHasAnnotation=livemark%2FfeedURI" +
                                 "&maxResults=" + MAX_RESULTS +
                                 "&excludeQueries=1"),
             parent: PlacesUtils.bookmarksMenuFolderId,
             position: menuIndex++,
             newInVersion: 1
           },
           RecentTags: {
             title: bundle.GetStringFromName("recentTagsTitle"),
--- a/browser/components/places/content/bookmarkProperties.js
+++ b/browser/components/places/content/bookmarkProperties.js
@@ -276,23 +276,36 @@ var BookmarkPropertiesPanel = {
                                      .getKeywordForBookmark(this._itemId);
           // Load In Sidebar
           this._loadInSidebar = PlacesUtils.annotations
                                            .itemHasAnnotation(this._itemId,
                                                               PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO);
           break;
 
         case "folder":
-          if (PlacesUtils.itemIsLivemark(this._itemId)) {
-            this._itemType = LIVEMARK_CONTAINER;
-            this._feedURI = PlacesUtils.livemarks.getFeedURI(this._itemId);
-            this._siteURI = PlacesUtils.livemarks.getSiteURI(this._itemId);
-          }
-          else
-            this._itemType = BOOKMARK_FOLDER;
+          this._itemType = BOOKMARK_FOLDER;
+          PlacesUtils.livemarks.getLivemark(
+            { id: this._itemId },
+            (function (aStatus, aLivemark) {
+              if (Components.isSuccessCode(aStatus)) {
+                this._itemType = LIVEMARK_CONTAINER;
+                this._feedURI = aLivemark.feedURI;
+                this._siteURI = aLivemark.siteURI;
+                this._fillEditProperties();
+
+                let acceptButton = document.documentElement.getButton("accept");
+                acceptButton.disabled = !this._inputIsValid();
+
+                let newHeight = window.outerHeight +
+                                this._element("descriptionField").boxObject.height;
+                window.resizeTo(window.outerWidth, newHeight);
+              }
+            }).bind(this)
+          );
+
           break;
       }
 
       // Description
       if (PlacesUtils.annotations
                      .itemHasAnnotation(this._itemId, PlacesUIUtils.DESCRIPTION_ANNO)) {
         this._description = PlacesUtils.annotations
                                        .getItemAnnotation(this._itemId,
@@ -336,18 +349,17 @@ var BookmarkPropertiesPanel = {
       case ACTION_EDIT:
         this._fillEditProperties();
         acceptButton.disabled = this._readOnly;
         break;
       case ACTION_ADD:
         this._fillAddProperties();
         // if this is an uri related dialog disable accept button until
         // the user fills an uri value.
-        if (this._itemType == BOOKMARK_ITEM ||
-            this._itemType == LIVEMARK_CONTAINER)
+        if (this._itemType == BOOKMARK_ITEM)
           acceptButton.disabled = !this._inputIsValid();
         break;
     }
 
     // When collapsible elements change their collapsed attribute we must
     // resize the dialog.
     // sizeToContent is not usable due to bug 90276, so we'll use resizeTo
     // instead and cache the element size. See WSucks in the legacy
@@ -513,26 +525,16 @@ var BookmarkPropertiesPanel = {
    */
   _inputIsValid: function BPP__inputIsValid() {
     if (this._itemType == BOOKMARK_ITEM &&
         !this._containsValidURI("locationField"))
       return false;
     if (this._isAddKeywordDialog && !this._element("keywordField").value.length)
       return false;
 
-    // Feed Location has to be a valid URI;
-    // Site Location has to be a valid URI or empty
-    if (this._itemType == LIVEMARK_CONTAINER) {
-      if (!this._containsValidURI("feedLocationField"))
-        return false;
-      if (!this._containsValidURI("siteLocationField") &&
-          (this._element("siteLocationField").value.length > 0))
-        return false;
-    }
-
     return true;
   },
 
   /**
    * Determines whether the XUL textbox with the given ID contains a
    * string that can be converted into an nsIURI.
    *
    * @param aTextboxID
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -1,49 +1,11 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Places Frontend Code.
- *
- * The Initial Developer of the Original Code is
- * Google Inc.
- * Portions created by the Initial Developer are Copyright (C) 2005
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Annie Sullivan <annie.sullivan@gmail.com>
- *   Ben Goodger <beng@google.com>
- *   Myk Melez <myk@mozilla.org>
- *   Marco Bonardo <mak77@bonardo.net>
- *   Asaf Romano <mano@mozilla.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
+/* 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/. */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 /**
  * The base view implements everything that's common to the toolbar and
  * menu views.
  */
@@ -165,17 +127,18 @@ PlacesViewBase.prototype = {
     let index = PlacesUtils.bookmarks.DEFAULT_INDEX;
     let container = this._resultNode;
     let orientation = Ci.nsITreeView.DROP_BEFORE;
     let isTag = false;
 
     let selectedNode = this.selectedNode;
     if (selectedNode) {
       let popup = document.popupNode;
-      if (!popup._placesNode || popup._placesNode == this._resultNode) {
+      if (!popup._placesNode || popup._placesNode == this._resultNode ||
+          popup._placesNode.itemId == -1) {
         // If a static menuitem is selected, or if the root node is selected,
         // the insertion point is inside the folder, at the end.
         container = selectedNode;
         orientation = Ci.nsITreeView.DROP_ON;
       }
       else {
         // In all other cases the insertion point is before that node.
         container = selectedNode.parent;
@@ -205,63 +168,72 @@ PlacesViewBase.prototype = {
 
   _cleanPopup: function PVB_cleanPopup(aPopup) {
     // Remove places popup children and update markers to keep track of
     // their indices.
     let start = aPopup._startMarker != -1 ? aPopup._startMarker + 1 : 0;
     let end = aPopup._endMarker != -1 ? aPopup._endMarker :
                                         aPopup.childNodes.length;
     let items = [];
-    let placesNodeFound = false;
+
+    // Automatically adjust the start and the end markers.
+    let firstNonStaticNodeFound = false;
     for (let i = start; i < end; ++i) {
       let item = aPopup.childNodes[i];
       if (item.getAttribute("builder") == "end") {
         // we need to do this for menus that have static content at the end but
         // are initially empty, eg. the history menu, we need to know where to
         // start inserting new items.
         aPopup._endMarker = i;
         break;
       }
+
       if (item._placesNode) {
         items.push(item);
-        placesNodeFound = true;
+        firstNonStaticNodeFound = true;
       }
       else {
-        // This is static content...
-        if (!placesNodeFound)
-          // ...at the start of the popup
-          // Initialized in menu.xml, in the base binding
+        // This is static content.
+        if (!firstNonStaticNodeFound) {
+          // We are at the beginning of the popup, in static content.
+          // The markers are initialized in menu.xml, in the base binding.
           aPopup._startMarker++;
+        }
         else {
-          // ...after places nodes
+          // We are at the end of the popup, after places nodes
           aPopup._endMarker = i;
           break;
         }
       }
     }
 
     for (let i = 0; i < items.length; ++i) {
       aPopup.removeChild(items[i]);
       if (aPopup._endMarker != -1)
         aPopup._endMarker--;
     }
   },
 
   _rebuildPopup: function PVB__rebuildPopup(aPopup) {
     this._cleanPopup(aPopup);
 
-    // If this is a livemark container check if the status menuitem has
-    // to be added or removed.
-    if (PlacesUtils.nodeIsLivemarkContainer(aPopup._placesNode))
-      this._ensureLivemarkStatusMenuItem(aPopup);
-
     let resultNode = aPopup._placesNode;
     if (!resultNode.containerOpen)
       return;
 
+    if (resultNode._feedURI) {
+      aPopup.removeAttribute("emptyplacesresult");
+      if (aPopup._emptyMenuItem) {
+        aPopup._emptyMenuItem.hidden = true;
+      }
+      aPopup._built = true;
+      this._populateLivemarkPopup(aPopup);
+      return;
+    }
+
     let cc = resultNode.childCount;
     if (cc > 0) {
       aPopup.removeAttribute("emptyplacesresult");
       if (aPopup._emptyMenuItem)
         aPopup._emptyMenuItem.hidden = true;
 
       for (let i = 0; i < cc; ++i) {
         let child = resultNode.getChild(i);
@@ -298,21 +270,24 @@ PlacesViewBase.prototype = {
     aPopup._emptyMenuItem = document.createElement("menuitem");
     aPopup._emptyMenuItem.setAttribute("label", label);
     aPopup._emptyMenuItem.setAttribute("disabled", true);
     aPopup.appendChild(aPopup._emptyMenuItem);
   },
 
   _createMenuItemForPlacesNode:
   function PVB__createMenuItemForPlacesNode(aPlacesNode) {
+    delete aPlacesNode._DOMElement;
     let element;
     let type = aPlacesNode.type;
-    if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR)
+    if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
       element = document.createElement("menuseparator");
+    }
     else {
+      let itemId = aPlacesNode.itemId;
       if (PlacesUtils.uriTypes.indexOf(type) != -1) {
         element = document.createElement("menuitem");
         element.className = "menuitem-iconic bookmark-item menuitem-with-favicon";
         element.setAttribute("scheme",
                              PlacesUIUtils.guessUrlSchemeForUI(aPlacesNode.uri));
       }
       else if (PlacesUtils.containerTypes.indexOf(type) != -1) {
         element = document.createElement("menu");
@@ -322,19 +297,29 @@ PlacesViewBase.prototype = {
           element.setAttribute("query", "true");
           if (PlacesUtils.nodeIsTagQuery(aPlacesNode))
             element.setAttribute("tagContainer", "true");
           else if (PlacesUtils.nodeIsDay(aPlacesNode))
             element.setAttribute("dayContainer", "true");
           else if (PlacesUtils.nodeIsHost(aPlacesNode))
             element.setAttribute("hostContainer", "true");
         }
-        else if (aPlacesNode.itemId != -1) {
-          if (PlacesUtils.nodeIsLivemarkContainer(aPlacesNode))
-            element.setAttribute("livemark", "true");
+        else if (itemId != -1) {
+          PlacesUtils.livemarks.getLivemark(
+            { id: itemId },
+            function (aStatus, aLivemark) {
+              if (Components.isSuccessCode(aStatus)) {
+                element.setAttribute("livemark", "true");
+                // Set an expando on the node, controller will use it to build
+                // its metadata.
+                aPlacesNode._feedURI = aLivemark.feedURI;
+                aPlacesNode._siteURI = aLivemark.siteURI;
+              }
+            }
+          );
         }
 
         let popup = document.createElement("menupopup");
         popup._placesNode = PlacesUtils.asContainer(aPlacesNode);
         if (this._nativeView) {
           popup._startMarker = -1;
           popup._endMarker = -1;
         }
@@ -387,52 +372,94 @@ PlacesViewBase.prototype = {
     }
 
     if (aPopup._endMarker != -1)
       aPopup._endMarker++;
 
     return element;
   },
 
+  _setLivemarkSiteURIMenuItem:
+  function PVB__setLivemarkSiteURIMenuItem(aPopup) {
+    let siteUrl = aPopup._placesNode._siteURI ? aPopup._placesNode._siteURI.spec
+                                              : null;
+    if (!siteUrl && aPopup._siteURIMenuitem) {
+      aPopup.removeChild(aPopup._siteURIMenuitem);
+      aPopup._siteURIMenuitem = null;
+      aPopup._startMarker--;
+      aPopup.removeChild(aPopup._siteURIMenuseparator);
+      aPopup._siteURIMenuseparator = null;
+      aPopup._startMarker--;
+    }
+    else if (siteUrl && !aPopup._siteURIMenuitem) {
+      // Add "Open (Feed Name)" menuitem.
+      aPopup._siteURIMenuitem = document.createElement("menuitem");
+      aPopup._siteURIMenuitem.className = "openlivemarksite-menuitem";
+      aPopup._siteURIMenuitem.setAttribute("targetURI", siteUrl);
+      aPopup._siteURIMenuitem.setAttribute("oncommand",
+        "openUILink(this.getAttribute('targetURI'), event);");
+
+      // If a user middle-clicks this item we serve the oncommand event.
+      // We are using checkForMiddleClick because of Bug 246720.
+      // Note: stopPropagation is needed to avoid serving middle-click
+      // with BT_onClick that would open all items in tabs.
+      aPopup._siteURIMenuitem.setAttribute("onclick",
+        "checkForMiddleClick(this, event); event.stopPropagation();");
+      let label =
+        PlacesUIUtils.getFormattedString("menuOpenLivemarkOrigin.label",
+                                         [aPopup.parentNode.getAttribute("label")])
+      aPopup._siteURIMenuitem.setAttribute("label", label);
+      aPopup.insertBefore(aPopup._siteURIMenuitem,
+                          aPopup.childNodes.item(aPopup._startMarker + 1));
+      aPopup._startMarker++;
+
+      aPopup._siteURIMenuseparator = document.createElement("menuseparator");
+      aPopup.insertBefore(aPopup._siteURIMenuseparator,
+                         aPopup.childNodes.item(aPopup._startMarker + 1));
+      aPopup._startMarker++;
+    }
+  },
+
   /**
    * Add, update or remove the livemark status menuitem.
    * @param aPopup
    *        The livemark container popup
+   * @param aStatus
+   *        The livemark status
    */
-  _ensureLivemarkStatusMenuItem:
-  function PVB_ensureLivemarkStatusMenuItem(aPopup) {
+  _setLivemarkStatusMenuItem:
+  function PVB_setLivemarkStatusMenuItem(aPopup, aStatus) {
     let itemId = aPopup._placesNode.itemId;
-    let as = PlacesUtils.annotations;
+    let statusMenuitem = aPopup._statusMenuitem;
+    let stringId = "";
+    if (aStatus == Ci.mozILivemark.STATUS_LOADING)
+      stringId = "bookmarksLivemarkLoading";
+    else if (aStatus == Ci.mozILivemark.STATUS_FAILED)
+      stringId = "bookmarksLivemarkFailed";
 
-    let lmStatus = null;
-    if (as.itemHasAnnotation(itemId, PlacesUtils.LMANNO_LOADFAILED))
-      lmStatus = "bookmarksLivemarkFailed";
-    else if (as.itemHasAnnotation(itemId, PlacesUtils.LMANNO_LOADING))
-      lmStatus = "bookmarksLivemarkLoading";
-
-    let lmStatusElt = aPopup._lmStatusMenuItem;
-    if (lmStatus && !lmStatusElt) {
+    if (stringId && !statusMenuitem) {
       // Create the status menuitem and cache it in the popup object.
-      lmStatusElt = document.createElement("menuitem");
-      lmStatusElt.setAttribute("lmStatus", lmStatus);
-      lmStatusElt.setAttribute("label", PlacesUIUtils.getString(lmStatus));
-      lmStatusElt.setAttribute("disabled", true);
-      aPopup.insertBefore(lmStatusElt,
+      statusMenuitem = document.createElement("menuitem");
+      statusMenuitem.setAttribute("livemarkStatus", stringId);
+      statusMenuitem.setAttribute("label", PlacesUIUtils.getString(stringId));
+      statusMenuitem.setAttribute("disabled", true);
+      aPopup.insertBefore(statusMenuitem,
                           aPopup.childNodes.item(aPopup._startMarker + 1));
-      aPopup._lmStatusMenuItem = lmStatusElt;
+      aPopup._statusMenuitem = statusMenuitem;
       aPopup._startMarker++;
     }
-    else if (lmStatus && lmStatusElt.getAttribute("lmStatus") != lmStatus) {
+    else if (stringId &&
+             statusMenuitem.getAttribute("livemarkStatus") != stringId) {
       // Status has changed, update the cached status menuitem.
-      lmStatusElt.setAttribute("label", this.getString(lmStatus));
+      statusMenuitem.setAttribute("label", PlacesUIUtils.getString(stringId));
     }
-    else if (!lmStatus && lmStatusElt) {
-      // No status, remove the cached menuitem.
-      aPopup.removeChild(aPopup._lmStatusMenuItem);
-      aPopup._lmStatusMenuItem = null;
+    else if (!stringId && statusMenuitem) {
+      // The livemark has finished loading.
+      aPopup.removeChild(aPopup._statusMenuitem);
+      aPopup._statusMenuitem = null;
       aPopup._startMarker--;
     }
   },
 
   toggleCutNode: function PVB_toggleCutNode(aNode, aValue) {
     let elt = aNode._DOMElement;
     if (elt) {
       // We may get the popup for menus, but we need the menu itself.
@@ -483,24 +510,32 @@ PlacesViewBase.prototype = {
     let elt = aPlacesNode._DOMElement;
     if (!elt)
       throw "aPlacesNode must have _DOMElement set";
 
     // All livemarks have a feedURI, so use it as our indicator of a livemark
     // being modified.
     if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
       let menu = elt.parentNode;
-      if (!menu.hasAttribute("livemark"))
+      if (!menu.hasAttribute("livemark")) {
         menu.setAttribute("livemark", "true");
-    }
+      }
 
-    if ([PlacesUtils.LMANNO_LOADING,
-         PlacesUtils.LMANNO_LOADFAILED].indexOf(aAnno) != -1) {
-      // Loading status changed, update the livemark status menuitem.
-      this._ensureLivemarkStatusMenuItem(elt);
+      PlacesUtils.livemarks.getLivemark(
+        { id: aPlacesNode.itemId },
+        (function (aStatus, aLivemark) {
+          if (Components.isSuccessCode(aStatus)) {
+            // Set an expando on the node, controller will use it to build
+            // its metadata.
+            aPlacesNode._feedURI = aLivemark.feedURI;
+            aPlacesNode._siteURI = aLivemark.siteURI;
+            this.invalidateContainer(aPlacesNode);
+          }
+        }).bind(this)
+      );
     }
   },
 
   nodeTitleChanged:
   function PVB_nodeTitleChanged(aPlacesNode, aNewTitle) {
     let elt = aPlacesNode._DOMElement;
     if (!elt)
       throw "aPlacesNode must have _DOMElement set";
@@ -575,17 +610,34 @@ PlacesViewBase.prototype = {
       // No worries: If elt is the last item (i.e. no nextSibling),
       // _insertNewItem/_insertNewItemToPopup will insert the new element as
       // the last item.
       let nextElt = elt.nextSibling;
       this._insertNewItemToPopup(aNewPlacesNode, parentElt, nextElt);
     }
   },
 
-  nodeHistoryDetailsChanged: function() { },
+  nodeHistoryDetailsChanged:
+  function PVB_nodeHistoryDetailsChanged(aPlacesNode, aTime, aCount) {
+    if (aPlacesNode.parent && aPlacesNode.parent._feedURI) {
+      // Find the node in the parent.
+      let popup = aPlacesNode.parent._DOMElement;
+      for (let i = popup._startMarker; i < popup.childNodes.length; i++) {
+        let child = popup.childNodes[i];
+        if (child._placesNode && child._placesNode.uri == aPlacesNode.uri) {
+          if (aCount)
+            child.setAttribute("visited", "true");
+          else
+            child.removeAttribute("visited");
+          break;
+        }
+      }
+    }
+  },
+
   nodeTagsChanged: function() { },
   nodeDateAddedChanged: function() { },
   nodeLastModifiedChanged: function() { },
   nodeKeywordChanged: function() { },
   sortingChanged: function() { },
   batching: function() { },
 
   nodeInserted:
@@ -637,20 +689,70 @@ PlacesViewBase.prototype = {
     }
   },
 
   containerStateChanged:
   function PVB_containerStateChanged(aPlacesNode, aOldState, aNewState) {
     if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED ||
         aNewState == Ci.nsINavHistoryContainerResultNode.STATE_CLOSED) {
       this.invalidateContainer(aPlacesNode);
+
+      if (PlacesUtils.nodeIsFolder(aPlacesNode)) {
+        let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions;
+        if (queryOptions.excludeItems) {
+          return;
+        }
+
+        PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId },
+          (function (aStatus, aLivemark) {
+            if (Components.isSuccessCode(aStatus)) {
+              let shouldInvalidate = !aPlacesNode._feedURI;
+              aPlacesNode._feedURI = aLivemark.feedURI;
+              aPlacesNode._siteURI = aLivemark.siteURI;
+              if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED) {
+                aLivemark.registerForUpdates(aPlacesNode, this);
+                aLivemark.reload();
+                if (shouldInvalidate)
+                  this.invalidateContainer(aPlacesNode);
+              }
+              else {
+                aLivemark.unregisterForUpdates(aPlacesNode);
+              }
+            }
+          }).bind(this)
+        );
+      }
     }
-    else {
-      throw "Unexpected state passed to containerStateChanged";
-    }
+  },
+
+  _populateLivemarkPopup: function PVB__populateLivemarkPopup(aPopup)
+  {
+    this._setLivemarkSiteURIMenuItem(aPopup);
+    this._setLivemarkStatusMenuItem(aPopup, Ci.mozILivemark.STATUS_LOADING);
+
+    PlacesUtils.livemarks.getLivemark({ id: aPopup._placesNode.itemId },
+      (function (aStatus, aLivemark) {
+        let placesNode = aPopup._placesNode;
+        if (!Components.isSuccessCode(aStatus) || !placesNode.containerOpen)
+          return;
+
+        this._setLivemarkStatusMenuItem(aPopup, aLivemark.status);
+        this._cleanPopup(aPopup);
+
+        let children = aLivemark.getNodesForContainer(placesNode);
+        for (let i = 0; i < children.length; i++) {
+          let child = children[i];
+          this.nodeInserted(placesNode, child, i);
+          if (child.accessCount)
+            child._DOMElement.setAttribute("visited", true);
+          else
+            child._DOMElement.removeAttribute("visited");
+        }
+      }).bind(this)
+    );
   },
 
   invalidateContainer: function PVB_invalidateContainer(aPlacesNode) {
     let elt = aPlacesNode._DOMElement;
     if (!elt)
       throw "aPlacesNode must have _DOMElement set";
 
     elt._built = false;
@@ -691,97 +793,65 @@ PlacesViewBase.prototype = {
    * @param aPopup
    *        a Places popup.
    */
   _mayAddCommandsItems: function PVB__mayAddCommandsItems(aPopup) {
     // The command items are never added to the root popup.
     if (aPopup == this._rootElt)
       return;
 
-    // Check if the popup contains at least 2 menuitems with places nodes
-    let numURINodes = 0;
-    let currentChild = aPopup.firstChild;
-    while (currentChild) {
-      if (currentChild.localName == "menuitem" && currentChild._placesNode) {
-        if (++numURINodes == 2)
-          break;
+    let hasMultipleURIs = false;
+
+    // Check if the popup contains at least 2 menuitems with places nodes.
+    // We don't currently support opening multiple uri nodes when they are not
+    // populated by the result.
+    if (aPopup._placesNode.childCount > 0) {
+      let currentChild = aPopup.firstChild;
+      let numURINodes = 0;
+      while (currentChild) {
+        if (currentChild.localName == "menuitem" && currentChild._placesNode) {
+          if (++numURINodes == 2)
+            break;
+        }
+        currentChild = currentChild.nextSibling;
       }
-      currentChild = currentChild.nextSibling;
-    }
-
-    let hasMultipleURIs = numURINodes > 1;
-    let itemId = aPopup._placesNode.itemId;
-    let siteURIString = "";
-    if (itemId != -1 && PlacesUtils.itemIsLivemark(itemId)) {
-      let siteURI = PlacesUtils.livemarks.getSiteURI(itemId);
-      if (siteURI)
-        siteURIString = siteURI.spec;
+      hasMultipleURIs = numURINodes > 1;
     }
 
-    if (!siteURIString && aPopup._endOptOpenSiteURI) {
-      aPopup.removeChild(aPopup._endOptOpenSiteURI);
-      aPopup._endOptOpenSiteURI = null;
-    }
+    if (!hasMultipleURIs) {
+      // We don't have to show any option.
+      if (aPopup._endOptOpenAllInTabs) {
+        aPopup.removeChild(aPopup._endOptOpenAllInTabs);
+        aPopup._endOptOpenAllInTabs = null;
+        aPopup._endMarker--;
 
-    if (!hasMultipleURIs && aPopup._endOptOpenAllInTabs) {
-      aPopup.removeChild(aPopup._endOptOpenAllInTabs);
-      aPopup._endOptOpenAllInTabs = null;
-    }
-
-    if (!(hasMultipleURIs || siteURIString)) {
-      // We don't have to show any option.
-      if (aPopup._endOptSeparator) {
         aPopup.removeChild(aPopup._endOptSeparator);
         aPopup._endOptSeparator = null;
-        aPopup._endMarker = -1;
+        aPopup._endMarker--;
       }
-      return;
     }
-
-    if (!aPopup._endOptSeparator) {
+    else if (!aPopup._endOptOpenAllInTabs) {
       // Create a separator before options.
       aPopup._endOptSeparator = document.createElement("menuseparator");
       aPopup._endOptSeparator.className = "bookmarks-actions-menuseparator";
-      aPopup._endMarker = aPopup.childNodes.length;
       aPopup.appendChild(aPopup._endOptSeparator);
-    }
-
-    if (siteURIString && !aPopup._endOptOpenSiteURI) {
-      // Add "Open (Feed Name)" menuitem if it's a livemark with a siteURI.
-      aPopup._endOptOpenSiteURI = document.createElement("menuitem");
-      aPopup._endOptOpenSiteURI.className = "openlivemarksite-menuitem";
-      aPopup._endOptOpenSiteURI.setAttribute("targetURI", siteURIString);
-      aPopup._endOptOpenSiteURI.setAttribute("oncommand",
-          "openUILink(this.getAttribute('targetURI'), event);");
+      aPopup._endMarker++;
 
-      // If a user middle-clicks this item we serve the oncommand event
-      // We are using checkForMiddleClick because of Bug 246720
-      // Note: stopPropagation is needed to avoid serving middle-click
-      // with BT_onClick that would open all items in tabs.
-      aPopup._endOptOpenSiteURI.setAttribute("onclick",
-          "checkForMiddleClick(this, event); event.stopPropagation();");
-      aPopup._endOptOpenSiteURI.setAttribute("label",
-          PlacesUIUtils.getFormattedString("menuOpenLivemarkOrigin.label",
-          [aPopup.parentNode.getAttribute("label")]));
-      aPopup.appendChild(aPopup._endOptOpenSiteURI);
-    }
-
-    if (hasMultipleURIs && !aPopup._endOptOpenAllInTabs) {
-      // Add the "Open All in Tabs" menuitem if there are
-      // at least two menuitems with places result nodes.
+      // Add the "Open All in Tabs" menuitem.
       aPopup._endOptOpenAllInTabs = document.createElement("menuitem");
       aPopup._endOptOpenAllInTabs.className = "openintabs-menuitem";
       aPopup._endOptOpenAllInTabs.setAttribute("oncommand",
         "PlacesUIUtils.openContainerNodeInTabs(this.parentNode._placesNode, event, " +
                                                "PlacesUIUtils.getViewForNode(this));");
       aPopup._endOptOpenAllInTabs.setAttribute("onclick",
         "checkForMiddleClick(this, event); event.stopPropagation();");
       aPopup._endOptOpenAllInTabs.setAttribute("label",
         gNavigatorBundle.getString("menuOpenAllInTabs.label"));
       aPopup.appendChild(aPopup._endOptOpenAllInTabs);
+      aPopup._endMarker++;
     }
   },
 
   _onPopupShowing: function PVB__onPopupShowing(aEvent) {
     // Avoid handling popupshowing of inner views.
     let popup = aEvent.originalTarget;
     if (popup._placesNode && PlacesUIUtils.getViewForNode(popup) == this) {
       if (!popup._placesNode.containerOpen)
@@ -896,16 +966,17 @@ PlacesToolbar.prototype = {
       // a rebuild of the toolbar, it has to be rebuilt.
       // Otherwise, it will be initialized when the toolbar overflows.
       this._chevronPopup.place = this.place;
     }
   },
 
   _insertNewItem:
   function PT__insertNewItem(aChild, aBefore) {
+    delete aChild._DOMElement;
     let type = aChild.type;
     let button;
     if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
       button = document.createElement("toolbarseparator");
     }
     else {
       button = document.createElement("toolbarbutton");
       button.className = "bookmark-item";
@@ -918,18 +989,29 @@ PlacesToolbar.prototype = {
         button.setAttribute("type", "menu");
         button.setAttribute("container", "true");
 
         if (PlacesUtils.nodeIsQuery(aChild)) {
           button.setAttribute("query", "true");
           if (PlacesUtils.nodeIsTagQuery(aChild))
             button.setAttribute("tagContainer", "true");
         }
-        else if (PlacesUtils.nodeIsLivemarkContainer(aChild)) {
-          button.setAttribute("livemark", "true");
+        else if (PlacesUtils.nodeIsFolder(aChild)) {
+          PlacesUtils.livemarks.getLivemark(
+            { id: aChild.itemId },
+            function (aStatus, aLivemark) {
+              if (Components.isSuccessCode(aStatus)) {
+                button.setAttribute("livemark", "true");
+                // Set an expando on the node, controller will use it to build
+                // its metadata.
+                aChild._feedURI = aLivemark.feedURI;
+                aChild._siteURI = aLivemark.siteURI;
+              }
+            }
+          );
         }
 
         let popup = document.createElement("menupopup");
         popup.setAttribute("placespopup", "true");
         button.appendChild(popup);
         popup._placesNode = PlacesUtils.asContainer(aChild);
 #ifndef XP_MACOSX
         popup.setAttribute("context", "placesContext");
@@ -1183,22 +1265,29 @@ PlacesToolbar.prototype = {
       elt = elt.parentNode;
 
     if (elt.parentNode == this._rootElt) {
       // Node is on the toolbar.
 
       // All livemarks have a feedURI, so use it as our indicator.
       if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
         elt.setAttribute("livemark", true);
-      }
 
-      if ([PlacesUtils.LMANNO_LOADING,
-           PlacesUtils.LMANNO_LOADFAILED].indexOf(aAnno) != -1) {
-        // Loading status changed, update the livemark status menuitem.
-        this._ensureLivemarkStatusMenuItem(elt.firstChild);
+        PlacesUtils.livemarks.getLivemark(
+          { id: aPlacesNode.itemId },
+          (function (aStatus, aLivemark) {
+            if (Components.isSuccessCode(aStatus)) {
+              // Set an expando on the node, controller will use it to build
+              // its metadata.
+              aPlacesNode._feedURI = aLivemark.feedURI;
+              aPlacesNode._siteURI = aLivemark.siteURI;
+              this.invalidateContainer(aPlacesNode);
+            }
+          }).bind(this)
+        );
       }
     }
     else {
       // Node is in a submenu.
       PlacesViewBase.prototype.nodeAnnotationChanged.apply(this, arguments);
     }
   },
 
@@ -1618,23 +1707,26 @@ PlacesToolbar.prototype = {
     if (parent.localName == "toolbarbutton")
       this._openedMenuButton = parent;
 
     return PlacesViewBase.prototype._onPopupShowing.apply(this, arguments);
   },
 
   _onPopupHidden: function PT__onPopupHidden(aEvent) {
     let popup = aEvent.target;
-
+    let placesNode = popup._placesNode;
     // Avoid handling popuphidden of inner views
-    if (popup._placesNode && PlacesUIUtils.getViewForNode(popup) == this) {
+    if (placesNode && PlacesUIUtils.getViewForNode(popup) == this) {
       // UI performance: folder queries are cheap, keep the resultnode open
       // so we don't rebuild its contents whenever the popup is reopened.
-      if (!PlacesUtils.nodeIsFolder(popup._placesNode))
-        popup._placesNode.containerOpen = false;
+      // Though, we want to always close feed containers so their expiration
+      // status will be checked at next opening.
+      if (!PlacesUtils.nodeIsFolder(placesNode) || placesNode._feedURI) {
+        placesNode.containerOpen = false;
+      }
     }
 
     let parent = popup.parentNode;
     if (parent.localName == "toolbarbutton") {
       this._openedMenuButton = null;
       // Clear the dragover attribute if present, if we are dragging into a
       // folder in the hierachy of current opened popup we don't clear
       // this attribute on clearOverFolder.  See Notify for closeTimer.
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -39,17 +39,16 @@
  * ***** END LICENSE BLOCK ***** */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 
 // XXXmano: we should move most/all of these constants to PlacesUtils
 const ORGANIZER_ROOT_BOOKMARKS = "place:folder=BOOKMARKS_MENU&excludeItems=1&queryType=1";
-const ORGANIZER_SUBSCRIPTIONS_QUERY = "place:annotation=livemark%2FfeedURI";
 
 // No change to the view, preserve current selection
 const RELOAD_ACTION_NOTHING = 0;
 // Inserting items new to the view, select the inserted rows
 const RELOAD_ACTION_INSERT = 1;
 // Removing items from the view, select the first item after the last selected
 const RELOAD_ACTION_REMOVE = 2;
 // Moving items within a view, don't treat the dropped items as additional
@@ -203,25 +202,21 @@ PlacesController.prototype = {
       return this._canInsert();
     case "placesCmd_new:separator":
       return this._canInsert() &&
              !PlacesUtils.asQuery(this._view.result.root).queryOptions.excludeItems &&
              this._view.result.sortingMode ==
                  Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
     case "placesCmd_show:info":
       var selectedNode = this._view.selectedNode;
-      if (selectedNode &&
-          PlacesUtils.getConcreteItemId(selectedNode) != -1  &&
-          !PlacesUtils.nodeIsLivemarkItem(selectedNode))
-        return true;
-      return false;
+      return selectedNode && PlacesUtils.getConcreteItemId(selectedNode) != -1
     case "placesCmd_reload":
       // Livemark containers
       var selectedNode = this._view.selectedNode;
-      return selectedNode && PlacesUtils.nodeIsLivemarkContainer(selectedNode);
+      return selectedNode && !!selectedNode._feedURI;
     case "placesCmd_sortBy:name":
       var selectedNode = this._view.selectedNode;
       return selectedNode &&
              PlacesUtils.nodeIsFolder(selectedNode) &&
              !PlacesUtils.nodeIsReadOnly(selectedNode) &&
              this._view.result.sortingMode ==
                  Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
     case "placesCmd_createBookmark":
@@ -504,17 +499,17 @@ PlacesController.prototype = {
           if (PlacesUtils.nodeIsBookmark(node)) {
             nodeData["bookmark"] = true;
             PlacesUtils.nodeIsTagQuery(node.parent)
 
             var parentNode = node.parent;
             if (parentNode) {
               if (PlacesUtils.nodeIsTagQuery(parentNode))
                 nodeData["tagChild"] = true;
-              else if (PlacesUtils.nodeIsLivemarkContainer(parentNode))
+              else if (parentNode._feedURI)
                 nodeData["livemarkChild"] = true;
             }
           }
           break;
       }
 
       // annotations
       if (uri) {
@@ -729,18 +724,27 @@ PlacesController.prototype = {
            "This method should be passed a URI as a nsIURI object, not as a string.");
   },
 
   /**
    * Reloads the selected livemark if any.
    */
   reloadSelectedLivemark: function PC_reloadSelectedLivemark() {
     var selectedNode = this._view.selectedNode;
-    if (selectedNode && PlacesUtils.nodeIsLivemarkContainer(selectedNode))
-      PlacesUtils.livemarks.reloadLivemarkFolder(selectedNode.itemId);
+    if (selectedNode) {
+      let itemId = selectedNode.itemId;
+      PlacesUtils.livemarks.getLivemark(
+        { id: itemId },
+        (function(aStatus, aLivemark) {
+          if (Components.isSuccessCode(aStatus)) {
+            aLivemark.reload(true);
+          }
+        }).bind(this)
+      );
+    }
   },
 
   /**
    * Opens the links in the selected folder, or the selected links in new tabs.
    */
   openSelectionInTabs: function PC_openLinksInTabs(aEvent) {
     var node = this._view.selectedNode;
     if (node && PlacesUtils.nodeIsContainer(node))
@@ -1060,20 +1064,22 @@ PlacesController.prototype = {
           addData(PlacesUtils.TYPE_HTML, index, overrideURI);
         }
 
         // This order is _important_! It controls how this and other
         // applications select data to be inserted based on type.
         addData(PlacesUtils.TYPE_X_MOZ_PLACE, i);
 
         // Drop the feed uri for livemark containers
-        if (PlacesUtils.nodeIsLivemarkContainer(node))
-          addURIData(i, PlacesUtils.livemarks.getFeedURI(node.itemId).spec);
-        else if (node.uri)
+        if (node._feedURI) {
+          addURIData(i, node._feedURI.spec);
+        }
+        else if (node.uri) {
           addURIData(i);
+        }
       }
     }
     finally {
       if (!didSuppressNotifications)
         result.suppressNotifications = false;
     }
   },
 
@@ -1133,18 +1139,17 @@ PlacesController.prototype = {
     // of them automatically.
     let copiedFolders = [];
     aNodes.forEach(function (node) {
       if (this._shouldSkipNode(node, copiedFolders))
         return;
       if (PlacesUtils.nodeIsFolder(node))
         copiedFolders.push(node);
 
-      let overrideURI = PlacesUtils.nodeIsLivemarkContainer(node) ?
-        PlacesUtils.livemarks.getFeedURI(node.itemId).spec : null;
+      let overrideURI = node._feedURI ? node._feedURI.spec : null;
       let resolveShortcuts = !PlacesControllerDragHelper.canMoveNode(node);
 
       contents.forEach(function (content) {
         content.entries.push(
           PlacesUtils.wrapNode(node, content.type, overrideURI, resolveShortcuts)
         );
       });
     }, this);
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -1,44 +1,11 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is the Places Bookmark Properties dialog.
- *
- * The Initial Developer of the Original Code is Google Inc.
- * Portions created by the Initial Developer are Copyright (C) 2006
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Asaf Romano <mano@mozilla.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
+/* 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 LAST_USED_ANNO = "bookmarkPropertiesDialog/folderLastUsed";
 const MAX_FOLDER_ITEM_IN_MENU_LIST = 5;
 
 var gEditItemOverlay = {
   _uri: null,
   _itemId: -1,
   _itemIds: [],
@@ -47,16 +14,17 @@ var gEditItemOverlay = {
   _allTags: [],
   _multiEdit: false,
   _itemType: -1,
   _readOnly: false,
   _hiddenRows: [],
   _observersAdded: false,
   _staticFoldersListBuilt: false,
   _initialized: false,
+  _titleOverride: "",
 
   // the first field which was edited after this panel was initialized for
   // a certain item
   _firstEditedField: "",
 
   get itemId() {
     return this._itemId;
   },
@@ -75,16 +43,18 @@ var gEditItemOverlay = {
   _determineInfo: function EIO__determineInfo(aInfo) {
     // hidden rows
     if (aInfo && aInfo.hiddenRows)
       this._hiddenRows = aInfo.hiddenRows;
     else
       this._hiddenRows.splice(0, this._hiddenRows.length);
     // force-read-only
     this._readOnly = aInfo && aInfo.forceReadOnly;
+    this._titleOverride = aInfo && aInfo.titleOverride ? aInfo.titleOverride
+                                                       : "";
   },
 
   _showHideRows: function EIO__showHideRows() {
     var isBookmark = this._itemId != -1 &&
                      this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK;
     var isQuery = false;
     if (this._uri)
       isQuery = this._uri.schemeIs("place");
@@ -155,42 +125,44 @@ var gEditItemOverlay = {
     this._determineInfo(aInfo);
     if (aFor instanceof Ci.nsIURI) {
       this._itemId = -1;
       this._uri = aFor;
       this._readOnly = true;
     }
     else {
       this._itemId = aFor;
+      // We can't store information on invalid itemIds.
+      this._readOnly = this._readOnly || this._itemId == -1;
+
       var containerId = PlacesUtils.bookmarks.getFolderIdForItem(this._itemId);
       this._itemType = PlacesUtils.bookmarks.getItemType(this._itemId);
       if (this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) {
         this._uri = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
-        if (!this._readOnly) // If readOnly wasn't forced through aInfo
-          this._readOnly = PlacesUtils.itemIsLivemark(containerId);
         this._initTextField("keywordField",
                             PlacesUtils.bookmarks
                                        .getKeywordForBookmark(this._itemId));
-        // Load In Sidebar checkbox
         this._element("loadInSidebarCheckbox").checked =
           PlacesUtils.annotations.itemHasAnnotation(this._itemId,
                                                     PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO);
       }
       else {
-        if (!this._readOnly) // If readOnly wasn't forced through aInfo
-          this._readOnly = false;
-
         this._uri = null;
-        this._isLivemark = PlacesUtils.itemIsLivemark(this._itemId);
-        if (this._isLivemark) {
-          var feedURI = PlacesUtils.livemarks.getFeedURI(this._itemId);
-          var siteURI = PlacesUtils.livemarks.getSiteURI(this._itemId);
-          this._initTextField("feedLocationField", feedURI.spec);
-          this._initTextField("siteLocationField", siteURI ? siteURI.spec : "");
-        }
+        this._isLivemark = false;
+        PlacesUtils.livemarks.getLivemark(
+          {id: this._itemId },
+          (function (aStatus, aLivemark) {
+            if (Components.isSuccessCode(aStatus)) {
+              this._isLivemark = true;
+              this._initTextField("feedLocationField", aLivemark.feedURI.spec, true);
+              this._initTextField("siteLocationField", aLivemark.siteURI ? aLivemark.siteURI.spec : "", true);
+              this._showHideRows();
+            }
+          }).bind(this)
+        );
       }
 
       // folder picker
       this._initFolderMenuList(containerId);
 
       // description field
       this._initTextField("descriptionField", 
                           PlacesUIUtils.getItemDescription(this._itemId));
@@ -374,20 +346,27 @@ var gEditItemOverlay = {
     throw Cr.NS_ERROR_NO_INTERFACE;
   },
 
   _element: function EIO__element(aID) {
     return document.getElementById("editBMPanel_" + aID);
   },
 
   _getItemStaticTitle: function EIO__getItemStaticTitle() {
-    if (this._itemId == -1)
-      return PlacesUtils.history.getPageTitle(this._uri);
+    if (this._titleOverride)
+      return this._titleOverride;
 
-    return PlacesUtils.bookmarks.getItemTitle(this._itemId);
+    let title = "";
+    if (this._itemId == -1) {
+      title = PlacesUtils.history.getPageTitle(this._uri);
+    }
+    else {
+      title = PlacesUtils.bookmarks.getItemTitle(this._itemId);
+    }
+    return title;
   },
 
   _initNamePicker: function EIO_initNamePicker() {
     var namePicker = this._element("namePicker");
     namePicker.value = this._getItemStaticTitle();
     namePicker.readOnly = this._readOnly;
 
     // clear the undo stack
@@ -420,16 +399,18 @@ var gEditItemOverlay = {
     this._uri = null;
     this._uris = [];
     this._tags = [];
     this._allTags = [];
     this._itemIds = [];
     this._multiEdit = false;
     this._firstEditedField = "";
     this._initialized = false;
+    this._titleOverride = "";
+    this._readOnly = false;
   },
 
   onTagsFieldBlur: function EIO_onTagsFieldBlur() {
     if (this._updateTags()) // if anything has changed
       this._mayUpdateFirstEditField("tagsField");
   },
 
   _updateTags: function EIO__updateTags() {
@@ -592,46 +573,16 @@ var gEditItemOverlay = {
   onKeywordFieldBlur: function EIO_onKeywordFieldBlur() {
     var keyword = this._element("keywordField").value;
     if (keyword != PlacesUtils.bookmarks.getKeywordForBookmark(this._itemId)) {
       var txn = PlacesUIUtils.ptm.editBookmarkKeyword(this._itemId, keyword);
       PlacesUIUtils.ptm.doTransaction(txn);
     }
   },
 
-  onFeedLocationFieldBlur: function EIO_onFeedLocationFieldBlur() {
-    var uri;
-    try {
-      uri = PlacesUIUtils.createFixedURI(this._element("feedLocationField").value);
-    }
-    catch(ex) { return; }
-
-    var currentFeedURI = PlacesUtils.livemarks.getFeedURI(this._itemId);
-    if (!currentFeedURI.equals(uri)) {
-      var txn = PlacesUIUtils.ptm.editLivemarkFeedURI(this._itemId, uri);
-      PlacesUIUtils.ptm.doTransaction(txn);
-    }
-  },
-
-  onSiteLocationFieldBlur: function EIO_onSiteLocationFieldBlur() {
-    var uri = null;
-    try {
-      uri = PlacesUIUtils.createFixedURI(this._element("siteLocationField").value);
-    }
-    catch(ex) {  }
-
-    var currentSiteURI = PlacesUtils.livemarks.getSiteURI(this._itemId);
-    if ((!uri && !currentSiteURI) ||
-        (uri && currentSiteURI && currentSiteURI.equals(uri))) {
-      return;
-    }
-    var txn = PlacesUIUtils.ptm.editLivemarkSiteURI(this._itemId, uri);
-    PlacesUIUtils.ptm.doTransaction(txn);
-  },
-
   onLoadInSidebarCheckboxCommand:
   function EIO_onLoadInSidebarCheckboxCommand() {
     var loadInSidebarChecked = this._element("loadInSidebarCheckbox").checked;
     var txn = PlacesUIUtils.ptm.setLoadInSidebar(this._itemId,
                                                  loadInSidebarChecked);
     PlacesUIUtils.ptm.doTransaction(txn);
   },
 
@@ -1014,25 +965,29 @@ var gEditItemOverlay = {
                           PlacesUIUtils.getItemDescription(this._itemId));
       break;
     case PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO:
       this._element("loadInSidebarCheckbox").checked =
         PlacesUtils.annotations.itemHasAnnotation(this._itemId,
                                                   PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO);
       break;
     case PlacesUtils.LMANNO_FEEDURI:
-      var feedURISpec = PlacesUtils.livemarks.getFeedURI(this._itemId).spec;
-      this._initTextField("feedLocationField", feedURISpec);
+      let feedURISpec =
+        PlacesUtils.annotations.getItemAnnotation(this._itemId,
+                                                  PlacesUtils.LMANNO_FEEDURI);
+      this._initTextField("feedLocationField", feedURISpec, true);
       break;
     case PlacesUtils.LMANNO_SITEURI:
-      var siteURISpec = "";
-      var siteURI = PlacesUtils.livemarks.getSiteURI(this._itemId);
-      if (siteURI)
-        siteURISpec = siteURI.spec;
-      this._initTextField("siteLocationField", siteURISpec);
+      let siteURISpec = "";
+      try {
+        siteURISpec =
+          PlacesUtils.annotations.getItemAnnotation(this._itemId,
+                                                    PlacesUtils.LMANNO_SITEURI);
+      } catch (ex) {}
+      this._initTextField("siteLocationField", siteURISpec, true);
       break;
     }
   },
 
   onItemMoved: function EIO_onItemMoved(aItemId, aOldParent, aOldIndex,
                                         aNewParent, aNewIndex, aItemType) {
     if (aItemId != this._itemId ||
         aNewParent == this._getFolderIdFromMenuList())
--- a/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -595,19 +595,17 @@ var PlacesOrganizer = {
     var infoBoxExpanderWrapper = document.getElementById("infoBoxExpanderWrapper");
     var additionalInfoBroadcaster = document.getElementById("additionalInfoBroadcaster");
 
     if (!aNode) {
       infoBoxExpanderWrapper.hidden = true;
       return;
     }
     if (aNode.itemId != -1 &&
-        ((PlacesUtils.nodeIsFolder(aNode) &&
-          !PlacesUtils.nodeIsLivemarkContainer(aNode)) ||
-         PlacesUtils.nodeIsLivemarkItem(aNode))) {
+        PlacesUtils.nodeIsFolder(aNode) && !aNode._feedURI) {
       if (infoBox.getAttribute("minimal") == "true")
         infoBox.setAttribute("wasminimal", "true");
       infoBox.removeAttribute("minimal");
       infoBoxExpanderWrapper.hidden = true;
     }
     else {
       if (infoBox.getAttribute("wasminimal") == "true")
         infoBox.setAttribute("minimal", "true");
@@ -683,18 +681,20 @@ var PlacesOrganizer = {
       var itemId = -1;
       if (concreteId != -1 && useConcreteId)
         itemId = concreteId;
       else if (aSelectedNode.itemId != -1)
         itemId = aSelectedNode.itemId;
       else
         itemId = PlacesUtils._uri(aSelectedNode.uri);
 
-      gEditItemOverlay.initPanel(itemId, { hiddenRows: ["folderPicker"],
-                                           forceReadOnly: readOnly });
+      gEditItemOverlay.initPanel(itemId, { hiddenRows: ["folderPicker"]
+                                         , forceReadOnly: readOnly
+                                         , titleOverride: aSelectedNode.title
+                                         });
 
       // Dynamically generated queries, like history date containers, have
       // itemId !=0 and do not exist in history.  For them the panel is
       // read-only, but empty, since it can't get a valid title for the object.
       // In such a case we force the title using the selectedNode one, for UI
       // polishness.
       if (aSelectedNode.itemId == -1 &&
           (PlacesUtils.nodeIsDay(aSelectedNode) ||
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -1,46 +1,11 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla History System
- *
- * The Initial Developer of the Original Code is
- * Google Inc.
- * Portions created by the Initial Developer are Copyright (C) 2005
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Brett Wilson <brettw@gmail.com> (Original author)
- *   Asaf Romano <mano@mozilla.com> (JavaScript version)
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
+/* 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/. */
 
 function PlacesTreeView(aFlatList, aOnOpenFlatContainer) {
   this._tree = null;
   this._result = null;
   this._selection = null;
   this._rootNode = null;
   this._rows = [];
   this._flatList = aFlatList;
@@ -124,16 +89,20 @@ PlacesTreeView.prototype = {
    *        A container result node.
    *
    * @return true if aContainer is a plain container, false otherwise.
    */
   _isPlainContainer: function PTV__isPlainContainer(aContainer) {
     if (aContainer._plainContainer !== undefined)
       return aContainer._plainContainer;
 
+    // Livemarks are always plain containers.
+    if (aContainer._feedURI)
+      return aContainer._plainContainer = true;
+
     // We don't know enough about non-query containers.
     if (!(aContainer instanceof Ci.nsINavHistoryQueryResultNode))
       return aContainer._plainContainer = false;
 
     switch (aContainer.queryOptions.resultType) {
       case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY:
       case Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY:
       case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY:
@@ -323,17 +292,18 @@ PlacesTreeView.prototype = {
         }
       }
 
       this._rows[row] = curChild;
       rowsInserted++;
 
       // Recursively do containers.
       if (!this._flatList &&
-          curChild instanceof Ci.nsINavHistoryContainerResultNode) {
+          curChild instanceof Ci.nsINavHistoryContainerResultNode &&
+          !curChild._feedURI) {
         let resource = this._getResourceForNode(curChild);
         let isopen = resource != null &&
                      PlacesUIUtils.localStore.HasAssertion(resource,
                                                            openLiteral,
                                                            trueLiteral, true);
         if (isopen != curChild.containerOpen)
           aToOpen.push(curChild);
         else if (curChild.containerOpen && curChild.childCount > 0)
@@ -620,30 +590,30 @@ PlacesTreeView.prototype = {
 
       // Update parent when inserting the first item, since twisty has changed.
       if (aParentNode.childCount == 1)
         this._tree.invalidateRow(parentRow);
     }
 
     // Compute the new row number of the node.
     let row = -1;
-    if (aNewIndex == 0 || this._isPlainContainer(aParentNode)) {
+    let cc = aParentNode.childCount;
+    if (aNewIndex == 0 || this._isPlainContainer(aParentNode) || cc == 0) {
       // We don't need to worry about sub hierarchies of the parent node
       // if it's a plain container, or if the new node is its first child.
       if (aParentNode == this._rootNode)
         row = aNewIndex;
       else
         row = parentRow + aNewIndex + 1;
     }
     else {
       // Here, we try to find the next visible element in the child list so we
       // can set the new visible index to be right before that.  Note that we
       // have to search down instead of up, because some siblings could have
       // children themselves that would be in the way.
-      let cc = aParentNode.childCount;
       let separatorsAreHidden = PlacesUtils.nodeIsSeparator(aNode) &&
                                 this.isSorted();
       for (let i = aNewIndex + 1; i < cc; i++) {
         let node = aParentNode.getChild(i);
         if (!separatorsAreHidden || PlacesUtils.nodeIsSeparator(node)) {
           // The children have not been shifted so the next item will have what
           // should be our index.
           row = this._getRowForNode(node, false, parentRow, i);
@@ -659,18 +629,20 @@ PlacesTreeView.prototype = {
                                             aNewIndex - 1);
         row = prevIndex + this._countVisibleRowsForNodeAtRow(prevIndex);
       }
     }
 
     this._rows.splice(row, 0, aNode);
     this._tree.rowCountChanged(row, 1);
 
-    if (PlacesUtils.nodeIsContainer(aNode) && PlacesUtils.asContainer(aNode).containerOpen)
+    if (PlacesUtils.nodeIsContainer(aNode) &&
+        PlacesUtils.asContainer(aNode).containerOpen) {
       this.invalidateContainer(aNode);
+    }
   },
 
   /**
    * THIS FUNCTION DOES NOT HANDLE cases where a collapsed node is being
    * removed but the node it is collapsed with is not being removed (this then
    * just swap out the removee with its collapsing partner). The only time
    * when we really remove things is when deleting URIs, which will apply to
    * all collapsees. This function is called sometimes when resorting items.
@@ -809,64 +781,132 @@ PlacesTreeView.prototype = {
     if (aColumnType != this.COLUMN_TYPE_LASTMODIFIED) {
       let lastModifiedColumn =
         this._findColumnByType(this.COLUMN_TYPE_LASTMODIFIED);
       if (lastModifiedColumn && !lastModifiedColumn.hidden)
         this._tree.invalidateCell(row, lastModifiedColumn);
     }
   },
 
+  _populateLivemarkContainer: function PTV__populateLivemarkContainer(aNode) {
+    PlacesUtils.livemarks.getLivemark({ id: aNode.itemId },
+      (function (aStatus, aLivemark) {
+        let placesNode = aNode;
+        // Need to check containerOpen since getLivemark is async.
+        if (!Components.isSuccessCode(aStatus) || !placesNode.containerOpen)
+          return;
+
+        let children = aLivemark.getNodesForContainer(placesNode);
+        for (let i = 0; i < children.length; i++) {
+          let child = children[i];
+          this.nodeInserted(placesNode, child, i);
+        }
+      }).bind(this));
+  },
+
   nodeTitleChanged: function PTV_nodeTitleChanged(aNode, aNewTitle) {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
   },
 
   nodeURIChanged: function PTV_nodeURIChanged(aNode, aNewURI) {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_URI);
   },
 
   nodeIconChanged: function PTV_nodeIconChanged(aNode) {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
   },
 
   nodeHistoryDetailsChanged:
   function PTV_nodeHistoryDetailsChanged(aNode, aUpdatedVisitDate,
                                          aUpdatedVisitCount) {
+    if (aNode.parent && aNode.parent._feedURI) {
+      // Find the node in the parent.
+      let parentRow = this._flatList ? 0 : this._getRowForNode(aNode.parent);
+      for (let i = parentRow; i < this._rows.length; i++) {
+        let child = this.nodeForTreeIndex(i);
+        if (child.uri == aNode.uri) {
+          delete child._cellProperties;
+          this._invalidateCellValue(child, this.COLUMN_TYPE_TITLE);
+          break;
+        }
+      }
+      return;
+    }
+
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATE);
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_VISITCOUNT);
   },
 
   nodeTagsChanged: function PTV_nodeTagsChanged(aNode) {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_TAGS);
   },
 
   nodeKeywordChanged: function PTV_nodeKeywordChanged(aNode, aNewKeyword) {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_KEYWORD);
   },
 
   nodeAnnotationChanged: function PTV_nodeAnnotationChanged(aNode, aAnno) {
     if (aAnno == PlacesUIUtils.DESCRIPTION_ANNO) {
       this._invalidateCellValue(aNode, this.COLUMN_TYPE_DESCRIPTION);
-    } else if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
-      // The livemark attribute is set as a cell property on the title cell.
-      this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
+    }
+    else if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
+      PlacesUtils.livemarks.getLivemark(
+        { id: aNode.itemId },
+        (function (aStatus, aLivemark) {
+          if (Components.isSuccessCode(aStatus)) {
+            aNode._feedURI = aLivemark.feedURI;
+            if (aNode._cellProperties) {
+              aNode._cellProperties.push(this._getAtomFor("livemark"));
+            }
+            // The livemark attribute is set as a cell property on the title cell.
+            this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
+          }
+        }).bind(this)
+      );
     }
   },
 
   nodeDateAddedChanged: function PTV_nodeDateAddedChanged(aNode, aNewValue) {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATEADDED);
   },
 
   nodeLastModifiedChanged:
   function PTV_nodeLastModifiedChanged(aNode, aNewValue) {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_LASTMODIFIED);
   },
 
   containerStateChanged:
   function PTV_containerStateChanged(aNode, aOldState, aNewState) {
     this.invalidateContainer(aNode);
+
+    if (PlacesUtils.nodeIsFolder(aNode) ||
+        (this._flatList && aNode == this._rootNode)) {
+      let queryOptions = PlacesUtils.asQuery(this._rootNode).queryOptions;
+      if (queryOptions.excludeItems) {
+        return;
+      }
+
+      PlacesUtils.livemarks.getLivemark({ id: aNode.itemId },
+        (function (aStatus, aLivemark) {
+          if (Components.isSuccessCode(aStatus)) {
+            let shouldInvalidate = !aNode._feedURI;
+            aNode._feedURI = aLivemark.feedURI;
+            if (aNewState == Components.interfaces.nsINavHistoryContainerResultNode.STATE_OPENED) {
+              aLivemark.registerForUpdates(aNode, this);
+              aLivemark.reload();
+              if (shouldInvalidate)
+                this.invalidateContainer(aNode);
+            }
+            else {
+              aLivemark.unregisterForUpdates(aNode);
+            }
+          }
+        }).bind(this)
+      );
+    }
   },
 
   invalidateContainer: function PTV_invalidateContainer(aContainer) {
     NS_ASSERT(this._result, "Need to have a result to update");
     if (!this._tree)
       return;
 
     let startReplacement, replaceCount;
@@ -950,16 +990,23 @@ PlacesTreeView.prototype = {
 
         // If we don't have a parent, we made it all the way to the root
         // and didn't find a match, so we can open our item.
         if (!parent && !item.containerOpen)
           item.containerOpen = true;
       }
     }
 
+    if (aContainer._feedURI) {
+      let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions;
+      if (!queryOptions.excludeItems) {
+        this._populateLivemarkContainer(aContainer);
+      }
+    }
+
     this._tree.endUpdateBatch();
 
     // Restore selection.
     this._restoreSelection(nodesToReselect, aContainer);
     this.selection.selectEventsSuppressed = false;
   },
 
   _columns: [],
@@ -1104,33 +1151,50 @@ PlacesTreeView.prototype = {
             properties.push(this._getAtomFor("tagContainer"));
           else if (PlacesUtils.nodeIsDay(node))
             properties.push(this._getAtomFor("dayContainer"));
           else if (PlacesUtils.nodeIsHost(node))
             properties.push(this._getAtomFor("hostContainer"));
         }
         else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER ||
                  nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) {
-          if (PlacesUtils.nodeIsLivemarkContainer(node))
+          if (node._feedURI) {
             properties.push(this._getAtomFor("livemark"));
+          }
+          else {
+            PlacesUtils.livemarks.getLivemark(
+              { id: node.itemId },
+              (function (aStatus, aLivemark) {
+                if (Components.isSuccessCode(aStatus)) {
+                  node._feedURI = aLivemark.feedURI;
+                  node._cellProperties.push(this._getAtomFor("livemark"));
+                  // The livemark attribute is set as a cell property on the title cell.
+                  this._invalidateCellValue(node, this.COLUMN_TYPE_TITLE);
+                }
+              }).bind(this)
+            );
+          }
         }
 
         if (itemId != -1) {
           let queryName = PlacesUIUtils.getLeftPaneQueryNameFromId(itemId);
           if (queryName)
             properties.push(this._getAtomFor("OrganizerQuery_" + queryName));
         }
       }
       else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR)
         properties.push(this._getAtomFor("separator"));
       else if (PlacesUtils.nodeIsURI(node)) {
         properties.push(this._getAtomFor(PlacesUIUtils.guessUrlSchemeForUI(node.uri)));
-        if (itemId != -1) {
-          if (PlacesUtils.nodeIsLivemarkContainer(node.parent))
-            properties.push(this._getAtomFor("livemarkItem"));
+
+        if (node.parent._feedURI) {
+          properties.push(this._getAtomFor("livemarkItem"));
+          if (node.accessCount) {
+            properties.push(this._getAtomFor("visited"));
+          }
         }
       }
 
       node._cellProperties = properties;
     }
     for (let i = 0; i < node._cellProperties.length; i++)
       aProperties.AppendElement(node._cellProperties[i]);
   },
@@ -1149,17 +1213,17 @@ PlacesTreeView.prototype = {
       if (this._flatList)
         return true;
 
       // treat non-expandable childless queries as non-containers
       if (PlacesUtils.nodeIsQuery(node)) {
         let parent = node.parent;
         if ((PlacesUtils.nodeIsQuery(parent) ||
              PlacesUtils.nodeIsFolder(parent)) &&
-            !node.hasChildren)
+            !PlacesUtils.asQuery(node).hasChildren)
           return PlacesUtils.asQuery(parent).queryOptions.expandQueries;
       }
       return true;
     }
     return false;
   },
 
   isContainerOpen: function PTV_isContainerOpen(aRow) {
@@ -1169,18 +1233,24 @@ PlacesTreeView.prototype = {
     // All containers are listed in the rows array.
     return this._rows[aRow].containerOpen;
   },
 
   isContainerEmpty: function PTV_isContainerEmpty(aRow) {
     if (this._flatList)
       return true;
 
+    let node = this._rows[aRow];
+    if (node._feedURI) {
+      let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions;
+      return queryOptions.excludeItems;
+    }
+
     // All containers are listed in the rows array.
-    return !this._rows[aRow].hasChildren;
+    return !node.hasChildren;
   },
 
   isSeparator: function PTV_isSeparator(aRow) {
     // All separators are listed in the rows array.
     let node = this._rows[aRow];
     return node && PlacesUtils.nodeIsSeparator(node);
   },
 
@@ -1413,25 +1483,28 @@ PlacesTreeView.prototype = {
       throw Cr.NS_ERROR_UNEXPECTED;
 
     let node = this._rows[aRow];
     if (this._flatList && this._openContainerCallback) {
       this._openContainerCallback(node);
       return;
     }
 
-    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");
+    // Persist containers open status, but never persist livemarks.
+    if (!node._feedURI) {
+      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");
 
-      if (node.containerOpen)
-        PlacesUIUtils.localStore.Unassert(resource, openLiteral, trueLiteral);
-      else
-        PlacesUIUtils.localStore.Assert(resource, openLiteral, trueLiteral, true);
+        if (node.containerOpen)
+          PlacesUIUtils.localStore.Unassert(resource, openLiteral, trueLiteral);
+        else
+          PlacesUIUtils.localStore.Assert(resource, openLiteral, trueLiteral, true);
+      }
     }
 
     node.containerOpen = !node.containerOpen;
   },
 
   cycleHeader: function PTV_cycleHeader(aColumn) {
     if (!this._result)
       throw Cr.NS_ERROR_UNEXPECTED;
@@ -1564,20 +1637,18 @@ PlacesTreeView.prototype = {
     // Only bookmark-nodes are editable, and those are never built lazily
     let node = this._rows[aRow];
     if (!node || node.itemId == -1)
       return false;
 
     // The following items are never editable:
     // * Read-only items.
     // * places-roots
-    // * livemark items
     // * separators
     if (PlacesUtils.nodeIsReadOnly(node) ||
-        PlacesUtils.nodeIsLivemarkItem(node) ||
         PlacesUtils.nodeIsSeparator(node))
       return false;
 
     if (PlacesUtils.nodeIsFolder(node)) {
       let itemId = PlacesUtils.getConcreteItemId(node);
       if (PlacesUtils.isRootItem(itemId))
         return false;
     }
--- a/browser/components/places/tests/browser/browser_457473_no_copy_guid.js
+++ b/browser/components/places/tests/browser/browser_457473_no_copy_guid.js
@@ -36,17 +36,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 function test() {
   // sanity check
   ok(PlacesUtils, "checking PlacesUtils, running in chrome context?");
   ok(PlacesUIUtils, "checking PlacesUIUtils, running in chrome context?");
 
   /*
-  - create, a test folder, add bookmark, separator, livemark to it
+  - create, a test folder, add bookmark, separator to it
   - fetch guids for all
   - copy the folder
   - test that guids are all different
   - undo copy
   - redo copy
   - test that guids for the copy stay the same
   */
 
@@ -61,20 +61,16 @@ function test() {
   testRootNode.containerOpen = true;
   is(testRootNode.childCount, 0, "confirm test root node is a container, and is empty");
 
   // create folder A, fill it w/ each item type
   var folderAId = PlacesUtils.bookmarks.createFolder(testRootId, "A", -1);
   PlacesUtils.bookmarks.insertBookmark(folderAId, PlacesUtils._uri("http://foo"),
                                        -1, "test bookmark");
   PlacesUtils.bookmarks.insertSeparator(folderAId, -1);
-  PlacesUtils.livemarks.createLivemarkFolderOnly(folderAId, "test livemark",
-                                                 PlacesUtils._uri("http://test"),
-                                                 PlacesUtils._uri("http://test"), -1);
-
   var folderANode = testRootNode.getChild(0);
   var folderAGUIDs = getGUIDs(folderANode);
 
   // test the test function
   ok(checkGUIDs(folderANode, folderAGUIDs, true), "confirm guid test works");
 
   // serialize the folder
   var serializedNode = PlacesUtils.wrapNode(folderANode, PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER);
@@ -120,18 +116,17 @@ function test() {
 }
 
 function getGUIDs(aNode) {
   PlacesUtils.asContainer(aNode);
   aNode.containerOpen = true;
   var GUIDs = {
     folder: PlacesUtils.bookmarks.getItemGUID(aNode.itemId),
     bookmark: PlacesUtils.bookmarks.getItemGUID(aNode.getChild(0).itemId),
-    separator: PlacesUtils.bookmarks.getItemGUID(aNode.getChild(1).itemId),
-    livemark: PlacesUtils.bookmarks.getItemGUID(aNode.getChild(2).itemId)
+    separator: PlacesUtils.bookmarks.getItemGUID(aNode.getChild(1).itemId)
   };
   aNode.containerOpen = false;
   return GUIDs;
 }
 
 function checkGUIDs(aFolderNode, aGUIDs, aShouldMatch) {
 
   function check(aNode, aGUID, aEquals) {
@@ -139,14 +134,13 @@ function checkGUIDs(aFolderNode, aGUIDs,
     return aEquals ? (nodeGUID == aGUID) : (nodeGUID != aGUID);
   }
 
   PlacesUtils.asContainer(aFolderNode);
   aFolderNode.containerOpen = true;
 
   var allMatch = check(aFolderNode, aGUIDs.folder, aShouldMatch) &&
                  check(aFolderNode.getChild(0), aGUIDs.bookmark, aShouldMatch) &&
-                 check(aFolderNode.getChild(1), aGUIDs.separator, aShouldMatch) &&
-                 check(aFolderNode.getChild(2), aGUIDs.livemark, aShouldMatch);
+                 check(aFolderNode.getChild(1), aGUIDs.separator, aShouldMatch)
 
   aFolderNode.containerOpen = false;
   return allMatch;
 }
--- a/browser/components/places/tests/browser/browser_library_views_liveupdate.js
+++ b/browser/components/places/tests/browser/browser_library_views_liveupdate.js
@@ -90,21 +90,16 @@ function startTest() {
   bs.setItemTitle(id, "bmf_edited");
   addedBookmarks.push(id);
   id = bs.insertBookmark(id,
                          PlacesUtils._uri("http://bmf1.mozilla.org/"),
                          bs.DEFAULT_INDEX,
                          "bmf1");
   addedBookmarks.push(id);
   bs.moveItem(id, bs.bookmarksMenuFolder, 0);
-  id = PlacesUtils.livemarks.createLivemarkFolderOnly(
-    bs.bookmarksMenuFolder, "bml",
-    PlacesUtils._uri("http://bml.siteuri.mozilla.org/"),
-    PlacesUtils._uri("http://bml.feeduri.mozilla.org/"), bs.DEFAULT_INDEX);
-  addedBookmarks.push(id);
 
   // TOOLBAR
   ok(true, "*** Acting on toolbar bookmarks");
   bs.insertBookmark(bs.toolbarFolder,
                     PlacesUtils._uri("http://tb1.mozilla.org/"),
                     bs.DEFAULT_INDEX,
                     "tb1");
   bs.setItemTitle(id, "tb1_edited");
@@ -123,20 +118,16 @@ function startTest() {
   bs.setItemTitle(id, "tbf_edited");
   addedBookmarks.push(id);
   id = bs.insertBookmark(id,
                          PlacesUtils._uri("http://tbf1.mozilla.org/"),
                          bs.DEFAULT_INDEX,
                          "bmf1");
   addedBookmarks.push(id);
   bs.moveItem(id, bs.toolbarFolder, 0);
-  id = PlacesUtils.livemarks.createLivemarkFolderOnly(
-    bs.toolbarFolder, "tbl", PlacesUtils._uri("http://tbl.siteuri.mozilla.org/"),
-    PlacesUtils._uri("http://tbl.feeduri.mozilla.org/"), bs.DEFAULT_INDEX);
-  addedBookmarks.push(id);
 
   // UNSORTED
   ok(true, "*** Acting on unsorted bookmarks");
   id = bs.insertBookmark(bs.unfiledBookmarksFolder,
                          PlacesUtils._uri("http://ub1.mozilla.org/"),
                          bs.DEFAULT_INDEX,
                          "ub1");
   bs.setItemTitle(id, "ub1_edited");
@@ -155,21 +146,16 @@ function startTest() {
   bs.setItemTitle(id, "ubf_edited");
   addedBookmarks.push(id);
   id = bs.insertBookmark(id,
                          PlacesUtils._uri("http://ubf1.mozilla.org/"),
                          bs.DEFAULT_INDEX,
                          "ubf1");
   addedBookmarks.push(id);
   bs.moveItem(id, bs.unfiledBookmarksFolder, 0);
-  id = PlacesUtils.livemarks.createLivemarkFolderOnly(
-    bs.unfiledBookmarksFolder, "bubl",
-    PlacesUtils._uri("http://bubl.siteuri.mozilla.org/"),
-    PlacesUtils._uri("http://bubl.feeduri.mozilla.org/"), bs.DEFAULT_INDEX);
-  addedBookmarks.push(id);
 
   // Remove all added bookmarks.
   addedBookmarks.forEach(function (aItem) {
     // If we remove an item after its containing folder has been removed,
     // this will throw, but we can ignore that.
     try {
       bs.removeItem(aItem);
     } catch (ex) {}
@@ -196,37 +182,17 @@ function finishTest() {
  */
 var bookmarksObserver = {
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsINavBookmarkObserver
   , Ci.nsIAnnotationObserver
   ]),
 
   // nsIAnnotationObserver
-  onItemAnnotationSet: function(aItemId, aAnnotationName) {
-    if (aAnnotationName == PlacesUtils.LMANNO_FEEDURI) {
-      // Check that item is recognized as a livemark.
-      let validator = function(aTreeRowIndex) {
-        let tree = gLibrary.PlacesOrganizer._places;
-        let livemarkAtom = Cc["@mozilla.org/atom-service;1"].
-                           getService(Ci.nsIAtomService).
-                           getAtom("livemark");
-        let properties = Cc["@mozilla.org/supports-array;1"].
-                         createInstance(Ci.nsISupportsArray);
-        tree.view.getCellProperties(aTreeRowIndex,
-                                    tree.columns.getColumnAt(0),
-                                    properties);
-        return properties.GetIndexOf(livemarkAtom) != -1;
-      };
-
-      var [node, index, valid] = getNodeForTreeItem(aItemId, gLibrary.PlacesOrganizer._places, validator);
-      isnot(node, null, "Found new Places node in left pane at " + index);
-      ok(valid, "Node is recognized as a livemark");
-    }
-  },
+  onItemAnnotationSet: function() {},
   onItemAnnotationRemoved: function() {},
   onPageAnnotationSet: function() {},
   onPageAnnotationRemoved: function() {},
 
   // nsINavBookmarkObserver
   onItemAdded: function PSB_onItemAdded(aItemId, aFolderId, aIndex, aItemType,
                                         aURI) {
     var node = null;
--- a/browser/components/places/tests/browser/browser_views_liveupdate.js
+++ b/browser/components/places/tests/browser/browser_views_liveupdate.js
@@ -113,21 +113,16 @@ function startTest() {
   addedBookmarks.push(id);
   id = bs.insertBookmark(id,
                          PlacesUtils._uri("http://bmf1.mozilla.org/"),
                          bs.DEFAULT_INDEX,
                          "bmf1");
   bs.setItemTitle(id, "bmf1_edited");
   addedBookmarks.push(id);
   bs.moveItem(id, bs.bookmarksMenuFolder, 0);
-  id = PlacesUtils.livemarks.createLivemarkFolderOnly(
-    bs.bookmarksMenuFolder, "bml",
-    PlacesUtils._uri("http://bml.siteuri.mozilla.org/"),
-    PlacesUtils._uri("http://bml.feeduri.mozilla.org/"), bs.DEFAULT_INDEX);
-  addedBookmarks.push(id);
 
   // TOOLBAR
   info("*** Acting on toolbar bookmarks");
   id = bs.insertBookmark(bs.toolbarFolder,
                          PlacesUtils._uri("http://tb1.mozilla.org/"),
                          bs.DEFAULT_INDEX,
                          "tb1");
   bs.setItemTitle(id, "tb1_edited");
@@ -149,20 +144,16 @@ function startTest() {
   addedBookmarks.push(id);
   id = bs.insertBookmark(id,
                          PlacesUtils._uri("http://tbf1.mozilla.org/"),
                          bs.DEFAULT_INDEX,
                          "tbf1");
   bs.setItemTitle(id, "tbf1_edited");
   addedBookmarks.push(id);
   bs.moveItem(id, bs.toolbarFolder, 0);
-  id = PlacesUtils.livemarks.createLivemarkFolderOnly(
-    bs.toolbarFolder, "tbl", PlacesUtils._uri("http://tbl.siteuri.mozilla.org/"),
-    PlacesUtils._uri("http://tbl.feeduri.mozilla.org/"), bs.DEFAULT_INDEX);
-  addedBookmarks.push(id);
 
   // UNSORTED
   info("*** Acting on unsorted bookmarks");
   id = bs.insertBookmark(bs.unfiledBookmarksFolder,
                          PlacesUtils._uri("http://ub1.mozilla.org/"),
                          bs.DEFAULT_INDEX,
                          "ub1");
   bs.setItemTitle(id, "ub1_edited");
@@ -182,21 +173,16 @@ function startTest() {
   addedBookmarks.push(id);
   id = bs.insertBookmark(id,
                          PlacesUtils._uri("http://ubf1.mozilla.org/"),
                          bs.DEFAULT_INDEX,
                          "bubf1");
   bs.setItemTitle(id, "bubf1_edited");
   addedBookmarks.push(id);
   bs.moveItem(id, bs.unfiledBookmarksFolder, 0);
-  id = PlacesUtils.livemarks.createLivemarkFolderOnly(
-    bs.unfiledBookmarksFolder, "bubl",
-    PlacesUtils._uri("http://bubl.siteuri.mozilla.org/"),
-    PlacesUtils._uri("http://bubl.feeduri.mozilla.org/"), bs.DEFAULT_INDEX);
-  addedBookmarks.push(id);
 
   // Remove all added bookmarks.
   addedBookmarks.forEach(function (aItem) {
     // If we remove an item after its containing folder has been removed,
     // this will throw, but we can ignore that.
     try {
       bs.removeItem(aItem);
     } catch (ex) {}
@@ -228,48 +214,17 @@ function finishTest() {
  */
 var bookmarksObserver = {
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsINavBookmarkObserver
   , Ci.nsIAnnotationObserver
   ]),
 
   // nsIAnnotationObserver
-  onItemAnnotationSet: function(aItemId, aAnnotationName) {
-    if (aAnnotationName == PlacesUtils.LMANNO_FEEDURI) {
-      var views = getViewsForFolder(PlacesUtils.bookmarks.getFolderIdForItem(aItemId));
-      ok(views.length > 0, "Found affected views (" + views.length + "): " + views);
-
-      // Check that item is recognized as a livemark.
-      let validator = function(aElementOrTreeIndex) {
-        if (typeof(aElementOrTreeIndex) == "number") {
-          var sidebar = document.getElementById("sidebar");
-          var tree = sidebar.contentDocument.getElementById("bookmarks-view");
-          let livemarkAtom = Cc["@mozilla.org/atom-service;1"].
-                             getService(Ci.nsIAtomService).
-                             getAtom("livemark");
-          let properties = Cc["@mozilla.org/supports-array;1"].
-                           createInstance(Ci.nsISupportsArray);
-          tree.view.getCellProperties(aElementOrTreeIndex,
-                                      tree.columns.getColumnAt(0),
-                                      properties);
-          return properties.GetIndexOf(livemarkAtom) != -1;
-        }
-        else {
-          return aElementOrTreeIndex.hasAttribute("livemark");
-        }
-      };
-
-      for (var i = 0; i < views.length; i++) {
-        var [node, index, valid] = searchItemInView(aItemId, views[i], validator);
-        isnot(node, null, "Found new Places node in " + views[i] + " at " + index);
-        ok(valid, "Node is recognized as a livemark");
-      }
-    }
-  },
+  onItemAnnotationSet: function() {},
   onItemAnnotationRemoved: function() {},
   onPageAnnotationSet: function() {},
   onPageAnnotationRemoved: function() {},
 
   // nsINavBookmarkObserver
   onItemAdded: function PSB_onItemAdded(aItemId, aFolderId, aIndex,
                                         aItemType, aURI) {
     var views = getViewsForFolder(aFolderId);
--- a/browser/components/places/tests/unit/head_bookmarks.js
+++ b/browser/components/places/tests/unit/head_bookmarks.js
@@ -97,15 +97,15 @@ let (XULAppInfo = {
 
 
 const FILENAME_BOOKMARKS_HTML = "bookmarks.html";
 let (backup_date = new Date().toLocaleFormat("%Y-%m-%d")) {
   const FILENAME_BOOKMARKS_JSON = "bookmarks-" + backup_date + ".json";
 }
 
 // Smart bookmarks constants.
-const SMART_BOOKMARKS_VERSION = 2;
+const SMART_BOOKMARKS_VERSION = 3;
 const SMART_BOOKMARKS_ON_TOOLBAR = 1;
 const SMART_BOOKMARKS_ON_MENU = 3; // Takes in count the additional separator.
 
 // Default bookmarks constants.
 const DEFAULT_BOOKMARKS_ON_TOOLBAR = 1;
 const DEFAULT_BOOKMARKS_ON_MENU = 1;
--- a/browser/components/places/tests/unit/test_384370.js
+++ b/browser/components/places/tests/unit/test_384370.js
@@ -31,29 +31,25 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-// The following components need to be initialized to perform tests without
-// asserting in debug builds (Bug 448804).
-Cc["@mozilla.org/browser/livemark-service;2"].getService(Ci.nsILivemarkService);
-Cc["@mozilla.org/feed-processor;1"].createInstance(Ci.nsIFeedProcessor);
-
 const LOAD_IN_SIDEBAR_ANNO = "bookmarkProperties/loadInSidebar";
 const DESCRIPTION_ANNO = "bookmarkProperties/description";
 const POST_DATA_ANNO = "bookmarkProperties/POSTData";
 
 do_check_eq(typeof PlacesUtils, "object");
 
 // main
 function run_test() {
+  do_test_pending();
   /*
     HTML+FEATURES SUMMARY:
     - import legacy bookmarks
     - export as json, import, test (tests integrity of html > json)
     - export as html, import, test (tests integrity of json > html)
 
     BACKUP/RESTORE SUMMARY:
     - create a bookmark in each root
@@ -87,31 +83,35 @@ function run_test() {
   // 2. run the test-suite
   // Note: we do not empty the db before this import to catch bugs like 380999
   try {
     importer.importHTMLFromFile(bookmarksFileOld, true);
   } catch(ex) { do_throw("couldn't import legacy bookmarks file: " + ex); }
   populate();
   validate();
 
-  // Test exporting a Places canonical json file.
-  // 1. export to bookmarks.exported.json
-  // 2. empty bookmarks db
-  // 3. import bookmarks.exported.json
-  // 4. run the test-suite
-  try {
-    PlacesUtils.backups.saveBookmarksToJSONFile(jsonFile);
-  } catch(ex) { do_throw("couldn't export to file: " + ex); }
-  LOG("exported json"); 
-  try {
-    PlacesUtils.restoreBookmarksFromJSONFile(jsonFile);
-  } catch(ex) { do_throw("couldn't import the exported file: " + ex); }
-  LOG("imported json"); 
-  validate();
-  LOG("validated import"); 
+  waitForAsyncUpdates(function () {
+    // Test exporting a Places canonical json file.
+    // 1. export to bookmarks.exported.json
+    // 2. empty bookmarks db
+    // 3. import bookmarks.exported.json
+    // 4. run the test-suite
+    try {
+      PlacesUtils.backups.saveBookmarksToJSONFile(jsonFile);
+    } catch(ex) { do_throw("couldn't export to file: " + ex); }
+    LOG("exported json");
+    try {
+      PlacesUtils.restoreBookmarksFromJSONFile(jsonFile);
+    } catch(ex) { do_throw("couldn't import the exported file: " + ex); }
+    LOG("imported json");
+    validate();
+    LOG("validated import");
+
+    waitForAsyncUpdates(do_test_finished);
+  });
 }
 
 var tagData = [
   { uri: uri("http://slint.us"), tags: ["indie", "kentucky", "music"] },
   { uri: uri("http://en.wikipedia.org/wiki/Diplodocus"), tags: ["dinosaur", "dj", "rad word"] }
 ];
 
 var bookmarkData = [
@@ -238,24 +238,27 @@ function testToolbarFolder() {
 
   // child count (add 2 for pre-existing items)
   do_check_eq(toolbar.childCount, bookmarkData.length + 2);
   
   // livemark
   var livemark = toolbar.getChild(1);
   // title
   do_check_eq("Latest Headlines", livemark.title);
-  // livemark check
-  do_check_true(PlacesUtils.livemarks.isLivemark(livemark.itemId));
-  // site url
-  do_check_eq("http://en-us.fxfeeds.mozilla.com/en-US/firefox/livebookmarks/",
-              PlacesUtils.livemarks.getSiteURI(livemark.itemId).spec);
-  // feed url
-  do_check_eq("http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml",
-              PlacesUtils.livemarks.getFeedURI(livemark.itemId).spec);
+
+  PlacesUtils.livemarks.getLivemark(
+    { id: livemark.itemId },
+    function (aStatus, aLivemark) {
+      do_check_true(Components.isSuccessCode(aStatus));
+      do_check_eq("http://en-us.fxfeeds.mozilla.com/en-US/firefox/livebookmarks/",
+                  aLivemark.siteURI.spec);
+      do_check_eq("http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml",
+                  aLivemark.feedURI.spec);
+    }
+  );
 
   // test added bookmark data
   var child = toolbar.getChild(2);
   do_check_eq(child.uri, bookmarkData[0].uri.spec);
   do_check_eq(child.title, bookmarkData[0].title);
   child = toolbar.getChild(3);
   do_check_eq(child.uri, bookmarkData[1].uri.spec);
   do_check_eq(child.title, bookmarkData[1].title);
--- a/browser/components/places/tests/unit/test_457441-import-export-corrupt-bookmarks-html.js
+++ b/browser/components/places/tests/unit/test_457441-import-export-corrupt-bookmarks-html.js
@@ -44,78 +44,80 @@
 // Get Services
 var hs = Cc["@mozilla.org/browser/nav-history-service;1"].
          getService(Ci.nsINavHistoryService);
 var dbConn = hs.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection;
 var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
          getService(Ci.nsINavBookmarksService);
 var as = Cc["@mozilla.org/browser/annotation-service;1"].
          getService(Ci.nsIAnnotationService);
-var lms = Cc["@mozilla.org/browser/livemark-service;2"].
-          getService(Ci.nsILivemarkService);
 var icos = Cc["@mozilla.org/browser/favicon-service;1"].
            getService(Ci.nsIFaviconService);
 var ps = Cc["@mozilla.org/preferences-service;1"].
          getService(Ci.nsIPrefBranch);
 var ies = Cc["@mozilla.org/browser/places/import-export-service;1"].
           getService(Ci.nsIPlacesImportExportService);
 
 const DESCRIPTION_ANNO = "bookmarkProperties/description";
 const LOAD_IN_SIDEBAR_ANNO = "bookmarkProperties/loadInSidebar";
 const POST_DATA_ANNO = "bookmarkProperties/POSTData";
 
 const TEST_FAVICON_PAGE_URL = "http://en-US.www.mozilla.com/en-US/firefox/central/";
 const TEST_FAVICON_DATA_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
 
-// main
 function run_test() {
+  do_test_pending();
+
   // avoid creating the places smart folder during tests
   ps.setIntPref("browser.places.smartBookmarksVersion", -1);
 
   // import bookmarks from corrupt file
   var corruptBookmarksFile = do_get_file("bookmarks.corrupt.html");
   try {
     ies.importHTMLFromFile(corruptBookmarksFile, true);
   } catch(ex) { do_throw("couldn't import corrupt bookmarks file: " + ex); }
 
   // Check that every bookmark is correct
   // Corrupt bookmarks should not have been imported
   database_check();
-
-  // Create corruption in database
-  var corruptItemId = bs.insertBookmark(bs.toolbarFolder,
-                                        uri("http://test.mozilla.org"),
-                                        bs.DEFAULT_INDEX, "We love belugas");
-  var stmt = dbConn.createStatement("UPDATE moz_bookmarks SET fk = NULL WHERE id = :itemId");
-  stmt.params.itemId = corruptItemId;
-  stmt.execute();
-  stmt.finalize();
+  waitForAsyncUpdates(function() {
+    // Create corruption in database
+    var corruptItemId = bs.insertBookmark(bs.toolbarFolder,
+                                          uri("http://test.mozilla.org"),
+                                          bs.DEFAULT_INDEX, "We love belugas");
+    var stmt = dbConn.createStatement("UPDATE moz_bookmarks SET fk = NULL WHERE id = :itemId");
+    stmt.params.itemId = corruptItemId;
+    stmt.execute();
+    stmt.finalize();
 
-  // Export bookmarks
-  var bookmarksFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
-  bookmarksFile.append("bookmarks.exported.html");
-  if (bookmarksFile.exists())
-    bookmarksFile.remove(false);
-  bookmarksFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, 0600);
-  if (!bookmarksFile.exists())
-    do_throw("couldn't create file: bookmarks.exported.html");
-  try {
-    ies.exportHTMLToFile(bookmarksFile);
-  } catch(ex) { do_throw("couldn't export to bookmarks.exported.html: " + ex); }
+    // Export bookmarks
+    var bookmarksFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
+    bookmarksFile.append("bookmarks.exported.html");
+    if (bookmarksFile.exists())
+      bookmarksFile.remove(false);
+    bookmarksFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, 0600);
+    if (!bookmarksFile.exists())
+      do_throw("couldn't create file: bookmarks.exported.html");
+    try {
+      ies.exportHTMLToFile(bookmarksFile);
+    } catch(ex) { do_throw("couldn't export to bookmarks.exported.html: " + ex); }
 
-  // Clear all bookmarks
-  remove_all_bookmarks();
+    // Clear all bookmarks
+    remove_all_bookmarks();
 
-  // Import bookmarks
-  try {
-    ies.importHTMLFromFile(bookmarksFile, true);
-  } catch(ex) { do_throw("couldn't import the exported file: " + ex); }
+    // Import bookmarks
+    try {
+      ies.importHTMLFromFile(bookmarksFile, true);
+    } catch(ex) { do_throw("couldn't import the exported file: " + ex); }
 
-  // Check that every bookmark is correct
-  database_check();
+    // Check that every bookmark is correct
+    database_check();
+
+    waitForAsyncUpdates(do_test_finished);
+  });
 }
 
 /*
  * Check for imported bookmarks correctness
  */
 function database_check() {
   // BOOKMARKS MENU
   var query = hs.getNewQuery();
@@ -187,24 +189,26 @@ function database_check() {
   var toolbar = result.root;
   toolbar.containerOpen = true;
   do_check_eq(toolbar.childCount, 3);
   
   // livemark
   var livemark = toolbar.getChild(1);
   // title
   do_check_eq("Latest Headlines", livemark.title);
-  // livemark check
-  do_check_true(lms.isLivemark(livemark.itemId));
-  // site url
-  do_check_eq("http://en-us.fxfeeds.mozilla.com/en-US/firefox/livebookmarks/",
-              lms.getSiteURI(livemark.itemId).spec);
-  // feed url
-  do_check_eq("http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml",
-              lms.getFeedURI(livemark.itemId).spec);
+  PlacesUtils.livemarks.getLivemark(
+    { id: livemark.itemId },
+    function (aStatus, aLivemark) {
+      do_check_true(Components.isSuccessCode(aStatus));
+      do_check_eq("http://en-us.fxfeeds.mozilla.com/en-US/firefox/livebookmarks/",
+                  aLivemark.siteURI.spec);
+      do_check_eq("http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml",
+                  aLivemark.feedURI.spec);
+    }
+  );
 
   // cleanup
   toolbar.containerOpen = false;
 
   // UNFILED BOOKMARKS
   query.setFolders([bs.unfiledBookmarksFolder], 1);
   result = hs.executeQuery(query, hs.getNewQueryOptions());
   var unfiledBookmarks = result.root;
--- a/browser/components/places/tests/unit/test_bookmarks_html.js
+++ b/browser/components/places/tests/unit/test_bookmarks_html.js
@@ -78,17 +78,16 @@ let test_bookmarks = {
     },
   ],
   toolbar: [
     { title: "Getting Started",
       url: "http://en-us.www.mozilla.com/en-US/firefox/central/",
       icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="
     },
     { title: "Latest Headlines",
-      description: "Livemark test comment",
       url: "http://en-us.fxfeeds.mozilla.com/en-US/firefox/livebookmarks/",
       feedUrl: "http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml",
     }
   ],
   unfiled: [
     { title: "Example.tld",
       url: "http://example.tld/",
     },
@@ -100,16 +99,20 @@ let gBookmarksFileOld;
 // Places bookmarks.html file pointer.
 let gBookmarksFileNew;
 
 let importer = Cc["@mozilla.org/browser/places/import-export-service;1"].
                getService(Ci.nsIPlacesImportExportService);
 
 function run_test()
 {
+  run_next_test();
+}
+
+add_test(function setup() {
   // Avoid creating smart bookmarks during the test.
   Services.prefs.setIntPref("browser.places.smartBookmarksVersion", -1);
 
   // File pointer to legacy bookmarks file.
   gBookmarksFileOld = do_get_file("bookmarks.preplaces.html");
 
   // File pointer to a new Places-exported bookmarks file.
   gBookmarksFileNew = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
@@ -126,41 +129,49 @@ function run_test()
   // Test importing a pre-Places canonical bookmarks file.
   // 1. import bookmarks.preplaces.html
   // 2. run the test-suite
   // Note: we do not empty the db before this import to catch bugs like 380999
   try {
     importer.importHTMLFromFile(gBookmarksFileOld, true);
   } catch(ex) { do_throw("couldn't import legacy bookmarks file: " + ex); }
 
-  testImportedBookmarks();
+  waitForAsyncUpdates(function () {
+    testImportedBookmarks();
 
-  // Prepare for next tests.
-  try {
-    importer.exportHTMLToFile(gBookmarksFileNew);
-  } catch(ex) { do_throw("couldn't export to file: " + ex); }
+    // Prepare for next tests.
+    try {
+      importer.exportHTMLToFile(gBookmarksFileNew);
+    } catch(ex) { do_throw("couldn't export to file: " + ex); }
 
-  remove_all_bookmarks();
-  run_next_test();
-}
+    waitForAsyncUpdates(function () {
+      remove_all_bookmarks();
+      run_next_test();
+    });
+  });
+});
 
 add_test(function test_import_new()
 {
   // Test importing a Places bookmarks.html file.
   // 1. import bookmarks.exported.html
   // 2. run the test-suite
 
   try {
     importer.importHTMLFromFile(gBookmarksFileNew, true);
   } catch(ex) { do_throw("couldn't import the exported file: " + ex); }
 
-  testImportedBookmarks();
+  waitForAsyncUpdates(function () {
+    testImportedBookmarks();
 
-  remove_all_bookmarks();
-  run_next_test();
+    waitForAsyncUpdates(function () {
+      remove_all_bookmarks();
+      run_next_test();
+    });
+  });
 });
 
 add_test(function test_emptytitle_export()
 {
   // Test exporting and importing with an empty-titled bookmark.
   // 1. import bookmarks
   // 1. create an empty-titled bookmark.
   // 2. export to bookmarks.exported.html
@@ -184,28 +195,32 @@ add_test(function test_emptytitle_export
   } catch(ex) { do_throw("couldn't export to file: " + ex); }
 
   remove_all_bookmarks();
 
   try {
     importer.importHTMLFromFile(gBookmarksFileNew, true);
   } catch(ex) { do_throw("couldn't import the exported file: " + ex); }
 
-  testImportedBookmarks();
+  waitForAsyncUpdates(function () {
+    testImportedBookmarks();
 
-  // Cleanup.
-  test_bookmarks.unfiled.pop();
-  PlacesUtils.bookmarks.removeItem(id);
+    // Cleanup.
+    test_bookmarks.unfiled.pop();
+    PlacesUtils.bookmarks.removeItem(id);
 
-  try {
-    importer.exportHTMLToFile(gBookmarksFileNew);
-  } catch(ex) { do_throw("couldn't export to file: " + ex); }
+    try {
+      importer.exportHTMLToFile(gBookmarksFileNew);
+    } catch(ex) { do_throw("couldn't export to file: " + ex); }
 
-  remove_all_bookmarks();
-  run_next_test();
+    waitForAsyncUpdates(function () {
+      remove_all_bookmarks();
+      run_next_test();
+    });
+  });
 });
 
 add_test(function test_import_preplaces_to_folder()
 {
   // Test importing a pre-Places canonical bookmarks file to a specific folder.
   // 1. create a new folder
   // 2. import bookmarks.preplaces.html to that folder
   // 3. run the test-suite
@@ -213,21 +228,25 @@ add_test(function test_import_preplaces_
   let testFolder = PlacesUtils.bookmarks.createFolder(
     PlacesUtils.bookmarksMenuFolderId, "test-import",
     PlacesUtils.bookmarks.DEFAULT_INDEX
   );
   try {
     importer.importHTMLFromFileToFolder(gBookmarksFileOld, testFolder, false);
   } catch(ex) { do_throw("couldn't import the exported file to folder: " + ex); }
 
-  // Import-to-folder creates subfolders for toolbar and unfiled.
-  testImportedBookmarksToFolder(testFolder);
+  waitForAsyncUpdates(function () {
+    // Import-to-folder creates subfolders for toolbar and unfiled.
+    testImportedBookmarksToFolder(testFolder);
 
-  remove_all_bookmarks();
-  run_next_test();
+    waitForAsyncUpdates(function () {
+      remove_all_bookmarks();
+      run_next_test();
+    });
+  });
 });
 
 add_test(function test_import_to_folder()
 {
   // Test importing a Places canonical bookmarks file to a specific folder.
   // 1. create a new folder
   // 2. import bookmarks.exported.html to that folder
   // 3. run the test-suite
@@ -235,21 +254,25 @@ add_test(function test_import_to_folder(
   let testFolder = PlacesUtils.bookmarks.createFolder(
     PlacesUtils.bookmarksMenuFolderId, "test-import",
     PlacesUtils.bookmarks.DEFAULT_INDEX
   );
   try {
     importer.importHTMLFromFileToFolder(gBookmarksFileNew, testFolder, false);
   } catch(ex) { do_throw("couldn't import the exported file to folder: " + ex); }
 
-  // Import-to-folder creates subfolders for toolbar and unfiled.
-  testImportedBookmarksToFolder(testFolder);
+  waitForAsyncUpdates(function () {
+    // Import-to-folder creates subfolders for toolbar and unfiled.
+    testImportedBookmarksToFolder(testFolder);
 
-  remove_all_bookmarks();
-  run_next_test();
+    waitForAsyncUpdates(function () {
+      remove_all_bookmarks();
+      run_next_test();
+    });
+  });
 });
 
 add_test(function test_import_ontop()
 {
   // Test importing the exported bookmarks.html file *on top of* the existing
   // bookmarks.
   // 1. empty bookmarks db
   // 2. import the exported bookmarks file
@@ -262,20 +285,24 @@ add_test(function test_import_ontop()
   } catch(ex) { do_throw("couldn't import the exported file: " + ex); }
   try {
     importer.exportHTMLToFile(gBookmarksFileNew);
   } catch(ex) { do_throw("couldn't export to file: " + ex); }
   try {
     importer.importHTMLFromFile(gBookmarksFileNew, true);
   } catch(ex) { do_throw("couldn't import the exported file: " + ex); }
 
-  testImportedBookmarks();
+  waitForAsyncUpdates(function () {
+    testImportedBookmarks();
 
-  remove_all_bookmarks();
-  run_next_test();
+    waitForAsyncUpdates(function () {
+      remove_all_bookmarks();
+      run_next_test();
+    });
+  });
 });
 
 function testImportedBookmarks()
 {
   for (let group in test_bookmarks) {
     let root;
     switch (group) {
       case "menu":
@@ -345,18 +372,24 @@ function checkItem(aExpected, aNode)
           do_check_eq(PlacesUtils.bookmarks.getItemDateAdded(id),
                       aExpected.dateAdded);
         break;
       case "lastModified":
           do_check_eq(PlacesUtils.bookmarks.getItemLastModified(id),
                       aExpected.lastModified);
         break;
       case "url":
-        if (!PlacesUtils.livemarks.isLivemark(id))
-          do_check_eq(aNode.uri, aExpected.url);
+        PlacesUtils.livemarks.getLivemark(
+          { id: id },
+          function (aStatus, aLivemark) {
+            if (!Components.isSuccessCode(aStatus)) {
+              do_check_eq(aNode.uri, aExpected.url);
+            }
+          }
+        );
         break;
       case "icon":
         let faviconURI = PlacesUtils.favicons.getFaviconForPage(
           NetUtil.newURI(aExpected.url)
         );
         let dataURL = PlacesUtils.favicons.getFaviconDataAsDataURL(faviconURI);
         // Avoid do_check_eq for console spam.
         do_check_true(dataURL == aExpected.icon);
@@ -373,21 +406,24 @@ function checkItem(aExpected, aNode)
                                .getItemAnnotation(id, PlacesUtils.POST_DATA_ANNO),
                     aExpected.postData);
         break;
       case "charset":
         do_check_eq(PlacesUtils.history.getCharsetForURI(NetUtil.newURI(aNode.uri)),
                     aExpected.charset);
         break;
       case "feedUrl":
-        do_check_true(PlacesUtils.livemarks.isLivemark(id));
-        do_check_eq(PlacesUtils.livemarks.getSiteURI(id).spec,
-                    aExpected.url);
-        do_check_eq(PlacesUtils.livemarks.getFeedURI(id).spec,
-                    aExpected.feedUrl);
+        PlacesUtils.livemarks.getLivemark(
+          { id: id },
+          function (aStatus, aLivemark) {
+            do_check_true(Components.isSuccessCode(aStatus));
+            do_check_eq(aLivemark.siteURI.spec, aExpected.url);
+            do_check_eq(aLivemark.feedURI.spec, Expected.feedUrl);
+          }
+        );
         break;
       case "children":
         let folder = aNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
         do_check_eq(folder.hasChildren, aExpected.children.length > 0);
         folder.containerOpen = true;
         do_check_eq(folder.childCount, aExpected.children.length);
 
         aExpected.children.forEach(function (item, index) checkItem(item, folder.getChild(index)));
--- a/browser/components/places/tests/unit/test_placesTxn.js
+++ b/browser/components/places/tests/unit/test_placesTxn.js
@@ -34,17 +34,16 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 var bmsvc = PlacesUtils.bookmarks;
-var lmsvc = PlacesUtils.livemarks;
 var ptSvc = PlacesUIUtils.ptm;
 var tagssvc = PlacesUtils.tagging;
 var annosvc = PlacesUtils.annotations;
 
 // create and add bookmarks observer
 var observer = {
   onBeginUpdateBatch: function() {
     this._beginUpdateBatch = true;
@@ -414,94 +413,16 @@ function run_test() {
   do_check_eq(observer._itemChangedId, bkmk1Id);
   do_check_eq(observer._itemChangedProperty, "keyword");
   do_check_eq(observer._itemChangedValue, "kw1"); 
   txn11.undoTransaction();
   do_check_eq(observer._itemChangedId, bkmk1Id);
   do_check_eq(observer._itemChangedProperty, "keyword");
   do_check_eq(observer._itemChangedValue, ""); 
 
-  // Testing create livemark
-  var txn12 = ptSvc.createLivemark(uri("http://feeduri.com"),
-                                   uri("http://siteuri.com"),
-                                   "Livemark1", root);
-  txn12.doTransaction();
-  var lvmkId = observer._itemAddedId;
-  do_check_true(lmsvc.isLivemark(lvmkId));
-  do_check_eq(lmsvc.getSiteURI(lvmkId).spec, "http://siteuri.com/");
-  do_check_eq(lmsvc.getFeedURI(lvmkId).spec, "http://feeduri.com/");
-  txn12.undoTransaction();
-  do_check_false(lmsvc.isLivemark(lvmkId));
-  txn12.redoTransaction();
-  lvmkId = observer._itemAddedId;
-  do_check_true(lmsvc.isLivemark(lvmkId));
-  do_check_eq(lmsvc.getSiteURI(lvmkId).spec, "http://siteuri.com/");
-  do_check_eq(lmsvc.getFeedURI(lvmkId).spec, "http://feeduri.com/");
-
-  // editLivemarkSiteURI
-  var txn13 = ptSvc.editLivemarkSiteURI(lvmkId, uri("http://new-siteuri.com/"));
-  txn13.doTransaction();
-  do_check_eq(observer._itemChangedId, lvmkId);
-  do_check_eq(observer._itemChangedProperty, "livemark/siteURI");
-  do_check_eq(lmsvc.getSiteURI(lvmkId).spec, "http://new-siteuri.com/");
-  txn13.undoTransaction();
-  do_check_eq(observer._itemChangedId, lvmkId);
-  do_check_eq(observer._itemChangedProperty, "livemark/siteURI");
-  do_check_eq(observer._itemChangedValue, "");
-  do_check_eq(lmsvc.getSiteURI(lvmkId).spec, "http://siteuri.com/");
-  txn13.redoTransaction();
-  do_check_eq(observer._itemChangedId, lvmkId);
-  do_check_eq(observer._itemChangedProperty, "livemark/siteURI");
-  do_check_eq(lmsvc.getSiteURI(lvmkId).spec, "http://new-siteuri.com/");
-  txn13.undoTransaction();
-  do_check_eq(observer._itemChangedId, lvmkId);
-  do_check_eq(observer._itemChangedProperty, "livemark/siteURI");
-  do_check_eq(observer._itemChangedValue, "");
-  do_check_eq(lmsvc.getSiteURI(lvmkId).spec, "http://siteuri.com/");
-
-  // editLivemarkFeedURI
-  var txn14 = ptSvc.editLivemarkFeedURI(lvmkId, uri("http://new-feeduri.com/"));
-  txn14.doTransaction();
-  do_check_eq(observer._itemChangedId, lvmkId);
-  do_check_eq(observer._itemChangedProperty, "livemark/feedURI");
-  do_check_eq(lmsvc.getFeedURI(lvmkId).spec, "http://new-feeduri.com/");
-  txn14.undoTransaction();
-  do_check_eq(observer._itemChangedId, lvmkId);
-  do_check_eq(observer._itemChangedProperty, "livemark/feedURI");
-  do_check_eq(observer._itemChangedValue, "");
-  do_check_eq(lmsvc.getFeedURI(lvmkId).spec, "http://feeduri.com/");
-  txn14.redoTransaction();
-  do_check_eq(observer._itemChangedId, lvmkId);
-  do_check_eq(observer._itemChangedProperty, "livemark/feedURI");
-  do_check_eq(observer._itemChangedValue, "");
-  do_check_eq(lmsvc.getFeedURI(lvmkId).spec, "http://new-feeduri.com/");
-  txn14.undoTransaction();
-  do_check_eq(observer._itemChangedId, lvmkId);
-  do_check_eq(observer._itemChangedProperty, "livemark/feedURI");
-  do_check_eq(observer._itemChangedValue, "");
-  do_check_eq(lmsvc.getFeedURI(lvmkId).spec, "http://feeduri.com/");
-
-  // Testing remove livemark
-  // Set an annotation and check that we don't lose it on undo
-  annosvc.setItemAnnotation(lvmkId, "livemark/testAnno", "testAnno",
-                            0, annosvc.EXPIRE_NEVER);
-  var txn15 = ptSvc.removeItem(lvmkId);
-  txn15.doTransaction();
-  do_check_false(lmsvc.isLivemark(lvmkId));
-  do_check_eq(observer._itemRemovedId, lvmkId);
-  txn15.undoTransaction();
-  lvmkId = observer._itemAddedId;
-  do_check_true(lmsvc.isLivemark(lvmkId));
-  do_check_eq(lmsvc.getSiteURI(lvmkId).spec, "http://siteuri.com/");
-  do_check_eq(lmsvc.getFeedURI(lvmkId).spec, "http://feeduri.com/");
-  do_check_eq(annosvc.getItemAnnotation(lvmkId, "livemark/testAnno"), "testAnno");
-  txn15.redoTransaction();
-  do_check_false(lmsvc.isLivemark(lvmkId));
-  do_check_eq(observer._itemRemovedId, lvmkId);
-
   // Test LoadInSidebar transaction.
   var txn16 = ptSvc.setLoadInSidebar(bkmk1Id, true);
   txn16.doTransaction();
   do_check_eq(observer._itemChangedId, bkmk1Id);
   do_check_eq(observer._itemChangedProperty, PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO);
   do_check_eq(observer._itemChanged_isAnnotationProperty, true);
   txn16.undoTransaction();
   do_check_eq(observer._itemChangedId, bkmk1Id);
--- a/browser/components/places/tests/unit/test_txnGUIDs.js
+++ b/browser/components/places/tests/unit/test_txnGUIDs.js
@@ -36,52 +36,70 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 /**
  * This test will ensure any transactions service that is going to create
  * a new item, won't replace the GUID when undoing and redoing the action.
  */
-var bmsvc = PlacesUtils.bookmarks;
-var txnsvc = PlacesUIUtils.ptm;
 
 function test_GUID_persistance(aTxn) {
   aTxn.doTransaction();
-  var itemId = bmsvc.getIdForItemAt(bmsvc.unfiledBookmarksFolder, 0);
-  var GUID = bmsvc.getItemGUID(itemId);
-  aTxn.undoTransaction();
-  aTxn.redoTransaction();
-  do_check_eq(GUID, bmsvc.getItemGUID(itemId));
-  aTxn.undoTransaction();
+  waitForAsyncUpdates(function () {
+    let itemId = PlacesUtils.bookmarks
+                            .getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0);
+    let GUID = PlacesUtils.bookmarks.getItemGUID(itemId);
+    aTxn.undoTransaction();
+    aTxn.redoTransaction();
+    waitForAsyncUpdates(function() {
+      let itemId = PlacesUtils.bookmarks
+                              .getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0);
+      do_check_eq(GUID, PlacesUtils.bookmarks.getItemGUID(itemId));
+      aTxn.undoTransaction();
+      waitForAsyncUpdates(run_next_test);
+    });
+  });
 }
 
 function run_test() {
-  // Create folder.
-  var createFolderTxn = txnsvc.createFolder("Test folder",
-                                            bmsvc.unfiledBookmarksFolder,
-                                            bmsvc.DEFAULT_INDEX);
-  test_GUID_persistance(createFolderTxn);
+  run_next_test();
+}
 
-  // Create bookmark.
-  var createBookmarkTxn = txnsvc.createItem(uri("http://www.example.com"),
-                                            bmsvc.unfiledBookmarksFolder,
-                                            bmsvc.DEFAULT_INDEX,
-                                            "Test bookmark");
-  test_GUID_persistance(createBookmarkTxn);
+add_test(function create_folder() {
+  let createFolderTxn = new PlacesCreateFolderTransaction(
+    "Test folder", PlacesUtils.unfiledBookmarksFolderId,
+    PlacesUtils.bookmarks.DEFAULT_INDEX
+  );
+  test_GUID_persistance(createFolderTxn);
+});
 
-  // Create separator.
-  var createSeparatorTxn = txnsvc.createSeparator(bmsvc.unfiledBookmarksFolder,
-                                                  bmsvc.DEFAULT_INDEX);
-  test_GUID_persistance(createFolderTxn);
+add_test(function create_bookmark() {
+  let createBookmarkTxn = new PlacesCreateBookmarkTransaction(
+    NetUtil.newURI("http://www.example.com"), PlacesUtils.unfiledBookmarksFolderId,
+    PlacesUtils.bookmarks.DEFAULT_INDEX, "Test bookmark"
+  );
+  test_GUID_persistance(createBookmarkTxn);
+});
+  
+add_test(function create_separator() {
+  let createSeparatorTxn = new PlacesCreateSeparatorTransaction(
+    PlacesUtils.unfiledBookmarksFolderId, PlacesUtils.bookmarks.DEFAULT_INDEX
+  );
+  test_GUID_persistance(createSeparatorTxn);
+});
 
-  // Create livemark.
-  var createLivemarkTxn = txnsvc.createLivemark(uri("http://feeduri.com"),
-                                               uri("http://siteuri.com"),
-                                               "Test livemark",
-                                               bmsvc.unfiledBookmarksFolder,
-                                               bmsvc.DEFAULT_INDEX);
+add_test(function tag_uri() {
+  let tagURITxn = new PlacesTagURITransaction(
+    NetUtil.newURI("http://www.example.com"), ["foo"]
+  );
+  test_GUID_persistance(tagURITxn);
+});
+
+add_test(function create_livemark() {
+  let createLivemarkTxn = new PlacesCreateLivemarkTransaction(
+    NetUtil.newURI("http://feeduri.com"), NetUtil.newURI("http://siteuri.com"),
+    "Test livemark", PlacesUtils.unfiledBookmarksFolderId,
+    PlacesUtils.bookmarks.DEFAULT_INDEX
+  );
   test_GUID_persistance(createLivemarkTxn);
+});
 
-  // Tag URI.
-  var tagURITxn = txnsvc.tagURI(uri("http://www.example.com"), ["foo"]);
-  test_GUID_persistance(tagURITxn);
-}
--- a/browser/components/privatebrowsing/src/nsPrivateBrowsingService.js
+++ b/browser/components/privatebrowsing/src/nsPrivateBrowsingService.js
@@ -184,28 +184,16 @@ PrivateBrowsingService.prototype = {
           let plainURL = win.gBrowser.currentURI.spec;
           if (plainURL.indexOf("view-source:") == 0) {
             plainURL = plainURL.substr(12);
             this._viewSrcURLs.push(plainURL);
           }
         }
         win.close();
       }
-        
-      var windowsEnum = Services.wm.getEnumerator("navigator:browser");
-      while (windowsEnum.hasMoreElements()) {
-        var window = windowsEnum.getNext();
-        window.getInterface(Ci.nsIWebNavigation)
-              .QueryInterface(Ci.nsIDocShellTreeItem)
-              .treeOwner
-              .QueryInterface(Ci.nsIInterfaceRequestor)
-              .getInterface(Ci.nsIXULWindow)
-              .docShell.QueryInterface(Ci.nsILoadContext)
-              .usePrivateBrowsing = this._inPrivateBrowsing;
-      }
 
       if (!this._quitting && this._saveSession) {
         let browserWindow = this._getBrowserWindow();
 
 	// if there are open browser windows, load a dummy session to get a distinct 
         // separation between private and non-private sessions
 	if (browserWindow) {
           // set an empty session to transition from/to pb mode, see bug 476463
@@ -224,16 +212,28 @@ PrivateBrowsingService.prototype = {
           browser.removeTab(browser.tabContainer.firstChild);
           browserWindow.getInterface(Ci.nsIWebNavigation)
                        .QueryInterface(Ci.nsIDocShellTreeItem)
                        .treeOwner
                        .QueryInterface(Ci.nsIInterfaceRequestor)
                        .getInterface(Ci.nsIXULWindow)
                        .docShell.contentViewer.resetCloseWindow();
         }
+
+        var windowsEnum = Services.wm.getEnumerator("navigator:browser");
+        while (windowsEnum.hasMoreElements()) {
+          var window = windowsEnum.getNext();
+          window.getInterface(Ci.nsIWebNavigation)
+                .QueryInterface(Ci.nsIDocShellTreeItem)
+                .treeOwner
+                .QueryInterface(Ci.nsIInterfaceRequestor)
+                .getInterface(Ci.nsIXULWindow)
+                .docShell.QueryInterface(Ci.nsILoadContext)
+                .usePrivateBrowsing = this._inPrivateBrowsing;
+        }
       }
     }
     else
       this._saveSession = false;
   },
 
   _onAfterPrivateBrowsingModeChange: function PBS__onAfterPrivateBrowsingModeChange() {
     // nothing to do here if we're enabling at startup or the current session is being
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_beforeunload.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_beforeunload.js
@@ -55,36 +55,36 @@ function test() {
       dialogWin.document.documentElement.getButton("accept").click();
     else if (rejectDialog-- > 0)
       dialogWin.document.documentElement.getButton("cancel").click();
   }
 
   Services.obs.addObserver(promptObserver, "common-dialog-loaded", false);
 
   waitForExplicitFinish();
-  let browser1 = gBrowser.getBrowserForTab(gBrowser.addTab());
+  let browser1 = gBrowser.addTab().linkedBrowser;
   browser1.addEventListener("load", function() {
     browser1.removeEventListener("load", arguments.callee, true);
 
-    let browser2 = gBrowser.getBrowserForTab(gBrowser.addTab());
+    let browser2 = gBrowser.addTab().linkedBrowser;
     browser2.addEventListener("load", function() {
       browser2.removeEventListener("load", arguments.callee, true);
 
       rejectDialog = 1;
       pb.privateBrowsingEnabled = true;
 
       ok(!pb.privateBrowsingEnabled, "Private browsing mode should not have been activated");
       is(confirmCalls, 1, "Only one confirm box should be shown");
       is(gBrowser.tabs.length, 3,
          "No tabs should be closed because private browsing mode transition was canceled");
-      is(gBrowser.getBrowserForTab(gBrowser.tabContainer.firstChild).currentURI.spec, "about:blank",
+      is(gBrowser.tabContainer.firstChild.linkedBrowser.currentURI.spec, "about:blank",
          "The first tab should be a blank tab");
-      is(gBrowser.getBrowserForTab(gBrowser.tabContainer.firstChild.nextSibling).currentURI.spec, TEST_PAGE_1,
+      is(gBrowser.tabContainer.firstChild.nextSibling.linkedBrowser.currentURI.spec, TEST_PAGE_1,
          "The middle tab should be the same one we opened");
-      is(gBrowser.getBrowserForTab(gBrowser.tabContainer.lastChild).currentURI.spec, TEST_PAGE_2,
+      is(gBrowser.tabContainer.lastChild.linkedBrowser.currentURI.spec, TEST_PAGE_2,
          "The last tab should be the same one we opened");
       is(rejectDialog, 0, "Only one confirm dialog should have been rejected");
 
       confirmCalls = 0;
       acceptDialog = 2;
       pb.privateBrowsingEnabled = true;
 
       ok(pb.privateBrowsingEnabled, "Private browsing mode should have been activated");
@@ -108,53 +108,59 @@ function test() {
             confirmCalls = 0;
             rejectDialog = 1;
             pb.privateBrowsingEnabled = false;
 
             ok(pb.privateBrowsingEnabled, "Private browsing mode should not have been deactivated");
             is(confirmCalls, 1, "Only one confirm box should be shown");
             is(gBrowser.tabs.length, 2,
                "No tabs should be closed because private browsing mode transition was canceled");
-            is(gBrowser.getBrowserForTab(gBrowser.tabContainer.firstChild).currentURI.spec, TEST_PAGE_1,
+            is(gBrowser.tabContainer.firstChild.linkedBrowser.currentURI.spec, TEST_PAGE_1,
                "The first tab should be the same one we opened");
-            is(gBrowser.getBrowserForTab(gBrowser.tabContainer.lastChild).currentURI.spec, TEST_PAGE_2,
+            is(gBrowser.tabContainer.lastChild.linkedBrowser.currentURI.spec, TEST_PAGE_2,
                "The last tab should be the same one we opened");
             is(rejectDialog, 0, "Only one confirm dialog should have been rejected");
 
+            // Ensure that all restored tabs are loaded without waiting for the
+            // user to bring them to the foreground, by resetting the related
+            // preference (see the "firefox.js" defaults file for details).
+            Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false);
+
             confirmCalls = 0;
             acceptDialog = 2;
             pb.privateBrowsingEnabled = false;
 
             ok(!pb.privateBrowsingEnabled, "Private browsing mode should have been deactivated");
             is(confirmCalls, 2, "Only two confirm boxes should be shown");
             is(gBrowser.tabs.length, 3,
                "Incorrect number of tabs after transition into private browsing");
 
             let loads = 0;
             function waitForLoad(event) {
               gBrowser.removeEventListener("load", arguments.callee, true);
 
               if (++loads != 3)
                 return;
 
-              is(gBrowser.getBrowserForTab(gBrowser.tabContainer.firstChild).currentURI.spec, "about:blank",
+              is(gBrowser.tabContainer.firstChild.linkedBrowser.currentURI.spec, "about:blank",
                  "The first tab should be a blank tab");
-              is(gBrowser.getBrowserForTab(gBrowser.tabContainer.firstChild.nextSibling).currentURI.spec, TEST_PAGE_1,
+              is(gBrowser.tabContainer.firstChild.nextSibling.linkedBrowser.currentURI.spec, TEST_PAGE_1,
                  "The middle tab should be the same one we opened");
-              is(gBrowser.getBrowserForTab(gBrowser.tabContainer.lastChild).currentURI.spec, TEST_PAGE_2,
+              is(gBrowser.tabContainer.lastChild.linkedBrowser.currentURI.spec, TEST_PAGE_2,
                  "The last tab should be the same one we opened");
               is(acceptDialog, 0, "Two confirm dialogs should have been accepted");
               is(acceptDialog, 0, "Two prompts should have been raised");
 
               acceptDialog = 2;
               gBrowser.removeTab(gBrowser.tabContainer.lastChild);
               gBrowser.removeTab(gBrowser.tabContainer.lastChild);
               gBrowser.getBrowserAtIndex(gBrowser.tabContainer.selectedIndex).contentWindow.focus();
 
               Services.obs.removeObserver(promptObserver, "common-dialog-loaded", false);
+              Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
               finish();
             }
             for (let i = 0; i < gBrowser.browsers.length; ++i)
               gBrowser.browsers[i].addEventListener("load", waitForLoad, true);
           }, true);
           gBrowser.selectedBrowser.loadURI(TEST_PAGE_2);
         }, true);
         gBrowser.selectedBrowser.loadURI(TEST_PAGE_1);
--- a/browser/components/sessionstore/test/browser_580512.js
+++ b/browser/components/sessionstore/test/browser_580512.js
@@ -35,17 +35,17 @@ function checkSecondWin(win) {
   document.documentElement.setAttribute("windowtype", "navigator:browser");
   finish();
 }
 
 function openWinWithCb(cb, argURIs, expectedURIs) {
   if (!expectedURIs)
     expectedURIs = argURIs;
 
-  var win = openDialog("chrome://browser/content/", "_blank",
+  var win = openDialog(getBrowserURL(), "_blank",
                        "chrome,all,dialog=no", argURIs.join("|"));
 
   win.addEventListener("load", function () {
     win.removeEventListener("load", arguments.callee, false);
     info("the window loaded");
 
     var expectedLoads = expectedURIs.length;
 
--- a/browser/components/sessionstore/test/browser_586068-cascaded_restore.js
+++ b/browser/components/sessionstore/test/browser_586068-cascaded_restore.js
@@ -85,16 +85,18 @@ function runNextTest() {
   else {
     ss.setBrowserState(stateBackup);
     executeSoon(finish);
   }
 }
 
 
 function test_cascade() {
+  Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false);
+
   // We have our own progress listener for this test, which we'll attach before our state is set
   let progressListener = {
     onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
       dump("\n\nload: " + aBrowser.currentURI.spec + "\n" + JSON.stringify(countTabs()) + "\n\n");
       if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
@@ -215,16 +217,18 @@ function test_select() {
   }
 
   window.gBrowser.addTabsProgressListener(progressListener);
   ss.setBrowserState(JSON.stringify(state));
 }
 
 
 function test_multiWindowState() {
+  Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false);
+
   // We have our own progress listener for this test, which we'll attach before our state is set
   let progressListener = {
     onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
       // We only care about load events when the tab still has
       // __SS_restoreState == TAB_STATE_RESTORING on it.
       // Since our listener is attached before the sessionstore one, this works out.
       if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
@@ -292,16 +296,18 @@ function test_multiWindowState() {
   Services.ww.registerNotification(windowObserver);
 
   window.gBrowser.addTabsProgressListener(progressListener);
   ss.setBrowserState(JSON.stringify(state));
 }
 
 
 function test_setWindowStateNoOverwrite() {
+  Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false);
+
   // We have our own progress listener for this test, which we'll attach before our state is set
   let progressListener = {
     onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
       // We only care about load events when the tab still has
       // __SS_restoreState == TAB_STATE_RESTORING on it.
       // Since our listener is attached before the sessionstore one, this works out.
       if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
@@ -361,16 +367,18 @@ function test_setWindowStateNoOverwrite(
   }
 
   window.gBrowser.addTabsProgressListener(progressListener);
   ss.setWindowState(window, JSON.stringify(state1), true);
 }
 
 
 function test_setWindowStateOverwrite() {
+  Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false);
+
   // We have our own progress listener for this test, which we'll attach before our state is set
   let progressListener = {
     onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
       // We only care about load events when the tab still has
       // __SS_restoreState == TAB_STATE_RESTORING on it.
       // Since our listener is attached before the sessionstore one, this works out.
       if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
@@ -430,16 +438,18 @@ function test_setWindowStateOverwrite() 
   }
 
   window.gBrowser.addTabsProgressListener(progressListener);
   ss.setWindowState(window, JSON.stringify(state1), true);
 }
 
 
 function test_setBrowserStateInterrupted() {
+  Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false);
+
   // We have our own progress listener for this test, which we'll attach before our state is set
   let progressListener = {
     onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
       // We only care about load events when the tab still has
       // __SS_restoreState == TAB_STATE_RESTORING on it.
       // Since our listener is attached before the sessionstore one, this works out.
       if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
@@ -622,16 +632,18 @@ function test_reload() {
   window.gBrowser.addTabsProgressListener(progressListener);
   ss.setBrowserState(JSON.stringify(state));
 }
 
 
 // This doesn't actually test anything, just does a cascaded restore with default
 // settings. This really just sets up to test that reloads work.
 function test_reloadCascadeSetup() {
+  Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false);
+
   // We have our own progress listener for this test, which we'll attach before our state is set
   let progressListener = {
     onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
       if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
         test_cascadeReloadSetup_progressCallback();
--- a/browser/components/sessionstore/test/head.js
+++ b/browser/components/sessionstore/test/head.js
@@ -32,16 +32,24 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
 
+// Some tests here assume that all restored tabs are loaded without waiting for
+// the user to bring them to the foreground. We ensure this by resetting the
+// related preference (see the "firefox.js" defaults file for details).
+Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false);
+registerCleanupFunction(function () {
+  Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
+});
+
 // This assumes that tests will at least have some state/entries
 function waitForBrowserState(aState, aSetStateCallback) {
   let windows = [window];
   let tabsRestored = 0;
   let expectedTabsRestored = 0;
   let expectedWindows = aState.windows.length;
   let windowsOpen = 1;
   let listening = false;
--- a/browser/components/tabview/test/head.js
+++ b/browser/components/tabview/test/head.js
@@ -1,11 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+// Some tests here assume that all restored tabs are loaded without waiting for
+// the user to bring them to the foreground. We ensure this by resetting the
+// related preference (see the "firefox.js" defaults file for details).
+Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false);
+registerCleanupFunction(function () {
+  Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
+});
+
+// ----------
 function createEmptyGroupItem(contentWindow, width, height, padding, animate) {
   let pageBounds = contentWindow.Items.getPageBounds();
   pageBounds.inset(padding, padding);
 
   let box = new contentWindow.Rect(pageBounds);
   box.width = width;
   box.height = height;
 
--- a/browser/confvars.sh
+++ b/browser/confvars.sh
@@ -37,27 +37,34 @@
 # ***** END LICENSE BLOCK *****
 
 MOZ_APP_BASENAME=Firefox
 MOZ_APP_VENDOR=Mozilla
 MOZ_UPDATER=1
 MOZ_PHOENIX=1
 
 if test "$OS_ARCH" = "WINNT"; then
+  MOZ_VERIFY_MAR_SIGNATURE=1
   if ! test "$HAVE_64BIT_OS"; then
     MOZ_MAINTENANCE_SERVICE=1
   fi
 fi
 
 MOZ_CHROME_FILE_FORMAT=omni
 MOZ_SAFE_BROWSING=1
 MOZ_SERVICES_SYNC=1
 MOZ_APP_VERSION=$FIREFOX_VERSION
 MOZ_EXTENSIONS_DEFAULT=" gnomevfs"
 # MOZ_APP_DISPLAYNAME will be set by branding/configure.sh
 # Changing either of these values requires a clobber to ensure correct results,
 # because branding dependencies are broken.
 MOZ_BRANDING_DIRECTORY=browser/branding/nightly
 MOZ_OFFICIAL_BRANDING_DIRECTORY=browser/branding/official
 MOZ_APP_ID={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+# This should usually be the same as the value MAR_CHANNEL_ID.
+# If more than one ID is needed, then you should use a comma separated list
+# of values.
+ACCEPTED_MAR_CHANNEL_IDS=firefox-mozilla-central
+# The MAR_CHANNEL_ID must not contain the following 3 characters: ",\t "
+MAR_CHANNEL_ID=firefox-mozilla-central
 MOZ_PROFILE_MIGRATOR=1
 MOZ_EXTENSION_MANAGER=1
 MOZ_APP_STATIC_INI=1
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -344,17 +344,17 @@ DebuggerView.Properties = {
    */
   _addScope: function DVP__addScope(aName, aId) {
     // make sure the parent container exists
     if (!this._vars) {
       return null;
     }
 
     // compute the id of the element if not specified
-    aId = aId || (aName + "-scope");
+    aId = aId || (aName.toLowerCase().trim().replace(" ", "-") + "-scope");
 
     // contains generic nodes and functionality
     let element = this._createPropertyElement(aName, aId, "scope", this._vars);
 
     // make sure the element was created successfully
     if (!element) {
       dump("The debugger scope container wasn't created properly: " + aId);
       return null;
@@ -536,22 +536,22 @@ DebuggerView.Properties = {
    * default id set as aVar.id->aKey-property.
    *
    * @param object aVar
    *        The parent variable element.
    * @param {Array} aProperty
    *        An array containing the key and grip properties, specifying
    *        the value and/or type & class of the variable (if the type
    *        is not specified, it will be inferred from the value).
-   *        e.g. ["someProp0": 42]
-   *             ["someProp1": true]
-   *             ["someProp2": "nasu"]
-   *             ["someProp3": { type: "undefined" }]
-   *             ["someProp4": { type: "null" }]
-   *             ["someProp5": { type: "object", class: "Object" }]
+   *        e.g. ["someProp0", 42]
+   *             ["someProp1", true]
+   *             ["someProp2", "nasu"]
+   *             ["someProp3", { type: "undefined" }]
+   *             ["someProp4", { type: "null" }]
+   *             ["someProp5", { type: "object", class: "Object" }]
    * @param string aName
    *        Optional, the property name.
    * @paarm string aId
    *        Optional, an id for the property html node.
    * @return object
    *         The newly created html node representing the added prop.
    */
   _addProperty: function DVP__addProperty(aVar, aProperty, aName, aId) {
--- a/browser/devtools/debugger/test/browser_dbg_debuggerstatement.js
+++ b/browser/devtools/debugger/test/browser_dbg_debuggerstatement.js
@@ -29,17 +29,18 @@ function test_early_debugger_statement(a
 {
   let paused = function(aEvent, aPacket) {
     ok(false, "Pause shouldn't be called before we've attached!\n");
     finish_test();
   };
   gClient.addListener("paused", paused);
   // This should continue without nesting an event loop and calling
   // the onPaused hook, because we haven't attached yet.
-  gTab.linkedBrowser.contentWindow.wrappedJSObject.runDebuggerStatement();
+  // TODO: uncomment this when bug 723563 is fixed.
+  //gTab.linkedBrowser.contentWindow.wrappedJSObject.runDebuggerStatement();
 
   gClient.removeListener("paused", paused);
 
   // Now attach and resume...
   gClient.request({ to: aActor.threadActor, type: "attach" }, function(aResponse) {
     gClient.request({ to: aActor.threadActor, type: "resume" }, function(aResponse) {
       test_debugger_statement(aActor);
     });
--- a/browser/devtools/debugger/test/browser_dbg_listtabs.js
+++ b/browser/devtools/debugger/test/browser_dbg_listtabs.js
@@ -82,17 +82,17 @@ function test_attach_removed_tab()
   removeTab(gTab2);
   gTab2 = null;
   gClient.addListener("paused", function(aEvent, aPacket) {
     ok(false, "Attaching to an exited tab actor shouldn't generate a pause.");
     finish_test();
   });
 
   gClient.request({ to: gTab2Actor, type: "attach" }, function(aResponse) {
-    is(aResponse.type, "exited", "Tab should consider itself exited.");
+    is(aResponse.error, "noSuchActor", "Tab should be gone.");
     finish_test();
   });
 }
 
 function finish_test()
 {
   gClient.close(function() {
     finish();
--- a/browser/devtools/highlighter/TreePanel.jsm
+++ b/browser/devtools/highlighter/TreePanel.jsm
@@ -59,74 +59,51 @@ const INSPECTOR_URI = "chrome://browser/
  */
 function TreePanel(aContext, aIUI) {
   this._init(aContext, aIUI);
 };
 
 TreePanel.prototype = {
   showTextNodesWithWhitespace: false,
   id: "treepanel", // DO NOT LOCALIZE
-  openInDock: true,
+  _open: false,
 
   /**
    * The tree panel container element.
    * @returns xul:panel|xul:vbox|null
    *          xul:panel is returned when the tree panel is not docked, or
    *          xul:vbox when when the tree panel is docked.
    *          null is returned when no container is available.
    */
   get container()
   {
-    if (this.openInDock) {
-      return this.document.getElementById("inspector-tree-box");
-    }
-
-    return this.document.getElementById("inspector-tree-panel");
+    return this.document.getElementById("inspector-tree-box");
   },
 
   /**
    * Main TreePanel boot-strapping method. Initialize the TreePanel with the
    * originating context and the InspectorUI global.
    * @param aContext nsIDOMWindow (xulwindow)
    * @param aIUI global InspectorUI object
    */
   _init: function TP__init(aContext, aIUI)
   {
     this.IUI = aIUI;
     this.window = aContext;
     this.document = this.window.document;
+    this.button =
+     this.IUI.chromeDoc.getElementById("inspector-treepanel-toolbutton");
 
     domplateUtils.setDOM(this.window);
 
     this.DOMHelpers = new DOMHelpers(this.window);
 
     let isOpen = this.isOpen.bind(this);
 
-    this.registrationObject = {
-      id: this.id,
-      label: this.IUI.strings.GetStringFromName("htmlPanel.label"),
-      tooltiptext: this.IUI.strings.GetStringFromName("htmlPanel.tooltiptext"),
-      accesskey: this.IUI.strings.GetStringFromName("htmlPanel.accesskey"),
-      context: this,
-      get isOpen() isOpen(),
-      show: this.open,
-      hide: this.close,
-      onSelect: this.select,
-      panel: this.openInDock ? null : this.container,
-      unregister: this.destroy,
-    };
     this.editingEvents = {};
-
-    if (!this.openInDock) {
-      this._boundClose = this.close.bind(this);
-      this.container.addEventListener("popuphiding", this._boundClose, false);
-    }
-
-    // Register the HTML panel with the highlighter
-    this.IUI.registerTool(this.registrationObject);
   },
 
   /**
    * Initialization function for the TreePanel.
    */
   initializeIFrame: function TP_initializeIFrame()
   {
     if (!this.initializingTreePanel || this.treeLoaded) {
@@ -149,138 +126,96 @@ TreePanel.prototype = {
       this.select(this.IUI.selection, true);
   },
 
   /**
    * Open the inspector's tree panel and initialize it.
    */
   open: function TP_open()
   {
-    if (this.initializingTreePanel && !this.treeLoaded) {
+    if (this._open) {
       return;
     }
 
+    this._open = true;
+
+    this.button.setAttribute("checked", true);
     this.initializingTreePanel = true;
-    if (!this.openInDock)
-      this.container.hidden = false;
 
     this.treeIFrame = this.document.getElementById("inspector-tree-iframe");
     if (!this.treeIFrame) {
       this.treeIFrame = this.document.createElement("iframe");
       this.treeIFrame.setAttribute("id", "inspector-tree-iframe");
       this.treeIFrame.flex = 1;
       this.treeIFrame.setAttribute("type", "content");
-    }
-
-    if (this.openInDock) { // Create vbox
-      this.openDocked();
-      return;
+      this.treeIFrame.setAttribute("context", "inspector-node-popup");
     }
 
-    let resizerBox = this.document.getElementById("tree-panel-resizer-box");
-    this.treeIFrame = this.container.insertBefore(this.treeIFrame, resizerBox);
-
-    let boundLoadedInitializeTreePanel = function loadedInitializeTreePanel()
-    {
-      this.treeIFrame.removeEventListener("load",
-        boundLoadedInitializeTreePanel, true);
-      this.initializeIFrame();
-    }.bind(this);
-
-    let boundTreePanelShown = function treePanelShown()
-    {
-      this.container.removeEventListener("popupshown",
-        boundTreePanelShown, false);
-
-      this.treeIFrame.addEventListener("load",
-        boundLoadedInitializeTreePanel, true);
-
-      let src = this.treeIFrame.getAttribute("src");
-      if (src != INSPECTOR_URI) {
-        this.treeIFrame.setAttribute("src", INSPECTOR_URI);
-      } else {
-        this.treeIFrame.contentWindow.location.reload();
-      }
-    }.bind(this);
-
-    this.container.addEventListener("popupshown", boundTreePanelShown, false);
-
-    const panelWidthRatio = 7 / 8;
-    const panelHeightRatio = 1 / 5;
-
-    let width = parseInt(this.IUI.win.outerWidth * panelWidthRatio);
-    let height = parseInt(this.IUI.win.outerHeight * panelHeightRatio);
-    let y = Math.min(this.document.defaultView.screen.availHeight - height,
-      this.IUI.win.innerHeight);
-
-    this.container.openPopup(this.browser, "overlap", 0, 0,
-      false, false);
-
-    this.container.moveTo(80, y);
-    this.container.sizeTo(width, height);
-  },
-
-  openDocked: function TP_openDocked()
-  {
     let treeBox = null;
-    let toolbar = this.IUI.toolbar.nextSibling; // Addons bar, typically
-    let toolbarParent =
-      this.IUI.browser.ownerDocument.getElementById("browser-bottombox");
     treeBox = this.document.createElement("vbox");
     treeBox.id = "inspector-tree-box";
-    treeBox.state = "open"; // for the registerTools API.
+    treeBox.state = "open";
     try {
       treeBox.height =
         Services.prefs.getIntPref("devtools.inspector.htmlHeight");
     } catch(e) {
       treeBox.height = 112;
     }
 
     treeBox.minHeight = 64;
-    treeBox.flex = 1;
-    toolbarParent.insertBefore(treeBox, toolbar);
+
+    this.splitter = this.document.createElement("splitter");
+    this.splitter.id = "inspector-tree-splitter";
 
-    this.IUI.toolbar.setAttribute("treepanel-open", "true");
+    let container = this.document.getElementById("appcontent");
+    container.appendChild(this.splitter);
+    container.appendChild(treeBox);
 
     treeBox.appendChild(this.treeIFrame);
 
-    let boundLoadedInitializeTreePanel = function loadedInitializeTreePanel()
+    this._boundLoadedInitializeTreePanel = function loadedInitializeTreePanel()
     {
       this.treeIFrame.removeEventListener("load",
-        boundLoadedInitializeTreePanel, true);
+        this._boundLoadedInitializeTreePanel, true);
+      delete this._boundLoadedInitializeTreePanel;
       this.initializeIFrame();
     }.bind(this);
 
     this.treeIFrame.addEventListener("load",
-      boundLoadedInitializeTreePanel, true);
+      this._boundLoadedInitializeTreePanel, true);
 
     let src = this.treeIFrame.getAttribute("src");
     if (src != INSPECTOR_URI) {
       this.treeIFrame.setAttribute("src", INSPECTOR_URI);
     } else {
       this.treeIFrame.contentWindow.location.reload();
     }
   },
 
   /**
    * Close the TreePanel.
    */
   close: function TP_close()
   {
-    if (this.openInDock) {
-      this.IUI.toolbar.removeAttribute("treepanel-open");
+    this._open = false;
 
-      let treeBox = this.container;
-      Services.prefs.setIntPref("devtools.inspector.htmlHeight", treeBox.height);
-      let treeBoxParent = treeBox.parentNode;
-      treeBoxParent.removeChild(treeBox);
-    } else {
-      this.container.hidePopup();
+    // Stop caring about the tree iframe load if it's in progress.
+    if (this._boundLoadedInitializeTreePanel) {
+      this.treeIFrame.removeEventListener("load",
+        this._boundLoadedInitializeTreePanel, true);
+      delete this._boundLoadedInitializeTreePanel;
     }
 
+    this.button.removeAttribute("checked");
+    let treeBox = this.container;
+    Services.prefs.setIntPref("devtools.inspector.htmlHeight", treeBox.height);
+    let treeBoxParent = treeBox.parentNode;
+    treeBoxParent.removeChild(this.splitter);
+    treeBoxParent.removeChild(treeBox);
+
     if (this.treePanelDiv) {
       this.treePanelDiv.ownerPanel = null;
       let parent = this.treePanelDiv.parentNode;
       parent.removeChild(this.treePanelDiv);
       delete this.treePanelDiv;
       delete this.treeBrowserDocument;
     }
 
@@ -288,20 +223,25 @@ TreePanel.prototype = {
   },
 
   /**
    * Is the TreePanel open?
    * @returns boolean
    */
   isOpen: function TP_isOpen()
   {
-    if (this.openInDock)
-      return this.treeLoaded && this.container;
+    return this._open;
+  },
 
-    return this.treeLoaded && this.container.state == "open";
+  /**
+   * Toggle the TreePanel.
+   */
+  toggle: function TP_toggle()
+  {
+    this.isOpen() ? this.close() : this.open();
   },
 
   /**
    * Create the ObjectBox for the given object.
    * @param object nsIDOMNode
    * @param isRoot boolean - Is this the root object?
    * @returns InsideOutBox
    */
@@ -665,16 +605,30 @@ TreePanel.prototype = {
         else
           return child.repObject;
       }
     }
     return null;
   },
 
   /**
+   * Remove a node box from the tree view.
+   * @param aElement
+   *        The DOM node to remove from the HTML IOBox.
+   */
+  deleteChildBox: function TP_deleteChildBox(aElement)
+  {
+    let childBox = this.ioBox.findObjectBox(aElement);
+    if (!childBox) {
+      return;
+    }
+    childBox.parentNode.removeChild(childBox);
+  },
+
+  /**
    * Destructor function. Cleanup.
    */
   destroy: function TP_destroy()
   {
     if (this.isOpen()) {
       this.close();
     }
 
@@ -700,21 +654,16 @@ TreePanel.prototype = {
       parent.removeChild(this.treeIFrame);
       delete this.treeIFrame;
     }
 
     if (this.ioBox) {
       this.ioBox.destroy();
       delete this.ioBox;
     }
-
-    if (!this.openInDock) {
-      this.container.removeEventListener("popuphiding", this._boundClose, false);
-      delete this._boundClose;
-    }
   }
 };
 
 
 /**
  * DOMHelpers
  * Makes DOM traversal easier. Goes through iframes.
  *
--- a/browser/devtools/highlighter/highlighter.jsm
+++ b/browser/devtools/highlighter/highlighter.jsm
@@ -38,32 +38,39 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
 Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 var EXPORTED_SYMBOLS = ["Highlighter"];
 
 const INSPECTOR_INVISIBLE_ELEMENTS = {
   "head": true,
   "base": true,
   "basefont": true,
   "isindex": true,
   "link": true,
   "meta": true,
   "script": true,
   "style": true,
   "title": true,
 };
 
+const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
+  // add ":visited" and ":link" after bug 713106 is fixed
+
 /**
  * A highlighter mechanism.
  *
  * The highlighter is built dynamically into the browser element.
  * The caller is in charge of destroying the highlighter (ie, the highlighter
  * won't be destroyed if a new tab is selected for example).
  *
  * API:
@@ -104,16 +111,18 @@ const INSPECTOR_INVISIBLE_ELEMENTS = {
  *
  * Events:
  *
  *   "closed" - Highlighter is closing
  *   "nodeselected" - A new node has been selected
  *   "highlighting" - Highlighter is highlighting
  *   "locked" - The selected node has been locked
  *   "unlocked" - The selected ndoe has been unlocked
+ *   "pseudoclasstoggled" - A pseudo-class lock has changed on the selected node
+
  *
  * Structure:
  *
  *   <stack id="highlighter-container">
  *     <vbox id="highlighter-veil-container">...</vbox>
  *     <box id="highlighter-controls>...</vbox>
  *   </stack>
  *
@@ -234,16 +243,27 @@ Highlighter.prototype = {
     this.invalidateSize(!!aScroll);
 
     if (oldNode !== this.node) {
       this.emitEvent("nodeselected");
     }
   },
 
   /**
+   * Notify that a pseudo-class lock was toggled on the highlighted element
+   *
+   * @param aPseudo - The pseudo-class to toggle, e.g. ":hover".
+   */
+  pseudoClassLockToggled: function Highlighter_pseudoClassLockToggled(aPseudo)
+  {  
+    this.emitEvent("pseudoclasstoggled", [aPseudo]);
+    this.updateInfobar();
+  },
+
+  /**
    * Update the highlighter size and position.
    */
   invalidateSize: function Highlighter_invalidateSize(aScroll)
   {
     let rect = null;
 
     if (this.node && this.isNodeHighlightable(this.node)) {
 
@@ -441,40 +461,91 @@ Highlighter.prototype = {
     let tagNameLabel = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
     tagNameLabel.id = "highlighter-nodeinfobar-tagname";
 
     let idLabel = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
     idLabel.id = "highlighter-nodeinfobar-id";
 
     let classesBox = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
     classesBox.id = "highlighter-nodeinfobar-classes";
+    
+    let pseudoClassesBox = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
+    pseudoClassesBox.id = "highlighter-nodeinfobar-pseudo-classes";
+    
     // Add some content to force a better boundingClientRect down below.
-    classesBox.textContent = "&nbsp;";
+    pseudoClassesBox.textContent = "&nbsp;";
 
     nodeInfobar.appendChild(tagNameLabel);
     nodeInfobar.appendChild(idLabel);
     nodeInfobar.appendChild(classesBox);
+    nodeInfobar.appendChild(pseudoClassesBox);
     container.appendChild(arrowBoxTop);
     container.appendChild(nodeInfobar);
     container.appendChild(arrowBoxBottom);
 
     aParent.appendChild(container);
 
+    nodeInfobar.onclick = (function _onInfobarRightClick(aEvent) {
+      if (aEvent.button == 2) {
+        this.openPseudoClassMenu();
+      }
+    }).bind(this);
+
     let barHeight = container.getBoundingClientRect().height;
 
     this.nodeInfo = {
       tagNameLabel: tagNameLabel,
       idLabel: idLabel,
       classesBox: classesBox,
+      pseudoClassesBox: pseudoClassesBox,
       container: container,
       barHeight: barHeight,
     };
   },
 
   /**
+   * Open the infobar's pseudo-class context menu.
+   */
+  openPseudoClassMenu: function Highlighter_openPseudoClassMenu()
+  {
+    let menu = this.chromeDoc.createElement("menupopup");
+    menu.id = "infobar-context-menu";
+
+    let popupSet = this.chromeDoc.getElementById("mainPopupSet");
+    popupSet.appendChild(menu);
+    
+    let fragment = this.buildPseudoClassMenu();
+    menu.appendChild(fragment);
+
+    menu.openPopup(this.nodeInfo.pseudoClassesBox, "end_before", 0, 0, true, false);
+  },  
+  
+  /**
+   * Create the menuitems for toggling the selection's pseudo-class state
+   *
+   * @returns DocumentFragment. The menuitems for toggling pseudo-classes.
+   */
+  buildPseudoClassMenu: function IUI_buildPseudoClassesMenu()
+  {
+    let fragment = this.chromeDoc.createDocumentFragment();
+    for (let i = 0; i < PSEUDO_CLASSES.length; i++) {
+      let pseudo = PSEUDO_CLASSES[i];
+      let item = this.chromeDoc.createElement("menuitem");
+      item.setAttribute("type", "checkbox");
+      item.setAttribute("label", pseudo);
+      item.addEventListener("command",
+                            this.pseudoClassLockToggled.bind(this, pseudo), false);
+      item.setAttribute("checked", DOMUtils.hasPseudoClassLock(this.node,
+                         pseudo));
+      fragment.appendChild(item);
+    }
+    return fragment;
+  },
+
+  /**
    * Highlight a rectangular region.
    *
    * @param object aRect
    *        The rectangle region to highlight.
    * @returns boolean
    *          True if the rectangle was highlighted, false otherwise.
    */
   highlightRectangle: function Highlighter_highlightRectangle(aRect)
@@ -538,16 +609,24 @@ Highlighter.prototype = {
     // ID
     this.nodeInfo.idLabel.textContent = this.node.id ? "#" + this.node.id : "";
 
     // Classes
     let classes = this.nodeInfo.classesBox;
 
     classes.textContent = this.node.classList.length ?
                             "." + Array.join(this.node.classList, ".") : "";
+
+    // Pseudo-classes
+    let pseudos = PSEUDO_CLASSES.filter(function(pseudo) {
+      return DOMUtils.hasPseudoClassLock(this.node, pseudo);
+    }, this);
+
+    let pseudoBox = this.nodeInfo.pseudoClassesBox;
+    pseudoBox.textContent = pseudos.join("");
   },
 
   /**
    * Move the Infobar to the right place in the highlighter.
    */
   moveInfobar: function Highlighter_moveInfobar()
   {
     if (this._highlightRect) {
@@ -612,18 +691,18 @@ Highlighter.prototype = {
     }
   },
 
   /**
    * Store page zoom factor.
    */
   computeZoomFactor: function Highlighter_computeZoomFactor() {
     this.zoom =
-      this.win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-      .getInterface(Components.interfaces.nsIDOMWindowUtils)
+      this.win.QueryInterface(Ci.nsIInterfaceRequestor)
+      .getInterface(Ci.nsIDOMWindowUtils)
       .screenPixelsPerCSSPixel;
   },
 
   /////////////////////////////////////////////////////////////////////////
   //// Event Emitter Mechanism
 
   addListener: function Highlighter_addListener(aEvent, aListener)
   {
@@ -800,8 +879,11 @@ Highlighter.prototype = {
     if (element && element != this.node) {
       this.highlight(element);
     }
   },
 };
 
 ///////////////////////////////////////////////////////////////////////////
 
+XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
+  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils)
+});
--- a/browser/devtools/highlighter/inspector.jsm
+++ b/browser/devtools/highlighter/inspector.jsm
@@ -37,16 +37,17 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
+const Cc = Components.classes;
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
 var EXPORTED_SYMBOLS = ["InspectorUI"];
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@@ -77,16 +78,18 @@ const INSPECTOR_NOTIFICATIONS = {
   RULEVIEWREADY: "inspector-ruleview-ready",
 
   // Event notifications for the attribute-value editor
   EDITOR_OPENED: "inspector-editor-opened",
   EDITOR_CLOSED: "inspector-editor-closed",
   EDITOR_SAVED: "inspector-editor-saved",
 };
 
+const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
+
 ///////////////////////////////////////////////////////////////////////////
 //// InspectorUI
 
 /**
  * Main controller class for the Inspector.
  *
  * @constructor
  * @param nsIDOMWindow aWindow
@@ -103,17 +106,16 @@ function InspectorUI(aWindow)
   this.INSPECTOR_NOTIFICATIONS = INSPECTOR_NOTIFICATIONS;
 }
 
 InspectorUI.prototype = {
   browser: null,
   tools: null,
   toolEvents: null,
   inspecting: false,
-  treePanelEnabled: true,
   ruleViewEnabled: true,
   isDirty: false,
   store: null,
 
   /**
    * Toggle the inspector interface elements on or off.
    *
    * @param aEvent
@@ -132,49 +134,87 @@ InspectorUI.prototype = {
    * Show the Sidebar.
    */
   showSidebar: function IUI_showSidebar()
   {
     this.sidebarBox.removeAttribute("hidden");
     this.sidebarSplitter.removeAttribute("hidden");
     this.stylingButton.checked = true;
 
-    // Activate the first tool in the sidebar, only if none previously-
-    // selected. We'll want to do a followup to remember selected tool-states.
+    // If no tool is already selected, show the last-used sidebar if available,
+    // otherwise just show the first.
+
     if (!Array.some(this.sidebarToolbar.children,
       function(btn) btn.hasAttribute("checked"))) {
-        let firstButtonId = this.getToolbarButtonId(this.sidebarTools[0].id);
-        this.chromeDoc.getElementById(firstButtonId).click();
+
+      let activePanel = this.sidebarTools[0];
+      let activeId = this.store.getValue(this.winID, "activeSidebar");
+      if (activeId && this.tools[activeId]) {
+        activePanel = this.tools[activeId];
+      }
+      this.activateSidebarPanel(activePanel.id);
     }
+
+    this.store.setValue(this.winID, "sidebarOpen", true);
+    Services.prefs.setBoolPref("devtools.inspector.sidebarOpen", true);
   },
 
   /**
-   * Hide the Sidebar.
+   * Tear down the sidebar.
    */
-  hideSidebar: function IUI_hideSidebar()
+  _destroySidebar: function IUI_destroySidebar()
   {
     this.sidebarBox.setAttribute("hidden", "true");
     this.sidebarSplitter.setAttribute("hidden", "true");
     this.stylingButton.checked = false;
   },
 
   /**
+   * Hide the sidebar.
+   */
+  hideSidebar: function IUI_hideSidebar()
+  {
+    this._destroySidebar();
+    this.store.setValue(this.winID, "sidebarOpen", false);
+    Services.prefs.setBoolPref("devtools.inspector.sidebarOpen", false);
+  },
+
+  /**
    * Show or hide the sidebar. Called from the Styling button on the
    * highlighter toolbar.
    */
   toggleSidebar: function IUI_toggleSidebar()
   {
     if (!this.isSidebarOpen) {
       this.showSidebar();
     } else {
       this.hideSidebar();
     }
   },
 
   /**
+   * Activate a sidebar panel by id.
+   */
+  activateSidebarPanel: function IUI_activateSidebarPanel(aID)
+  {
+    let buttonId = this.getToolbarButtonId(aID);
+    this.chromeDoc.getElementById(buttonId).click();
+  },
+
+  get activeSidebarPanel()
+  {
+    for each (let tool in this.sidebarTools) {
+      if (this.sidebarDeck.selectedPanel == this.getToolIframe(tool)) {
+        return tool.id;
+      }
+    }
+    return null;
+  },
+
+  /**
    * Getter to test if the Sidebar is open or not.
    */
   get isSidebarOpen()
   {
     return this.stylingButton.checked &&
           !this.sidebarBox.hidden &&
           !this.sidebarSplitter.hidden;
   },
@@ -188,16 +228,32 @@ InspectorUI.prototype = {
     if (this.inspecting) {
       this.stopInspecting();
     } else {
       this.startInspecting();
     }
   },
 
   /**
+   * Toggle the TreePanel.
+   */
+  toggleHTMLPanel: function TP_toggle()
+  {
+    if (this.treePanel.isOpen()) {
+      this.treePanel.close();
+      Services.prefs.setBoolPref("devtools.inspector.htmlPanelOpen", false);
+      this.store.setValue(this.winID, "htmlPanelOpen", false);
+    } else {
+      this.treePanel.open();
+      Services.prefs.setBoolPref("devtools.inspector.htmlPanelOpen", true);
+      this.store.setValue(this.winID, "htmlPanelOpen", true);
+    }
+  },
+
+  /**
    * Is the inspector UI open? Simply check if the toolbar is visible or not.
    *
    * @returns boolean
    */
   get isInspectorOpen()
   {
     return this.toolbar && !this.toolbar.hidden && this.highlighter;
   },
@@ -255,19 +311,17 @@ InspectorUI.prototype = {
     this.toolbar = this.chromeDoc.getElementById("inspector-toolbar");
     this.inspectMenuitem = this.chromeDoc.getElementById("Tools:Inspect");
     this.inspectToolbutton =
       this.chromeDoc.getElementById("inspector-inspect-toolbutton");
 
     this.initTools();
     this.chromeWin.Tilt.setup();
 
-    if (this.treePanelEnabled) {
-      this.treePanel = new TreePanel(this.chromeWin, this);
-    }
+    this.treePanel = new TreePanel(this.chromeWin, this);
 
     if (Services.prefs.getBoolPref("devtools.ruleview.enabled") &&
         !this.toolRegistered("ruleview")) {
       this.registerRuleView();
     }
 
     if (Services.prefs.getBoolPref("devtools.styleinspector.enabled") &&
         !this.toolRegistered("styleinspector")) {
@@ -305,16 +359,17 @@ InspectorUI.prototype = {
       label: this.strings.GetStringFromName("ruleView.label"),
       tooltiptext: this.strings.GetStringFromName("ruleView.tooltiptext"),
       accesskey: this.strings.GetStringFromName("ruleView.accesskey"),
       context: this,
       get isOpen() isOpen(),
       show: this.openRuleView,
       hide: this.closeRuleView,
       onSelect: this.selectInRuleView,
+      onChanged: this.changeInRuleView,
       panel: null,
       unregister: this.destroyRuleView,
       sidebar: true,
     };
 
     this.registerTool(this.ruleViewObject);
   },
 
@@ -344,16 +399,26 @@ InspectorUI.prototype = {
       }
       this.isDirty = this.store.getValue(this.winID, "isDirty");
     } else {
       // First time inspecting, set state to no selection + live inspection.
       this.store.addStore(this.winID);
       this.store.setValue(this.winID, "selectedNode", null);
       this.store.setValue(this.winID, "inspecting", true);
       this.store.setValue(this.winID, "isDirty", this.isDirty);
+
+      this.store.setValue(this.winID, "htmlPanelOpen",
+        Services.prefs.getBoolPref("devtools.inspector.htmlPanelOpen"));
+
+      this.store.setValue(this.winID, "sidebarOpen",
+        Services.prefs.getBoolPref("devtools.inspector.sidebarOpen"));
+
+      this.store.setValue(this.winID, "activeSidebar",
+        Services.prefs.getCharPref("devtools.inspector.activeSidebar"));
+
       this.win.addEventListener("pagehide", this, true);
     }
   },
 
   /**
    * Browse nodes according to the breadcrumbs layout, only for some specific
    * elements of the UI.
    */
@@ -395,16 +460,18 @@ InspectorUI.prototype = {
    */
   closeInspectorUI: function IUI_closeInspectorUI(aKeepStore)
   {
     // if currently editing an attribute value, closing the
     // highlighter/HTML panel dismisses the editor
     if (this.treePanel && this.treePanel.editingContext)
       this.treePanel.closeEditor();
 
+    this.treePanel.destroy();
+
     if (this.closing || !this.win || !this.browser) {
       return;
     }
 
     let winId = new String(this.winID); // retain this to notify observers.
 
     this.closing = true;
     this.toolbar.hidden = true;
@@ -412,16 +479,17 @@ InspectorUI.prototype = {
     this.removeNavigationKeys();
 
     this.progressListener.destroy();
     delete this.progressListener;
 
     if (!aKeepStore) {
       this.store.deleteStore(this.winID);
       this.win.removeEventListener("pagehide", this, true);
+      this.clearPseudoClassLocks();
     } else {
       // Update the store before closing.
       if (this.selection) {
         this.store.setValue(this.winID, "selectedNode",
           this.selection);
       }
       this.store.setValue(this.winID, "inspecting", this.inspecting);
       this.store.setValue(this.winID, "isDirty", this.isDirty);
@@ -430,23 +498,22 @@ InspectorUI.prototype = {
     if (this.store.isEmpty()) {
       this.tabbrowser.tabContainer.removeEventListener("TabSelect", this, false);
     }
 
     this.chromeWin.removeEventListener("keypress", this, false);
 
     this.stopInspecting();
 
-    this.saveToolState(this.winID);
     this.toolsDo(function IUI_toolsHide(aTool) {
       this.unregisterTool(aTool);
     }.bind(this));
 
     // close the sidebar
-    this.hideSidebar();
+    this._destroySidebar();
 
     if (this.highlighter) {
       this.highlighter.destroy();
       this.highlighter = null;
     }
 
     if (this.breadcrumbs) {
       this.breadcrumbs.destroy();
@@ -498,65 +565,108 @@ InspectorUI.prototype = {
       return;
     }
 
     this.inspectToolbutton.checked = false;
 
     this.inspecting = false;
     this.toolsDim(false);
     if (this.highlighter.getNode()) {
-      this.select(this.highlighter.getNode(), true, true, !aPreventScroll);
+      this.select(this.highlighter.getNode(), true, !aPreventScroll);
     } else {
       this.select(null, true, true);
     }
     this.highlighter.lock();
   },
 
   /**
-   * Select an object in the tree view.
+   * Select an object in the inspector.
    * @param aNode
    *        node to inspect
    * @param forceUpdate
    *        force an update?
    * @param aScroll boolean
    *        scroll the tree panel?
+   * @param aFrom [optional] string
+   *        which part of the UI the selection occured from
    */
-  select: function IUI_select(aNode, forceUpdate, aScroll)
+  select: function IUI_select(aNode, forceUpdate, aScroll, aFrom)
   {
     // if currently editing an attribute value, using the
     // highlighter dismisses the editor
     if (this.treePanel && this.treePanel.editingContext)
       this.treePanel.closeEditor();
 
     if (!aNode)
       aNode = this.defaultSelection;
 
     if (forceUpdate || aNode != this.selection) {
+      if (aFrom != "breadcrumbs") {
+        this.clearPseudoClassLocks();
+      }
+      
       this.selection = aNode;
       if (!this.inspecting) {
         this.highlighter.highlight(this.selection);
       }
     }
 
     this.breadcrumbs.update();
     this.chromeWin.Tilt.update(aNode);
+    this.treePanel.select(aNode, aScroll);
 
     this.toolsSelect(aScroll);
   },
+  
+  /**
+   * Toggle the pseudo-class lock on the currently inspected element. If the
+   * pseudo-class is :hover or :active, that pseudo-class will also be toggled
+   * on every ancestor of the element, mirroring real :hover and :active
+   * behavior.
+   * 
+   * @param aPseudo the pseudo-class lock to toggle, e.g. ":hover"
+   */
+  togglePseudoClassLock: function IUI_togglePseudoClassLock(aPseudo)
+  {
+    if (DOMUtils.hasPseudoClassLock(this.selection, aPseudo)) {
+      this.breadcrumbs.nodeHierarchy.forEach(function(crumb) {
+        DOMUtils.removePseudoClassLock(crumb.node, aPseudo);
+      });
+    } else {
+      let hierarchical = aPseudo == ":hover" || aPseudo == ":active";
+      let node = this.selection;
+      do {
+        DOMUtils.addPseudoClassLock(node, aPseudo);
+        node = node.parentNode;
+      } while (hierarchical && node.parentNode)
+    }
+    this.nodeChanged();
+  },
+
+  /**
+   * Clear all pseudo-class locks applied to elements in the node hierarchy
+   */
+  clearPseudoClassLocks: function IUI_clearPseudoClassLocks()
+  {
+    this.breadcrumbs.nodeHierarchy.forEach(function(crumb) {
+      DOMUtils.clearPseudoClassLocks(crumb.node);
+    });
+  },
 
   /**
    * Called when the highlighted node is changed by a tool.
    *
    * @param object aUpdater
    *        The tool that triggered the update (if any), that tool's
    *        onChanged will not be called.
    */
   nodeChanged: function IUI_nodeChanged(aUpdater)
   {
     this.highlighter.invalidateSize();
+    this.breadcrumbs.updateSelectors();
     this.toolsOnChanged(aUpdater);
   },
 
   /////////////////////////////////////////////////////////////////////////
   //// Event Handling
 
   highlighterReady: function IUI_highlighterReady()
   {
@@ -572,27 +682,42 @@ InspectorUI.prototype = {
     this.highlighter.addListener("unlocked", function() {
       self.startInspecting();
     });
 
     this.highlighter.addListener("nodeselected", function() {
       self.select(self.highlighter.getNode(), false, false);
     });
 
+    this.highlighter.addListener("pseudoclasstoggled", function(aPseudo) {
+      self.togglePseudoClassLock(aPseudo);
+    });
+
     if (this.store.getValue(this.winID, "inspecting")) {
       this.startInspecting();
+      this.highlighter.unlock();
+    } else {
+      this.highlighter.lock();
     }
 
-    this.restoreToolState(this.winID);
+    Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.STATE_RESTORED, null);
 
     this.win.focus();
+    this.highlighter.highlight();
+
+    if (this.store.getValue(this.winID, "htmlPanelOpen")) {
+      this.treePanel.open();
+    }
+
+    if (this.store.getValue(this.winID, "sidebarOpen")) {
+      this.showSidebar();
+    }
+
     Services.obs.notifyObservers({wrappedJSObject: this},
                                  INSPECTOR_NOTIFICATIONS.OPENED, null);
-
-    this.highlighter.highlight();
   },
 
   /**
    * Main callback handler for events.
    *
    * @param event
    *        The event to be handled.
    */
@@ -709,16 +834,56 @@ InspectorUI.prototype = {
           this.highlighter.highlight(node, true);
         }
         event.preventDefault();
         event.stopPropagation();
         break;
     }
   },
 
+  /**
+   * Copy the innerHTML of the selected Node to the clipboard. Called via the
+   * Inspector:CopyInner command.
+   */
+  copyInnerHTML: function IUI_copyInnerHTML()
+  {
+    let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+                    getService(Ci.nsIClipboardHelper);
+    clipboard.copyString(this.selection.innerHTML);
+  },
+
+  /**
+   * Copy the outerHTML of the selected Node to the clipboard. Called via the
+   * Inspector:CopyOuter command.
+   */
+  copyOuterHTML: function IUI_copyOuterHTML()
+  {
+    let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+                    getService(Ci.nsIClipboardHelper);
+    clipboard.copyString(this.selection.outerHTML);
+  },
+
+  /**
+   * Delete the selected node. Called via the Inspector:DeleteNode command.
+   */
+  deleteNode: function IUI_deleteNode()
+  {
+    let selection = this.selection;
+    let parent = this.selection.parentNode;
+
+    // remove the node from the treepanel
+    this.treePanel.deleteChildBox(selection);
+
+    // remove the node from content
+    parent.removeChild(selection);
+    this.breadcrumbs.invalidateHierarchy();
+
+    // select the parent node in the highlighter, treepanel, breadcrumbs
+    this.inspectNode(parent);
+  },
 
   /////////////////////////////////////////////////////////////////////////
   //// CssRuleView methods
 
   /**
    * Is the cssRuleView open?
    */
   isRuleViewOpen: function IUI_isRuleViewOpen()
@@ -791,16 +956,25 @@ InspectorUI.prototype = {
    * Update the selected node in the Css Rule View.
    * @param {nsIDOMnode} the selected node.
    */
   selectInRuleView: function IUI_selectInRuleView(aNode)
   {
     if (this.ruleView)
       this.ruleView.highlight(aNode);
   },
+  
+  /**
+   * Update the rules for the current node in the Css Rule View.
+   */
+  changeInRuleView: function IUI_selectInRuleView()
+  {
+    if (this.ruleView)
+      this.ruleView.nodeChanged();
+  },
 
   ruleViewChanged: function IUI_ruleViewChanged()
   {
     this.isDirty = true;
     this.nodeChanged(this.ruleViewObject);
   },
 
   /**
@@ -1082,16 +1256,18 @@ InspectorUI.prototype = {
    * Show the specified tool.
    * @param aTool Object (see comment for IUI_registerTool)
    */
   toolShow: function IUI_toolShow(aTool)
   {
     let btn = this.chromeDoc.getElementById(this.getToolbarButtonId(aTool.id));
     btn.setAttribute("checked", "true");
     if (aTool.sidebar) {
+      Services.prefs.setCharPref("devtools.inspector.activeSidebar", aTool.id);
+      this.store.setValue(this.winID, "activeSidebar", aTool.id);
       this.sidebarDeck.selectedPanel = this.getToolIframe(aTool);
       this.sidebarTools.forEach(function(other) {
         if (other != aTool)
           this.chromeDoc.getElementById(
             this.getToolbarButtonId(other.id)).removeAttribute("checked");
       }.bind(this));
     }
 
@@ -1176,67 +1352,16 @@ InspectorUI.prototype = {
     // the iframe.
     if (aRegObj.unregister)
       aRegObj.unregister.call(aRegObj.context);
 
     delete this.tools[aRegObj.id];
   },
 
   /**
-   * Save a list of open tools to the inspector store.
-   *
-   * @param aWinID The ID of the window used to save the associated tools
-   */
-  saveToolState: function IUI_saveToolState(aWinID)
-  {
-    let openTools = {};
-    this.toolsDo(function IUI_toolsSetId(aTool) {
-      if (aTool.isOpen) {
-        openTools[aTool.id] = true;
-      }
-    });
-    this.store.setValue(aWinID, "openTools", openTools);
-  },
-
-  /**
-   * Restore tools previously save using saveToolState().
-   *
-   * @param aWinID The ID of the window to which the associated tools are to be
-   *               restored.
-   */
-  restoreToolState: function IUI_restoreToolState(aWinID)
-  {
-    let openTools = this.store.getValue(aWinID, "openTools");
-    let activeSidebarTool;
-    if (openTools) {
-      this.toolsDo(function IUI_toolsOnShow(aTool) {
-        if (aTool.id in openTools) {
-          if (aTool.sidebar && !this.isSidebarOpen) {
-            this.showSidebar();
-            activeSidebarTool = aTool;
-          }
-          this.toolShow(aTool);
-        }
-      }.bind(this));
-      this.sidebarTools.forEach(function(tool) {
-        if (tool != activeSidebarTool)
-          this.chromeDoc.getElementById(
-            this.getToolbarButtonId(tool.id)).removeAttribute("checked");
-      }.bind(this));
-    }
-    if (this.store.getValue(this.winID, "inspecting")) {
-      this.highlighter.unlock();
-    } else {
-      this.highlighter.lock();
-    }
-
-    Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.STATE_RESTORED, null);
-  },
-
-  /**
    * For each tool in the tools collection select the current node that is
    * selected in the highlighter
    * @param aScroll boolean
    *        Do you want to scroll the treepanel?
    */
   toolsSelect: function IUI_toolsSelect(aScroll)
   {
     let selection = this.selection;
@@ -1249,33 +1374,33 @@ InspectorUI.prototype = {
 
   /**
    * Dim or undim each tool in the tools collection
    * @param aState true = dim, false = undim
    */
   toolsDim: function IUI_toolsDim(aState)
   {
     this.toolsDo(function IUI_toolsDim(aTool) {
-      if (aTool.isOpen && "dim" in aTool) {
+      if ("dim" in aTool) {
         aTool.dim.call(aTool.context, aState);
       }
     });
   },
 
   /**
    * Notify registered tools of changes to the highlighted element.
    *
    * @param object aUpdater
    *        The tool that triggered the update (if any), that tool's
    *        onChanged will not be called.
    */
   toolsOnChanged: function IUI_toolsChanged(aUpdater)
   {
     this.toolsDo(function IUI_toolsOnChanged(aTool) {
-      if (aTool.isOpen && ("onChanged" in aTool) && aTool != aUpdater) {
+      if (("onChanged" in aTool) && aTool != aUpdater) {
         aTool.onChanged.call(aTool.context);
       }
     });
   },
 
   /**
    * Loop through all registered tools and pass each into the provided function
    * @param aFunction The function to which each tool is to be passed
@@ -1659,16 +1784,23 @@ HTMLBreadcrumbs.prototype = {
   {
     let text = aNode.tagName.toLowerCase();
     if (aNode.id) {
       text += "#" + aNode.id;
     }
     for (let i = 0; i < aNode.classList.length; i++) {
       text += "." + aNode.classList[i];
     }
+    for (let i = 0; i < PSEUDO_CLASSES.length; i++) {
+      let pseudo = PSEUDO_CLASSES[i];
+      if (DOMUtils.hasPseudoClassLock(aNode, pseudo)) {
+        text += pseudo;  
+      }      
+    }
+
     return text;
   },
 
 
   /**
    * Build <label>s that represent the node:
    *   <label class="inspector-breadcrumbs-tag">tagName</label>
    *   <label class="inspector-breadcrumbs-id">#id</label>
@@ -1684,29 +1816,38 @@ HTMLBreadcrumbs.prototype = {
     let tagLabel = this.IUI.chromeDoc.createElement("label");
     tagLabel.className = "inspector-breadcrumbs-tag plain";
 
     let idLabel = this.IUI.chromeDoc.createElement("label");
     idLabel.className = "inspector-breadcrumbs-id plain";
 
     let classesLabel = this.IUI.chromeDoc.createElement("label");
     classesLabel.className = "inspector-breadcrumbs-classes plain";
+    
+    let pseudosLabel = this.IUI.chromeDoc.createElement("label");
+    pseudosLabel.className = "inspector-breadcrumbs-pseudo-classes plain";
 
     tagLabel.textContent = aNode.tagName.toLowerCase();
     idLabel.textContent = aNode.id ? ("#" + aNode.id) : "";
 
     let classesText = "";
     for (let i = 0; i < aNode.classList.length; i++) {
       classesText += "." + aNode.classList[i];
     }
     classesLabel.textContent = classesText;
 
+    let pseudos = PSEUDO_CLASSES.filter(function(pseudo) {
+      return DOMUtils.hasPseudoClassLock(aNode, pseudo);
+    }, this);
+    pseudosLabel.textContent = pseudos.join("");
+
     fragment.appendChild(tagLabel);
     fragment.appendChild(idLabel);
     fragment.appendChild(classesLabel);
+    fragment.appendChild(pseudosLabel);
 
     return fragment;
   },
 
   /**
    * Open the sibling menu.
    *
    * @param aButton the button representing the node.
@@ -1736,17 +1877,17 @@ HTMLBreadcrumbs.prototype = {
           item.setAttribute("checked", "true");
         }
 
         item.setAttribute("type", "radio");
         item.setAttribute("label", this.prettyPrintNodeAsText(nodes[i]));
 
         item.onmouseup = (function(aNode) {
           return function() {
-            inspector.select(aNode, true, true);
+            inspector.select(aNode, true, true, "breadcrumbs");
           }
         })(nodes[i]);
 
         fragment.appendChild(item);
       }
     }
     this.menu.appendChild(fragment);
     this.menu.openPopup(aButton, "before_start", 0, 0, true, false);
@@ -1890,17 +2031,17 @@ HTMLBreadcrumbs.prototype = {
     let inspector = this.IUI;
     button.appendChild(this.prettyPrintNodeAsXUL(aNode));
     button.className = "inspector-breadcrumbs-button";
 
     button.setAttribute("tooltiptext", this.prettyPrintNodeAsText(aNode));
 
     button.onBreadcrumbsClick = function onBreadcrumbsClick() {
       inspector.stopInspecting();
-      inspector.select(aNode, true, true);
+      inspector.select(aNode, true, true, "breadcrumbs");
     };
 
     button.onclick = (function _onBreadcrumbsRightClick(aEvent) {
       if (aEvent.button == 2) {
         this.openSiblingMenu(button, aNode);
       }
     }).bind(this);
 
@@ -2005,16 +2146,30 @@ HTMLBreadcrumbs.prototype = {
   scroll: function BC_scroll()
   {
     // FIXME bug 684352: make sure its immediate neighbors are visible too.
 
     let scrollbox = this.container;
     let element = this.nodeHierarchy[this.currentIndex].button;
     scrollbox.ensureElementIsVisible(element);
   },
+  
+  updateSelectors: function BC_updateSelectors()
+  {
+    for (let i = this.nodeHierarchy.length - 1; i >= 0; i--) {
+      let crumb = this.nodeHierarchy[i];
+      let button = crumb.button;
+
+      while(button.hasChildNodes()) {
+        button.removeChild(button.firstChild);
+      }
+      button.appendChild(this.prettyPrintNodeAsXUL(crumb.node));
+      button.setAttribute("tooltiptext", this.prettyPrintNodeAsText(crumb.node));
+    }
+  },
 
   /**
    * Update the breadcrumbs display when a new node is selected.
    */
   update: function BC_update()
   {
     this.menu.hidePopup();
 
@@ -2046,16 +2201,18 @@ HTMLBreadcrumbs.prototype = {
       idx = this.indexOf(selection);
       this.setCursor(idx);
     }
     // Add the first child of the very last node of the breadcrumbs if possible.
     this.ensureFirstChild();
 
     // Make sure the selected node and its neighbours are visible.
     this.scroll();
+
+    this.updateSelectors();
   },
 
 }
 
 /////////////////////////////////////////////////////////////////////////
 //// Initializers
 
 XPCOMUtils.defineLazyGetter(InspectorUI.prototype, "strings",
@@ -2065,8 +2222,11 @@ XPCOMUtils.defineLazyGetter(InspectorUI.
   });
 
 XPCOMUtils.defineLazyGetter(this, "StyleInspector", function () {
   var obj = {};
   Cu.import("resource:///modules/devtools/StyleInspector.jsm", obj);
   return obj.StyleInspector;
 });
 
+XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
+  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
+});
--- a/browser/devtools/highlighter/test/Makefile.in
+++ b/browser/devtools/highlighter/test/Makefile.in
@@ -65,16 +65,19 @@ include $(topsrcdir)/config/rules.mk
 		browser_inspector_keybindings.js \
 		browser_inspector_breadcrumbs.html \
 		browser_inspector_breadcrumbs.js \
 		browser_inspector_bug_699308_iframe_navigation.js \
 		browser_inspector_changes.js \
 		browser_inspector_ruleviewstore.js \
 		browser_inspector_duplicate_ruleview.js \
 		browser_inspector_invalidate.js \
+		browser_inspector_sidebarstate.js \
+		browser_inspector_treePanel_menu.js \
+		browser_inspector_pseudoclass_lock.js \
 		head.js \
 		$(NULL)
 
 # Disabled due to constant failures
 # 		browser_inspector_treePanel_click.js \
 
 libs::	$(_BROWSER_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
--- a/browser/devtools/highlighter/test/browser_inspector_editor.js
+++ b/browser/devtools/highlighter/test/browser_inspector_editor.js
@@ -29,17 +29,17 @@ function setupEditorTests()
   Services.obs.addObserver(setupHTMLPanel, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.toggleInspectorUI();
 }
 
 function setupHTMLPanel()
 {
   Services.obs.removeObserver(setupHTMLPanel, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
   Services.obs.addObserver(runEditorTests, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
-  InspectorUI.toolShow(InspectorUI.treePanel.registrationObject);
+  InspectorUI.toggleHTMLPanel();
 }
 
 function runEditorTests()
 {
   Services.obs.removeObserver(runEditorTests, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
   InspectorUI.stopInspecting();
   InspectorUI.inspectNode(doc.body, true);
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/highlighter/test/browser_inspector_pseudoclass_lock.js
@@ -0,0 +1,154 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
+
+let doc;
+let div;
+
+let pseudo = ":hover";
+
+function test()
+{
+  waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+    doc = content.document;
+    waitForFocus(createDocument, content);
+  }, true);
+
+  content.location = "data:text/html,pseudo-class lock tests";
+}
+
+function createDocument()
+{  
+  div = doc.createElement("div");
+  div.textContent = "test div";
+
+  let head = doc.getElementsByTagName('head')[0];
+  let style = doc.createElement('style');
+  let rules = doc.createTextNode('div { color: red; } div:hover { color: blue; }');
+
+  style.appendChild(rules);
+  head.appendChild(style);
+  doc.body.appendChild(div);
+  
+  setupTests();
+}
+
+function setupTests()
+{
+  Services.obs.addObserver(selectNode,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
+  InspectorUI.openInspectorUI();
+}
+
+function selectNode()
+{
+  Services.obs.removeObserver(selectNode,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
+
+  executeSoon(function() {
+    InspectorUI.highlighter.addListener("nodeselected", openRuleView);
+    InspectorUI.inspectNode(div);
+  });
+}
+
+function openRuleView()
+{
+  Services.obs.addObserver(performTests,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
+
+  InspectorUI.showSidebar();
+  InspectorUI.openRuleView();
+}
+
+function performTests()
+{
+  Services.obs.removeObserver(performTests,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY);
+
+  InspectorUI.highlighter.removeListener("nodeselected", performTests);
+
+  // toggle the class
+  InspectorUI.highlighter.pseudoClassLockToggled(pseudo);
+
+  testAdded();
+
+  // toggle the lock off
+  InspectorUI.highlighter.pseudoClassLockToggled(pseudo);
+
+  testRemoved();
+  testRemovedFromUI();
+
+  // toggle it back on
+  InspectorUI.highlighter.pseudoClassLockToggled(pseudo);  
+
+  // close the inspector
+  Services.obs.addObserver(testInspectorClosed,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
+  InspectorUI.closeInspectorUI();
+}
+
+function testAdded()
+{
+  // lock is applied to it and ancestors
+  let node = div;
+  do {
+    is(DOMUtils.hasPseudoClassLock(node, pseudo), true,
+       "pseudo-class lock has been applied");
+    node = node.parentNode;
+  } while (node.parentNode)
+
+  // infobar selector contains pseudo-class
+  let pseudoClassesBox = document.getElementById("highlighter-nodeinfobar-pseudo-classes");
+  is(pseudoClassesBox.textContent, pseudo, "pseudo-class in infobar selector");
+  
+  // ruleview contains pseudo-class rule
+  is(InspectorUI.ruleView.element.children.length, 3,
+     "rule view is showing 3 rules for pseudo-class locked div");
+     
+  is(InspectorUI.ruleView.element.children[1]._ruleEditor.rule.selectorText,
+     "div:hover", "rule view is showing " + pseudo + " rule");
+}
+
+function testRemoved()
+{
+  // lock removed from node and ancestors  
+  let node = div;
+  do {
+    is(DOMUtils.hasPseudoClassLock(node, pseudo), false,
+       "pseudo-class lock has been removed");
+    node = node.parentNode;
+  } while (node.parentNode)
+}
+
+function testRemovedFromUI()
+{
+  // infobar selector doesn't contain pseudo-class
+  let pseudoClassesBox = document.getElementById("highlighter-nodeinfobar-pseudo-classes");
+  is(pseudoClassesBox.textContent, "", "pseudo-class removed from infobar selector");    
+
+  // ruleview no longer contains pseudo-class rule
+  is(InspectorUI.ruleView.element.children.length, 2,
+     "rule view is showing 2 rules after removing lock");    
+}
+
+function testInspectorClosed()
+{
+  Services.obs.removeObserver(testInspectorClosed,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
+
+  testRemoved();
+
+  finishUp();  
+}
+
+function finishUp()
+{
+  doc = div = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
--- a/browser/devtools/highlighter/test/browser_inspector_registertools.js
+++ b/browser/devtools/highlighter/test/browser_inspector_registertools.js
@@ -143,55 +143,16 @@ function startToolTests(evt)
   InspectorUI.toolShow(tool1);
   InspectorUI.toolShow(tool3);
 
   info("Checking panel states 4");
   ok(tool1.isOpen, "Panel 1 is open");
   ok(!tool2.isOpen, "Panel 2 is closed");
   ok(tool3.isOpen, "Panel 3 is open");
 
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function() {
-    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
-    waitForFocus(testSecondTab, content);
-  }, true);
-
-  content.location = "data:text/html,registertool new tab test for inspector";
-}
-
-function testSecondTab()
-{
-  info("Opened second tab");
-  info("Checking panel states 5");
-
-  let tools = InspectorUI.tools;
-  ok(!(tool1 in tools), "Panel 1 not in tools");
-  ok(!(tool2 in tools), "Panel 2 not in tools");
-  ok(!(tool3 in tools), "Panel 3 not in tools");
-
-  info("Closing current tab");
-  Services.obs.addObserver(testOriginalTab, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
-  gBrowser.removeCurrentTab();
-}
-
-function testOriginalTab()
-{
-  Services.obs.removeObserver(testOriginalTab, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
-  info("Checking panel states 6");
-
-  info("Tools: " + InspectorUI.tools);
-  // reacquaint ourselves with our tools
-  tool1 = InspectorUI.tools["tool_1"];
-  tool2 = InspectorUI.tools["tool_2"];
-  tool3 = InspectorUI.tools["tool_3"];
-
-  ok(tool1.isOpen, "Panel 1 is open after reactivation");
-  ok(!tool2.isOpen, "Panel 2 is closed after reactivation");
-  ok(tool3.isOpen, "Panel 3 is open after reactivation");
-
   Services.obs.addObserver(unregisterTools, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
   InspectorUI.closeInspectorUI(true);
 }
 
 function unregisterTools()
 {
   Services.obs.removeObserver(unregisterTools, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
   let tools = InspectorUI.tools;
--- a/browser/devtools/highlighter/test/browser_inspector_ruleviewstore.js
+++ b/browser/devtools/highlighter/test/browser_inspector_ruleviewstore.js
@@ -123,18 +123,18 @@ function inspectorFocusTab1()
 }
 
 function ruleViewOpened2()
 {
   let prop = InspectorUI.ruleView._elementStyle.rules[0].textProps[0];
   is(prop.name, "background-color", "First prop is the background color prop.");
   ok(!prop.enabled, "First prop should be disabled.");
 
+  InspectorUI.closeInspectorUI();
   gBrowser.removeCurrentTab();
-  InspectorUI.closeInspectorUI();
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
 
   tab1 = gBrowser.addTab();
new file mode 100644
--- /dev/null
+++ b/browser/devtools/highlighter/test/browser_inspector_sidebarstate.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let doc;
+
+function createDocument()
+{
+  doc.body.innerHTML = '<h1>Sidebar state test</h1>';
+  doc.title = "Sidebar State Test";
+
+  // Open the sidebar and wait for the default view (the rule view) to show.
+  Services.obs.addObserver(inspectorRuleViewOpened,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
+
+  InspectorUI.openInspectorUI();
+  InspectorUI.showSidebar();
+}
+
+function inspectorRuleViewOpened()
+{
+  Services.obs.removeObserver(inspectorRuleViewOpened,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY);
+  is(InspectorUI.activeSidebarPanel, "ruleview", "Rule View is selected by default");
+
+  // Select the computed view and turn off the inspector.
+  InspectorUI.activateSidebarPanel("styleinspector");
+
+  Services.obs.addObserver(inspectorClosed,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
+  InspectorUI.closeInspectorUI();
+}
+
+function inspectorClosed()
+{
+  // Reopen the inspector, expect the computed view to be loaded.
+  Services.obs.removeObserver(inspectorClosed,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
+
+  Services.obs.addObserver(computedViewPopulated,
+    "StyleInspector-populated", false);
+
+  InspectorUI.openInspectorUI();
+}
+
+function computedViewPopulated()
+{
+  Services.obs.removeObserver(computedViewPopulated,
+    "StyleInspector-populated");
+  is(InspectorUI.activeSidebarPanel, "styleinspector", "Computed view is selected by default.");
+
+  finishTest();
+}
+
+
+function finishTest()
+{
+  InspectorUI.closeInspectorUI();
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+function test()
+{
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+    doc = content.document;
+    waitForFocus(createDocument, content);
+  }, true);
+
+  content.location = "data:text/html,basic tests for inspector";
+}
+
--- a/browser/devtools/highlighter/test/browser_inspector_tab_switch.js
+++ b/browser/devtools/highlighter/test/browser_inspector_tab_switch.js
@@ -91,16 +91,17 @@ function inspectorTabOpen2()
   ok(!InspectorUI.treePanel, "Inspector Tree Panel is closed");
   ok(!InspectorUI.isSidebarOpen, "Inspector Sidebar is not open");
   is(InspectorUI.store.length, 1, "Inspector.store.length = 1");
 
   // Activate the inspector again.
   executeSoon(function() {
     Services.obs.addObserver(inspectorUIOpen2,
       InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
+    clearUserPrefs();
     InspectorUI.openInspectorUI();
   });
 }
 
 function inspectorUIOpen2()
 {
   Services.obs.removeObserver(inspectorUIOpen2,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
@@ -131,34 +132,34 @@ function inspectorFocusTab1()
   ok(InspectorUI.inspecting, "Inspector is highlighting");
   ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
   is(InspectorUI.store.length, 2, "Inspector.store.length = 2");
   is(InspectorUI.selection, div, "selection matches the div element");
 
   Services.obs.addObserver(inspectorOpenTreePanelTab1,
     InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
 
-  InspectorUI.treePanel.open();
+  InspectorUI.toggleHTMLPanel();
 }
 
 function inspectorOpenTreePanelTab1()
 {
   Services.obs.removeObserver(inspectorOpenTreePanelTab1,
     InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
 
   ok(InspectorUI.inspecting, "Inspector is highlighting");
   ok(InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is open");
   is(InspectorUI.store.length, 2, "Inspector.store.length = 2");
   is(InspectorUI.selection, div, "selection matches the div element");
 
   Services.obs.addObserver(inspectorSidebarStyleView1, "StyleInspector-opened", false);
 
   executeSoon(function() {
     InspectorUI.showSidebar();
-    InspectorUI.toolShow(InspectorUI.stylePanel.registrationObject);
+    InspectorUI.activateSidebarPanel("styleinspector");
   });
 }
 
 function inspectorSidebarStyleView1()
 {
   Services.obs.removeObserver(inspectorSidebarStyleView1, "StyleInspector-opened");
   ok(InspectorUI.isSidebarOpen, "Inspector Sidebar is open");
   ok(InspectorUI.stylePanel, "Inspector Has a Style Panel Instance");
new file mode 100644
--- /dev/null
+++ b/browser/devtools/highlighter/test/browser_inspector_treePanel_menu.js
@@ -0,0 +1,97 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+function test() {
+
+  waitForExplicitFinish();
+
+  let doc;
+  let node1;
+  let div;
+
+  function createDocument() {
+    div = doc.createElement("div");
+    let h1 = doc.createElement("h1");
+    let p1 = doc.createElement("p");
+    let p2 = doc.createElement("p");
+    doc.title = "Inspector Tree Menu Test";
+    h1.textContent = "Inspector Tree Menu Test";
+    p1.textContent = "This is some example text";
+    div.appendChild(h1);
+    div.appendChild(p1);
+    doc.body.appendChild(div);
+    node1 = p1;
+    setupTest();
+  }
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onload() {
+    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+    doc = content.document;
+    waitForFocus(createDocument, content);
+  }, true);
+
+  content.location = content.location = "data:text/html,basic tests for inspector";;
+
+  function setupTest() {
+    Services.obs.addObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.toggleInspectorUI();
+  }
+
+  function runTests() {
+    Services.obs.removeObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
+    Services.obs.addObserver(testCopyInnerMenu, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
+    InspectorUI.stopInspecting();
+    InspectorUI.inspectNode(node1, true);
+    InspectorUI.treePanel.open();
+  }
+
+  function testCopyInnerMenu() {
+    let copyInner = document.getElementById("inspectorHTMLCopyInner");
+    ok(copyInner, "the popup menu has a copy inner html menu item");
+
+    waitForClipboard("This is some example text",
+                     function() { copyInner.doCommand(); },
+                     testCopyOuterMenu, testCopyOuterMenu);
+  }
+
+  function testCopyOuterMenu() {
+    let copyOuter = document.getElementById("inspectorHTMLCopyOuter");
+    ok(copyOuter, "the popup menu has a copy outer html menu item");
+
+    waitForClipboard("<p>This is some example text</p>",
+                     function() { copyOuter.doCommand(); },
+                     testDeleteNode, testDeleteNode);
+  }
+
+  function testDeleteNode() {
+    let deleteNode = document.getElementById("inspectorHTMLDelete");
+    ok(deleteNode, "the popup menu has a delete menu item");
+
+    InspectorUI.highlighter.addListener("nodeselected", deleteTest);
+
+    let commandEvent = document.createEvent("XULCommandEvent");
+    commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
+                                  false, false, null);
+    deleteNode.dispatchEvent(commandEvent);
+  }
+
+  function deleteTest() {
+    InspectorUI.highlighter.removeListener("nodeSelected", deleteTest);
+    Services.obs.addObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
+    is(InspectorUI.selection, div, "parent node selected");
+    let p = doc.querySelector("P");
+    is(p, null, "node deleted");
+    executeSoon(function() {
+      InspectorUI.closeInspectorUI();
+    });
+  }
+
+  function finishUp() {
+    Services.obs.removeObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
+    doc = node1 = div = null;
+    gBrowser.removeCurrentTab();
+    finish();
+  }
+}
--- a/browser/devtools/highlighter/test/head.js
+++ b/browser/devtools/highlighter/test/head.js
@@ -36,16 +36,26 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 const Cu = Components.utils;
 let tempScope = {};
 Cu.import("resource:///modules/devtools/LayoutHelpers.jsm", tempScope);
 let LayoutHelpers = tempScope.LayoutHelpers;
 
+// Clear preferences that may be set during the course of tests.
+function clearUserPrefs()
+{
+  Services.prefs.clearUserPref("devtools.inspector.htmlPanelOpen");
+  Services.prefs.clearUserPref("devtools.inspector.sidebarOpen");
+  Services.prefs.clearUserPref("devtools.inspector.activeSidebar");
+}
+
+registerCleanupFunction(clearUserPrefs);
+
 function isHighlighting()
 {
   let veil = InspectorUI.highlighter.veilTransparentBox;
   return !(veil.style.visibility == "hidden");
 }
 
 function getHighlitNode()
 {
@@ -73,8 +83,9 @@ function getHighlitNode()
 
 function midPoint(aPointA, aPointB)
 {
   let pointC = { };
   pointC.x = (aPointB.x - aPointA.x) / 2 + aPointA.x;
   pointC.y = (aPointB.y - aPointA.y) / 2 + aPointA.y;
   return pointC;
 }
+
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -436,28 +436,29 @@ var Scratchpad = {
     if (!error) {
       this.writeAsComment(result);
     } else {
       this.writeAsErrorComment(error);
     }
   },
 
   /**
-   * Write out a value at the current insertion point as a block comment
+   * Write out a value at the next line from the current insertion point.
+   * The comment block will always be preceded by a newline character.
    * @param object aValue
    *        The Object to write out as a string
    */
   writeAsComment: function SP_writeAsComment(aValue)
   {
     let selection = this.getSelectionRange();
     let insertionPoint = selection.start != selection.end ?
                          selection.end : // after selected text
                          this.editor.getCharCount(); // after text end
                          
-    let newComment = "/*\n" + aValue + "\n*/";
+    let newComment = "\n/*\n" + aValue + "\n*/";
     
     this.setText(newComment, insertionPoint, insertionPoint);
 
     // Select the new comment.
     this.selectRange(insertionPoint, insertionPoint + newComment.length);
   },
 
   /**
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug690552_display_outputs_errors.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug690552_display_outputs_errors.js
@@ -16,17 +16,17 @@ function test()
       "comments for 'display' and not sent to the console in Scratchpad";
 }
 
 function runTests()
 {
   var scratchpad = gScratchpadWindow.Scratchpad;
 
   var message = "\"Hello World!\""
-  var openComment = "/*\n";
+  var openComment = "\n/*\n";
   var closeComment = "\n*/";
   var error = "throw new Error(\"Ouch!\")";
   let messageArray = {};
   let count = {};
 
   scratchpad.setText(message);
   scratchpad.display();
   is(scratchpad.getText(),
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_679467_falsy.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_679467_falsy.js
@@ -25,30 +25,30 @@ function testFalsy()
 
   finish();
 }
 
 function verifyFalsies(sp)
 {
   sp.setText("undefined");
   sp.display();
-  is(sp.selectedText, "/*\nundefined\n*/", "'undefined' is displayed");
+  is(sp.selectedText, "\n/*\nundefined\n*/", "'undefined' is displayed");
 
   sp.setText("false");
   sp.display();
-  is(sp.selectedText, "/*\nfalse\n*/", "'false' is displayed");
+  is(sp.selectedText, "\n/*\nfalse\n*/", "'false' is displayed");
 
   sp.setText("0");
   sp.display();
-  is(sp.selectedText, "/*\n0\n*/", "'0' is displayed");
+  is(sp.selectedText, "\n/*\n0\n*/", "'0' is displayed");
 
   sp.setText("null");
   sp.display();
-  is(sp.selectedText, "/*\nnull\n*/", "'null' is displayed");
+  is(sp.selectedText, "\n/*\nnull\n*/", "'null' is displayed");
 
   sp.setText("NaN");
   sp.display();
-  is(sp.selectedText, "/*\nNaN\n*/", "'NaN' is displayed");
+  is(sp.selectedText, "\n/*\nNaN\n*/", "'NaN' is displayed");
 
   sp.setText("''");
   sp.display();
-  is(sp.selectedText, "/*\n\n*/", "empty string is displayed");
+  is(sp.selectedText, "\n/*\n\n*/", "empty string is displayed");
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js
@@ -35,23 +35,23 @@ function runTests()
   is(content.wrappedJSObject.foobarBug636725, 2,
      "run() updated window.foobarBug636725");
 
   sp.display();
 
   is(content.wrappedJSObject.foobarBug636725, 3,
      "display() updated window.foobarBug636725");
 
-  is(sp.getText(), "++window.foobarBug636725/*\n3\n*/",
+  is(sp.getText(), "++window.foobarBug636725\n/*\n3\n*/",
      "display() shows evaluation result in the textbox");
 
-  is(sp.selectedText, "/*\n3\n*/", "selectedText is correct");
+  is(sp.selectedText, "\n/*\n3\n*/", "selectedText is correct");
   let selection = sp.getSelectionRange();
   is(selection.start, 24, "selection.start is correct");
-  is(selection.end, 31, "selection.end is correct");
+  is(selection.end, 32, "selection.end is correct");
 
   // Test selection run() and display().
 
   sp.setText("window.foobarBug636725 = 'a';\n" +
              "window.foobarBug636725 = 'b';");
 
   sp.selectRange(1, 2);
 
@@ -89,26 +89,26 @@ function runTests()
   sp.selectRange(0, 22);
 
   sp.display();
 
   is(content.wrappedJSObject.foobarBug636725, "a",
      "display() worked for the selected range");
 
   is(sp.getText(), "window.foobarBug636725" +
-                   "/*\na\n*/" +
+                   "\n/*\na\n*/" +
                    " = 'c';\n" +
                    "window.foobarBug636725 = 'b';",
      "display() shows evaluation result in the textbox");
 
-  is(sp.selectedText, "/*\na\n*/", "selectedText is correct");
+  is(sp.selectedText, "\n/*\na\n*/", "selectedText is correct");
 
   selection = sp.getSelectionRange();
   is(selection.start, 22, "selection.start is correct");
-  is(selection.end, 29, "selection.end is correct");
+  is(selection.end, 30, "selection.end is correct");
 
   sp.deselect();
 
   ok(!sp.selectedText, "selectedText is empty");
 
   selection = sp.getSelectionRange();
   is(selection.start, selection.end, "deselect() works");
 
--- a/browser/devtools/styleinspector/CssRuleView.jsm
+++ b/browser/devtools/styleinspector/CssRuleView.jsm
@@ -111,17 +111,17 @@ function ElementStyle(aElement, aStore)
 
   let doc = aElement.ownerDocument;
 
   // To figure out how shorthand properties are interpreted by the
   // engine, we will set properties on a dummy element and observe
   // how their .style attribute reflects them as computed values.
   this.dummyElement = doc.createElementNS(this.element.namespaceURI,
                                           this.element.tagName);
-  this._populate();
+  this.populate();
 }
 // We're exporting _ElementStyle for unit tests.
 var _ElementStyle = ElementStyle;
 
 ElementStyle.prototype = {
 
   // The element we're looking at.
   element: null,
@@ -142,17 +142,17 @@ ElementStyle.prototype = {
       this.onChanged();
     }
   },
 
   /**
    * Refresh the list of rules to be displayed for the active element.
    * Upon completion, this.rules[] will hold a list of Rule objects.
    */
-  _populate: function ElementStyle_populate()
+  populate: function ElementStyle_populate()
   {
     this.rules = [];
 
     let element = this.element;
     do {
       this._addElementRules(element);
     } while ((element = element.parentNode) &&
              element.nodeType === Ci.nsIDOMNode.ELEMENT_NODE);
@@ -708,25 +708,43 @@ CssRuleView.prototype = {
 
     this._elementStyle = new ElementStyle(aElement, this.store);
     this._elementStyle.onChanged = function() {
       this._changed();
     }.bind(this);
 
     this._createEditors();
   },
+  
+  /**
+   * Update the rules for the currently highlighted element.
+   */
+  nodeChanged: function CssRuleView_nodeChanged()
+  {
+    this._clearRules();
+    this._elementStyle.populate();
+    this._createEditors();
+  },  
+
+  /**
+   * Clear the rules.
+   */
+  _clearRules: function CssRuleView_clearRules()
+  {
+    while (this.element.hasChildNodes()) {
+      this.element.removeChild(this.element.lastChild);
+    }
+  },
 
   /**
    * Clear the rule view.
    */
   clear: function CssRuleView_clear()
   {
-    while (this.element.hasChildNodes()) {
-      this.element.removeChild(this.element.lastChild);
-    }
+    this._clearRules();
     this._viewedElement = null;
     this._elementStyle = null;
   },
 
   /**
    * Called when the user has made changes to the ElementStyle.
    * Emits an event that clients can listen to.
    */
--- a/browser/devtools/styleinspector/StyleInspector.jsm
+++ b/browser/devtools/styleinspector/StyleInspector.jsm
@@ -115,16 +115,21 @@ StyleInspector.prototype = {
           this.iframe.getAttribute("src") ==
           "chrome://browser/content/devtools/csshtmltree.xul") {
         let selectedNode = this.selectedNode || null;
         this.cssHtmlTree = new CssHtmlTree(this);
         this.cssLogic.highlight(selectedNode);
         this.cssHtmlTree.highlight(selectedNode);
         this.iframe.removeEventListener("load", boundIframeOnLoad, true);
         this.iframeReady = true;
+
+        // Now that we've loaded, select any node we were previously asked
+        // to show.
+        this.selectNode(this.selectedNode);
+
         Services.obs.notifyObservers(null, "StyleInspector-opened", null);
       }
     }.bind(this);
 
     this.iframe = this.IUI.getToolIframe(this.registrationObject);
 
     this.iframe.addEventListener("load", boundIframeOnLoad, true);
   },
@@ -210,21 +215,26 @@ StyleInspector.prototype = {
   },
 
   /**
    * Check if the style inspector is open.
    * @returns boolean
    */
   isOpen: function SI_isOpen()
   {
-    return this.openDocked ? this.iframeReady && this.IUI.isSidebarOpen &&
+    return this.openDocked ? this.IUI.isSidebarOpen &&
             (this.IUI.sidebarDeck.selectedPanel == this.iframe) :
            this.panel && this.panel.state && this.panel.state == "open";
   },
 
+  isLoaded: function SI_isLoaded()
+  {
+    return this.openDocked ? this.iframeReady : this.iframeReady && this.panelReady;
+  },
+
   /**
    * Select from Path (via CssHtmlTree_pathClick)
    * @param aNode The node to inspect.
    */
   selectFromPath: function SI_selectFromPath(aNode)
   {
     if (this.IUI && this.IUI.selection) {
       if (aNode != this.IUI.selection) {
@@ -237,28 +247,28 @@ StyleInspector.prototype = {
 
   /**
    * Select a node to inspect in the Style Inspector panel
    * @param aNode The node to inspect.
    */
   selectNode: function SI_selectNode(aNode)
   {
     this.selectedNode = aNode;
-    if (this.isOpen() && !this.dimmed) {
+    if (this.isLoaded() && !this.dimmed) {
       this.cssLogic.highlight(aNode);
       this.cssHtmlTree.highlight(aNode);
     }
   },
 
   /**
    * Update the display for the currently-selected node.
    */
   updateNode: function SI_updateNode()
   {
-    if (this.isOpen() && !this.dimmed) {
+    if (this.isLoaded() && !this.dimmed) {
       this.cssLogic.highlight(this.selectedNode);
       this.cssHtmlTree.refreshPanel();
     }
   },
 
   /**
    * Dim or undim a panel by setting or removing a dimmed attribute.
    * @param aState
--- a/browser/devtools/tilt/Tilt.jsm
+++ b/browser/devtools/tilt/Tilt.jsm
@@ -122,76 +122,81 @@ Tilt.prototype = {
       return;
     }
 
     // create a visualizer instance for the current tab
     this.visualizers[id] = new TiltVisualizer({
       chromeWindow: this.chromeWindow,
       contentWindow: this.chromeWindow.gBrowser.selectedBrowser.contentWindow,
       parentNode: this.chromeWindow.gBrowser.selectedBrowser.parentNode,
-      requestAnimationFrame: this.chromeWindow.mozRequestAnimationFrame,
       notifications: this.NOTIFICATIONS
     });
 
     // make sure the visualizer object was initialized properly
     if (!this.visualizers[id].isInitialized()) {
       this.destroy(id);
       return;
     }
 
     Services.obs.notifyObservers(null, TILT_NOTIFICATIONS.INITIALIZING, null);
   },
 
   /**
-   * Destroys a specific instance of the visualizer.
+   * Starts destroying a specific instance of the visualizer.
    *
    * @param {String} aId
    *                 the identifier of the instance in the visualizers array
    * @param {Boolean} aAnimateFlag
    *                  optional, set to true to display a destruction transition
    */
   destroy: function T_destroy(aId, aAnimateFlag)
   {
-    // if the visualizer is already destroyed, don't do anything
-    if (!this.visualizers[aId]) {
+    // if the visualizer is destroyed or destroying, don't do anything
+    if (!this.visualizers[aId] || this._isDestroying) {
+      return;
+    }
+    this._isDestroying = true;
+
+    let controller = this.visualizers[aId].controller;
+    let presenter = this.visualizers[aId].presenter;
+
+    let content = presenter.contentWindow;
+    let pageXOffset = content.pageXOffset * presenter.transforms.zoom;
+    let pageYOffset = content.pageYOffset * presenter.transforms.zoom;
+    TiltUtils.setDocumentZoom(this.chromeWindow, presenter.transforms.zoom);
+
+    // if we're not doing any outro animation, just finish destruction directly
+    if (!aAnimateFlag) {
+      this._finish(aId);
       return;
     }
 
-    if (!this.isDestroying) {
-      this.isDestroying = true;
+    // otherwise, trigger the outro animation and notify necessary observers
+    Services.obs.notifyObservers(null, TILT_NOTIFICATIONS.DESTROYING, null);
 
-      let finalize = function T_finalize(aId) {
-        this.visualizers[aId].removeOverlay();
-        this.visualizers[aId].cleanup();
-        this.visualizers[aId] = null;
-
-        this.isDestroying = false;
-        this.chromeWindow.gBrowser.selectedBrowser.focus();
-        Services.obs.notifyObservers(null, TILT_NOTIFICATIONS.DESTROYED, null);
-      };
+    controller.removeEventListeners();
+    controller.arcball.reset([-pageXOffset, -pageYOffset]);
+    presenter.executeDestruction(this._finish.bind(this, aId));
+  },
 
-      if (!aAnimateFlag) {
-        finalize.call(this, aId);
-        return;
-      }
-
-      let controller = this.visualizers[aId].controller;
-      let presenter = this.visualizers[aId].presenter;
+  /**
+   * Finishes detroying a specific instance of the visualizer.
+   *
+   * @param {String} aId
+   *                 the identifier of the instance in the visualizers array
+   */
+  _finish: function T__finish(aId)
+  {
+    this.visualizers[aId].removeOverlay();
+    this.visualizers[aId].cleanup();
+    this.visualizers[aId] = null;
 
-      let content = presenter.contentWindow;
-      let pageXOffset = content.pageXOffset * presenter.transforms.zoom;
-      let pageYOffset = content.pageYOffset * presenter.transforms.zoom;
-
-      Services.obs.notifyObservers(null, TILT_NOTIFICATIONS.DESTROYING, null);
-      TiltUtils.setDocumentZoom(this.chromeWindow, presenter.transforms.zoom);
-
-      controller.removeEventListeners();
-      controller.arcball.reset([-pageXOffset, -pageYOffset]);
-      presenter.executeDestruction(finalize.bind(this, aId));
-    }
+    this._isDestroying = false;
+    this.chromeWindow.gBrowser.selectedBrowser.focus();
+    Services.obs.notifyObservers(null, TILT_NOTIFICATIONS.DESTROYED, null);
   },
 
   /**
    * Handles any supplementary post-initialization work, done immediately
    * after a TILT_NOTIFICATIONS.INITIALIZING notification.
    */
   _whenInitializing: function T__whenInitializing()
   {
@@ -281,26 +286,28 @@ Tilt.prototype = {
       this.chromeWindow.InspectorUI.INSPECTOR_NOTIFICATIONS.DESTROYED, false);
 
     this.chromeWindow.gBrowser.tabContainer.addEventListener("TabSelect",
       this._onTabSelect.bind(this), false);
 
 
     // FIXME: this shouldn't be done here, see bug #705131
     let onOpened = function() {
-      if (this.currentInstance) {
-        this.chromeWindow.InspectorUI.stopInspecting();
-        this.inspectButton.disabled = true;
-        this.highlighterContainer.style.display = "none";
+      if (this.inspector && this.highlighter && this.currentInstance) {
+        this.inspector.stopInspecting();
+        this.inspector.inspectToolbutton.disabled = true;
+        this.highlighter.hide();
       }
     }.bind(this);
 
     let onClosed = function() {
-      this.inspectButton.disabled = false;
-      this.highlighterContainer.style.display = "";
+      if (this.inspector && this.highlighter) {
+        this.inspector.inspectToolbutton.disabled = false;
+        this.highlighter.show();
+      }
     }.bind(this);
 
     Services.obs.addObserver(onOpened,
       this.chromeWindow.InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
     Services.obs.addObserver(onClosed,
       this.chromeWindow.InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
     Services.obs.addObserver(onOpened,
       TILT_NOTIFICATIONS.INITIALIZING, false);
@@ -333,36 +340,31 @@ Tilt.prototype = {
    * Gets the visualizer instance for the current tab.
    */
   get currentInstance()
   {
     return this.visualizers[this.currentWindowId];
   },
 
   /**
+   * Gets the current InspectorUI instance.
+   */
+  get inspector()
+  {
+    return this.chromeWindow.InspectorUI;
+  },
+
+  /**
+   * Gets the current Highlighter instance from the InspectorUI.
+   */
+  get highlighter()
+  {
+    return this.inspector.highlighter;
+  },
+
+  /**
    * Gets the Tilt button in the Inspector toolbar.
    */
   get tiltButton()
   {
-    return this.chromeWindow.document.getElementById(
-      "inspector-3D-button");
-  },
-
-  /**
-   * Gets the Inspect button in the Inspector toolbar.
-   * FIXME: this shouldn't be needed here, remove after bug #705131
-   */
-  get inspectButton()
-  {
-    return this.chromeWindow.document.getElementById(
-      "inspector-inspect-toolbutton");
-  },
-
-  /**
-   * Gets the Highlighter contaniner stack.
-   * FIXME: this shouldn't be needed here, remove after bug #705131
-   */
-  get highlighterContainer()
-  {
-    return this.chromeWindow.document.getElementById(
-      "highlighter-container");
+    return this.chromeWindow.document.getElementById("inspector-3D-button");
   }
 };
--- a/browser/devtools/tilt/TiltGL.jsm
+++ b/browser/devtools/tilt/TiltGL.jsm
@@ -87,16 +87,18 @@ TiltGL.Renderer = function TGL_Renderer(
   this.context.clearColor(0, 0, 0, 0);
   this.context.clearDepth(1);
 
   /**
    * Variables representing the current framebuffer width and height.
    */
   this.width = aCanvas.width;
   this.height = aCanvas.height;
+  this.initialWidth = this.width;
+  this.initialHeight = this.height;
 
   /**
    * The current model view matrix.
    */
   this.mvMatrix = mat4.identity(mat4.create());
 
   /**
    * The current projection matrix.
@@ -859,32 +861,40 @@ TiltGL.Program.prototype = {
     let utils = TiltGL.ProgramUtils;
 
     // check if the program wasn't already active
     if (utils._activeProgram !== id) {
       utils._activeProgram = id;
 
       // use the the program if it wasn't already set
       this._context.useProgram(this._ref);
-
-      // check if the required vertex attributes aren't already set
-      if (utils._enabledAttributes < this._attributes.length) {
-        utils._enabledAttributes = this._attributes.length;
+      this.cleanupVertexAttrib();
 
-        // enable any necessary vertex attributes using the cache
-        for (let i in this._attributes) {
-          if (this._attributes.hasOwnProperty(i)) {
-            this._context.enableVertexAttribArray(this._attributes[i]);
-          }
-        }
+      // enable any necessary vertex attributes using the cache
+      for each (let attribute in this._attributes) {
+        this._context.enableVertexAttribArray(attribute);
+        utils._enabledAttributes.push(attribute);
       }
     }
   },
 
   /**
+   * Disables all currently enabled vertex attribute arrays.
+   */
+  cleanupVertexAttrib: function TGLP_cleanupVertexAttrib()
+  {
+    let utils = TiltGL.ProgramUtils;
+
+    for each (let attribute in utils._enabledAttributes) {
+      this._context.disableVertexAttribArray(attribute);
+    }
+    utils._enabledAttributes = [];
+  },
+
+  /**
    * Binds a vertex buffer as an array buffer for a specific shader attribute.
    *
    * @param {String} aAtribute
    *                 the attribute name obtained from the shader
    * @param {Float32Array} aBuffer
    *                       the buffer to be bound
    */
   bindVertexBuffer: function TGLP_bindVertexBuffer(aAtribute, aBuffer)
@@ -944,19 +954,19 @@ TiltGL.Program.prototype = {
    *                 the sampler name to bind the texture to
    * @param {TiltGL.Texture} aTexture
    *                       the texture to be bound
    */
   bindTexture: function TGLP_bindTexture(aSampler, aTexture)
   {
     let gl = this._context;
 
-    gl.uniform1i(this._uniforms[aSampler], 0);
     gl.activeTexture(gl.TEXTURE0);
     gl.bindTexture(gl.TEXTURE_2D, aTexture._ref);
+    gl.uniform1i(this._uniforms[aSampler], 0);
   },
 
   /**
    * Function called when this object is destroyed.
    */
   finalize: function TGLP_finalize()
   {
     if (this._context) {
@@ -1172,17 +1182,17 @@ TiltGL.ProgramUtils = {
   /**
    * Represents the current active shader, identified by an id.
    */
   _activeProgram: -1,
 
   /**
    * Represents the current enabled attributes.
    */
-  _enabledAttributes: -1
+  _enabledAttributes: []
 };
 
 /**
  * This constructor creates a texture from an Image.
  *
  * @param {Object} aContext
  *                 a WebGL context
  * @param {Object} aProperties
@@ -1410,17 +1420,17 @@ TiltGL.TextureUtils = {
     // generate mipmap if necessary
     if (aProperties.mipmap) {
       gl.generateMipmap(gl.TEXTURE_2D);
     }
   },
 
   /**
    * This shim renders a content window to a canvas element, but clamps the
-   * maximum width and height of the canvas to half the WebGL MAX_TEXTURE_SIZE.
+   * maximum width and height of the canvas to the WebGL MAX_TEXTURE_SIZE.
    *
    * @param {Window} aContentWindow
    *                 the content window to get a texture from
    * @param {Number} aMaxImageSize
    *                 the maximum image size to be used
    *
    * @return {Image} the new content window image
    */
@@ -1610,10 +1620,10 @@ TiltGL.create3DContext = function TGL_cr
 };
 
 /**
  * Clears the cache and sets all the variables to default.
  */
 TiltGL.clearCache = function TGL_clearCache()
 {
   TiltGL.ProgramUtils._activeProgram = -1;
-  TiltGL.ProgramUtils._enabledAttributes = -1;
+  TiltGL.ProgramUtils._enabledAttributes = [];
 };
--- a/browser/devtools/tilt/TiltUtils.jsm
+++ b/browser/devtools/tilt/TiltUtils.jsm
@@ -513,18 +513,18 @@ TiltUtils.bindObjectFunc = function TU_b
  */
 TiltUtils.destroyObject = function TU_destroyObject(aScope)
 {
   if (!aScope) {
     return;
   }
 
   // objects in Tilt usually use a function to handle internal destruction
-  if ("function" === typeof aScope.finalize) {
-    aScope.finalize();
+  if ("function" === typeof aScope._finalize) {
+    aScope._finalize();
   }
   for (let i in aScope) {
     if (aScope.hasOwnProperty(i)) {
       delete aScope[i];
     }
   }
 };
 
--- a/browser/devtools/tilt/TiltVisualizer.jsm
+++ b/browser/devtools/tilt/TiltVisualizer.jsm
@@ -50,33 +50,40 @@ const INVISIBLE_ELEMENTS = {
   "link": true,
   "meta": true,
   "option": true,
   "script": true,
   "style": true,
   "title": true
 };
 
+// a node is represented in the visualization mesh as a rectangular stack
+// of 5 quads composed of 12 vertices; we draw these as triangles using an
+// index buffer of 12 unsigned int elements, obviously one for each vertex;
+// if a webpage has enough nodes to overflow the index buffer elements size,
+// weird things may happen; thus, when necessary, we'll split into groups
+const MAX_GROUP_NODES = Math.pow(2, Uint16Array.BYTES_PER_ELEMENT * 8) / 12 - 1;
+
 const STACK_THICKNESS = 15;
 const WIREFRAME_COLOR = [0, 0, 0, 0.25];
-const INTRO_TRANSITION_DURATION = 50;
-const OUTRO_TRANSITION_DURATION = 40;
+const INTRO_TRANSITION_DURATION = 1000;
+const OUTRO_TRANSITION_DURATION = 800;
 const INITIAL_Z_TRANSLATION = 400;
 const MOVE_INTO_VIEW_ACCURACY = 50;
 
 const MOUSE_CLICK_THRESHOLD = 10;
-const MOUSE_INTRO_DELAY = 10;
+const MOUSE_INTRO_DELAY = 200;
 const ARCBALL_SENSITIVITY = 0.5;
 const ARCBALL_ROTATION_STEP = 0.15;
 const ARCBALL_TRANSLATION_STEP = 35;
 const ARCBALL_ZOOM_STEP = 0.1;
 const ARCBALL_ZOOM_MIN = -3000;
 const ARCBALL_ZOOM_MAX = 500;
-const ARCBALL_RESET_FACTOR = 0.9;
-const ARCBALL_RESET_INTERVAL = 1000 / 60;
+const ARCBALL_RESET_SPHERICAL_FACTOR = 0.1;
+const ARCBALL_RESET_LINEAR_FACTOR = 0.01;
 
 const TILT_CRAFTER = "resource:///modules/devtools/TiltWorkerCrafter.js";
 const TILT_PICKER = "resource:///modules/devtools/TiltWorkerPicker.js";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource:///modules/devtools/TiltGL.jsm");
 Cu.import("resource:///modules/devtools/TiltMath.jsm");
 Cu.import("resource:///modules/devtools/TiltUtils.jsm");
@@ -87,17 +94,16 @@ let EXPORTED_SYMBOLS = ["TiltVisualizer"
 /**
  * Initializes the visualization presenter and controller.
  *
  * @param {Object} aProperties
  *                 an object containing the following properties:
  *        {Window} chromeWindow: a reference to the top level window
  *        {Window} contentWindow: the content window holding the visualized doc
  *       {Element} parentNode: the parent node to hold the visualization
- *      {Function} requestAnimationFrame: responsible with scheduling loops
  *        {Object} notifications: necessary notifications for Tilt
  *      {Function} onError: optional, function called if initialization failed
  *      {Function} onLoad: optional, function called if initialization worked
  */
 function TiltVisualizer(aProperties)
 {
   // make sure the properties parameter is a valid object
   aProperties = aProperties || {};
@@ -116,17 +122,16 @@ function TiltVisualizer(aProperties)
   });
 
   /**
    * Visualization logic and drawing loop.
    */
   this.presenter = new TiltVisualizer.Presenter(this.canvas,
     aProperties.chromeWindow,
     aProperties.contentWindow,
-    aProperties.requestAnimationFrame,
     aProperties.notifications,
     aProperties.onError || null,
     aProperties.onLoad || null);
 
   /**
    * Visualization mouse and keyboard controller.
    */
   this.controller = new TiltVisualizer.Controller(this.canvas, this.presenter);
@@ -179,28 +184,25 @@ TiltVisualizer.prototype = {
  * This object manages the visualization logic and drawing loop.
  *
  * @param {HTMLCanvasElement} aCanvas
  *                            the canvas element used for rendering
  * @param {Window} aChromeWindow
  *                 a reference to the top-level window
  * @param {Window} aContentWindow
  *                 the content window holding the document to be visualized
- * @param {Function} aRequestAnimationFrame
- *                   function responsible with scheduling loop frames
  * @param {Object} aNotifications
  *                 necessary notifications for Tilt
  * @param {Function} onError
  *                   function called if initialization failed
  * @param {Function} onLoad
  *                   function called if initialization worked
  */
 TiltVisualizer.Presenter = function TV_Presenter(
-  aCanvas, aChromeWindow, aContentWindow, aRequestAnimationFrame, aNotifications,
-  onError, onLoad)
+  aCanvas, aChromeWindow, aContentWindow, aNotifications, onError, onLoad)
 {
   /**
    * A canvas overlay used for drawing the visualization.
    */
   this.canvas = aCanvas;
 
   /**
    * Save a reference to the top-level window, to access InspectorUI or Tilt.
@@ -215,35 +217,36 @@ TiltVisualizer.Presenter = function TV_P
   /**
    * Shortcut for accessing notifications strings.
    */
   this.NOTIFICATIONS = aNotifications;
 
   /**
    * Create the renderer, containing useful functions for easy drawing.
    */
-  this.renderer = new TiltGL.Renderer(aCanvas, onError, onLoad);
+  this._renderer = new TiltGL.Renderer(aCanvas, onError, onLoad);
 
   /**
    * A custom shader used for drawing the visualization mesh.
    */
-  this.visualizationProgram = null;
+  this._visualizationProgram = null;
 
   /**
    * The combined mesh representing the document visualization.
    */
-  this.texture = null;
-  this.meshStacks = null;
-  this.meshWireframe = null;
-  this.traverseData = null;
+  this._texture = null;
+  this._meshData = null;
+  this._meshStacks = null;
+  this._meshWireframe = null;
+  this._traverseData = null;
 
   /**
    * A highlight quad drawn over a stacked dom node.
    */
-  this.highlight = {
+  this._highlight = {
     disabled: true,
     v0: vec3.create(),
     v1: vec3.create(),
     v2: vec3.create(),
     v3: vec3.create()
   };
 
   /**
@@ -263,396 +266,435 @@ TiltVisualizer.Presenter = function TV_P
   this._currentSelection = -1; // the selected node index
   this._initialSelection = false; // true if an initial selection was made
   this._initialMeshConfiguration = false; // true if the 3D mesh was configured
 
   /**
    * Variable specifying if the scene should be redrawn.
    * This should happen usually when the visualization is translated/rotated.
    */
-  this.redraw = true;
+  this._redraw = true;
+
+  /**
+   * Total time passed since the rendering started.
+   * If the rendering is paused, this property won't get updated.
+   */
+  this._time = 0;
 
   /**
-   * A frame counter, incremented each time the scene is redrawn.
+   * Frame delta time (the ammount of time passed for each frame).
+   * This is used to smoothly interpolate animation transfroms.
    */
-  this.frames = 0;
+  this._delta = 0;
+  this._prevFrameTime = 0;
+  this._currFrameTime = 0;
+
+
+  this._setup();
+  this._loop();
+};
+
+TiltVisualizer.Presenter.prototype = {
 
   /**
    * The initialization logic.
    */
-  let setup = function TVP_setup()
+  _setup: function TVP__setup()
   {
-    let renderer = this.renderer;
+    let renderer = this._renderer;
     let inspector = this.chromeWindow.InspectorUI;
 
     // if the renderer was destroyed, don't continue setup
     if (!renderer || !renderer.context) {
       return;
     }
 
     // create the visualization shaders and program to draw the stacks mesh
-    this.visualizationProgram = new renderer.Program({
+    this._visualizationProgram = new renderer.Program({
       vs: TiltVisualizer.MeshShader.vs,
       fs: TiltVisualizer.MeshShader.fs,
       attributes: ["vertexPosition", "vertexTexCoord", "vertexColor"],
       uniforms: ["mvMatrix", "projMatrix", "sampler"]
     });
 
     // get the document zoom to properly scale the visualization
     if (inspector.highlighter) {
       this.transforms.zoom = inspector.highlighter.zoom;
     }
 
-    this.setupTexture();
-    this.setupMeshData();
-    this.setupEventListeners();
+    // bind the owner object to the necessary functions
+    TiltUtils.bindObjectFunc(this, "^_on");
+    TiltUtils.bindObjectFunc(this, "_loop");
+
+    this._setupTexture();
+    this._setupMeshData();
+    this._setupEventListeners();
     this.canvas.focus();
-  }.bind(this);
+  },
 
   /**
    * The animation logic.
    */
-  let loop = function TVP_loop()
+  _loop: function TVP__loop()
   {
-    let renderer = this.renderer;
+    let renderer = this._renderer;
 
     // if the renderer was destroyed, don't continue rendering
     if (!renderer || !renderer.context) {
       return;
     }
 
     // prepare for the next frame of the animation loop
-    aRequestAnimationFrame(loop);
+    this.chromeWindow.mozRequestAnimationFrame(this._loop);
 
     // only redraw if we really have to
-    if (this.redraw) {
-      this.redraw = false;
-      this.drawVisualization();
+    if (this._redraw) {
+      this._redraw = false;
+      this._drawVisualization();
+    }
+
+    // update the current presenter transfroms from the controller
+    if ("function" === typeof this._controllerUpdate) {
+      this._controllerUpdate(this._time, this._delta);
     }
 
-    // call the attached ondraw function and handle all keyframe notifications
-    if ("function" === typeof this.ondraw) {
-      this.ondraw(this.frames);
-    }
+    this._handleFrameDelta();
+    this._handleKeyframeNotifications();
+  },
 
-    this.handleKeyframeNotifications();
-  }.bind(this);
-
-  setup();
-  loop();
-};
-
-TiltVisualizer.Presenter.prototype = {
+  /**
+   * Calculates the current frame delta time.
+   */
+  _handleFrameDelta: function TVP__handleFrameDelta()
+  {
+    this._prevFrameTime = this._currFrameTime;
+    this._currFrameTime = this.chromeWindow.mozAnimationStartTime;
+    this._delta = this._currFrameTime - this._prevFrameTime;
+  },
 
   /**
    * Draws the visualization mesh and highlight quad.
    */
-  drawVisualization: function TVP_drawVisualization()
+  _drawVisualization: function TVP__drawVisualization()
   {
-    let renderer = this.renderer;
+    let renderer = this._renderer;
     let transforms = this.transforms;
     let w = renderer.width;
     let h = renderer.height;
+    let ih = renderer.initialHeight;
 
     // if the mesh wasn't created yet, don't continue rendering
-    if (!this.meshStacks || !this.meshWireframe) {
+    if (!this._meshStacks || !this._meshWireframe) {
       return;
     }
 
     // clear the context to an opaque black background
     renderer.clear();
     renderer.perspective();
 
     // apply a transition transformation using an ortho and perspective matrix
     let ortho = mat4.ortho(0, w, h, 0, -1000, 1000);
 
-    if (!this.isExecutingDestruction) {
-      let f = this.frames / INTRO_TRANSITION_DURATION;
+    if (!this._isExecutingDestruction) {
+      let f = this._time / INTRO_TRANSITION_DURATION;
       renderer.lerp(renderer.projMatrix, ortho, f, 8);
     } else {
-      let f = this.frames / OUTRO_TRANSITION_DURATION;
+      let f = this._time / OUTRO_TRANSITION_DURATION;
       renderer.lerp(renderer.projMatrix, ortho, 1 - f, 8);
     }
 
     // apply the preliminary transformations to the model view
-    renderer.translate(w * 0.5, h * 0.5, -INITIAL_Z_TRANSLATION);
+    renderer.translate(w * 0.5, ih * 0.5, -INITIAL_Z_TRANSLATION);
 
     // calculate the camera matrix using the rotation and translation
     renderer.translate(transforms.translation[0], 0,
                        transforms.translation[2]);
 
     renderer.transform(quat4.toMat4(transforms.rotation));
 
     // offset the visualization mesh to center
     renderer.translate(transforms.offset[0],
                        transforms.offset[1] + transforms.translation[1], 0);
 
     renderer.scale(transforms.zoom, transforms.zoom);
 
     // draw the visualization mesh
     renderer.strokeWeight(2);
     renderer.depthTest(true);
-    this.drawMeshStacks();
-    this.drawMeshWireframe();
-    this.drawHighlight();
+    this._drawMeshStacks();
+    this._drawMeshWireframe();
+    this._drawHighlight();
 
     // make sure the initial transition is drawn until finished
-    if (this.frames < INTRO_TRANSITION_DURATION ||
-        this.frames < OUTRO_TRANSITION_DURATION) {
-      this.redraw = true;
+    if (this._time < INTRO_TRANSITION_DURATION ||
+        this._time < OUTRO_TRANSITION_DURATION) {
+      this._redraw = true;
     }
-    this.frames++;
+    this._time += this._delta;
   },
 
   /**
    * Draws the meshStacks object.
    */
-  drawMeshStacks: function TVP_drawMeshStacks()
+  _drawMeshStacks: function TVP__drawMeshStacks()
   {
-    let renderer = this.renderer;
-    let mesh = this.meshStacks;
+    let renderer = this._renderer;
+    let mesh = this._meshStacks;
 
-    let visualizationProgram = this.visualizationProgram;
-    let texture = this.texture;
+    let visualizationProgram = this._visualizationProgram;
+    let texture = this._texture;
     let mvMatrix = renderer.mvMatrix;
     let projMatrix = renderer.projMatrix;
 
     // use the necessary shader
     visualizationProgram.use();
 
-    // bind the attributes and uniforms as necessary
-    visualizationProgram.bindVertexBuffer("vertexPosition", mesh.vertices);
-    visualizationProgram.bindVertexBuffer("vertexTexCoord", mesh.texCoord);
-    visualizationProgram.bindVertexBuffer("vertexColor", mesh.color);
+    for (let i = 0, len = mesh.length; i < len; i++) {
+      let group = mesh[i];
+
+      // bind the attributes and uniforms as necessary
+      visualizationProgram.bindVertexBuffer("vertexPosition", group.vertices);
+      visualizationProgram.bindVertexBuffer("vertexTexCoord", group.texCoord);
+      visualizationProgram.bindVertexBuffer("vertexColor", group.color);
 
-    visualizationProgram.bindUniformMatrix("mvMatrix", mvMatrix);
-    visualizationProgram.bindUniformMatrix("projMatrix", projMatrix);
-    visualizationProgram.bindTexture("sampler", texture);
+      visualizationProgram.bindUniformMatrix("mvMatrix", mvMatrix);
+      visualizationProgram.bindUniformMatrix("projMatrix", projMatrix);
+      visualizationProgram.bindTexture("sampler", texture);
 
-    // draw the vertices as TRIANGLES indexed elements
-    renderer.drawIndexedVertices(renderer.context.TRIANGLES, mesh.indices);
+      // draw the vertices as TRIANGLES indexed elements
+      renderer.drawIndexedVertices(renderer.context.TRIANGLES, group.indices);
+    }
 
     // save the current model view and projection matrices
     mesh.mvMatrix = mat4.create(mvMatrix);
     mesh.projMatrix = mat4.create(projMatrix);
   },
 
   /**
    * Draws the meshWireframe object.
    */
-  drawMeshWireframe: function TVP_drawMeshWireframe()
+  _drawMeshWireframe: function TVP__drawMeshWireframe()
   {
-    let renderer = this.renderer;
-    let mesh = this.meshWireframe;
+    let renderer = this._renderer;
+    let mesh = this._meshWireframe;
 
-    // use the necessary shader
-    renderer.useColorShader(mesh.vertices, WIREFRAME_COLOR);
+    for (let i = 0, len = mesh.length; i < len; i++) {
+      let group = mesh[i];
 
-    // draw the vertices as LINES indexed elements
-    renderer.drawIndexedVertices(renderer.context.LINES, mesh.indices);
+      // use the necessary shader
+      renderer.useColorShader(group.vertices, WIREFRAME_COLOR);
+
+      // draw the vertices as LINES indexed elements
+      renderer.drawIndexedVertices(renderer.context.LINES, group.indices);
+    }
   },
 
   /**
    * Draws a highlighted quad around a currently selected node.
    */
-  drawHighlight: function TVP_drawHighlight()
+  _drawHighlight: function TVP__drawHighlight()
   {
     // check if there's anything to highlight (i.e any node is selected)
-    if (!this.highlight.disabled) {
+    if (!this._highlight.disabled) {
 
       // set the corresponding state to draw the highlight quad
-      let renderer = this.renderer;
-      let highlight = this.highlight;
+      let renderer = this._renderer;
+      let highlight = this._highlight;
 
       renderer.depthTest(false);
       renderer.fill(highlight.fill, 0.5);
       renderer.stroke(highlight.stroke);
       renderer.strokeWeight(highlight.strokeWeight);
       renderer.quad(highlight.v0, highlight.v1, highlight.v2, highlight.v3);
     }
   },
 
   /**
    * Creates or refreshes the texture applied to the visualization mesh.
    */
-  setupTexture: function TVP_setupTexture()
+  _setupTexture: function TVP__setupTexture()
   {
-    let renderer = this.renderer;
+    let renderer = this._renderer;
 
     // destroy any previously created texture
-    TiltUtils.destroyObject(this.texture);
+    TiltUtils.destroyObject(this._texture); this._texture = null;
 
     // if the renderer was destroyed, don't continue setup
     if (!renderer || !renderer.context) {
       return;
     }
 
     // get the maximum texture size
-    this.maxTextureSize =
+    this._maxTextureSize =
       renderer.context.getParameter(renderer.context.MAX_TEXTURE_SIZE);
 
     // use a simple shim to get the image representation of the document
     // this will be removed once the MOZ_window_region_texture bug #653656
     // is finished; currently just converting the document image to a texture
     // applied to the mesh
-    this.texture = new renderer.Texture({
+    this._texture = new renderer.Texture({
       source: TiltGL.TextureUtils.createContentImage(this.contentWindow,
-                                                     this.maxTextureSize),
+                                                     this._maxTextureSize),
       format: "RGB"
     });
 
-    if ("function" === typeof this.onSetupTexture) {
-      this.onSetupTexture();
-      this.onSetupTexture = null;
+    if ("function" === typeof this._onSetupTexture) {
+      this._onSetupTexture();
+      this._onSetupTexture = null;
     }
   },
 
   /**
    * Create the combined mesh representing the document visualization by
    * traversing the document & adding a stack for each node that is drawable.
    *
-   * @param {Object} aData
+   * @param {Object} aMeshData
    *                 object containing the necessary mesh verts, texcoord etc.
    */
-  setupMesh: function TVP_setupMesh(aData)
+  _setupMesh: function TVP__setupMesh(aMeshData)
   {
-    let renderer = this.renderer;
+    let renderer = this._renderer;
 
     // destroy any previously created mesh
-    TiltUtils.destroyObject(this.meshStacks);
-    TiltUtils.destroyObject(this.meshWireframe);
+    TiltUtils.destroyObject(this._meshStacks); this._meshStacks = [];
+    TiltUtils.destroyObject(this._meshWireframe); this._meshWireframe = [];
 
     // if the renderer was destroyed, don't continue setup
     if (!renderer || !renderer.context) {
       return;
     }
 
     // save the mesh data for future use
-    this.meshData = aData;
+    this._meshData = aMeshData;
+
+    // create a sub-mesh for each group in the mesh data
+    for (let i = 0, len = aMeshData.groups.length; i < len; i++) {
+      let group = aMeshData.groups[i];
 
-    // create the visualization mesh using the vertices, texture coordinates
-    // and indices computed when traversing the document object model
-    this.meshStacks = {
-      vertices: new renderer.VertexBuffer(aData.vertices, 3),
-      texCoord: new renderer.VertexBuffer(aData.texCoord, 2),
-      color: new renderer.VertexBuffer(aData.color, 3),
-      indices: new renderer.IndexBuffer(aData.stacksIndices)
-    };
+      // create the visualization mesh using the vertices, texture coordinates
+      // and indices computed when traversing the document object model
+      this._meshStacks.push({
+        vertices: new renderer.VertexBuffer(group.vertices, 3),
+        texCoord: new renderer.VertexBuffer(group.texCoord, 2),
+        color: new renderer.VertexBuffer(group.color, 3),
+        indices: new renderer.IndexBuffer(group.stacksIndices)
+      });
 
-    // additionally, create a wireframe representation to make the
-    // visualization a bit more pretty
-    this.meshWireframe = {
-      vertices: this.meshStacks.vertices,
-      indices: new renderer.IndexBuffer(aData.wireframeIndices)
-    };
+      // additionally, create a wireframe representation to make the
+      // visualization a bit more pretty
+      this._meshWireframe.push({
+        vertices: this._meshStacks[i].vertices,
+        indices: new renderer.IndexBuffer(group.wireframeIndices)
+      });
+    }
 
     // if there's no initial selection made, highlight the required node
     if (!this._initialSelection) {
       this._initialSelection = true;
       this.highlightNode(this.chromeWindow.InspectorUI.selection);
+
+      if (this._currentSelection === 0) { // if the "html" node is selected
+        this._highlight.disabled = true;
+      }
     }
 
+    // configure the required mesh transformations and background only once
     if (!this._initialMeshConfiguration) {
       this._initialMeshConfiguration = true;
 
-      let width = renderer.width;
-      let height = renderer.height;
-
       // set the necessary mesh offsets
-      this.transforms.offset[0] = -width * 0.5;
-      this.transforms.offset[1] = -height * 0.5;
+      this.transforms.offset[0] = -renderer.width * 0.5;
+      this.transforms.offset[1] = -renderer.height * 0.5;
 
       // make sure the canvas is opaque now that the initialization is finished
       this.canvas.style.background = TiltVisualizerStyle.canvas.background;
 
-      this.drawVisualization();
-      this.redraw = true;
+      this._drawVisualization();
+      this._redraw = true;
     }
 
-    if ("function" === typeof this.onSetupMesh) {
-      this.onSetupMesh();
-      this.onSetupMesh = null;
+    if ("function" === typeof this._onSetupMesh) {
+      this._onSetupMesh();
+      this._onSetupMesh = null;
     }
   },
 
   /**
-   * Computes the mesh vertices, texture coordinates etc.
+   * Computes the mesh vertices, texture coordinates etc. by groups of nodes.
    */
-  setupMeshData: function TVP_setupMeshData()
+  _setupMeshData: function TVP__setupMeshData()
   {
-    let renderer = this.renderer;
+    let renderer = this._renderer;
 
     // if the renderer was destroyed, don't continue setup
     if (!renderer || !renderer.context) {
       return;
     }
 
     // traverse the document and get the depths, coordinates and local names
-    this.traverseData = TiltUtils.DOM.traverse(this.contentWindow, {
+    this._traverseData = TiltUtils.DOM.traverse(this.contentWindow, {
       invisibleElements: INVISIBLE_ELEMENTS,
       minSize: ELEMENT_MIN_SIZE,
-      maxX: this.texture.width,
-      maxY: this.texture.height
+      maxX: this._texture.width,
+      maxY: this._texture.height
     });
 
     let worker = new ChromeWorker(TILT_CRAFTER);
 
     worker.addEventListener("message", function TVP_onMessage(event) {
-      this.setupMesh(event.data);
+      this._setupMesh(event.data);
     }.bind(this), false);
 
     // calculate necessary information regarding vertices, texture coordinates
     // etc. in a separate thread, as this process may take a while
     worker.postMessage({
+      maxGroupNodes: MAX_GROUP_NODES,
       thickness: STACK_THICKNESS,
       style: TiltVisualizerStyle.nodes,
-      texWidth: this.texture.width,
-      texHeight: this.texture.height,
-      nodesInfo: this.traverseData.info
+      texWidth: this._texture.width,
+      texHeight: this._texture.height,
+      nodesInfo: this._traverseData.info
     });
   },
 
   /**
    * Sets up event listeners necessary for the presenter.
    */
-  setupEventListeners: function TVP_setupEventListeners()
+  _setupEventListeners: function TVP__setupEventListeners()
   {
-    // bind the owner object to the necessary functions
-    TiltUtils.bindObjectFunc(this, "^on");
-
-    this.contentWindow.addEventListener("resize", this.onResize, false);
+    this.contentWindow.addEventListener("resize", this._onResize, false);
   },
 
   /**
    * Called when the content window of the current browser is resized.
    */
-  onResize: function TVP_onResize(e)
+  _onResize: function TVP_onResize(e)
   {
     let zoom = this.chromeWindow.InspectorUI.highlighter.zoom;
     let width = e.target.innerWidth * zoom;
     let height = e.target.innerHeight * zoom;
 
     // handle aspect ratio changes to update the projection matrix
-    this.renderer.width = width;
-    this.renderer.height = height;
+    this._renderer.width = width;
+    this._renderer.height = height;
 
-    this.redraw = true;
+    this._redraw = true;
   },
 
   /**
    * Highlights a specific node.
    *
    * @param {Element} aNode
    *                  the html node to be highlighted
    * @param {String} aFlags
    *                 flags specifying highlighting options
    */
   highlightNode: function TVP_highlightNode(aNode, aFlags)
   {
-    this.highlightNodeFor(this.traverseData.nodes.indexOf(aNode), aFlags);
+    this.highlightNodeFor(this._traverseData.nodes.indexOf(aNode), aFlags);
   },
 
   /**
    * Picks a stacked dom node at the x and y screen coordinates and highlights
    * the selected node in the mesh.
    *
    * @param {Number} x
    *                 the current horizontal coordinate of the mouse
@@ -700,41 +742,41 @@ TiltVisualizer.Presenter.prototype = {
     });
   },
 
   /**
    * Sets the corresponding highlight coordinates and color based on the
    * information supplied.
    *
    * @param {Number} aNodeIndex
-   *                 the index of the node in the this.traverseData array
+   *                 the index of the node in the this._traverseData array
    * @param {String} aFlags
    *                 flags specifying highlighting options
    */
   highlightNodeFor: function TVP_highlightNodeFor(aNodeIndex, aFlags)
   {
-    this.redraw = true;
+    this._redraw = true;
 
     // if the node was already selected, don't do anything
     if (this._currentSelection === aNodeIndex) {
       return;
     }
 
     // if an invalid or nonexisted node is specified, disable the highlight
     if (aNodeIndex < 0) {
       this._currentSelection = -1;
-      this.highlight.disabled = true;
+      this._highlight.disabled = true;
 
       Services.obs.notifyObservers(null, this.NOTIFICATIONS.UNHIGHLIGHTING, null);
       return;
     }
 
-    let highlight = this.highlight;
-    let info = this.traverseData.info[aNodeIndex];
-    let node = this.traverseData.nodes[aNodeIndex];
+    let highlight = this._highlight;
+    let info = this._traverseData.info[aNodeIndex];
+    let node = this._traverseData.nodes[aNodeIndex];
     let style = TiltVisualizerStyle.nodes;
 
     highlight.disabled = false;
     highlight.fill = style[info.name] || style.highlight.defaultFill;
     highlight.stroke = style.highlight.defaultStroke;
     highlight.strokeWeight = style.highlight.defaultStrokeWeight;
 
     let x = info.coord.left;
@@ -756,50 +798,53 @@ TiltVisualizer.Presenter.prototype = {
 
     // if something is highlighted, make sure it's inside the current viewport;
     // the point which should be moved into view is considered the center [x, y]
     // position along the top edge of the currently selected node
 
     if (aFlags && aFlags.indexOf("moveIntoView") !== -1)
     {
       this.controller.arcball.moveIntoView(vec3.lerp(
-        vec3.scale(this.highlight.v0, this.transforms.zoom, []),
-        vec3.scale(this.highlight.v1, this.transforms.zoom, []), 0.5));
+        vec3.scale(this._highlight.v0, this.transforms.zoom, []),
+        vec3.scale(this._highlight.v1, this.transforms.zoom, []), 0.5));
     }
 
     Services.obs.notifyObservers(null, this.NOTIFICATIONS.HIGHLIGHTING, null);
   },
 
   /**
    * Deletes a node from the visualization mesh.
    *
    * @param {Number} aNodeIndex
-   *                 the index of the node in the this.traverseData array;
+   *                 the index of the node in the this._traverseData array;
    *                 if not specified, it will default to the current selection
    */
   deleteNode: function TVP_deleteNode(aNodeIndex)
   {
     // we probably don't want to delete the html or body node.. just sayin'
     if ((aNodeIndex = aNodeIndex || this._currentSelection) < 1) {
       return;
     }
 
-    let renderer = this.renderer;
-    let meshData = this.meshData;
+    let renderer = this._renderer;
 
-    for (let i = 0, k = 36 * aNodeIndex; i < 36; i++) {
-      meshData.vertices[i + k] = 0;
+    let groupIndex = parseInt(aNodeIndex / MAX_GROUP_NODES);
+    let nodeIndex = parseInt((aNodeIndex + (groupIndex ? 1 : 0)) % MAX_GROUP_NODES);
+    let group = this._meshStacks[groupIndex];
+    let vertices = group.vertices.components;
+
+    for (let i = 0, k = 36 * nodeIndex; i < 36; i++) {
+      vertices[i + k] = 0;
     }
 
-    this.meshStacks.vertices = new renderer.VertexBuffer(meshData.vertices, 3);
-    this.highlight.disabled = true;
-    this.redraw = true;
+    group.vertices = new renderer.VertexBuffer(vertices, 3);
+    this._highlight.disabled = true;
+    this._redraw = true;
 
-    Services.obs.notifyObservers(null,
-      this.NOTIFICATIONS.NODE_REMOVED, null);
+    Services.obs.notifyObservers(null, this.NOTIFICATIONS.NODE_REMOVED, null);
   },
 
   /**
    * Picks a stacked dom node at the x and y screen coordinates and issues
    * a callback function with the found intersection.
    *
    * @param {Number} x
    *                 the current horizontal coordinate of the mouse
@@ -811,17 +856,17 @@ TiltVisualizer.Presenter.prototype = {
    *      {Function} onfail: function to be called if no intersections
    */
   pickNode: function TVP_pickNode(x, y, aProperties)
   {
     // make sure the properties parameter is a valid object
     aProperties = aProperties || {};
 
     // if the mesh wasn't created yet, don't continue picking
-    if (!this.meshStacks || !this.meshWireframe) {
+    if (!this._meshStacks || !this._meshWireframe) {
       return;
     }
 
     let worker = new ChromeWorker(TILT_PICKER);
 
     worker.addEventListener("message", function TVP_onMessage(event) {
       if (event.data) {
         if ("function" === typeof aProperties.onpick) {
@@ -830,33 +875,32 @@ TiltVisualizer.Presenter.prototype = {
       } else {
         if ("function" === typeof aProperties.onfail) {
           aProperties.onfail();
         }
       }
     }, false);
 
     let zoom = this.chromeWindow.InspectorUI.highlighter.zoom;
-    let width = this.renderer.width * zoom;
-    let height = this.renderer.height * zoom;
-    let mesh = this.meshStacks;
+    let width = this._renderer.width * zoom;
+    let height = this._renderer.height * zoom;
     x *= zoom;
     y *= zoom;
 
     // create a ray following the mouse direction from the near clipping plane
     // to the far clipping plane, to check for intersections with the mesh,
     // and do all the heavy lifting in a separate thread
     worker.postMessage({
       thickness: STACK_THICKNESS,
-      vertices: mesh.vertices.components,
+      vertices: this._meshData.allVertices,
 
       // create the ray destined for 3D picking
       ray: vec3.createRay([x, y, 0], [x, y, 1], [0, 0, width, height],
-        mesh.mvMatrix,
-        mesh.projMatrix)
+        this._meshStacks.mvMatrix,
+        this._meshStacks.projMatrix)
     });
   },
 
   /**
    * Delegate translation method, used by the controller.
    *
    * @param {Array} aTranslation
    *                the new translation on the [x, y, z] axis
@@ -869,17 +913,17 @@ TiltVisualizer.Presenter.prototype = {
     let transforms = this.transforms;
 
     // only update the translation if it's not already set
     if (transforms.translation[0] !== x ||
         transforms.translation[1] !== y ||
         transforms.translation[2] !== z) {
 
       vec3.set(aTranslation, transforms.translation);
-      this.redraw = true;
+      this._redraw = true;
     }
   },
 
   /**
    * Delegate rotation method, used by the controller.
    *
    * @param {Array} aQuaternion
    *                the rotation quaternion, as [x, y, z, w]
@@ -894,113 +938,118 @@ TiltVisualizer.Presenter.prototype = {
 
     // only update the rotation if it's not already set
     if (transforms.rotation[0] !== x ||
         transforms.rotation[1] !== y ||
         transforms.rotation[2] !== z ||
         transforms.rotation[3] !== w) {
 
       quat4.set(aQuaternion, transforms.rotation);
-      this.redraw = true;
+      this._redraw = true;
     }
   },
 
   /**
    * Handles notifications at specific frame counts.
    */
-  handleKeyframeNotifications: function TV_handleKeyframeNotifications()
+  _handleKeyframeNotifications: function TV__handleKeyframeNotifications()
   {
-    if (!TiltVisualizer.Prefs.introTransition && !this.isExecutingDestruction) {
-      this.frames = INTRO_TRANSITION_DURATION;
+    if (!TiltVisualizer.Prefs.introTransition && !this._isExecutingDestruction) {
+      this._time = INTRO_TRANSITION_DURATION;
     }
-    if (!TiltVisualizer.Prefs.outroTransition && this.isExecutingDestruction) {
-      this.frames = OUTRO_TRANSITION_DURATION;
+    if (!TiltVisualizer.Prefs.outroTransition && this._isExecutingDestruction) {
+      this._time = OUTRO_TRANSITION_DURATION;
     }
 
-    if (this.frames === INTRO_TRANSITION_DURATION &&
-       !this.isExecutingDestruction) {
+    if (this._time >= INTRO_TRANSITION_DURATION &&
+       !this._isInitializationFinished &&
+       !this._isExecutingDestruction) {
 
+      this._isInitializationFinished = true;
       Services.obs.notifyObservers(null, this.NOTIFICATIONS.INITIALIZED, null);
 
-      if ("function" === typeof this.onInitializationFinished) {
-        this.onInitializationFinished();
+      if ("function" === typeof this._onInitializationFinished) {
+        this._onInitializationFinished();
       }
     }
 
-    if (this.frames === OUTRO_TRANSITION_DURATION &&
-        this.isExecutingDestruction) {
+    if (this._time >= OUTRO_TRANSITION_DURATION &&
+       !this._isDestructionFinished &&
+        this._isExecutingDestruction) {
 
+      this._isDestructionFinished = true;
       Services.obs.notifyObservers(null, this.NOTIFICATIONS.BEFORE_DESTROYED, null);
 
-      if ("function" === typeof this.onDestructionFinished) {
-        this.onDestructionFinished();
+      if ("function" === typeof this._onDestructionFinished) {
+        this._onDestructionFinished();
       }
     }
   },
 
   /**
    * Starts executing the destruction sequence and issues a callback function
    * when finished.
    *
    * @param {Function} aCallback
    *                   the destruction finished callback
    */
   executeDestruction: function TV_executeDestruction(aCallback)
   {
-    if (!this.isExecutingDestruction) {
-      this.isExecutingDestruction = true;
-      this.onDestructionFinished = aCallback;
+    if (!this._isExecutingDestruction) {
+      this._isExecutingDestruction = true;
+      this._onDestructionFinished = aCallback;
 
       // if we execute the destruction after the initialization finishes,
       // proceed normally; otherwise, skip everything and immediately issue
       // the callback
 
-      if (this.frames > OUTRO_TRANSITION_DURATION) {
-        this.frames = 0;
-        this.redraw = true;
+      if (this._time > OUTRO_TRANSITION_DURATION) {
+        this._time = 0;
+        this._redraw = true;
       } else {
         aCallback();
       }
     }
   },
 
   /**
    * Checks if this object was initialized properly.
    *
    * @return {Boolean} true if the object was initialized properly
    */
   isInitialized: function TVP_isInitialized()
   {
-    return this.renderer && this.renderer.context;
+    return this._renderer && this._renderer.context;
   },
 
   /**
    * Function called when this object is destroyed.
    */
-  finalize: function TVP_finalize()
+  _finalize: function TVP__finalize()
   {
-    TiltUtils.destroyObject(this.visualizationProgram);
-    TiltUtils.destroyObject(this.texture);
+    TiltUtils.destroyObject(this._visualizationProgram);
+    TiltUtils.destroyObject(this._texture);
 
-    if (this.meshStacks) {
-      TiltUtils.destroyObject(this.meshStacks.vertices);
-      TiltUtils.destroyObject(this.meshStacks.texCoord);
-      TiltUtils.destroyObject(this.meshStacks.color);
-      TiltUtils.destroyObject(this.meshStacks.indices);
+    if (this._meshStacks) {
+      this._meshStacks.forEach(function(group) {
+        TiltUtils.destroyObject(group.vertices);
+        TiltUtils.destroyObject(group.texCoord);
+        TiltUtils.destroyObject(group.color);
+        TiltUtils.destroyObject(group.indices);
+      });
+    }
+    if (this._meshWireframe) {
+      this._meshWireframe.forEach(function(group) {
+        TiltUtils.destroyObject(group.indices);
+      });
     }
 
-    if (this.meshWireframe) {
-      TiltUtils.destroyObject(this.meshWireframe.indices);
-    }
+    TiltUtils.destroyObject(this._renderer);
 
-    TiltUtils.destroyObject(this.highlight);
-    TiltUtils.destroyObject(this.transforms);
-    TiltUtils.destroyObject(this.renderer);
-
-    this.contentWindow.removeEventListener("resize", this.onResize, false);
+    this.contentWindow.removeEventListener("resize", this._onResize, false);
   }
 };
 
 /**
  * A mouse and keyboard controller implementation.
  *
  * @param {HTMLCanvasElement} aCanvas
  *                            the visualization canvas element
@@ -1018,141 +1067,143 @@ TiltVisualizer.Controller = function TV_
    * Save a reference to the presenter to modify its model-view transforms.
    */
   this.presenter = aPresenter;
   this.presenter.controller = this;
 
   /**
    * The initial controller dimensions and offset, in pixels.
    */
-  this.zoom = aPresenter.transforms.zoom;
-  this.left = (aPresenter.contentWindow.pageXOffset || 0) * this.zoom;
-  this.top = (aPresenter.contentWindow.pageYOffset || 0) * this.zoom;
-  this.width = aCanvas.width;
-  this.height = aCanvas.height;
+  this._zoom = aPresenter.transforms.zoom;
+  this._left = (aPresenter.contentWindow.pageXOffset || 0) * this._zoom;
+  this._top = (aPresenter.contentWindow.pageYOffset || 0) * this._zoom;
+  this._width = aCanvas.width;
+  this._height = aCanvas.height;
 
   /**
    * Arcball used to control the visualization using the mouse.
    */
   this.arcball = new TiltVisualizer.Arcball(
-    this.presenter.chromeWindow, this.width, this.height, 0,
+    this.presenter.chromeWindow, this._width, this._height, 0,
     [
-      this.width + this.left < aPresenter.maxTextureSize ? -this.left : 0,
-      this.height + this.top < aPresenter.maxTextureSize ? -this.top : 0
+      this._width + this._left < aPresenter._maxTextureSize ? -this._left : 0,
+      this._height + this._top < aPresenter._maxTextureSize ? -this._top : 0
     ]);
 
   /**
    * Object containing the rotation quaternion and the translation amount.
    */
-  this.coordinates = null;
+  this._coordinates = null;
 
   // bind the owner object to the necessary functions
-  TiltUtils.bindObjectFunc(this, "update");
-  TiltUtils.bindObjectFunc(this, "^on");
+  TiltUtils.bindObjectFunc(this, "_update");
+  TiltUtils.bindObjectFunc(this, "^_on");
 
   // add the necessary event listeners
   this.addEventListeners();
 
   // attach this controller's update function to the presenter ondraw event
-  aPresenter.ondraw = this.update;
+  this.presenter._controllerUpdate = this._update;
 };
 
 TiltVisualizer.Controller.prototype = {
 
   /**
    * Adds events listeners required by this controller.
    */
   addEventListeners: function TVC_addEventListeners()
   {
     let canvas = this.canvas;
     let presenter = this.presenter;
 
     // bind commonly used mouse and keyboard events with the controller
-    canvas.addEventListener("mousedown", this.onMouseDown, false);
-    canvas.addEventListener("mouseup", this.onMouseUp, false);
-    canvas.addEventListener("mousemove", this.onMouseMove, false);
-    canvas.addEventListener("mouseover", this.onMouseOver, false);
-    canvas.addEventListener("mouseout", this.onMouseOut, false);
-    canvas.addEventListener("MozMousePixelScroll", this.onMozScroll, false);
-    canvas.addEventListener("keydown", this.onKeyDown, false);
-    canvas.addEventListener("keyup", this.onKeyUp, false);
-    canvas.addEventListener("keypress", this.onKeyPress, true);
-    canvas.addEventListener("blur", this.onBlur, false);
+    canvas.addEventListener("mousedown", this._onMouseDown, false);
+    canvas.addEventListener("mouseup", this._onMouseUp, false);
+    canvas.addEventListener("mousemove", this._onMouseMove, false);
+    canvas.addEventListener("mouseover", this._onMouseOver, false);
+    canvas.addEventListener("mouseout", this._onMouseOut, false);
+    canvas.addEventListener("MozMousePixelScroll", this._onMozScroll, false);
+    canvas.addEventListener("keydown", this._onKeyDown, false);
+    canvas.addEventListener("keyup", this._onKeyUp, false);
+    canvas.addEventListener("keypress", this._onKeyPress, true);
+    canvas.addEventListener("blur", this._onBlur, false);
 
     // handle resize events to change the arcball dimensions
-    presenter.contentWindow.addEventListener("resize", this.onResize, false);
+    presenter.contentWindow.addEventListener("resize", this._onResize, false);
   },
 
   /**
    * Removes all added events listeners required by this controller.
    */
   removeEventListeners: function TVC_removeEventListeners()
   {
     let canvas = this.canvas;
     let presenter = this.presenter;
 
-    canvas.removeEventListener("mousedown", this.onMouseDown, false);
-    canvas.removeEventListener("mouseup", this.onMouseUp, false);
-    canvas.removeEventListener("mousemove", this.onMouseMove, false);
-    canvas.removeEventListener("mouseover", this.onMouseOver, false);
-    canvas.removeEventListener("mouseout", this.onMouseOut, false);
-    canvas.removeEventListener("MozMousePixelScroll", this.onMozScroll, false);
-    canvas.removeEventListener("keydown", this.onKeyDown, false);
-    canvas.removeEventListener("keyup", this.onKeyUp, false);
-    canvas.removeEventListener("keypress", this.onKeyPress, true);
-    canvas.removeEventListener("blur", this.onBlur, false);
+    canvas.removeEventListener("mousedown", this._onMouseDown, false);
+    canvas.removeEventListener("mouseup", this._onMouseUp, false);
+    canvas.removeEventListener("mousemove", this._onMouseMove, false);
+    canvas.removeEventListener("mouseover", this._onMouseOver, false);
+    canvas.removeEventListener("mouseout", this._onMouseOut, false);
+    canvas.removeEventListener("MozMousePixelScroll", this._onMozScroll, false);
+    canvas.removeEventListener("keydown", this._onKeyDown, false);
+    canvas.removeEventListener("keyup", this._onKeyUp, false);
+    canvas.removeEventListener("keypress", this._onKeyPress, true);
+    canvas.removeEventListener("blur", this._onBlur, false);
 
-    presenter.contentWindow.removeEventListener("resize", this.onResize,false);
+    presenter.contentWindow.removeEventListener("resize", this._onResize, false);
   },
 
   /**
    * Function called each frame, updating the visualization camera transforms.
    *
-   * @param {Number} aFrames
-   *                 the current animation frame count
+   * @param {Number} aTime
+   *                 total time passed since rendering started
+   * @param {Number} aDelta
+   *                 the current animation frame delta
    */
-  update: function TVC_update(aFrames)
+  _update: function TVC__update(aTime, aDelta)
   {
-    this.frames = aFrames;
-    this.coordinates = this.arcball.update();
+    this._time = aTime;
+    this._coordinates = this.arcball.update(aDelta);
 
-    this.presenter.setRotation(this.coordinates.rotation);
-    this.presenter.setTranslation(this.coordinates.translation);
+    this.presenter.setRotation(this._coordinates.rotation);
+    this.presenter.setTranslation(this._coordinates.translation);
   },
 
   /**
    * Called once after every time a mouse button is pressed.
    */
-  onMouseDown: function TVC_onMouseDown(e)
+  _onMouseDown: function TVC__onMouseDown(e)
   {
     e.target.focus();
     e.preventDefault();
     e.stopPropagation();
 
-    if (this.frames < MOUSE_INTRO_DELAY) {
+    if (this._time < MOUSE_INTRO_DELAY) {
       return;
     }
 
     // calculate x and y coordinates using using the client and target offset
     let button = e.which;
     this._downX = e.clientX - e.target.offsetLeft;
     this._downY = e.clientY - e.target.offsetTop;
 
     this.arcball.mouseDown(this._downX, this._downY, button);
   },
 
   /**
    * Called every time a mouse button is released.
    */
-  onMouseUp: function TVC_onMouseUp(e)
+  _onMouseUp: function TVC__onMouseUp(e)
   {
     e.preventDefault();
     e.stopPropagation();
 
-    if (this.frames < MOUSE_INTRO_DELAY) {
+    if (this._time < MOUSE_INTRO_DELAY) {
       return;
     }
 
     // calculate x and y coordinates using using the client and target offset
     let button = e.which;
     let upX = e.clientX - e.target.offsetLeft;
     let upY = e.clientY - e.target.offsetTop;
 
@@ -1165,123 +1216,131 @@ TiltVisualizer.Controller.prototype = {
     }
 
     this.arcball.mouseUp(upX, upY, button);
   },
 
   /**
    * Called every time the mouse moves.
    */
-  onMouseMove: function TVC_onMouseMove(e)
+  _onMouseMove: function TVC__onMouseMove(e)
   {
     e.preventDefault();
     e.stopPropagation();
 
-    if (this.frames < MOUSE_INTRO_DELAY) {
+    if (this._time < MOUSE_INTRO_DELAY) {
       return;
     }
 
     // calculate x and y coordinates using using the client and target offset
     let moveX = e.clientX - e.target.offsetLeft;
     let moveY = e.clientY - e.target.offsetTop;
 
     this.arcball.mouseMove(moveX, moveY);
   },
 
   /**
    * Called when the mouse leaves the visualization bounds.
    */
-  onMouseOver: function TVC_onMouseOver(e)
+  _onMouseOver: function TVC__onMouseOver(e)
   {
     e.preventDefault();
     e.stopPropagation();
 
     this.arcball.mouseOver();
   },
 
   /**
    * Called when the mouse leaves the visualization bounds.
    */
-  onMouseOut: function TVC_onMouseOut(e)
+  _onMouseOut: function TVC__onMouseOut(e)
   {
     e.preventDefault();
     e.stopPropagation();
 
     this.arcball.mouseOut();
   },
 
   /**
    * Called when the mouse wheel is used.
    */
-  onMozScroll: function TVC_onMozScroll(e)
+  _onMozScroll: function TVC__onMozScroll(e)
   {
     e.preventDefault();
     e.stopPropagation();
 
     this.arcball.zoom(e.detail);
   },
 
   /**
    * Called when a key is pressed.
    */
-  onKeyDown: function TVC_onKeyDown(e)
+  _onKeyDown: function TVC__onKeyDown(e)
   {
     let code = e.keyCode || e.which;
 
     if (!e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
       e.preventDefault();
       e.stopPropagation();
       this.arcball.keyDown(code);
     } else {
       this.arcball.cancelKeyEvents();
     }
   },
 
   /**
    * Called when a key is released.
    */
-  onKeyUp: function TVC_onKeyUp(e)
+  _onKeyUp: function TVC__onKeyUp(e)
   {
     let code = e.keyCode || e.which;
 
     if (code === e.DOM_VK_X) {
       this.presenter.deleteNode();
     }
+    if (code === e.DOM_VK_F) {
+      let highlight = this.presenter._highlight;
+      let zoom = this.presenter.transforms.zoom;
+
+      this.arcball.moveIntoView(vec3.lerp(
+        vec3.scale(highlight.v0, zoom, []),
+        vec3.scale(highlight.v1, zoom, []), 0.5));
+    }
     if (!e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
       e.preventDefault();
       e.stopPropagation();
       this.arcball.keyUp(code);
     }
   },
 
   /**
    * Called when a key is pressed.
    */
-  onKeyPress: function TVC_onKeyPress(e)
+  _onKeyPress: function TVC__onKeyPress(e)
   {
     let tilt = this.presenter.chromeWindow.Tilt;
 
     if (e.keyCode === e.DOM_VK_ESCAPE) {
       e.preventDefault();
       e.stopPropagation();
       tilt.destroy(tilt.currentWindowId, true);
     }
   },
 
   /**
    * Called when the canvas looses focus.
    */
-  onBlur: function TVC_onBlur(e) {
+  _onBlur: function TVC__onBlur(e) {
     this.arcball.cancelKeyEvents();
   },
 
   /**
    * Called when the content window of the current browser is resized.
    */
-  onResize: function TVC_onResize(e)
+  _onResize: function TVC__onResize(e)
   {
     let zoom = this.presenter.chromeWindow.InspectorUI.highlighter.zoom;
     let width = e.target.innerWidth * zoom;
     let height = e.target.innerHeight * zoom;
 
     this.arcball.resize(width, height);
   },
 
@@ -1293,23 +1352,24 @@ TiltVisualizer.Controller.prototype = {
   isInitialized: function TVC_isInitialized()
   {
     return this.arcball ? true : false;
   },
 
   /**
    * Function called when this object is destroyed.
    */
-  finalize: function TVC_finalize()
+  _finalize: function TVC__finalize()
   {
     TiltUtils.destroyObject(this.arcball);
-    TiltUtils.destroyObject(this.coordinates);
+    TiltUtils.destroyObject(this._coordinates);
 
     this.removeEventListeners();
-    this.presenter.ondraw = null;
+    this.presenter.controller = null;
+    this.presenter._controllerUpdate = null;
   }
 };
 
 /**
  * This is a general purpose 3D rotation controller described by Ken Shoemake
  * in the Graphics Interface ’92 Proceedings. It features good behavior
  * easy implementation, cheap execution.
  *
@@ -1389,19 +1449,22 @@ TiltVisualizer.Arcball = function TV_Arc
 
 TiltVisualizer.Arcball.prototype = {
 
   /**
    * Call this function whenever you need the updated rotation quaternion
    * and the zoom amount. These values will be returned as "rotation" and
    * "translation" properties inside an object.
    *
+   * @param {Number} aDelta
+   *                 the current animation frame delta
+   *
    * @return {Object} the rotation quaternion and the translation amount
    */
-  update: function TVA_update()
+  update: function TVA_update(aDelta)
   {
     let mousePress = this._mousePress;
     let mouseRelease = this._mouseRelease;
     let mouseMove = this._mouseMove;
     let mouseLerp = this._mouseLerp;
     let mouseButton = this._mouseButton;
 
     // smoothly update the mouse coordinates
@@ -1429,17 +1492,17 @@ TiltVisualizer.Arcball.prototype = {
 
     // left mouse button handles rotation
     if (mouseButton === 1 || this._rotating) {
       // the rotation doesn't stop immediately after the left mouse button is
       // released, so add a flag to smoothly continue it until it ends
       this._rotating = true;
 
       // find the sphere coordinates of the mouse positions
-      this.pointToSphere(x, y, this.width, this.height, this.radius, endVec);
+      this._pointToSphere(x, y, this.width, this.height, this.radius, endVec);
 
       // compute the vector perpendicular to the start & end vectors
       vec3.cross(startVec, endVec, pVec);
 
       // if the begin and end vectors don't coincide
       if (vec3.length(pVec) > 0) {
         deltaRot[0] = pVec[0];
         deltaRot[1] = pVec[1];
@@ -1549,21 +1612,29 @@ TiltVisualizer.Arcball.prototype = {
       (additionalRot[2] - deltaAdditionalRot[2]) * ARCBALL_SENSITIVITY;
 
     deltaAdditionalTrans[0] +=
       (additionalTrans[0] - deltaAdditionalTrans[0]) * ARCBALL_SENSITIVITY;
     deltaAdditionalTrans[1] +=
       (additionalTrans[1] - deltaAdditionalTrans[1]) * ARCBALL_SENSITIVITY;
 
     // create an additional rotation based on the key events
-    quat4.fromEuler(deltaAdditionalRot[0], deltaAdditionalRot[1], 0, deltaRot);
+    quat4.fromEuler(
+      deltaAdditionalRot[0],
+      deltaAdditionalRot[1],
+      deltaAdditionalRot[2], deltaRot);
 
     // create an additional translation based on the key events
     vec3.set([deltaAdditionalTrans[0], deltaAdditionalTrans[1], 0], deltaTrans);
 
+    // handle the reset animation steps if necessary
+    if (this._resetInProgress) {
+      this._nextResetStep(aDelta || 1);
+    }
+
     // return the current rotation and translation
     return {
       rotation: quat4.multiply(deltaRot, currentRot),
       translation: vec3.add(deltaTrans, currentTrans)
     };
   },
 
   /**
@@ -1578,21 +1649,21 @@ TiltVisualizer.Arcball.prototype = {
    *                 which mouse button was pressed
    */
   mouseDown: function TVA_mouseDown(x, y, aButton)
   {
     // save the mouse down state and prepare for rotations or translations
     this._mousePress[0] = x;
     this._mousePress[1] = y;
     this._mouseButton = aButton;
-    this._cancelResetInterval();
+    this._cancelReset();
     this._save();
 
     // find the sphere coordinates of the mouse positions
-    this.pointToSphere(
+    this._pointToSphere(
       x, y, this.width, this.height, this.radius, this._startVec);
 
     quat4.set(this._currentRot, this._lastRot);
   },
 
   /**
    * Function handling the mouseUp event.
    * Call this when a mouse button was released.
@@ -1654,31 +1725,31 @@ TiltVisualizer.Arcball.prototype = {
    * Call this, for example, when the mouse wheel was scrolled or zoom keys
    * were pressed.
    *
    * @param {Number} aZoom
    *                 the zoom direction and speed
    */
   zoom: function TVA_zoom(aZoom)
   {
-    this._cancelResetInterval();
+    this._cancelReset();
     this._zoomAmount = TiltMath.clamp(this._zoomAmount - aZoom,
       ARCBALL_ZOOM_MIN, ARCBALL_ZOOM_MAX);
   },
 
   /**
    * Function handling the keyDown event.
    * Call this when a key was pressed.
    *
    * @param {Number} aCode
    *                 the code corresponding to the key pressed
    */
   keyDown: function TVA_keyDown(aCode)
   {
-    this._cancelResetInterval();
+    this._cancelReset();
     this._keyCode[aCode] = true;
   },
 
   /**
    * Function handling the keyUp event.
    * Call this when a key was released.
    *
    * @param {Number} aCode
@@ -1700,17 +1771,17 @@ TiltVisualizer.Arcball.prototype = {
    *                 the width of canvas
    * @param {Number} aHeight
    *                 the height of canvas
    * @param {Number} aRadius
    *                 optional, the radius of the arcball
    * @param {Array} aSphereVec
    *                a 3d vector to store the sphere coordinates
    */
-  pointToSphere: function TVA_pointToSphere(
+  _pointToSphere: function TVA__pointToSphere(
     x, y, aWidth, aHeight, aRadius, aSphereVec)
   {
     // adjust point coords and scale down to range of [-1..1]
     x = (x - aWidth * 0.5) / aRadius;
     y = (y - aHeight * 0.5) / aRadius;
 
     // compute the square length of the vector to the point from the center
     let normal = 0;
@@ -1746,16 +1817,42 @@ TiltVisualizer.Arcball.prototype = {
    */
   cancelMouseEvents: function TVA_cancelMouseEvents()
   {
     this._rotating = false;
     this._mouseButton = -1;
   },
 
   /**
+   * Incremental translation method.
+   *
+   * @param {Array} aTranslation
+   *                the translation ammount on the [x, y] axis
+   */
+  translate: function TVP_translate(aTranslation)
+  {
+    this._additionalTrans[0] += aTranslation[0];
+    this._additionalTrans[1] += aTranslation[1];
+  },
+
+  /**
+   * Incremental rotation method.
+   *
+   * @param {Array} aRotation
+   *                the rotation ammount along the [x, y, z] axis
+   */
+  rotate: function TVP_rotate(aRotation)
+  {
+    // explicitly rotate along y, x, z values because they're eulerian angles
+    this._additionalRot[0] += TiltMath.radians(aRotation[1]);
+    this._additionalRot[1] += TiltMath.radians(aRotation[0]);
+    this._additionalRot[2] += TiltMath.radians(aRotation[2]);
+  },
+
+  /**
    * Moves a target point into view only if it's outside the currently visible
    * area bounds (in which case it also resets any additional transforms).
    *
    * @param {Arary} aPoint
    *                the [x, y] point which should be brought into view
    */
   moveIntoView: function TVA_moveIntoView(aPoint) {
     let visiblePointX = -(this._currentTrans[0] + this._additionalTrans[0]);
@@ -1798,84 +1895,93 @@ TiltVisualizer.Arcball.prototype = {
    *
    * @param {Array} aFinalTranslation
    *                optional, final vector translation
    * @param {Array} aFinalRotation
    *                optional, final quaternion rotation
    */
   reset: function TVA_reset(aFinalTranslation, aFinalRotation)
   {
-    if ("function" === typeof this.onResetStart) {
-      this.onResetStart();
-      this.onResetStart = null;
+    if ("function" === typeof this._onResetStart) {
+      this._onResetStart();
+      this._onResetStart = null;
     }
 
-    let func = this._nextResetIntervalStep.bind(this);
-
     this.cancelMouseEvents();
     this.cancelKeyEvents();
-    this._cancelResetInterval();
+    this._cancelReset();
 
     this._save();
     this._resetFinalTranslation = vec3.create(aFinalTranslation);
     this._resetFinalRotation = quat4.create(aFinalRotation);
-    this._resetInterval =
-      this.chromeWindow.setInterval(func, ARCBALL_RESET_INTERVAL);
+    this._resetInProgress = true;
   },
 
   /**
    * Cancels the current arcball reset animation if there is one.
    */
-  _cancelResetInterval: function TVA__cancelResetInterval()
+  _cancelReset: function TVA__cancelReset()
   {
-    if (this._resetInterval) {
-      this.chromeWindow.clearInterval(this._resetInterval);
-
-      this._resetInterval = null;
+    if (this._resetInProgress) {
+      this._resetInProgress = false;
       this._save();
 
-      if ("function" === typeof this.onResetFinish) {
-        this.onResetFinish();
-        this.onResetFinish = null;
+      if ("function" === typeof this._onResetFinish) {
+        this._onResetFinish();
+        this._onResetFinish = null;
+        this._onResetStep = null;
       }
     }
   },
 
   /**
    * Executes the next step in the arcball reset animation.
+   *
+   * @param {Number} aDelta