Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 03 Jun 2013 17:51:25 -0400
changeset 145348 ba22a7c4e2e59283ce07a0208e805a6db7d25fd1
parent 145347 281b0297de651f36b688e09a8e5600d2ff8057e1 (current diff)
parent 145313 57d30169ddd474792585b967f79d3e8a3202178c (diff)
child 145349 88b09f11a45a03689ba012aea271c83217a0bd77
push id2697
push userbbajaj@mozilla.com
push dateMon, 05 Aug 2013 18:49:53 +0000
treeherdermozilla-beta@dfec938c7b63 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone24.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/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1472,17 +1472,17 @@
                 this.tabContainer._handleTabTelemetryStart(t, aURI);
 
                 // kick the animation off
                 t.setAttribute("fadein", "true");
 
                 // This call to adjustTabstrip is redundant but needed so that
                 // when opening a second tab, the first tab's close buttons
                 // appears immediately rather than when the transition ends.
-                if (this.tabContainer.childNodes.length == 2)
+                if (this.tabs.length - this._removingTabs.length == 2)
                   this.tabContainer.adjustTabstrip();
               }.bind(this));
             }
 
             return t;
           ]]>
         </body>
       </method>
@@ -1613,16 +1613,23 @@
             }
 
             this.tabContainer._handleTabTelemetryStart(aTab);
 
             this._blurTab(aTab);
             aTab.style.maxWidth = ""; // ensure that fade-out transition happens
             aTab.removeAttribute("fadein");
 
+            if (this.tabs.length - this._removingTabs.length == 1) {
+              // The second tab just got closed and we will end up with a single
+              // one. Remove the first tab's close button immediately (if needed)
+              // rather than after the tabclose animation ends.
+              this.tabContainer.adjustTabstrip();
+            }
+
             setTimeout(function (tab, tabbrowser) {
               if (tab.parentNode &&
                   window.getComputedStyle(tab).maxWidth == "0.1px") {
                 NS_ASSERT(false, "Giving up waiting for the tab closing animation to finish (bug 608589)");
                 tabbrowser._endRemoveTab(tab);
               }
             }, 3000, aTab, this);
           ]]>
@@ -3158,34 +3165,46 @@
             this.visible = window.toolbar.visible;
           else
             this.visible = true;
         ]]></body>
       </method>
 
       <method name="adjustTabstrip">
         <body><![CDATA[
+          let numTabs = this.childNodes.length -
+                        this.tabbrowser._removingTabs.length;
           // modes for tabstrip
           // 0 - button on active tab only
           // 1 - close buttons on all tabs
           // 2 - no close buttons at all
           // 3 - close button at the end of the tabstrip
           switch (this.mCloseButtons) {
           case 0:
-            if (this.childNodes.length == 1 && this._closeWindowWithLastTab)
+            if (numTabs == 1 && this._closeWindowWithLastTab)
               this.setAttribute("closebuttons", "hidden");
             else
               this.setAttribute("closebuttons", "activetab");
             break;
           case 1:
-            if (this.childNodes.length == 1) {
+            if (numTabs == 1) {
               if (this._closeWindowWithLastTab)
                 this.setAttribute("closebuttons", "hidden");
               else
                 this.setAttribute("closebuttons", "alltabs");
+            } else if (numTabs == 2) {
+              // This is an optimization to avoid layout flushes by calling
+              // getBoundingClientRect() when we just opened a second tab. In
+              // this case it's highly unlikely that the tab width is smaller
+              // than mTabClipWidth and the tab close button obscures too much
+              // of the tab's label. In the edge case of the window being too
+              // narrow (or if tabClipWidth has been set to a way higher value),
+              // we'll correct the 'closebuttons' attribute after the tabopen
+              // animation has finished.
+              this.setAttribute("closebuttons", "alltabs");
             } else {
               let tab = this.tabbrowser.visibleTabs[this.tabbrowser._numPinnedTabs];
               if (tab && tab.getBoundingClientRect().width > this.mTabClipWidth)
                 this.setAttribute("closebuttons", "alltabs");
               else
                 this.setAttribute("closebuttons", "activetab");
             }
             break;
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -195,16 +195,17 @@ endif
                  browser_CTP_drag_drop.js \
                  browser_pluginplaypreview.js \
                  browser_pluginplaypreview2.js \
                  browser_private_browsing_window.js \
                  browser_relatedTabs.js \
                  browser_sanitize-passwordDisabledHosts.js \
                  browser_sanitize-sitepermissions.js \
                  browser_sanitize-timespans.js \
+                 browser_tabopen_reflows.js \
                  browser_clearplugindata.js \
                  browser_clearplugindata.html \
                  browser_clearplugindata_noage.html \
                  browser_popupUI.js \
                  browser_sanitizeDialog.js \
                  browser_save_link-perwindowpb.js \
                  browser_save_private_link_perwindowpb.js \
                  browser_save_video.js \
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_tabopen_reflows.js
@@ -0,0 +1,96 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+XPCOMUtils.defineLazyGetter(this, "docShell", () => {
+  return window.QueryInterface(Ci.nsIInterfaceRequestor)
+               .getInterface(Ci.nsIWebNavigation)
+               .QueryInterface(Ci.nsIDocShell);
+});
+
+const EXPECTED_REFLOWS = [
+  // b.stop() call in tabbrowser.addTab()
+  "stop@chrome://global/content/bindings/browser.xml|" +
+    "addTab@chrome://browser/content/tabbrowser.xml|",
+
+  // tabbrowser.adjustTabstrip() call after tabopen animation has finished
+  "adjustTabstrip@chrome://browser/content/tabbrowser.xml|" +
+    "_handleNewTab@chrome://browser/content/tabbrowser.xml|" +
+    "onxbltransitionend@chrome://browser/content/tabbrowser.xml|",
+
+  // switching focus in updateCurrentBrowser() causes reflows
+  "updateCurrentBrowser@chrome://browser/content/tabbrowser.xml|" +
+    "onselect@chrome://browser/content/browser.xul|",
+
+  // switching focus in openLinkIn() causes reflows
+  "openLinkIn@chrome://browser/content/utilityOverlay.js|" +
+    "openUILinkIn@chrome://browser/content/utilityOverlay.js|" +
+    "BrowserOpenTab@chrome://browser/content/browser.js|",
+
+  // accessing element.scrollPosition in _fillTrailingGap() flushes layout
+  "get_scrollPosition@chrome://global/content/bindings/scrollbox.xml|" +
+    "_fillTrailingGap@chrome://browser/content/tabbrowser.xml|" +
+    "_handleNewTab@chrome://browser/content/tabbrowser.xml|" +
+    "onxbltransitionend@chrome://browser/content/tabbrowser.xml|"
+];
+
+/*
+ * This test ensures that there are no unexpected
+ * uninterruptible reflows when opening new tabs.
+ */
+function test() {
+  waitForExplicitFinish();
+
+  // Add a reflow observer and open a new tab.
+  docShell.addWeakReflowObserver(observer);
+  BrowserOpenTab();
+
+  // Wait until the tabopen animation has finished.
+  waitForTransitionEnd(function () {
+    // Remove reflow observer and clean up.
+    docShell.removeWeakReflowObserver(observer);
+    gBrowser.removeCurrentTab();
+
+    finish();
+  });
+}
+
+let observer = {
+  reflow: function (start, end) {
+    // Gather information about the current code path.
+    let path = (new Error().stack).split("\n").slice(1).map(line => {
+      return line.replace(/:\d+$/, "");
+    }).join("|");
+
+    // Stack trace is empty. Reflow was triggered by native code.
+    if (path === "") {
+      return;
+    }
+
+    // Check if this is an expected reflow.
+    for (let stack of EXPECTED_REFLOWS) {
+      if (path.startsWith(stack)) {
+        ok(true, "expected uninterruptible reflow '" + stack + "'");
+        return;
+      }
+    }
+
+    ok(false, "unexpected uninterruptible reflow '" + path + "'");
+  },
+
+  reflowInterruptible: function (start, end) {
+    // We're not interested in interruptible reflows.
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
+                                         Ci.nsISupportsWeakReference])
+};
+
+function waitForTransitionEnd(callback) {
+  let tab = gBrowser.selectedTab;
+  tab.addEventListener("transitionend", function onEnd(event) {
+    if (event.propertyName === "max-width") {
+      tab.removeEventListener("transitionend", onEnd);
+      executeSoon(callback);
+    }
+  });
+}
--- a/browser/devtools/debugger/test/browser_dbg_globalactor-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_globalactor-01.js
@@ -22,36 +22,33 @@ function test()
          "testTabActor's actorPrefix should be used.");
       gClient.request({ to: globalActor, type: "ping" }, function(aResponse) {
         is(aResponse.pong, "pong", "Actor should respond to requests.");
         // Send another ping to see if the same actor is used.
         gClient.request({ to: globalActor, type: "ping" }, function(aResponse) {
           is(aResponse.pong, "pong", "Actor should respond to requests.");
 
           // Make sure that lazily-created actors are created only once.
-          let connections = Object.keys(DebuggerServer._connections);
-          info(connections.length + " connections are established.");
-          let connPrefix = connections[connections.length - 1];
-          ok(DebuggerServer._connections[connPrefix],
-             connPrefix + " is a valid connection.");
+          let conn = transport._serverConnection;
           // First we look for the pool of global actors.
-          let extraPools = DebuggerServer._connections[connPrefix]._extraPools;
+          let extraPools = conn._extraPools;
+
           let globalPool;
           for (let pool of extraPools) {
             if (Object.keys(pool._actors).some(function(elem) {
               // Tab actors are in the global pool.
-              let re = new RegExp(connPrefix + "tab", "g");
+              let re = new RegExp(conn._prefix + "tab", "g");
               return elem.match(re) !== null;
             })) {
               globalPool = pool;
               break;
             }
           }
           // Then we look if the global pool contains only one test actor.
-          let actorPrefix = connPrefix + "testone";
+          let actorPrefix = conn._prefix + "testone";
           let actors = Object.keys(globalPool._actors).join();
           info("Global actors: " + actors);
           isnot(actors.indexOf(actorPrefix), -1, "The test actor exists in the pool.");
           is(actors.indexOf(actorPrefix), actors.lastIndexOf(actorPrefix),
              "Only one actor exists in the pool.");
 
           finish_test();
         });
--- a/content/media/omx/OmxDecoder.cpp
+++ b/content/media/omx/OmxDecoder.cpp
@@ -93,16 +93,17 @@ status_t MediaStreamSource::initCheck() 
   return OK;
 }
 
 ssize_t MediaStreamSource::readAt(off64_t offset, void *data, size_t size)
 {
   char *ptr = static_cast<char *>(data);
   size_t todo = size;
   while (todo > 0) {
+    Mutex::Autolock autoLock(mLock);
     uint32_t bytesRead;
     if ((offset != mResource->Tell() &&
          NS_FAILED(mResource->Seek(nsISeekableStream::NS_SEEK_SET, offset))) ||
         NS_FAILED(mResource->Read(ptr, todo, &bytesRead))) {
       return ERROR_IO;
     }
 
     if (bytesRead == 0) {
--- a/content/media/omx/OmxDecoder.h
+++ b/content/media/omx/OmxDecoder.h
@@ -37,16 +37,17 @@ class VideoGraphicBuffer : public Graphi
 
 namespace android {
 
 // MediaStreamSource is a DataSource that reads from a MPAPI media stream.
 class MediaStreamSource : public DataSource {
   typedef mozilla::MediaResource MediaResource;
   typedef mozilla::AbstractMediaDecoder AbstractMediaDecoder;
 
+  Mutex mLock;
   nsRefPtr<MediaResource> mResource;
   AbstractMediaDecoder *mDecoder;
 public:
   MediaStreamSource(MediaResource* aResource,
                     AbstractMediaDecoder *aDecoder);
 
   virtual status_t initCheck() const;
   virtual ssize_t readAt(off64_t offset, void *data, size_t size);
--- a/dom/contacts/fallback/ContactDB.jsm
+++ b/dom/contacts/fallback/ContactDB.jsm
@@ -11,16 +11,17 @@ function debug(s) { dump("-*- ContactDB 
 
 const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
 Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
+Cu.import("resource://gre/modules/devtools/Console.jsm");
 
 const DB_NAME = "contacts";
 const DB_VERSION = 11;
 const STORE_NAME = "contacts";
 const SAVED_GETALL_STORE_NAME = "getallcache";
 const CHUNK_SIZE = 20;
 const REVISION_STORE = "revision";
 const REVISION_KEY = "revision";
@@ -795,17 +796,18 @@ ContactDB.prototype = {
    *        - filterOp
    *        - filterValue
    *        - count
    */
   find: function find(aSuccessCb, aFailureCb, aOptions) {
     if (DEBUG) debug("ContactDB:find val:" + aOptions.filterValue + " by: " + aOptions.filterBy + " op: " + aOptions.filterOp);
     let self = this;
     this.newTxn("readonly", STORE_NAME, function (txn, store) {
-      if (aOptions && (["equals", "contains", "match"].indexOf(aOptions.filterOp) >= 0)) {
+      let filterOps = ["equals", "contains", "match", "startsWith"];
+      if (aOptions && (filterOps.indexOf(aOptions.filterOp) >= 0)) {
         self._findWithIndex(txn, store, aOptions);
       } else {
         self._findAll(txn, store, aOptions);
       }
     }, aSuccessCb, aFailureCb);
   },
 
   _findWithIndex: function _findWithIndex(txn, store, options) {
@@ -857,16 +859,21 @@ ContactDB.prototype = {
           return txn.abort();
         }
 
         let index = store.index("telMatch");
         let normalized = PhoneNumberUtils.normalize(options.filterValue,
                                                     /*numbersOnly*/ true);
         request = index.mozGetAll(normalized, limit);
       } else {
+        // XXX: "contains" should be handled separately, this is "startsWith"
+        if (options.filterOp === 'contains' && key !== 'tel') {
+          console.warn("ContactDB: 'contains' only works for 'tel'. " +
+                       "Falling back to 'startsWith'.");
+        }
         // not case sensitive
         let tmp = options.filterValue.toString().toLowerCase();
         if (key === "tel") {
           tmp = PhoneNumberUtils.normalize(tmp, /*numbersOnly*/ true);
         }
         let range = this._global.IDBKeyRange.bound(tmp, tmp + "\uFFFF");
         let index = store.index(key + "LowerCase");
         request = index.mozGetAll(range, limit);
--- a/dom/contacts/tests/test_contacts_basics.html
+++ b/dom/contacts/tests/test_contacts_basics.html
@@ -333,17 +333,17 @@ var steps = [
       sample_id1 = createResult1.id;
       checkContacts(properties1, createResult1);
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving by substring 1");
     var options = {filterBy: ["givenName"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: properties1.givenName[1].substring(0,3)};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       is(req.result.length, 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       checkContacts(createResult1, properties1);
       dump("findResult: " + JSON.stringify(findResult1) + "\n");
@@ -381,17 +381,17 @@ var steps = [
   },
   function () {
     ok(true, "Retrieving by substring and update");
     mozContacts.oncontactchange = function(event) {
        is(event.contactID, findResult1.id, "Same contactID");
        is(event.reason, "update", "Same reason");
      }
     var options = {filterBy: ["givenName"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: properties1.givenName[0].substring(0,3)};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       is(req.result.length, 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       findResult1.jobTitle = ["new Job"];
       ok(findResult1.id == sample_id1, "Same ID");
       checkContacts(createResult1, properties1);
@@ -412,17 +412,17 @@ var steps = [
       ok(createResult2.id, "The contact now has an ID.");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving by substring 2");
     var options = {filterBy: ["givenName"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: properties1.givenName[0].substring(0,3)};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       is(req.result.length, 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       checkContacts(createResult1, findResult1);
       next();
     };
@@ -438,17 +438,17 @@ var steps = [
     req.onsuccess = function () {
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving by substring 3");
     var options = {filterBy: ["givenName"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: properties1.givenName[1].substring(0,3)};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       is(req.result.length, 0, "Found no contact.");
       next();
     };
     req.onerror = onFailure;
   },
@@ -462,17 +462,17 @@ var steps = [
     req.onsuccess = function () {
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving by substring 4");
     var options = {filterBy: ["givenName"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: properties1.givenName[1].substring(0,3)};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       is(req.result.length, 0, "Found no contact.");
       next();
     };
     req.onerror = onFailure;
   },
@@ -502,17 +502,17 @@ var steps = [
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving by substring tel1");
     var options = {filterBy: ["tel"],
                    filterOp: "contains",
-                   filterValue: properties1.tel[1].value.substring(0,5)};
+                   filterValue: properties1.tel[1].value.substring(2,5)};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       is(req.result.length, 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       checkContacts(createResult1, properties1);
       next();
     };
@@ -627,77 +627,77 @@ var steps = [
     req.onerror = function() {
       ok(true, "Failed");
       next();
     }
   },
   function () {
     ok(true, "Retrieving by substring tel2");
     var options = {filterBy: ["tel"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: "9876"};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       is(req.result.length, 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       checkContacts(createResult1, properties1);
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving by substring tel3");
     var options = {filterBy: ["tel"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: "98763456"};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       is(req.result.length, 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       checkContacts(createResult1, properties1);
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving by substring 5");
     var options = {filterBy: ["givenName"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: properties1.givenName[0].substring(0,3)};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       is(req.result.length, 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       checkContacts(createResult1, properties1);
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving by substring 6");
     var options = {filterBy: ["familyName", "givenName"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: properties1.givenName[0].substring(0,3)};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       is(req.result.length, 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       checkContacts(createResult1, properties1);
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving by substring3, Testing multi entry");
     var options = {filterBy: ["givenName", "familyName"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: properties1.familyName[1].substring(0,3).toLowerCase()};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       is(req.result.length, 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       checkContacts(createResult1, properties1);
       next();
@@ -788,32 +788,32 @@ var steps = [
       }
       req2.onerror = onFailure;
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Searching contacts by query");
     var options = {filterBy: ["givenName", "email"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: properties1.givenName[0].substring(0,4)};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       is(req.result.length, 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       checkContacts(findResult1, properties1);
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Searching contacts by query");
     var options = {filterBy: ["givenName", "email"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: properties1.givenName[0]};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       is(req.result.length, 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       checkContacts(findResult1, properties1);
       next();
@@ -839,17 +839,17 @@ var steps = [
     ok(true, "Modifying contact3");
     findResult1.email = [{value: properties1.nickname}];
     findResult1.nickname = "TEST";
     var newContact = new mozContact();
     newContact.init(findResult1);
     req = mozContacts.save(newContact);
     req.onsuccess = function () {
       var options = {filterBy: ["email", "givenName"],
-                     filterOp: "contains",
+                     filterOp: "startsWith",
                      filterValue: properties1.givenName[0]};
       // One contact has it in nickname and the other in email
       var req2 = mozContacts.find(options);
       req2.onsuccess = function () {
         is(req2.result.length, 2, "Found exactly 2 contacts.");
         ok(req2.result[0].id != req2.result[1].id, "Different ID");
         next();
       }
@@ -911,62 +911,62 @@ var steps = [
       is(req.result.length, 2, "Found exactly 2 contact.");
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     console.log("Searching contacts by query1");
     var options = {filterBy: ["givenName", "email"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: properties1.givenName[0].substring(0, 4)}
     req = mozContacts.find(options)
     req.onsuccess = function () {
       is(req.result.length, 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       checkContacts(findResult1, createResult1);
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Searching contacts by query2");
     var options = {filterBy: ["givenName", "email"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: properties2.givenName[0].substring(0, 4)};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       is(req.result.length, 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       is(findResult1.adr.length, 2, "Adr length 2");
       checkContacts(findResult1, createResult2);
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Searching contacts by tel");
     var options = {filterBy: ["tel"],
                    filterOp: "contains",
-                   filterValue: properties2.tel[0].value.substring(0, 7)};
+                   filterValue: properties2.tel[0].value.substring(3, 7)};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       is(req.result.length, 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id2, "Same ID");
       checkContacts(findResult1, createResult2);
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Searching contacts by email");
     var options = {filterBy: ["email"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: properties2.email[0].value.substring(0, 4)};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       is(req.result.length, 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id2, "Same ID");
       checkContacts(findResult1, createResult2);
       next();
@@ -1032,30 +1032,30 @@ var steps = [
       is(req.result.length, 10, "10 Entries.");
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving all contacts2");
     var options = {filterBy: ["givenName"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: properties1.givenName[0].substring(0, 4)};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       is(req.result.length, 20, "20 Entries.");
       checkContacts(createResult1, req.result[19]);
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving all contacts3");
     var options = {filterBy: ["givenName", "tel", "email"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: properties1.givenName[0].substring(0, 4),
                    filterLimit: 15 };
     req = mozContacts.find(options);
     req.onsuccess = function () {
       is(req.result.length, 15, "15 Entries.");
       checkContacts(createResult1, req.result[10]);
       next();
     }
@@ -1096,17 +1096,17 @@ var steps = [
       ok(cloned.givenName == "Tom", "New Name");
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving all contacts");
     var options = {filterBy: ["givenName"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: properties2.givenName[0].substring(0, 4)};
     req = mozContacts.find({});
     req.onsuccess = function () {
       is(req.result.length, 2, "2 Entries.");
       next();
     }
     req.onerror = onFailure;
   },
@@ -1280,19 +1280,19 @@ var steps = [
     req.onsuccess = function () {
       ok(createResult2.id, "The contact now has an ID.");
       sample_id2 = createResult2.id;
       next();
     };
     req.onerror = onFailure;
   },
   function () {
-    ok(true, "Test category search with contains");
+    ok(true, "Test category search with startsWith");
     var options = {filterBy: ["category"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: properties2.category[0]};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       is(req.result.length, 1, "1 Entry.");
       checkContacts(req.result[0], createResult2);
       next();
     }
     req.onerror = onFailure;
@@ -1329,17 +1329,17 @@ var steps = [
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Test category search with equals");
     var options = {filterBy: ["givenName"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: "5"};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       is(req.result.length, 1, "1 Entry.");
       checkContacts(req.result[0], createResult1);
       next();
     }
     req.onerror = onFailure;
@@ -1452,9 +1452,9 @@ function next() {
   index += 1;
 }
 
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(next);
 </script>
 </pre>
 </body>
-</html>
\ No newline at end of file
+</html>
--- a/dom/contacts/tests/test_contacts_blobs.html
+++ b/dom/contacts/tests/test_contacts_blobs.html
@@ -177,17 +177,17 @@ var steps = [
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving by substring");
     var options = {filterBy: ["givenName"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: properties1.givenName.substring(0,3)};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       ok(req.result.length == 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       verifyBlobArray(createResult1.photo, properties1.photo);
     };
@@ -203,17 +203,17 @@ var steps = [
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving by substring");
     var options = {filterBy: ["givenName"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: properties2.givenName.substring(0,3)};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       ok(req.result.length == 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       verifyBlobArray(createResult1.photo, properties2.photo);
     };
@@ -242,17 +242,17 @@ var steps = [
       is(createResult1.photo, null, "No photo")
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving by substring");
     var options = {filterBy: ["givenName"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: "asdf"};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       ok(req.result.length == 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       is(findResult1.photo, null, "No photo");
       next();
@@ -301,9 +301,9 @@ function next() {
   index += 1;
 }
 
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(next);
 </script>
 </pre>
 </body>
-</html>
\ No newline at end of file
+</html>
--- a/dom/contacts/tests/test_contacts_international.html
+++ b/dom/contacts/tests/test_contacts_international.html
@@ -94,31 +94,31 @@ var steps = [
       ok(createResult2.id, "The contact now has an ID.");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Searching for local number");
     var options = {filterBy: ["tel"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: number1.local};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       ok(req.result.length == 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Searching for international number");
     var options = {filterBy: ["tel"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: number1.international};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       ok(req.result.length == 0, "Found exactly 0 contacts.");
       next();
     };
     req.onerror = onFailure;
   },
@@ -166,55 +166,55 @@ var steps = [
     req = mozContacts.save(findResult1);
     req.onsuccess = function () {
       next();
     };
   },
   function () {
     ok(true, "Searching for local number");
     var options = {filterBy: ["tel"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: number1.local};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       ok(req.result.length == 0, "Found exactly 0 contact.");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Searching for local number");
     var options = {filterBy: ["tel"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: number1.international};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       ok(req.result.length == 0, "Found exactly 0 contact.");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Searching for local number");
     var options = {filterBy: ["tel"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: number2.local};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       ok(req.result.length == 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Searching for local number");
     var options = {filterBy: ["tel"],
-                   filterOp: "contains",
+                   filterOp: "startsWith",
                    filterValue: number2.international};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       ok(req.result.length == 0, "Found exactly 1 contact.");
       next();
     };
     req.onerror = onFailure;
   },
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -3053,16 +3053,24 @@ nsDOMDeviceStorage::GetRootDirectoryForF
   }
   if (!ds || !ds->mRootDirectory) {
     return NS_ERROR_FAILURE;
   }
   return ds->mRootDirectory->Clone(aRootDirectory);
 }
 
 NS_IMETHODIMP
+nsDOMDeviceStorage::GetDefault(bool* aDefault) {
+  nsString defaultStorageName;
+  GetWritableStorageName(mStorageType, defaultStorageName);
+  *aDefault = mStorageName.Equals(defaultStorageName);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMDeviceStorage::GetStorageName(nsAString& aStorageName)
 {
   aStorageName = mStorageName;
   return NS_OK;
 }
 
 already_AddRefed<DOMCursor>
 nsDOMDeviceStorage::Enumerate(const nsAString& aPath,
--- a/dom/interfaces/contacts/nsIContactProperties.idl
+++ b/dom/interfaces/contacts/nsIContactProperties.idl
@@ -36,17 +36,17 @@ interface nsIContactFindSortOptions : ns
   attribute DOMString sortBy;       // "givenName" or "familyName"
   attribute DOMString sortOrder;    // e.g. "descending"
 };
 
 [scriptable, uuid(28ce07d0-45d9-4b7a-8843-521df4edd8bc)]
 interface nsIContactFindOptions : nsIContactFindSortOptions
 {
   attribute DOMString filterValue;  // e.g. "Tom"
-  attribute DOMString filterOp;     // e.g. "contains"
+  attribute DOMString filterOp;     // e.g. "startsWith"
   attribute jsval filterBy;         // DOMString[], e.g. ["givenName", "nickname"]
   attribute unsigned long filterLimit;
 };
 
 [scriptable, uuid(6cb78b21-4218-414b-8a84-3b7bf0088b34)]
 interface nsIContactProperties : nsISupports
 {
   attribute jsval         name;               // DOMString[]
--- a/dom/interfaces/devicestorage/nsIDOMDeviceStorage.idl
+++ b/dom/interfaces/devicestorage/nsIDOMDeviceStorage.idl
@@ -6,17 +6,17 @@
 #include "nsIDOMEventTarget.idl"
 interface nsIDOMBlob;
 interface nsIDOMDOMRequest;
 interface nsIDOMDOMCursor;
 interface nsIDOMDeviceStorageChangeEvent;
 interface nsIDOMEventListener;
 interface nsIFile;
 
-[scriptable, uuid(7cef14d4-d767-4da7-a18e-32c5e009a8e4), builtinclass]
+[scriptable, uuid(7c1b2305-0f14-4c07-8a8a-359eeb850068), builtinclass]
 interface nsIDOMDeviceStorage : nsIDOMEventTarget
 {
     [implicit_jscontext] attribute jsval onchange;
     nsIDOMDOMRequest add(in nsIDOMBlob aBlob);
     nsIDOMDOMRequest addNamed(in nsIDOMBlob aBlob, in DOMString aName);
 
     nsIDOMDOMRequest get([Null(Stringify)] in DOMString aName);
     nsIDOMDOMRequest getEditable([Null(Stringify)] in DOMString aName);
@@ -25,10 +25,14 @@ interface nsIDOMDeviceStorage : nsIDOMEv
     nsIDOMDOMRequest freeSpace();
     nsIDOMDOMRequest usedSpace();
     nsIDOMDOMRequest available();
 
     // Note that the storageName is just a name (like sdcard), and doesn't
     // include any path information.
     readonly attribute DOMString storageName;
 
+    // Determines if this storage area is the one which will be used by default
+    // for storing new files.
+    readonly attribute bool default;
+
     [noscript] nsIFile getRootDirectoryForFile(in DOMString aName);
 };
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -262,21 +262,39 @@ var DebuggerServer = {
    *          the newly-created connection.
    */
   connectPipe: function DS_connectPipe() {
     this._checkInit();
 
     let serverTransport = new LocalDebuggerTransport;
     let clientTransport = new LocalDebuggerTransport(serverTransport);
     serverTransport.other = clientTransport;
-    this._onConnection(serverTransport);
+    let connection = this._onConnection(serverTransport);
+
+    // I'm putting this here because I trust you.
+    //
+    // There are times, when using a local connection, when you're going
+    // to be tempted to just get direct access to the server.  Resist that
+    // temptation!  If you succumb to that temptation, you will make the
+    // fine developers that work on Fennec and Firefox OS sad.  They're
+    // professionals, they'll try to act like they understand, but deep
+    // down you'll know that you hurt them.
+    //
+    // This reference allows you to give in to that temptation.  There are
+    // times this makes sense: tests, for example, and while porting a
+    // previously local-only codebase to the remote protocol.
+    //
+    // But every time you use this, you will feel the shame of having
+    // used a property that starts with a '_'.
+    clientTransport._serverConnection = connection;
 
     return clientTransport;
   },
 
+
   // nsIServerSocketListener implementation
 
   onSocketAccepted:
   makeInfallible(function DS_onSocketAccepted(aSocket, aTransport) {
     if (!this._allowConnection()) {
       return;
     }
     dumpn("New debugging connection on " + aTransport.host + ":" + aTransport.port);
@@ -313,16 +331,18 @@ var DebuggerServer = {
     let conn = new DebuggerServerConnection(connID, aTransport);
     this._connections[connID] = conn;
 
     // Create a root actor for the connection and send the hello packet.
     conn.rootActor = this.createRootActor(conn);
     conn.addActor(conn.rootActor);
     aTransport.send(conn.rootActor.sayHello());
     aTransport.ready();
+
+    return conn;
   },
 
   /**
    * Remove the connection from the debugging server.
    */
   _connectionClosed: function DS_connectionClosed(aConnection) {
     delete this._connections[aConnection.prefix];
   },