merge m-c to fx-team
authorTim Taubert <ttaubert@mozilla.com>
Tue, 12 Feb 2013 19:08:06 +0100
changeset 131480 161a347bda5be7575b33d7603a078061be595290
parent 131472 860d7a47b675b212f98ccbc2cfe6a242ec0c377c (current diff)
parent 131479 c4fc63d7317bdbd14dd9a309ea935e722d1f6181 (diff)
child 131481 de6aed05a69b8af67e7b061057b6b8a47b42992c
child 131535 d388b8073cce4ab68914382332de9700574e3802
child 131804 4c5e63f15a8582498e1481b31faf7fc9970d76a7
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
161a347bda5b / 21.0a1 / 20130213031137 / files
nightly linux64
161a347bda5b / 21.0a1 / 20130213031137 / files
nightly mac
161a347bda5b / 21.0a1 / 20130213031137 / files
nightly win32
161a347bda5b / 21.0a1 / 20130213031137 / files
nightly win64
161a347bda5b / 21.0a1 / 20130213031137 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge m-c to fx-team
browser/themes/gnomestripe/browser.css
--- 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.