Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 12 Feb 2013 15:05:37 -0500
changeset 131535 d388b8073cce4ab68914382332de9700574e3802
parent 131534 4ed4f90669b584635ff3e2aa75856f653c11e048 (current diff)
parent 131480 161a347bda5be7575b33d7603a078061be595290 (diff)
child 131536 ca180542fbcdd2e93c635c928e7911fc05b7faff
push id2323
push userbbajaj@mozilla.com
push dateMon, 01 Apr 2013 19:47:02 +0000
treeherdermozilla-beta@7712be144d91 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone21.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound.
--- a/browser/components/thumbnails/PageThumbs.jsm
+++ b/browser/components/thumbnails/PageThumbs.jsm
@@ -7,17 +7,17 @@
 this.EXPORTED_SYMBOLS = ["PageThumbs", "PageThumbsStorage"];
 
 const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
 const PREF_STORAGE_VERSION = "browser.pagethumbnails.storage_version";
-const LATEST_STORAGE_VERSION = 2;
+const LATEST_STORAGE_VERSION = 3;
 
 const EXPIRATION_MIN_CHUNK_SIZE = 50;
 const EXPIRATION_INTERVAL_SECS = 3600;
 
 /**
  * Name of the directory in the profile that contains the thumbnails.
  */
 const THUMBNAIL_DIRECTORY = "thumbnails";
@@ -359,47 +359,51 @@ let PageThumbsStorageMigrator = {
 
   set currentVersion(aVersion) {
     Services.prefs.setIntPref(PREF_STORAGE_VERSION, aVersion);
   },
 
   migrate: function Migrator_migrate() {
     let version = this.currentVersion;
 
-    if (version < 1) {
-      this.removeThumbnailsFromRoamingProfile();
-    }
-    if (version < 2) {
-      this.renameThumbnailsFolder();
+    // Storage version 1 never made it to beta.
+    // At the time of writing only Windows had (ProfD != ProfLD) and we
+    // needed to move thumbnails from the roaming profile to the locale
+    // one so that they're not needlessly included in backups and/or
+    // written via SMB.
+
+    // Storage version 2 also never made it to beta.
+    // The thumbnail folder structure has been changed and old thumbnails
+    // were not migrated. Instead, we just renamed the current folder to
+    // "<name>-old" and will remove it later.
+
+    if (version < 3) {
+      this.migrateToVersion3();
     }
 
     this.currentVersion = LATEST_STORAGE_VERSION;
   },
 
-  removeThumbnailsFromRoamingProfile:
-  function Migrator_removeThumbnailsFromRoamingProfile() {
-    let local = FileUtils.getDir("ProfLD", [THUMBNAIL_DIRECTORY]);
+  /**
+   * Bug 239254 added support for having the disk cache and thumbnail
+   * directories on a local path (i.e. ~/.cache/) under Linux. We'll first
+   * try to move the old thumbnails to their new location. If that's not
+   * possible (because ProfD might be on a different file system than
+   * ProfLD) we'll just discard them.
+   */
+  migrateToVersion3: function Migrator_migrateToVersion3() {
+    let local = FileUtils.getDir("ProfLD", [THUMBNAIL_DIRECTORY], true);
     let roaming = FileUtils.getDir("ProfD", [THUMBNAIL_DIRECTORY]);
 
-    if (!roaming.equals(local) && roaming.exists()) {
-      roaming.followLinks = false;
-      try {
-        roaming.remove(true);
-      } catch (e) {
-        // The directory might not exist or we're not permitted to remove it.
-      }
-    }
-  },
-
-  renameThumbnailsFolder: function Migrator_renameThumbnailsFolder() {
-    let dir = FileUtils.getDir("ProfLD", [THUMBNAIL_DIRECTORY]);
-    try {
-      dir.moveTo(null, dir.leafName + "-old");
-    } catch (e) {
-      // The directory might not exist or we're not permitted to rename it.
+    if (!roaming.equals(local)) {
+      PageThumbsWorker.postMessage({
+        type: "moveOrDeleteAllThumbnails",
+        from: roaming.path,
+        to: local.path
+      });
     }
   }
 };
 
 let PageThumbsExpiration = {
   _filters: [],
 
   init: function Expiration_init() {
--- a/browser/components/thumbnails/PageThumbsWorker.js
+++ b/browser/components/thumbnails/PageThumbsWorker.js
@@ -20,16 +20,19 @@ let PageThumbsWorker = {
 
     switch (msg.type) {
       case "removeFile":
         data.result = this.removeFile(msg);
         break;
       case "expireFilesInDirectory":
         data.result = this.expireFilesInDirectory(msg);
         break;
+      case "moveOrDeleteAllThumbnails":
+        data.result = this.moveOrDeleteAllThumbnails(msg);
+        break;
       default:
         data.result = false;
         data.detail = "message not understood";
         break;
     }
 
     self.postMessage(data);
   },
@@ -62,12 +65,44 @@ let PageThumbsWorker = {
   getFileEntriesInDirectory:
   function Worker_getFileEntriesInDirectory(aPath, aSkipFiles) {
     let skip = new Set(aSkipFiles);
     let iter = new OS.File.DirectoryIterator(aPath);
 
     return [entry
             for (entry in iter)
             if (!entry.isDir && !entry.isSymLink && !skip.has(entry.name))];
+  },
+
+  moveOrDeleteAllThumbnails:
+  function Worker_moveOrDeleteAllThumbnails(msg) {
+    if (!OS.File.exists(msg.from))
+      return true;
+
+    let iter = new OS.File.DirectoryIterator(msg.from);
+    for (let entry in iter) {
+      if (entry.isDir || entry.isSymLink) {
+        continue;
+      }
+
+      let from = OS.Path.join(msg.from, entry.name);
+      let to = OS.Path.join(msg.to, entry.name);
+
+      try {
+        OS.File.move(from, to, {noOverwrite: true, noCopy: true});
+      } catch (e) {
+        OS.File.remove(from);
+      }
+    }
+    iter.close();
+
+    try {
+      OS.File.removeEmptyDir(msg.from);
+    } catch (e) {
+      // This could fail if there's something in
+      // the folder we're not permitted to remove.
+    }
+
+    return true;
   }
 };
 
 self.onmessage = PageThumbsWorker.handleMessage.bind(PageThumbsWorker);
--- a/browser/components/thumbnails/test/Makefile.in
+++ b/browser/components/thumbnails/test/Makefile.in
@@ -12,16 +12,17 @@ include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _BROWSER_FILES = \
 	browser_thumbnails_capture.js \
 	browser_thumbnails_expiration.js \
 	browser_thumbnails_privacy.js \
 	browser_thumbnails_redirect.js \
 	browser_thumbnails_storage.js \
+	browser_thumbnails_storage_migrate3.js \
 	browser_thumbnails_bug726727.js \
 	head.js \
 	background_red.html \
 	background_red_redirect.sjs \
 	privacy_cache_control.sjs \
 	$(NULL)
 
 libs::	$(_BROWSER_FILES)
--- a/browser/components/thumbnails/test/browser_thumbnails_redirect.js
+++ b/browser/components/thumbnails/test/browser_thumbnails_redirect.js
@@ -15,18 +15,8 @@ function runTests() {
   // Create a tab, redirecting to a page with a red background.
   yield addTab(URL);
   yield captureAndCheckColor(255, 0, 0, "we have a red thumbnail");
 
   // Wait until the referrer's thumbnail's file has been written.
   yield whenFileExists(URL);
   yield checkThumbnailColor(URL, 255, 0, 0, "referrer has a red thumbnail");
 }
-
-function whenFileExists(aURL) {
-  let callback = next;
-
-  let file = PageThumbsStorage.getFileForURL(aURL);
-  if (!file.exists())
-    callback = function () whenFileExists(aURL);
-
-  executeSoon(callback);
-}
--- a/browser/components/thumbnails/test/browser_thumbnails_storage.js
+++ b/browser/components/thumbnails/test/browser_thumbnails_storage.js
@@ -84,25 +84,14 @@ function clearHistory(aUseRange) {
   s.range = null;
   s.ignoreTimespan = true;
 
   executeSoon(next);
 }
 
 function createThumbnail() {
   addTab(URL, function () {
-    whenFileExists(function () {
+    whenFileExists(URL, function () {
       gBrowser.removeTab(gBrowser.selectedTab);
       next();
     });
   });
 }
-
-function whenFileExists(aCallback) {
-  let callback;
-  if (thumbnailExists(URL)) {
-    callback = aCallback;
-  } else {
-    callback = function () whenFileExists(aCallback);
-  }
-
-  executeSoon(callback);
-}
new file mode 100644
--- /dev/null
+++ b/browser/components/thumbnails/test/browser_thumbnails_storage_migrate3.js
@@ -0,0 +1,100 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const URL = "http://mochi.test:8888/migration3";
+const URL2 = URL + "#2";
+const URL3 = URL + "#3";
+const THUMBNAIL_DIRECTORY = "thumbnails";
+const PREF_STORAGE_VERSION = "browser.pagethumbnails.storage_version";
+
+let tmp = {};
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+  .getService(Ci.mozIJSSubScriptLoader)
+  .loadSubScript("resource:///modules/PageThumbs.jsm", tmp);
+let {PageThumbsStorageMigrator} = tmp;
+
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+  "resource://gre/modules/FileUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gDirSvc",
+  "@mozilla.org/file/directory_service;1", "nsIProperties");
+
+/**
+ * This test makes sure we correctly migrate to thumbnail storage version 3.
+ * This means copying existing thumbnails from the roaming to the local profile
+ * directory and should just apply to Linux.
+ */
+function runTests() {
+  let dirSvc = Cc["@mozilla.org/file/directory_service;1"]
+                 .getService(Ci.nsIProperties);
+
+  // Prepare a local profile directory.
+  let localProfile = FileUtils.getDir("ProfD", ["local-test"], true);
+  changeLocation("ProfLD", localProfile);
+
+  let local = FileUtils.getDir("ProfLD", [THUMBNAIL_DIRECTORY], true);
+  let roaming = FileUtils.getDir("ProfD", [THUMBNAIL_DIRECTORY], true);
+
+  // Set up some data in the roaming profile.
+  let name = PageThumbsStorage.getLeafNameForURL(URL);
+  let file = FileUtils.getFile("ProfD", [THUMBNAIL_DIRECTORY, name]);
+  writeDummyFile(file);
+
+  name = PageThumbsStorage.getLeafNameForURL(URL2);
+  file = FileUtils.getFile("ProfD", [THUMBNAIL_DIRECTORY, name]);
+  writeDummyFile(file);
+
+  name = PageThumbsStorage.getLeafNameForURL(URL3);
+  file = FileUtils.getFile("ProfD", [THUMBNAIL_DIRECTORY, name]);
+  writeDummyFile(file);
+
+  // Pretend to have one of the thumbnails
+  // already in place at the new storage site.
+  name = PageThumbsStorage.getLeafNameForURL(URL3);
+  file = FileUtils.getFile("ProfLD", [THUMBNAIL_DIRECTORY, name]);
+  writeDummyFile(file, "no-overwrite-plz");
+
+  // Kick off thumbnail storage migration.
+  PageThumbsStorageMigrator.migrateToVersion3();
+  ok(true, "migration finished");
+
+  // Wait until the first thumbnail was moved to its new location.
+  yield whenFileExists(URL);
+  ok(true, "first thumbnail moved");
+
+  // Wait for the second thumbnail to be moved as well.
+  yield whenFileExists(URL2);
+  ok(true, "second thumbnail moved");
+
+  yield whenFileRemoved(roaming);
+  ok(true, "roaming thumbnail directory removed");
+
+  // Check that our existing thumbnail wasn't overwritten.
+  is(getFileContents(file), "no-overwrite-plz",
+    "existing thumbnail was not overwritten");
+}
+
+function changeLocation(aLocation, aNewDir) {
+  let oldDir = gDirSvc.get(aLocation, Ci.nsILocalFile);
+  gDirSvc.undefine(aLocation);
+  gDirSvc.set(aLocation, aNewDir);
+
+  registerCleanupFunction(function () {
+    gDirSvc.undefine(aLocation);
+    gDirSvc.set(aLocation, oldDir);
+  });
+}
+
+function writeDummyFile(aFile, aContents) {
+  let fos = FileUtils.openSafeFileOutputStream(aFile);
+  let data = aContents || "dummy";
+  fos.write(data, data.length);
+  FileUtils.closeSafeFileOutputStream(fos);
+}
+
+function getFileContents(aFile) {
+  let istream = Cc["@mozilla.org/network/file-input-stream;1"]
+                  .createInstance(Ci.nsIFileInputStream);
+  istream.init(aFile, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
+  return NetUtil.readInputStreamToString(istream, istream.available());
+}
--- a/browser/components/thumbnails/test/head.js
+++ b/browser/components/thumbnails/test/head.js
@@ -209,8 +209,41 @@ function addVisits(aPlaceInfo, aCallback
       handleCompletion: function UP_handleCompletion() {
         if (aCallback)
           aCallback();
       }
     }
   );
 }
 
+/**
+ * Calls a given callback when the thumbnail for a given URL has been found
+ * on disk. Keeps trying until the thumbnail has been created.
+ *
+ * @param aURL The URL of the thumbnail's page.
+ * @param [optional] aCallback
+ *        Function to be invoked on completion.
+ */
+function whenFileExists(aURL, aCallback) {
+  let callback = aCallback;
+  if (!thumbnailExists(aURL)) {
+    callback = function () whenFileExists(aURL, aCallback);
+  }
+
+  executeSoon(callback || next);
+}
+
+/**
+ * Calls a given callback when the given file has been removed.
+ * Keeps trying until the file is removed.
+ *
+ * @param aFile The file that is being removed
+ * @param [optional] aCallback
+ *        Function to be invoked on completion.
+ */
+function whenFileRemoved(aFile, aCallback) {
+  let callback = aCallback;
+  if (aFile.exists()) {
+    callback = function () whenFileRemoved(aFile, aCallback);
+  }
+
+  executeSoon(callback || next);
+}
--- a/browser/devtools/debugger/test/Makefile.in
+++ b/browser/devtools/debugger/test/Makefile.in
@@ -27,16 +27,17 @@ MOCHITEST_BROWSER_TESTS = \
 	browser_dbg_propertyview-03.js \
 	browser_dbg_propertyview-04.js \
 	browser_dbg_propertyview-05.js \
 	browser_dbg_propertyview-06.js \
 	browser_dbg_propertyview-07.js \
 	browser_dbg_propertyview-08.js \
 	browser_dbg_propertyview-09.js \
 	browser_dbg_propertyview-10.js \
+	browser_dbg_propertyview-11.js \
 	browser_dbg_propertyview-edit-value.js \
 	browser_dbg_propertyview-edit-watch.js \
 	browser_dbg_propertyview-data-big.js \
 	browser_dbg_propertyview-data-getset-01.js \
 	browser_dbg_propertyview-data-getset-02.js \
 	browser_dbg_propertyview-data.js \
 	browser_dbg_propertyview-filter-01.js \
 	browser_dbg_propertyview-filter-02.js \
--- a/browser/devtools/debugger/test/browser_dbg_frame-parameters.html
+++ b/browser/devtools/debugger/test/browser_dbg_frame-parameters.html
@@ -20,16 +20,17 @@
           debugger;
         }
         function load() {
           var a = { a: 1, b: "beta", c: true };
           var e = eval("test(a, 'beta', 3, false, null)");
         }
         var button = document.querySelector("button");
         button.addEventListener("click", load, false);
+        var buttonAsProto = Object.create(button);
       });
     </script>
 
   </head>
   <body>
     <button>Click me!</button>
   </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-11.js
@@ -0,0 +1,282 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the property view correctly displays WebIDL attributes in DOM
+ * objects.
+ */
+
+const TAB_URL = EXAMPLE_URL + "browser_dbg_frame-parameters.html";
+
+let gPane = null;
+let gTab = null;
+let gDebugger = null;
+
+function test()
+{
+  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
+    gTab = aTab;
+    gPane = aPane;
+    gDebugger = gPane.panelWin;
+
+    gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
+    gDebugger.DebuggerView.Variables.nonEnumVisible = true;
+    testFrameParameters();
+  });
+}
+
+function testFrameParameters()
+{
+  let count = 0;
+  gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
+    // We expect 2 Debugger:FetchedVariables events, one from the global object
+    // scope and the regular one.
+    if (++count < 2) {
+      info("Number of received Debugger:FetchedVariables events: " + count);
+      return;
+    }
+    gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
+    Services.tm.currentThread.dispatch({ run: function() {
+
+      let anonymousScope = gDebugger.DebuggerView.Variables._list.querySelectorAll(".scope")[1],
+          globalScope = gDebugger.DebuggerView.Variables._list.querySelectorAll(".scope")[2],
+          anonymousNodes = anonymousScope.querySelector(".details").childNodes,
+          globalNodes = globalScope.querySelector(".details").childNodes,
+          gVars = gDebugger.DebuggerView.Variables;
+
+
+      is(gDebugger.DebuggerController.activeThread.state, "paused",
+        "Should only be getting stack frames while paused.");
+
+      is(anonymousNodes[1].querySelector(".name").getAttribute("value"), "button",
+        "Should have the right property name for |button|.");
+
+      is(anonymousNodes[1].querySelector(".value").getAttribute("value"), "[object HTMLButtonElement]",
+        "Should have the right property value for |button|.");
+
+      is(anonymousNodes[2].querySelector(".name").getAttribute("value"), "buttonAsProto",
+        "Should have the right property name for |buttonAsProto|.");
+
+      is(anonymousNodes[2].querySelector(".value").getAttribute("value"), "[object Object]",
+        "Should have the right property value for |buttonAsProto|.");
+
+      is(globalNodes[3].querySelector(".name").getAttribute("value"), "document",
+        "Should have the right property name for |document|.");
+
+      is(globalNodes[3].querySelector(".value").getAttribute("value"), "[object HTMLDocument]",
+        "Should have the right property value for |document|.");
+
+      let buttonNode = gVars.getItemForNode(anonymousNodes[1]);
+      let buttonAsProtoNode = gVars.getItemForNode(anonymousNodes[2]);
+      let documentNode = gVars.getItemForNode(globalNodes[3]);
+
+      is(buttonNode.expanded, false,
+        "The buttonNode should not be expanded at this point.");
+      is(buttonAsProtoNode.expanded, false,
+        "The buttonAsProtoNode should not be expanded at this point.");
+      is(documentNode.expanded, false,
+        "The documentNode should not be expanded at this point.");
+
+      // Expand the 'button', 'buttonAsProto' and 'document' tree nodes. This
+      // causes their properties to be retrieved and displayed.
+      buttonNode.expand();
+      buttonAsProtoNode.expand();
+      documentNode.expand();
+
+      is(buttonNode.expanded, true,
+        "The buttonNode should be expanded at this point.");
+      is(buttonAsProtoNode.expanded, true,
+        "The buttonAsProtoNode should be expanded at this point.");
+      is(documentNode.expanded, true,
+        "The documentNode should be expanded at this point.");
+
+      // Poll every few milliseconds until the properties are retrieved.
+      // It's important to set the timer in the chrome window, because the
+      // content window timers are disabled while the debuggee is paused.
+      let count1 = 0;
+      let intervalID = window.setInterval(function(){
+        info("count1: " + count1);
+        if (++count1 > 50) {
+          ok(false, "Timed out while polling for the properties.");
+          window.clearInterval(intervalID);
+          return resumeAndFinish();
+        }
+        if (!buttonNode._retrieved ||
+            !buttonAsProtoNode._retrieved ||
+            !documentNode._retrieved) {
+          return;
+        }
+        window.clearInterval(intervalID);
+
+        // Test the prototypes of these objects.
+        is(buttonNode.get("__proto__").target.querySelector(".name")
+           .getAttribute("value"), "__proto__",
+          "Should have the right property name for '__proto__' in buttonNode.");
+        ok(buttonNode.get("__proto__").target.querySelector(".value")
+           .getAttribute("value").search(/object/) != -1,
+          "'__proto__' in buttonNode should be an object.");
+
+        is(buttonAsProtoNode.get("__proto__").target.querySelector(".name")
+           .getAttribute("value"), "__proto__",
+          "Should have the right property name for '__proto__' in buttonAsProtoNode.");
+        ok(buttonAsProtoNode.get("__proto__").target.querySelector(".value")
+           .getAttribute("value").search(/object/) != -1,
+          "'__proto__' in buttonAsProtoNode should be an object.");
+
+        is(documentNode.get("__proto__").target.querySelector(".name")
+           .getAttribute("value"), "__proto__",
+          "Should have the right property name for '__proto__' in documentNode.");
+        ok(documentNode.get("__proto__").target.querySelector(".value")
+           .getAttribute("value").search(/object/) != -1,
+          "'__proto__' in documentNode should be an object.");
+
+        let buttonProtoNode = buttonNode.get("__proto__");
+        let buttonAsProtoProtoNode = buttonAsProtoNode.get("__proto__");
+        let documentProtoNode = documentNode.get("__proto__");
+
+        is(buttonProtoNode.expanded, false,
+          "The buttonProtoNode should not be expanded at this point.");
+        is(buttonAsProtoProtoNode.expanded, false,
+          "The buttonAsProtoProtoNode should not be expanded at this point.");
+        is(documentProtoNode.expanded, false,
+          "The documentProtoNode should not be expanded at this point.");
+
+        // Expand the prototypes of 'button', 'buttonAsProto' and 'document'
+        // tree nodes. This causes their properties to be retrieved and
+        // displayed.
+        buttonProtoNode.expand();
+        buttonAsProtoProtoNode.expand();
+        documentProtoNode.expand();
+
+        is(buttonProtoNode.expanded, true,
+          "The buttonProtoNode should be expanded at this point.");
+        is(buttonAsProtoProtoNode.expanded, true,
+          "The buttonAsProtoProtoNode should be expanded at this point.");
+        is(documentProtoNode.expanded, true,
+          "The documentProtoNode should be expanded at this point.");
+
+
+        // Poll every few milliseconds until the properties are retrieved.
+        // It's important to set the timer in the chrome window, because the
+        // content window timers are disabled while the debuggee is paused.
+        let count2 = 0;
+        let intervalID1 = window.setInterval(function(){
+          info("count2: " + count2);
+          if (++count2 > 50) {
+            ok(false, "Timed out while polling for the properties.");
+            window.clearInterval(intervalID1);
+            return resumeAndFinish();
+          }
+          if (!buttonProtoNode._retrieved ||
+              !buttonAsProtoProtoNode._retrieved ||
+              !documentProtoNode._retrieved) {
+            return;
+          }
+          window.clearInterval(intervalID1);
+
+          // Now the main course: make sure that the native getters for WebIDL
+          // attributes have been called and a value has been returned.
+          is(buttonProtoNode.get("type").target.querySelector(".name")
+             .getAttribute("value"), "type",
+            "Should have the right property name for 'type' in buttonProtoNode.");
+          is(buttonProtoNode.get("type").target.querySelector(".value")
+             .getAttribute("value"), '"submit"',
+            "'type' in buttonProtoNode should have the right value.");
+          is(buttonProtoNode.get("formMethod").target.querySelector(".name")
+             .getAttribute("value"), "formMethod",
+            "Should have the right property name for 'formMethod' in buttonProtoNode.");
+          is(buttonProtoNode.get("formMethod").target.querySelector(".value")
+             .getAttribute("value"), '"get"',
+            "'formMethod' in buttonProtoNode should have the right value.");
+
+          is(documentProtoNode.get("baseURI").target.querySelector(".name")
+             .getAttribute("value"), "baseURI",
+            "Should have the right property name for 'baseURI' in documentProtoNode.");
+          is(documentProtoNode.get("baseURI").target.querySelector(".value")
+             .getAttribute("value"), '"' + TAB_URL + '"',
+            "'baseURI' in documentProtoNode should have the right value.");
+          is(documentProtoNode.get("URL").target.querySelector(".name")
+             .getAttribute("value"), "URL",
+            "Should have the right property name for 'URL' in documentProtoNode.");
+          is(documentProtoNode.get("URL").target.querySelector(".value")
+             .getAttribute("value"), '"' + TAB_URL + '"',
+            "'URL' in documentProtoNode should have the right value.");
+
+          let buttonAsProtoProtoProtoNode = buttonAsProtoProtoNode.get("__proto__");
+
+          is(buttonAsProtoProtoProtoNode.expanded, false,
+            "The buttonAsProtoProtoProtoNode should not be expanded at this point.");
+
+          // Expand the prototype of the prototype of 'buttonAsProto' tree
+          // node. This causes its properties to be retrieved and displayed.
+          buttonAsProtoProtoProtoNode.expand();
+
+          is(buttonAsProtoProtoProtoNode.expanded, true,
+            "The buttonAsProtoProtoProtoNode should be expanded at this point.");
+
+          // Poll every few milliseconds until the properties are retrieved.
+          // It's important to set the timer in the chrome window, because the
+          // content window timers are disabled while the debuggee is paused.
+          let count3 = 0;
+          let intervalID2 = window.setInterval(function(){
+            info("count3: " + count3);
+            if (++count3 > 50) {
+              ok(false, "Timed out while polling for the properties.");
+              window.clearInterval(intervalID2);
+              return resumeAndFinish();
+            }
+            if (!buttonAsProtoProtoProtoNode._retrieved) {
+              return;
+            }
+            window.clearInterval(intervalID2);
+
+            // Test this more involved case that reuses an object that is
+            // present in another cache line.
+            is(buttonAsProtoProtoProtoNode.get("type").target.querySelector(".name")
+               .getAttribute("value"), "type",
+              "Should have the right property name for 'type' in buttonAsProtoProtoProtoNode.");
+            is(buttonAsProtoProtoProtoNode.get("type").target.querySelector(".value")
+               .getAttribute("value"), '"submit"',
+              "'type' in buttonAsProtoProtoProtoNode should have the right value.");
+            is(buttonAsProtoProtoProtoNode.get("formMethod").target.querySelector(".name")
+               .getAttribute("value"), "formMethod",
+              "Should have the right property name for 'formMethod' in buttonAsProtoProtoProtoNode.");
+            is(buttonAsProtoProtoProtoNode.get("formMethod").target.querySelector(".value")
+               .getAttribute("value"), '"get"',
+              "'formMethod' in buttonAsProtoProtoProtoNode should have the right value.");
+
+            resumeAndFinish();
+          }, 100);
+        }, 100);
+      }, 100);
+    }}, 0);
+  }, false);
+
+  EventUtils.sendMouseEvent({ type: "click" },
+    content.document.querySelector("button"),
+    content.window);
+}
+
+function resumeAndFinish() {
+  gDebugger.addEventListener("Debugger:AfterFramesCleared", function listener() {
+    gDebugger.removeEventListener("Debugger:AfterFramesCleared", listener, true);
+    Services.tm.currentThread.dispatch({ run: function() {
+      let frames = gDebugger.DebuggerView.StackFrames._container._list;
+
+      is(frames.querySelectorAll(".dbg-stackframe").length, 0,
+        "Should have no frames.");
+
+      closeDebuggerAndFinish();
+    }}, 0);
+  }, true);
+
+  gDebugger.DebuggerController.activeThread.resume();
+}
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebugger = null;
+});
--- a/browser/devtools/inspector/InspectorPanel.jsm
+++ b/browser/devtools/inspector/InspectorPanel.jsm
@@ -7,16 +7,17 @@
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 this.EXPORTED_SYMBOLS = ["InspectorPanel"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
 Cu.import("resource:///modules/devtools/EventEmitter.jsm");
+Cu.import("resource:///modules/devtools/CssLogic.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "MarkupView",
   "resource:///modules/devtools/MarkupView.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Selection",
   "resource:///modules/devtools/Selection.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "HTMLBreadcrumbs",
   "resource:///modules/devtools/Breadcrumbs.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Highlighter",
@@ -641,16 +642,31 @@ InspectorPanel.prototype = {
     }
     let toCopy = this.selection.node.outerHTML;
     if (toCopy) {
       clipboardHelper.copyString(toCopy);
     }
   },
 
   /**
+   * Copy a unique selector of the selected Node to the clipboard.
+   */
+  copyUniqueSelector: function InspectorPanel_copyUniqueSelector()
+  {
+    if (!this.selection.isNode()) {
+      return;
+    }
+
+    let toCopy = CssLogic.findCssSelector(this.selection.node);
+    if (toCopy) {
+      clipboardHelper.copyString(toCopy);
+    }
+  },
+
+  /**
    * Delete the selected node.
    */
   deleteNode: function IUI_deleteNode() {
     if (!this.selection.isNode() ||
          this.selection.isRoot()) {
       return;
     }
 
--- a/browser/devtools/inspector/inspector.xul
+++ b/browser/devtools/inspector/inspector.xul
@@ -34,16 +34,20 @@
       <menuitem id="node-menu-copyinner"
         label="&inspectorHTMLCopyInner.label;"
         accesskey="&inspectorHTMLCopyInner.accesskey;"
         oncommand="inspector.copyInnerHTML()"/>
       <menuitem id="node-menu-copyouter"
         label="&inspectorHTMLCopyOuter.label;"
         accesskey="&inspectorHTMLCopyOuter.accesskey;"
         oncommand="inspector.copyOuterHTML()"/>
+      <menuitem id="node-menu-copyuniqueselector"
+        label="&inspectorCopyUniqueSelector.label;"
+        accesskey="&inspectorCopyUniqueSelector.accesskey;"
+        oncommand="inspector.copyUniqueSelector()"/>
       <menuseparator/>
       <menuitem id="node-menu-delete"
         label="&inspectorHTMLDelete.label;"
         accesskey="&inspectorHTMLDelete.accesskey;"
         oncommand="inspector.deleteNode()"/>
       <menuseparator/>
       <menuitem id="node-menu-pseudo-hover"
         label=":hover" type="checkbox"
--- a/browser/devtools/inspector/test/browser_inspector_menu.js
+++ b/browser/devtools/inspector/test/browser_inspector_menu.js
@@ -51,16 +51,25 @@ function test() {
   }
 
   function testCopyOuterMenu() {
     let copyOuter = inspector.panelDoc.getElementById("node-menu-copyouter");
     ok(copyOuter, "the popup menu has a copy outer html menu item");
 
     waitForClipboard("<p>This is some example text</p>",
                      function() { copyOuter.doCommand(); },
+                     testCopyUniqueSelectorMenu, testCopyUniqueSelectorMenu);
+  }
+
+  function testCopyUniqueSelectorMenu() {
+    let copyUniqueSelector = inspector.panelDoc.getElementById("node-menu-copyuniqueselector");
+    ok(copyUniqueSelector, "the popup menu has a copy unique selector menu item");
+
+    waitForClipboard("body > div:nth-child(1) > p:nth-child(2)",
+                     function() { copyUniqueSelector.doCommand(); },
                      testDeleteNode, testDeleteNode);
   }
 
   function testDeleteNode() {
     let deleteNode = inspector.panelDoc.getElementById("node-menu-delete");
     ok(deleteNode, "the popup menu has a delete menu item");
 
     inspector.selection.once("detached", deleteTest);
--- a/browser/devtools/styleinspector/CssLogic.jsm
+++ b/browser/devtools/styleinspector/CssLogic.jsm
@@ -817,16 +817,90 @@ CssLogic.shortSource = function CssLogic
     return url.query;
   }
 
   let dataUrl = aSheet.href.match(/^(data:[^,]*),/);
   return dataUrl ? dataUrl[1] : aSheet.href;
 }
 
 /**
+ * Find the position of [element] in [nodeList].
+ * @returns an index of the match, or -1 if there is no match
+ */
+function positionInNodeList(element, nodeList) {
+  for (var i = 0; i < nodeList.length; i++) {
+    if (element === nodeList[i]) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+/**
+ * Find a unique CSS selector for a given element
+ * @returns a string such that ele.ownerDocument.querySelector(reply) === ele
+ * and ele.ownerDocument.querySelectorAll(reply).length === 1
+ */
+CssLogic.findCssSelector = function CssLogic_findCssSelector(ele) {
+  var document = ele.ownerDocument;
+  if (ele.id && document.getElementById(ele.id) === ele) {
+    return '#' + ele.id;
+  }
+
+  // Inherently unique by tag name
+  var tagName = ele.tagName.toLowerCase();
+  if (tagName === 'html') {
+    return 'html';
+  }
+  if (tagName === 'head') {
+    return 'head';
+  }
+  if (tagName === 'body') {
+    return 'body';
+  }
+
+  if (ele.parentNode == null) {
+    console.log('danger: ' + tagName);
+  }
+
+  // We might be able to find a unique class name
+  var selector, index, matches;
+  if (ele.classList.length > 0) {
+    for (var i = 0; i < ele.classList.length; i++) {
+      // Is this className unique by itself?
+      selector = '.' + ele.classList.item(i);
+      matches = document.querySelectorAll(selector);
+      if (matches.length === 1) {
+        return selector;
+      }
+      // Maybe it's unique with a tag name?
+      selector = tagName + selector;
+      matches = document.querySelectorAll(selector);
+      if (matches.length === 1) {
+        return selector;
+      }
+      // Maybe it's unique using a tag name and nth-child
+      index = positionInNodeList(ele, ele.parentNode.children) + 1;
+      selector = selector + ':nth-child(' + index + ')';
+      matches = document.querySelectorAll(selector);
+      if (matches.length === 1) {
+        return selector;
+      }
+    }
+  }
+
+  // So we can be unique w.r.t. our parent, and use recursion
+  index = positionInNodeList(ele, ele.parentNode.children) + 1;
+  selector = CssLogic_findCssSelector(ele.parentNode) + ' > ' +
+          tagName + ':nth-child(' + index + ')';
+
+  return selector;
+};
+
+/**
  * A safe way to access cached bits of information about a stylesheet.
  *
  * @constructor
  * @param {CssLogic} aCssLogic pointer to the CssLogic instance working with
  * this CssSheet object.
  * @param {CSSStyleSheet} aDomSheet reference to a DOM CSSStyleSheet object.
  * @param {number} aIndex tells the index/position of the stylesheet within the
  * main document.
--- a/browser/locales/en-US/chrome/browser/devtools/inspector.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/inspector.dtd
@@ -1,13 +1,16 @@
 <!ENTITY inspectorHTMLCopyInner.label       "Copy Inner HTML">
 <!ENTITY inspectorHTMLCopyInner.accesskey   "I">
 
 <!ENTITY inspectorHTMLCopyOuter.label       "Copy Outer HTML">
 <!ENTITY inspectorHTMLCopyOuter.accesskey   "O">
 
+<!ENTITY inspectorCopyUniqueSelector.label       "Copy Unique Selector">
+<!ENTITY inspectorCopyUniqueSelector.accesskey   "U">
+
 <!ENTITY inspectorHTMLDelete.label          "Delete Node">
 <!ENTITY inspectorHTMLDelete.accesskey      "D">
 
 <!ENTITY inspector.selectButton.tooltip     "Select element with mouse">
 
 <!ENTITY inspectorSearchHTML.label          "Search HTML">
 <!ENTITY inspectorSearchHTML.key            "F">
--- a/browser/themes/gnomestripe/browser.css
+++ b/browser/themes/gnomestripe/browser.css
@@ -2125,16 +2125,20 @@ html|*.highlighter-nodeinfobar-pseudo-cl
 
 html|*#gcli-tooltip-frame,
 html|*#gcli-output-frame {
   padding: 0;
   border-width: 0;
   background-color: transparent;
 }
 
+.gcli-panel {
+  padding: 0;
+}
+
 #gcli-output,
 #gcli-tooltip {
   border-width: 0;
   background-color: transparent;
   -moz-appearance: none;
   margin-bottom: -2px;
 }
 
--- a/browser/themes/gnomestripe/devtools/commandline.css
+++ b/browser/themes/gnomestripe/devtools/commandline.css
@@ -67,16 +67,20 @@
 .gcli-row-out pre {
   color: hsl(210,30%,95%);
 }
 
 .gcli-row-out pre {
   font-size: 80%;
 }
 
+.gcli-row-out td {
+  white-space: nowrap;
+}
+
 .gcli-out-shortcut,
 .gcli-help-synopsis {
   padding: 0 3px;
   margin: 0 4px;
   font-weight: normal;
   font-size: 90%;
   border-radius: 3px;
   background-color: hsl(210,11%,16%);
--- a/browser/themes/gnomestripe/devtools/common.css
+++ b/browser/themes/gnomestripe/devtools/common.css
@@ -169,18 +169,16 @@
 }
 
 .devtools-searchinput > .textbox-input-box > .textbox-input::-moz-placeholder {
   color: hsl(208,10%,66%);
   opacity: 1.0;
 }
 
 .devtools-no-search-result {
-  transition-property: box-shadow, border-color, background-image;
-  transition-duration: 150ms;
   box-shadow: inset 0 0 0 1px hsla(0,68%,6%,.35);
   border-color: hsl(10,70%,40%) hsl(10,75%,37%) hsl(10,80%,35%) !important;
   background-image: url(magnifying-glass.png), -moz-linear-gradient(hsla(1,16%,76%,.45), hsla(1,16%,76%,.75));
 }
 
 /* Close button */
 
 .devtools-closebutton {
--- a/browser/themes/gnomestripe/devtools/inspector.css
+++ b/browser/themes/gnomestripe/devtools/inspector.css
@@ -220,17 +220,17 @@
 }
 
 #inspector-searchbox:not([focused]):not([filled]) > .textbox-input-box {
   overflow: hidden;
 }
 
 #inspector-searchbox:not([focused]):not([filled]) {
   max-width: 20px !important;
-  -moz-padding-end: 6px;
+  -moz-padding-end: 5px;
   -moz-padding-start: 22px;
   background-position: 8px center, top left, top left;
 }
 
 #inspector-searchbox[focused],
 #inspector-searchbox[filled] {
   max-width: 200px !important;
 }
--- a/browser/themes/gnomestripe/devtools/orion.css
+++ b/browser/themes/gnomestripe/devtools/orion.css
@@ -50,25 +50,40 @@
   cursor: pointer;
   width: 16px;
   height: 16px;
   display: inline-block;
   vertical-align: middle;
   background-position: center;
   background-repeat: no-repeat;
 }
-.annotationHTML.task {
+.annotation.task .annotationHTML {
   background-image: url("chrome://browser/skin/devtools/orion-task.png");
 }
-.annotationHTML.breakpoint {
+.annotation.breakpoint .annotationHTML {
   background-image: url("chrome://browser/skin/devtools/orion-breakpoint.png");
 }
-.annotationHTML.debugLocation {
+.annotation.debugLocation .annotationHTML {
   background-image: url("chrome://browser/skin/devtools/orion-debug-location.png");
 }
+.annotation.breakpoint.debugLocation .annotationHTML,
+.annotation.task.debugLocation .annotationHTML {
+  background-position: center, center;
+  background-repeat: no-repeat, no-repeat;
+  background-size: 75%, 100%;
+}
+.annotation.breakpoint.debugLocation .annotationHTML {
+  background-image: url("chrome://browser/skin/devtools/orion-debug-location.png"),
+                    url("chrome://browser/skin/devtools/orion-breakpoint.png");
+}
+
+.annotation.task.debugLocation .annotationHTML {
+  background-image: url("chrome://browser/skin/devtools/orion-debug-location.png"),
+                    url("chrome://browser/skin/devtools/orion-task.png");
+}
 
 /* Styles for the overview ruler  */
 .annotationOverview {
   cursor: pointer;
   border-radius: 2px;
   left: 2px;
   width: 8px;
 }
--- a/browser/themes/pinstripe/devtools/commandline.css
+++ b/browser/themes/pinstripe/devtools/commandline.css
@@ -69,16 +69,20 @@
 .gcli-row-out pre {
   color: hsl(210,30%,95%);
 }
 
 .gcli-row-out pre {
   font-size: 80%;
 }
 
+.gcli-row-out td {
+  white-space: nowrap;
+}
+
 .gcli-out-shortcut,
 .gcli-help-synopsis {
   padding: 0 3px;
   margin: 0 4px;
   font-weight: normal;
   font-size: 90%;
   border-radius: 3px;
   background-color: hsl(210,11%,16%);
--- a/browser/themes/pinstripe/devtools/common.css
+++ b/browser/themes/pinstripe/devtools/common.css
@@ -174,18 +174,16 @@
 }
 
 .devtools-searchinput > .textbox-input-box > .textbox-input::-moz-placeholder {
   opacity: 1.0;
   color: hsl(208,10%,66%);
 }
 
 .devtools-no-search-result {
-  transition-property: box-shadow, border-color, background-image;
-  transition-duration: 150ms;
   box-shadow: inset 0 0 0 1px hsla(0,68%,6%,.35);
   border-color: hsl(10,70%,40%) hsl(10,75%,37%) hsl(10,80%,35%) !important;
   background-image: url(magnifying-glass.png), -moz-linear-gradient(hsla(1,16%,76%,.45), hsla(1,16%,76%,.75));
 }
 
 /* Close button */
 
 .devtools-closebutton {
--- a/browser/themes/pinstripe/devtools/orion.css
+++ b/browser/themes/pinstripe/devtools/orion.css
@@ -50,25 +50,40 @@
   cursor: pointer;
   width: 16px;
   height: 16px;
   display: inline-block;
   vertical-align: middle;
   background-position: center;
   background-repeat: no-repeat;
 }
-.annotationHTML.task {
+.annotation.task .annotationHTML {
   background-image: url("chrome://browser/skin/devtools/orion-task.png");
 }
-.annotationHTML.breakpoint {
+.annotation.breakpoint .annotationHTML {
   background-image: url("chrome://browser/skin/devtools/orion-breakpoint.png");
 }
-.annotationHTML.debugLocation {
+.annotation.debugLocation .annotationHTML {
   background-image: url("chrome://browser/skin/devtools/orion-debug-location.png");
 }
+.annotation.breakpoint.debugLocation .annotationHTML,
+.annotation.task.debugLocation .annotationHTML {
+  background-position: center, center;
+  background-repeat: no-repeat, no-repeat;
+  background-size: 75%, 100%;
+}
+.annotation.breakpoint.debugLocation .annotationHTML {
+  background-image: url("chrome://browser/skin/devtools/orion-debug-location.png"),
+                    url("chrome://browser/skin/devtools/orion-breakpoint.png");
+}
+
+.annotation.task.debugLocation .annotationHTML {
+  background-image: url("chrome://browser/skin/devtools/orion-debug-location.png"),
+                    url("chrome://browser/skin/devtools/orion-task.png");
+}
 
 /* Styles for the overview ruler  */
 .annotationOverview {
   cursor: pointer;
   border-radius: 2px;
   left: 2px;
   width: 8px;
 }
--- a/browser/themes/winstripe/devtools/commandline.css
+++ b/browser/themes/winstripe/devtools/commandline.css
@@ -67,16 +67,20 @@
 .gcli-row-out pre {
   color: hsl(210,30%,95%);
 }
 
 .gcli-row-out pre {
   font-size: 80%;
 }
 
+.gcli-row-out td {
+  white-space: nowrap;
+}
+
 .gcli-out-shortcut,
 .gcli-help-synopsis {
   padding: 0 3px;
   margin: 0 4px;
   font-weight: normal;
   font-size: 90%;
   border-radius: 3px;
   background-color: hsl(210,24%,16%);
--- a/browser/themes/winstripe/devtools/common.css
+++ b/browser/themes/winstripe/devtools/common.css
@@ -180,17 +180,16 @@
 }
 
 .devtools-searchinput > .textbox-input-box > .textbox-input::-moz-placeholder {
   opacity: 1.0;
   color: hsl(208,10%,66%);
 }
 
 .devtools-no-search-result {
-  transition-property: box-shadow, border-color, background-image;
   box-shadow: inset 0 0 0 1px hsla(0,68%,6%,.35);
   border-color: hsl(10,70%,40%) hsl(10,75%,37%) hsl(10,80%,35%) !important;
   background-image: url(magnifying-glass.png), -moz-linear-gradient(hsla(1,16%,76%,.45), hsla(1,16%,76%,.75));
 }
 
 /* Close button */
 
 .devtools-closebutton {
--- a/browser/themes/winstripe/devtools/orion.css
+++ b/browser/themes/winstripe/devtools/orion.css
@@ -50,25 +50,40 @@
   cursor: pointer;
   width: 16px;
   height: 16px;
   display: inline-block;
   vertical-align: middle;
   background-position: center;
   background-repeat: no-repeat;
 }
-.annotationHTML.task {
+.annotation.task .annotationHTML {
   background-image: url("chrome://browser/skin/devtools/orion-task.png");
 }
-.annotationHTML.breakpoint {
+.annotation.breakpoint .annotationHTML {
   background-image: url("chrome://browser/skin/devtools/orion-breakpoint.png");
 }
-.annotationHTML.debugLocation {
+.annotation.debugLocation .annotationHTML {
   background-image: url("chrome://browser/skin/devtools/orion-debug-location.png");
 }
+.annotation.breakpoint.debugLocation .annotationHTML,
+.annotation.task.debugLocation .annotationHTML {
+  background-position: center, center;
+  background-repeat: no-repeat, no-repeat;
+  background-size: 75%, 100%;
+}
+.annotation.breakpoint.debugLocation .annotationHTML {
+  background-image: url("chrome://browser/skin/devtools/orion-debug-location.png"),
+                    url("chrome://browser/skin/devtools/orion-breakpoint.png");
+}
+
+.annotation.task.debugLocation .annotationHTML {
+  background-image: url("chrome://browser/skin/devtools/orion-debug-location.png"),
+                    url("chrome://browser/skin/devtools/orion-task.png");
+}
 
 /* Styles for the overview ruler  */
 .annotationOverview {
   cursor: pointer;
   border-radius: 2px;
   left: 2px;
   width: 8px;
 }
--- a/netwerk/cache/nsCacheService.cpp
+++ b/netwerk/cache/nsCacheService.cpp
@@ -714,27 +714,18 @@ nsCacheProfilePrefObserver::ReadPrefs(ns
             nsCOMPtr<nsIFile> profDir;
             NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
                                    getter_AddRefs(profDir));
             NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
                                    getter_AddRefs(directory));
             if (!directory)
                 directory = profDir;
             else if (profDir) {
-                bool same;
-                if (NS_SUCCEEDED(profDir->Equals(directory, &same)) && !same) {
-                    // We no longer store the cache directory in the main
-                    // profile directory, so we should cleanup the old one.
-                    rv = profDir->AppendNative(NS_LITERAL_CSTRING("Cache"));
-                    if (NS_SUCCEEDED(rv)) {
-                        bool exists;
-                        if (NS_SUCCEEDED(profDir->Exists(&exists)) && exists)
-                            nsDeleteDir::DeleteDir(profDir, false);
-                    }
-                }
+                nsCacheService::MoveOrRemoveDiskCache(profDir, directory, 
+                                                      "Cache");
             }
         }
         // use file cache in build tree only if asked, to avoid cache dir litter
         if (!directory && PR_GetEnv("NECKO_DEV_ENABLE_DISK_CACHE")) {
             rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
                                         getter_AddRefs(directory));
         }
         if (directory)
@@ -790,16 +781,20 @@ nsCacheProfilePrefObserver::ReadPrefs(ns
             // try to get the profile directory (there may not be a profile yet)
             nsCOMPtr<nsIFile> profDir;
             NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
                                    getter_AddRefs(profDir));
             NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
                                    getter_AddRefs(directory));
             if (!directory)
                 directory = profDir;
+            else if (profDir) {
+                nsCacheService::MoveOrRemoveDiskCache(profDir, directory, 
+                                                      "OfflineCache");
+            }
         }
 #if DEBUG
         if (!directory) {
             // use current process directory during development
             rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
                                         getter_AddRefs(directory));
         }
 #endif
@@ -3003,16 +2998,67 @@ nsCacheService::SetDiskSmartSize_Locked(
         DispatchToCacheIOThread(event);
     } else {
         return NS_ERROR_FAILURE;
     }
 
     return NS_OK;
 }
 
+void
+nsCacheService::MoveOrRemoveDiskCache(nsIFile *aOldCacheDir, 
+                                      nsIFile *aNewCacheDir,
+                                      const char *aCacheSubdir)
+{
+    bool same;
+    if (NS_FAILED(aOldCacheDir->Equals(aNewCacheDir, &same)) || same)
+        return;
+
+    nsCOMPtr<nsIFile> aOldCacheSubdir;
+    aOldCacheDir->Clone(getter_AddRefs(aOldCacheSubdir));
+
+    nsresult rv = aOldCacheSubdir->AppendNative(
+        nsDependentCString(aCacheSubdir));
+    if (NS_FAILED(rv))
+        return;
+
+    bool exists;
+    if (NS_FAILED(aOldCacheSubdir->Exists(&exists)) || !exists)
+        return;
+
+    nsCOMPtr<nsIFile> aNewCacheSubdir;
+    aNewCacheDir->Clone(getter_AddRefs(aNewCacheSubdir));
+
+    rv = aNewCacheSubdir->AppendNative(nsDependentCString(aCacheSubdir));
+    if (NS_FAILED(rv))
+        return;
+    
+    nsAutoCString newPath;
+    rv = aNewCacheSubdir->GetNativePath(newPath);
+    if (NS_FAILED(rv))
+        return;
+        
+    if (NS_SUCCEEDED(aNewCacheSubdir->Exists(&exists)) && !exists) {
+        // New cache directory does not exist, try to move the old one here
+        // rename needs an empty target directory
+        rv = aNewCacheSubdir->Create(nsIFile::DIRECTORY_TYPE, 0777); 
+        if (NS_SUCCEEDED(rv)) {
+            nsAutoCString oldPath;
+            rv = aOldCacheSubdir->GetNativePath(oldPath);
+            if (NS_FAILED(rv))
+                return;
+            if(rename(oldPath.get(), newPath.get()) == 0)
+                return;
+        }
+    }
+    
+    // Delay delete by 1 minute to avoid IO thrash on startup.
+    nsDeleteDir::DeleteDir(aOldCacheSubdir, false, 60000);
+}
+
 static bool
 IsEntryPrivate(nsCacheEntry* entry)
 {
     return entry->IsPrivate();
 }
 
 void
 nsCacheService::LeavePrivateBrowsing()
--- a/netwerk/cache/nsCacheService.h
+++ b/netwerk/cache/nsCacheService.h
@@ -191,16 +191,20 @@ public:
 
     static void      SetMemoryCache();
 
     static void      SetCacheCompressionLevel(int32_t level);
 
     // Starts smart cache size computation if disk device is available
     static nsresult  SetDiskSmartSize();
 
+    static void      MoveOrRemoveDiskCache(nsIFile *aOldCacheDir,
+                                           nsIFile *aNewCacheDir,
+                                           const char *aCacheSubdir);
+
     nsresult         Init();
     void             Shutdown();
 
     static bool      IsInitialized()
     {
       if (!gService) {
           return false;
       }
--- a/startupcache/StartupCache.cpp
+++ b/startupcache/StartupCache.cpp
@@ -176,16 +176,30 @@ StartupCache::Init()
     nsCOMPtr<nsIFile> file;
     rv = NS_GetSpecialDirectory("ProfLDS",
                                 getter_AddRefs(file));
     if (NS_FAILED(rv)) {
       // return silently, this will fail in mochitests's xpcshell process.
       return rv;
     }
 
+    nsCOMPtr<nsIFile> profDir;
+    NS_GetSpecialDirectory("ProfDS", getter_AddRefs(profDir));
+    if (profDir) {
+      bool same;
+      if (NS_SUCCEEDED(profDir->Equals(file, &same)) && !same) {
+        // We no longer store the startup cache in the main profile
+        // directory, so we should cleanup the old one.
+        if (NS_SUCCEEDED(
+              profDir->AppendNative(NS_LITERAL_CSTRING("startupCache")))) {
+          profDir->Remove(true);
+        }
+      }
+    }
+
     rv = file->AppendNative(NS_LITERAL_CSTRING("startupCache"));
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Try to create the directory if it's not there yet
     rv = file->Create(nsIFile::DIRECTORY_TYPE, 0777);
     if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)
       return rv;
 
--- a/toolkit/devtools/debugger/server/dbg-script-actors.js
+++ b/toolkit/devtools/debugger/server/dbg-script-actors.js
@@ -48,16 +48,27 @@ function ThreadActor(aHooks, aGlobal)
    * cache holds two scripts with the URL http://foo.com/ starting at lines 4
    * and 10, then the corresponding cache will be:
    * this._scripts: {
    *   'http://foo.com/': [,,,,[Debugger.Script],,,,,,[Debugger.Script]]
    * }
    */
   this._scripts = {};
 
+  // A cache of prototype chains for objects that have received a
+  // prototypeAndProperties request. Due to the way the debugger frontend works,
+  // this corresponds to a cache of prototype chains that the user has been
+  // inspecting in the variables tree view. This allows the debugger to evaluate
+  // native getter methods for WebIDL attributes that are meant to be called on
+  // the instace and not on the prototype.
+  //
+  // The map keys are Debugger.Object instances requested by the client and the
+  // values are arrays of Debugger.Objects that make up their prototype chain.
+  this._protoChains = new Map();
+
   this.findGlobals = this.globalManager.findGlobals.bind(this);
   this.onNewGlobal = this.globalManager.onNewGlobal.bind(this);
 }
 
 /**
  * The breakpoint store must be shared across instances of ThreadActor so that
  * page reloads don't blow away all of our breakpoints.
  */
@@ -177,16 +188,17 @@ ThreadActor.prototype = {
 
   disconnect: function TA_disconnect() {
     if (this._state == "paused") {
       this.onResume();
     }
 
     this._state = "exited";
 
+    this._protoChains.clear();
     this.clearDebuggees();
 
     if (!this.dbg) {
       return;
     }
     this.dbg.enabled = false;
     this.dbg = null;
   },
@@ -1218,16 +1230,61 @@ ThreadActor.prototype = {
         let bp = existing[line];
         // Limit search to the line numbers contained in the new script.
         if (bp && line >= aScript.startLine && line <= endLine) {
           this._setBreakpoint(bp);
         }
       }
     }
     return true;
+  },
+
+  /**
+   * Finds the prototype chain cache for the provided object and returns the
+   * full cache entry, or null if the object is not found in the cache.
+   *
+   * @param aObject Debugger.Object
+   *        The object to look up.
+   * @returns the array of objects that correspond to the found cache entry.
+   */
+  _findProtoChain: function TA__findProtoChain(aObject) {
+    if (this._protoChains.has(aObject)) {
+      return this._protoChains.get(aObject);
+    }
+    for (let [obj, chain] of this._protoChains) {
+      if (chain.indexOf(aObject) != -1) {
+        return chain;
+      }
+    }
+    return null;
+  },
+
+  /**
+   * Removes the specified object and its prototype chain from the prototype
+   * chain cache. Returns true if the removal was successful and false if the
+   * object was not found in the cache.
+   *
+   * @param aObject Debugger.Object
+   *        The object to remove from the cache.
+   * @returns true if the object was removed, false if it was not found.
+   */
+  _removeFromProtoChain:function TA__removeFromProtoChain(aObject) {
+    let retval = false;
+    if (this._protoChains.has(aObject)) {
+      this._protoChains.delete(aObject);
+      retval = true;
+    }
+    for (let [obj, chain] of this._protoChains) {
+      let index = chain.indexOf(aObject);
+      if (index != -1) {
+        chain.splice(index);
+        retval = true;
+      }
+    }
+    return retval;
   }
 
 };
 
 ThreadActor.prototype.requestTypes = {
   "attach": ThreadActor.prototype.onAttach,
   "detach": ThreadActor.prototype.onDetach,
   "resume": ThreadActor.prototype.onResume,
@@ -1527,16 +1584,21 @@ update(ObjectActor.prototype, {
   },
 
   /**
    * Releases this actor from the pool.
    */
   release: function OA_release() {
     this.registeredPool.objectActors.delete(this.obj);
     this.registeredPool.removeActor(this);
+    this.disconnect();
+  },
+
+  disconnect: function OA_disconnect() {
+    this.threadActor._removeFromProtoChain(this.obj);
   },
 
   /**
    * Handle a protocol request to provide the names of the properties defined on
    * the object and not its prototype.
    *
    * @param aRequest object
    *        The protocol request object.
@@ -1551,27 +1613,37 @@ update(ObjectActor.prototype, {
    * Handle a protocol request to provide the prototype and own properties of
    * the object.
    *
    * @param aRequest object
    *        The protocol request object.
    */
   onPrototypeAndProperties:
   PauseScopedActor.withPaused(function OA_onPrototypeAndProperties(aRequest) {
+    if (this.obj.proto) {
+      // Store the object and its prototype to the prototype chain cache, so that
+      // we can evaluate native getter methods for WebIDL attributes that are
+      // meant to be called on the instace and not on the prototype.
+      //
+      // TODO: after bug 801084, we could restrict the cache to objects where
+      // this.obj.hostAnnotations.isWebIDLObject == true
+      let chain = this.threadActor._findProtoChain(this.obj);
+      if (!chain) {
+        chain = [];
+        this.threadActor._protoChains.set(this.obj, chain);
+        chain.push(this.obj);
+      }
+      if (chain.indexOf(this.obj.proto) == -1) {
+        chain.push(this.obj.proto);
+      }
+    }
+
     let ownProperties = {};
-    for each (let name in this.obj.getOwnPropertyNames()) {
-      try {
-        let desc = this.obj.getOwnPropertyDescriptor(name);
-        ownProperties[name] = this._propertyDescriptor(desc);
-      } catch (e if e.name == "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO") {
-        // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
-        // allowed.
-        dumpn("Error while getting the property descriptor for " + name +
-              ": " + e.name);
-      }
+    for (let name of this.obj.getOwnPropertyNames()) {
+      ownProperties[name] = this._propertyDescriptor(name);
     }
     return { from: this.actorID,
              prototype: this.threadActor.createValueGrip(this.obj.proto),
              ownProperties: ownProperties };
   }),
 
   /**
    * Handle a protocol request to provide the prototype of the object.
@@ -1592,40 +1664,98 @@ update(ObjectActor.prototype, {
    *        The protocol request object.
    */
   onProperty: PauseScopedActor.withPaused(function OA_onProperty(aRequest) {
     if (!aRequest.name) {
       return { error: "missingParameter",
                message: "no property name was specified" };
     }
 
-    let desc = this.obj.getOwnPropertyDescriptor(aRequest.name);
     return { from: this.actorID,
-             descriptor: this._propertyDescriptor(desc) };
+             descriptor: this._propertyDescriptor(aRequest.name) };
   }),
 
   /**
    * A helper method that creates a property descriptor for the provided object,
    * properly formatted for sending in a protocol response.
    *
-   * @param aObject object
-   *        The object that the descriptor is generated for.
+   * @param string aName
+   *        The property that the descriptor is generated for.
    */
-  _propertyDescriptor: function OA_propertyDescriptor(aObject) {
-    let descriptor = {};
-    descriptor.configurable = aObject.configurable;
-    descriptor.enumerable = aObject.enumerable;
-    if (aObject.value !== undefined) {
-      descriptor.writable = aObject.writable;
-      descriptor.value = this.threadActor.createValueGrip(aObject.value);
+  _propertyDescriptor: function OA_propertyDescriptor(aName) {
+    let desc;
+    try {
+      desc = this.obj.getOwnPropertyDescriptor(aName);
+    } catch (e) {
+      // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
+      // allowed (bug 560072). Inform the user with a bogus, but hopefully
+      // explanatory, descriptor.
+      return {
+        configurable: false,
+        writable: false,
+        enumerable: false,
+        value: e.name
+      };
+    }
+
+    let retval = {
+      configurable: desc.configurable,
+      enumerable: desc.enumerable
+    };
+
+    if (desc.value !== undefined) {
+      retval.writable = desc.writable;
+      retval.value = this.threadActor.createValueGrip(desc.value);
     } else {
-      descriptor.get = this.threadActor.createValueGrip(aObject.get);
-      descriptor.set = this.threadActor.createValueGrip(aObject.set);
+
+      if ("get" in desc) {
+        let fn = desc.get;
+        if (fn && fn.callable && fn.class == "Function" &&
+            fn.script === undefined) {
+          // Maybe this is a DOM getter. Try calling it on every object in the
+          // prototype chain, until it doesn't throw.
+          let rv, chain = this.threadActor._findProtoChain(this.obj);
+          let index = chain.indexOf(this.obj);
+          for (let i = index; i >= 0; i--) {
+            // If we had hostAnnotations (bug 801084) we would have been able to
+            // filter on chain[i].hostAnnotations.isWebIDLObject or similar.
+            rv = fn.call(chain[i]);
+            // If the error D.O. wasn't completely opaque (bug 812764?), we
+            // could perhaps treat other errors differently.
+            if (rv && !("throw" in rv)) {
+              // If calling the getter produced a return value, create a data
+              // property descriptor.
+              if ("return" in rv) {
+                retval.value = this.threadActor.createValueGrip(rv.return);
+              } else if ("yield" in rv) {
+                retval.value = this.threadActor.createValueGrip(rv.yield);
+              }
+              break;
+            }
+          }
+
+          // If calling the getter didn't produce a data property descriptor,
+          // use the original accessor property descriptor.
+          if (!("value" in retval)) {
+            retval.get = this.threadActor.createValueGrip(fn);
+          }
+        } else {
+          // It doesn't look like a WebIDL attribute getter, just use the getter
+          // from the original accessor property descriptor.
+          retval.get = this.threadActor.createValueGrip(fn);
+        }
+      }
+
+      // If we couldn't convert it to a data property and there is a setter in
+      // the original property descriptor, use it.
+      if ("set" in desc && !("value" in retval)) {
+        retval.set = this.threadActor.createValueGrip(desc.set);
+      }
     }
-    return descriptor;
+    return retval;
   },
 
   /**
    * Handle a protocol request to provide the source code of a function.
    *
    * @param aRequest object
    *        The protocol request object.
    */
--- a/toolkit/xre/nsXREDirProvider.cpp
+++ b/toolkit/xre/nsXREDirProvider.cpp
@@ -1137,18 +1137,32 @@ nsXREDirProvider::GetUserDataDirectoryHo
 #elif defined(MOZ_WIDGET_GONK)
   rv = NS_NewNativeLocalFile(NS_LITERAL_CSTRING("/data/b2g"), true,
                              getter_AddRefs(localDir));
 #elif defined(XP_UNIX)
   const char* homeDir = getenv("HOME");
   if (!homeDir || !*homeDir)
     return NS_ERROR_FAILURE;
 
-  rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true,
-                             getter_AddRefs(localDir));
+  if (aLocal) {
+    // If $XDG_CACHE_HOME is defined use it, otherwise use $HOME/.cache.
+    const char* cacheHome = getenv("XDG_CACHE_HOME");
+    if (cacheHome && *cacheHome) {
+      rv = NS_NewNativeLocalFile(nsDependentCString(cacheHome), true,
+                                 getter_AddRefs(localDir));
+    } else {
+      rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true,
+                                 getter_AddRefs(localDir));
+      if (NS_SUCCEEDED(rv))
+        rv = localDir->AppendNative(NS_LITERAL_CSTRING(".cache"));
+    }
+  } else {
+    rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true,
+                               getter_AddRefs(localDir));
+  }
 #else
 #error "Don't know how to get product dir on your platform"
 #endif
 
   NS_IF_ADDREF(*aFile = localDir);
   return rv;
 }
 
@@ -1223,17 +1237,17 @@ nsXREDirProvider::GetUserDataDirectory(n
                                        const nsACString* aProfileName,
                                        const nsACString* aAppName,
                                        const nsACString* aVendorName)
 {
   nsCOMPtr<nsIFile> localDir;
   nsresult rv = GetUserDataDirectoryHome(getter_AddRefs(localDir), aLocal);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = AppendProfilePath(localDir, aProfileName, aAppName, aVendorName);
+  rv = AppendProfilePath(localDir, aProfileName, aAppName, aVendorName, aLocal);
   NS_ENSURE_SUCCESS(rv, rv);
 
 #ifdef DEBUG_jungshik
   nsAutoCString cwd;
   localDir->GetNativePath(cwd);
   printf("nsXREDirProvider::GetUserDataDirectory: %s\n", cwd.get());
 #endif
   rv = EnsureDirectoryExists(localDir);
@@ -1344,17 +1358,18 @@ nsXREDirProvider::AppendSysUserExtension
   return NS_OK;
 }
 
 
 nsresult
 nsXREDirProvider::AppendProfilePath(nsIFile* aFile,
                                     const nsACString* aProfileName,
                                     const nsACString* aAppName,
-                                    const nsACString* aVendorName)
+                                    const nsACString* aVendorName,
+                                    PRBool aLocal)
 {
   NS_ASSERTION(aFile, "Null pointer!");
   
   if (!gAppData) {
     return NS_ERROR_FAILURE;
   }
 
   nsAutoCString profile;
@@ -1406,28 +1421,31 @@ nsXREDirProvider::AppendProfilePath(nsIF
   // The parent of this directory is set in GetUserDataDirectoryHome
   // XXX: handle gAppData->profile properly
   // XXXsmaug ...and the rest of the profile creation!
   MOZ_ASSERT(!aAppName,
              "Profile creation for external applications is not implemented!");
   rv = aFile->AppendNative(nsDependentCString("mozilla"));
   NS_ENSURE_SUCCESS(rv, rv);
 #elif defined(XP_UNIX)
-  // Make it hidden (i.e. using the ".")
-  nsAutoCString folder(".");
+  nsAutoCString folder;
+  // Make it hidden (by starting with "."), except when local (the
+  // profile is already under ~/.cache or XDG_CACHE_HOME).
+  if (!aLocal)
+    folder.Assign('.');
 
   if (!profile.IsEmpty()) {
     // Skip any leading path characters
     const char* profileStart = profile.get();
     while (*profileStart == '/' || *profileStart == '\\')
       profileStart++;
 
     // On the off chance that someone wanted their folder to be hidden don't
     // let it become ".."
-    if (*profileStart == '.')
+    if (*profileStart == '.' && !aLocal)
       profileStart++;
 
     folder.Append(profileStart);
     ToLowerCase(folder);
 
     rv = AppendProfileString(aFile, folder.BeginReading());
   }
   else {
--- a/toolkit/xre/nsXREDirProvider.h
+++ b/toolkit/xre/nsXREDirProvider.h
@@ -109,17 +109,18 @@ protected:
   static nsresult EnsureDirectoryExists(nsIFile* aDirectory);
   void EnsureProfileFileExists(nsIFile* aFile);
 
   // Determine the profile path within the UAppData directory. This is different
   // on every major platform.
   static nsresult AppendProfilePath(nsIFile* aFile,
                                     const nsACString* aProfileName,
                                     const nsACString* aAppName,
-                                    const nsACString* aVendorName);
+                                    const nsACString* aVendorName,
+                                    PRBool aLocal);
 
   static nsresult AppendSysUserExtensionPath(nsIFile* aFile);
 
   // Internal helper that splits a path into components using the '/' and '\\'
   // delimiters.
   static inline nsresult AppendProfileString(nsIFile* aFile, const char* aPath);
 
   // Calculate and register extension and theme bundle directories.