Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 06 May 2013 08:29:37 -0400
changeset 141910 4995d566bd6dd50bca5cf1a1a0b9cf047cc0d0dc
parent 141909 96e445cf42fbb25000069c19e4144ccf3dc755f0 (current diff)
parent 141868 afb7995ef276f6e1209d6cb605163bd24dd34766 (diff)
child 141911 aed0d9db6a9ce16b86f1a23cb7f391fd4c8d077f
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone23.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/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -376,16 +376,17 @@ pref("dom.mozBrowserFramesEnabled", true
 // We'll run out of PIDs on UNIX-y systems before we hit this limit.
 pref("dom.ipc.processCount", 100000);
 
 pref("dom.ipc.browser_frames.oop_by_default", false);
 
 // Temporary permission hack for WebSMS
 pref("dom.sms.enabled", true);
 pref("dom.sms.strict7BitEncoding", false); // Disabled by default.
+pref("dom.sms.requestStatusReport", false); // Disabled by default.
 
 // Temporary permission hack for WebContacts
 pref("dom.mozContacts.enabled", true);
 
 // WebAlarms
 pref("dom.mozAlarms.enabled", true);
 
 // SimplePush
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -159,16 +159,21 @@ SettingsListener.observe('language.curre
     function(value) {
       Services.prefs.setCharPref('dom.mms.retrieval_mode', value);
   });
 
   SettingsListener.observe('ril.sms.strict7BitEncoding.enabled', false,
     function(value) {
       Services.prefs.setBoolPref('dom.sms.strict7BitEncoding', value);
   });
+
+  SettingsListener.observe('ril.sms.requestStatusReport.enabled', false,
+    function(value) {
+      Services.prefs.setBoolPref('dom.sms.requestStatusReport', value);
+  });
 })();
 
 //=================== DeviceInfo ====================
 Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
 Components.utils.import('resource://gre/modules/ctypes.jsm');
 (function DeviceInfoToSettings() {
   XPCOMUtils.defineLazyServiceGetter(this, 'gSettingsService',
                                      '@mozilla.org/settingsService;1',
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -962,18 +962,18 @@ let SessionStoreInternal = {
       // save the state without this window to disk
       this.saveStateDelayed();
     }
 
     for (let i = 0; i < tabbrowser.tabs.length; i++) {
       this.onTabRemove(aWindow, tabbrowser.tabs[i], true);
     }
 
-    // cache the window state until the window is completely gone
-    aWindow.__SS_dyingCache = winData;
+    // Cache the window state until it is completely gone.
+    DyingWindowCache.set(aWindow, winData);
 
     delete aWindow.__SSi;
   },
 
   /**
    * On quit application requested
    */
   onQuitApplicationRequested: function ssi_onQuitApplicationRequested() {
@@ -1052,19 +1052,18 @@ let SessionStoreInternal = {
     // If the browser is shutting down, simply return after clearing the
     // session data on disk as this notification fires after the
     // quit-application notification so the browser is about to exit.
     if (this._loadState == STATE_QUITTING)
       return;
     this._lastSessionState = null;
     let openWindows = {};
     this._forEachBrowserWindow(function(aWindow) {
-      Array.forEach(aWindow.gBrowser.tabs, function(aTab) {
-        delete aTab.linkedBrowser.__SS_data;
-        delete aTab.linkedBrowser.__SS_tabStillLoading;
+      Array.forEach(aWindow.gBrowser.tabs, aTab => {
+        RestoringTabsData.remove(aTab.linkedBrowser);
         delete aTab.linkedBrowser.__SS_formDataSaved;
         delete aTab.linkedBrowser.__SS_hostSchemeData;
         if (aTab.linkedBrowser.__SS_restoreState)
           this._resetTabRestoringState(aTab);
       });
       openWindows[aWindow.__SSi] = true;
     });
     // also clear all data about closed tabs and windows
@@ -1245,18 +1244,17 @@ let SessionStoreInternal = {
    */
   onTabRemove: function ssi_onTabRemove(aWindow, aTab, aNoNotification) {
     let browser = aTab.linkedBrowser;
     browser.removeEventListener("load", this, true);
 
     let mm = browser.messageManager;
     MESSAGES.forEach(msg => mm.removeMessageListener(msg, this));
 
-    delete browser.__SS_data;
-    delete browser.__SS_tabStillLoading;
+    RestoringTabsData.remove(aTab.linkedBrowser);
     delete browser.__SS_formDataSaved;
     delete browser.__SS_hostSchemeData;
 
     // If this tab was in the middle of restoring or still needs to be restored,
     // we need to reset that state. If the tab was restoring, we will attempt to
     // restore the next tab.
     let previousState = browser.__SS_restoreState;
     if (previousState) {
@@ -1323,18 +1321,17 @@ let SessionStoreInternal = {
     // following "load" is too late for deleting the data caches)
     // It's possible to get a load event after calling stop on a browser (when
     // overwriting tabs). We want to return early if the tab hasn't been restored yet.
     if (aBrowser.__SS_restoreState &&
         aBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
       return;
     }
 
-    delete aBrowser.__SS_data;
-    delete aBrowser.__SS_tabStillLoading;
+    RestoringTabsData.remove(aBrowser);
     delete aBrowser.__SS_formDataSaved;
     this.saveStateDelayed(aWindow);
 
     // attempt to update the current URL we send in a crash report
     this._updateCrashReportURL(aWindow);
   },
 
   /**
@@ -1443,22 +1440,26 @@ let SessionStoreInternal = {
     // determine how many windows are meant to be restored
     this._restoreCount = state.windows ? state.windows.length : 0;
 
     // restore to the given state
     this.restoreWindow(window, state, true);
   },
 
   getWindowState: function ssi_getWindowState(aWindow) {
-    if (!aWindow.__SSi && !aWindow.__SS_dyingCache)
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
-
-    if (!aWindow.__SSi)
-      return this._toJSONString({ windows: [aWindow.__SS_dyingCache] });
-    return this._toJSONString(this._getWindowState(aWindow));
+    if ("__SSi" in aWindow) {
+      return this._toJSONString(this._getWindowState(aWindow));
+    }
+
+    if (DyingWindowCache.has(aWindow)) {
+      let data = DyingWindowCache.get(aWindow);
+      return this._toJSONString({ windows: [data] });
+    }
+
+    throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
   },
 
   setWindowState: function ssi_setWindowState(aWindow, aState, aOverwrite) {
     if (!aWindow.__SSi)
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
 
     this.restoreWindow(aWindow, aState, aOverwrite);
   },
@@ -1504,32 +1505,38 @@ let SessionStoreInternal = {
 
     this.restoreHistoryPrecursor(aWindow, [newTab], [tabState], 0, 0, 0,
                                  true /* Load this tab right away. */);
 
     return newTab;
   },
 
   getClosedTabCount: function ssi_getClosedTabCount(aWindow) {
-    if (!aWindow.__SSi && aWindow.__SS_dyingCache)
-      return aWindow.__SS_dyingCache._closedTabs.length;
-    if (!aWindow.__SSi)
-      // XXXzeniko shouldn't we throw here?
-      return 0; // not a browser window, or not otherwise tracked by SS.
-
-    return this._windows[aWindow.__SSi]._closedTabs.length;
+    if ("__SSi" in aWindow) {
+      return this._windows[aWindow.__SSi]._closedTabs.length;
+    }
+
+    if (DyingWindowCache.has(aWindow)) {
+      return DyingWindowCache.get(aWindow)._closedTabs.length;
+    }
+
+    throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
   },
 
   getClosedTabData: function ssi_getClosedTabDataAt(aWindow) {
-    if (!aWindow.__SSi && !aWindow.__SS_dyingCache)
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
-
-    if (!aWindow.__SSi)
-      return this._toJSONString(aWindow.__SS_dyingCache._closedTabs);
-    return this._toJSONString(this._windows[aWindow.__SSi]._closedTabs);
+    if ("__SSi" in aWindow) {
+      return this._toJSONString(this._windows[aWindow.__SSi]._closedTabs);
+    }
+
+    if (DyingWindowCache.has(aWindow)) {
+      let data = DyingWindowCache.get(aWindow);
+      return this._toJSONString(data._closedTabs);
+    }
+
+    throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
   },
 
   undoCloseTab: function ssi_undoCloseTab(aWindow, aIndex) {
     if (!aWindow.__SSi)
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
 
     var closedTabs = this._windows[aWindow.__SSi]._closedTabs;
 
@@ -1599,24 +1606,26 @@ let SessionStoreInternal = {
     if (!(aIndex in this._closedWindows))
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
 
     // remove closed window from the array
     this._closedWindows.splice(aIndex, 1);
   },
 
   getWindowValue: function ssi_getWindowValue(aWindow, aKey) {
-    if (aWindow.__SSi) {
+    if ("__SSi" in aWindow) {
       var data = this._windows[aWindow.__SSi].extData || {};
       return data[aKey] || "";
     }
-    if (aWindow.__SS_dyingCache) {
-      data = aWindow.__SS_dyingCache.extData || {};
+
+    if (DyingWindowCache.has(aWindow)) {
+      let data = DyingWindowCache.get(aWindow).extData || {};
       return data[aKey] || "";
     }
+
     throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
   },
 
   setWindowValue: function ssi_setWindowValue(aWindow, aKey, aStringValue) {
     if (aWindow.__SSi) {
       if (!this._windows[aWindow.__SSi].extData) {
         this._windows[aWindow.__SSi].extData = {};
       }
@@ -1633,52 +1642,58 @@ let SessionStoreInternal = {
         this._windows[aWindow.__SSi].extData[aKey])
       delete this._windows[aWindow.__SSi].extData[aKey];
   },
 
   getTabValue: function ssi_getTabValue(aTab, aKey) {
     let data = {};
     if (aTab.__SS_extdata) {
       data = aTab.__SS_extdata;
-    }
-    else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) {
-      // If the tab hasn't been fully restored, get the data from the to-be-restored data
-      data = aTab.linkedBrowser.__SS_data.extData;
+    } else {
+      let tabData = RestoringTabsData.get(aTab.linkedBrowser);
+      if (tabData && "extData" in tabData) {
+        // If the tab hasn't been fully restored yet,
+        // get the data from the to-be-restored data.
+        data = tabData.extData;
+      }
     }
     return data[aKey] || "";
   },
 
   setTabValue: function ssi_setTabValue(aTab, aKey, aStringValue) {
     // If the tab hasn't been restored, then set the data there, otherwise we
     // could lose newly added data.
     let saveTo;
     if (aTab.__SS_extdata) {
       saveTo = aTab.__SS_extdata;
-    }
-    else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) {
-      saveTo = aTab.linkedBrowser.__SS_data.extData;
-    }
-    else {
-      aTab.__SS_extdata = {};
-      saveTo = aTab.__SS_extdata;
+    } else {
+      let tabData = RestoringTabsData.get(aTab.linkedBrowser);
+      if (tabData && "extData" in tabData) {
+        saveTo = tabData.extData;
+      } else {
+        aTab.__SS_extdata = {};
+        saveTo = aTab.__SS_extdata;
+      }
     }
     saveTo[aKey] = aStringValue;
     this.saveStateDelayed(aTab.ownerDocument.defaultView);
   },
 
   deleteTabValue: function ssi_deleteTabValue(aTab, aKey) {
     // We want to make sure that if data is accessed early, we attempt to delete
-    // that data from __SS_data as well. Otherwise we'll throw in cases where
-    // data can be set or read.
+    // that data from to-be-restored data as well. Otherwise we'll throw in
+    // cases where data can be set or read.
     let deleteFrom;
     if (aTab.__SS_extdata) {
       deleteFrom = aTab.__SS_extdata;
-    }
-    else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) {
-      deleteFrom = aTab.linkedBrowser.__SS_data.extData;
+    } else {
+      let tabData = RestoringTabsData.get(aTab.linkedBrowser);
+      if (tabData && "extData" in tabData) {
+        deleteFrom = tabData.extData;
+      }
     }
 
     if (deleteFrom && deleteFrom[aKey])
       delete deleteFrom[aKey];
   },
 
   persistTabAttribute: function ssi_persistTabAttribute(aName) {
     if (aName in this.xulAttributes)
@@ -1882,19 +1897,19 @@ let SessionStoreInternal = {
    */
   _collectTabData: function ssi_collectTabData(aTab, aFullData) {
     var tabData = { entries: [], lastAccessed: aTab.lastAccessed };
     var browser = aTab.linkedBrowser;
 
     if (!browser || !browser.currentURI)
       // can happen when calling this function right after .addTab()
       return tabData;
-    else if (browser.__SS_data && browser.__SS_tabStillLoading) {
+    else if (RestoringTabsData.has(browser)) {
       // use the data to be restored when the tab hasn't been completely loaded
-      tabData = browser.__SS_data;
+      tabData = RestoringTabsData.get(browser);
       if (aTab.pinned)
         tabData.pinned = true;
       else
         delete tabData.pinned;
       tabData.hidden = aTab.hidden;
 
       // If __SS_extdata is set then we'll use that since it might be newer.
       if (aTab.__SS_extdata)
@@ -1907,26 +1922,17 @@ let SessionStoreInternal = {
     }
 
     var history = null;
     try {
       history = browser.sessionHistory;
     }
     catch (ex) { } // this could happen if we catch a tab during (de)initialization
 
-    // XXXzeniko anchor navigation doesn't reset __SS_data, so we could reuse
-    //           data even when we shouldn't (e.g. Back, different anchor)
-    if (history && browser.__SS_data &&
-        browser.__SS_data.entries[history.index] &&
-        browser.__SS_data.entries[history.index].url == browser.currentURI.spec &&
-        history.index < this._sessionhistory_max_entries - 1 && !aFullData) {
-      tabData = browser.__SS_data;
-      tabData.index = history.index + 1;
-    }
-    else if (history && history.count > 0) {
+    if (history && history.count > 0) {
       browser.__SS_hostSchemeData = [];
       try {
         for (var j = 0; j < history.count; j++) {
           let entry = this._serializeHistoryEntry(history.getEntryAtIndex(j, false),
                                                   aFullData, aTab.pinned, browser.__SS_hostSchemeData);
           tabData.entries.push(entry);
         }
         // If we make it through the for loop, then we're ok and we should clear
@@ -1945,20 +1951,16 @@ let SessionStoreInternal = {
           aTab.ownerDocument.defaultView.focus();
           aTab.ownerDocument.defaultView.gBrowser.selectedTab = aTab;
           NS_ASSERT(false, "SessionStore failed gathering complete history " +
                            "for the focused window/tab. See bug 669196.");
           aTab.__SS_broken_history = true;
         }
       }
       tabData.index = history.index + 1;
-
-      // make sure not to cache privacy sensitive data which shouldn't get out
-      if (!aFullData)
-        browser.__SS_data = tabData;
     }
     else if (browser.currentURI.spec != "about:blank" ||
              browser.contentDocument.body.hasChildNodes()) {
       tabData.entries[0] = { url: browser.currentURI.spec };
       tabData.index = 1;
     }
 
     // If there is a userTypedValue set, then either the user has typed something
@@ -2175,17 +2177,17 @@ let SessionStoreInternal = {
    * @param aTabData
    *        tabData object to add the information to
    * @param aFullData
    *        always return privacy sensitive data (use with care)
    */
   _updateTextAndScrollDataForTab:
     function ssi_updateTextAndScrollDataForTab(aWindow, aBrowser, aTabData, aFullData) {
     // we shouldn't update data for incompletely initialized tabs
-    if (aBrowser.__SS_data && aBrowser.__SS_tabStillLoading)
+    if (RestoringTabsData.has(aBrowser))
       return;
 
     var tabIndex = (aTabData.index || aTabData.entries.length) - 1;
     // entry data needn't exist for tabs just initialized with an incomplete session state
     if (!aTabData.entries[tabIndex])
       return;
 
     let selectedPageStyle = aBrowser.markupDocumentViewer.authorStyleDisabled ? "_nostyle" :
@@ -2979,21 +2981,19 @@ let SessionStoreInternal = {
       if (tabData.hidden)
         tabbrowser.hideTab(tab);
       else
         tabbrowser.showTab(tab);
 
       for (let name in tabData.attributes)
         this.xulAttributes[name] = true;
 
-      browser.__SS_tabStillLoading = true;
-
       // keep the data around to prevent dataloss in case
       // a tab gets closed before it's been properly restored
-      browser.__SS_data = tabData;
+      RestoringTabsData.set(browser, tabData);
       browser.__SS_restoreState = TAB_STATE_NEEDS_RESTORE;
       browser.setAttribute("pending", "true");
       tab.setAttribute("pending", "true");
 
       // Make sure that set/getTabValue will set/read the correct data by
       // wiping out any current value in tab.__SS_extdata.
       delete tab.__SS_extdata;
 
@@ -3152,17 +3152,17 @@ let SessionStoreInternal = {
    * @param aTab
    *        the tab to restore
    *
    * @returns true/false indicating whether or not a load actually happened
    */
   restoreTab: function ssi_restoreTab(aTab) {
     let window = aTab.ownerDocument.defaultView;
     let browser = aTab.linkedBrowser;
-    let tabData = browser.__SS_data;
+    let tabData = RestoringTabsData.get(browser);
 
     // There are cases within where we haven't actually started a load. In that
     // that case we'll reset state changes we made and return false to the caller
     // can handle appropriately.
     let didStartLoad = false;
 
     // Make sure that the tabs progress listener is attached to this window
     this._ensureTabsProgressListener(window);
@@ -4090,25 +4090,23 @@ let SessionStoreInternal = {
            !(aTabState.entries.length == 1 &&
              aTabState.entries[0].url == "about:blank" &&
              !aTabState.userTypedValue);
   },
 
   /**
    * Determine if we can restore history into this tab.
    * This will be false when a tab has been removed (usually between
-   * restoreHistoryPrecursor && restoreHistory) or if the tab is still marked
-   * as loading.
+   * restoreHistoryPrecursor && restoreHistory).
    *
    * @param aTab
    * @returns boolean
    */
   _canRestoreTabHistory: function ssi_canRestoreTabHistory(aTab) {
-    return aTab.parentNode && aTab.linkedBrowser &&
-           aTab.linkedBrowser.__SS_tabStillLoading;
+    return aTab.parentNode && aTab.linkedBrowser;
   },
 
   /**
    * This is going to take a state as provided at startup (via
    * nsISessionStartup.state) and split it into 2 parts. The first part
    * (defaultState) will be a state that should still be restored at startup,
    * while the second part (state) is a state that should be saved for later.
    * defaultState will be comprised of windows with only pinned tabs, extracted
@@ -4571,16 +4569,64 @@ let TabRestoreQueue = {
       visible.splice(index, 1);
       hidden.push(tab);
     } else {
       throw new Error("restore queue: visible tab not found");
     }
   }
 };
 
+// A map storing tabData belonging to xul:browsers of tabs. This will hold data
+// while a tab is restoring (i.e. loading). Because we can't query or use the
+// incomplete state of a loading tab we'll use data stored in the map if browser
+// state is collected while a tab is still restoring or if it's closed before
+// having restored fully.
+let RestoringTabsData = {
+  _data: new WeakMap(),
+
+  has: function (browser) {
+    return this._data.has(browser);
+  },
+
+  get: function (browser) {
+    return this._data.get(browser);
+  },
+
+  set: function (browser, data) {
+    this._data.set(browser, data);
+  },
+
+  remove: function (browser) {
+    this._data.delete(browser);
+  }
+};
+
+// A map storing a closed window's state data until it goes aways (is GC'ed).
+// This ensures that API clients can still read (but not write) states of
+// windows they still hold a reference to but we don't.
+let DyingWindowCache = {
+  _data: new WeakMap(),
+
+  has: function (window) {
+    return this._data.has(window);
+  },
+
+  get: function (window) {
+    return this._data.get(window);
+  },
+
+  set: function (window, data) {
+    this._data.set(window, data);
+  },
+
+  remove: function (window) {
+    this._data.delete(window);
+  }
+};
+
 // This is used to help meter the number of restoring tabs. This is the control
 // point for telling the next tab to restore. It gets attached to each gBrowser
 // via gBrowser.addTabsProgressListener
 let gRestoreTabsProgressListener = {
   onStateChange: function(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
     // Ignore state changes on browsers that we've already restored and state
     // changes that aren't applicable.
     if (aBrowser.__SS_restoreState &&
--- a/browser/components/sessionstore/test/Makefile.in
+++ b/browser/components/sessionstore/test/Makefile.in
@@ -17,16 +17,17 @@ include $(DEPTH)/config/autoconf.mk
 
 DISABLED_XPCSHELL_TESTS = \
 	unit \
 	$(NULL)
 
 MOCHITEST_BROWSER_FILES = \
 	head.js \
 	browser_capabilities.js \
+	browser_dying_cache.js \
 	browser_form_restore_events.js \
 	browser_form_restore_events_sample.html \
 	browser_formdata_format.js \
 	browser_formdata_format_sample.html \
 	browser_input.js \
 	browser_input_sample.html \
 	browser_pageshow.js \
 	browser_248970_b_perwindowpb.js \
--- a/browser/components/sessionstore/test/browser_579868.js
+++ b/browser/components/sessionstore/test/browser_579868.js
@@ -13,19 +13,16 @@ function test() {
 
     // Tell the session storer that the tab is pinned
     let newTabState = '{"entries":[{"url":"about:rights"}],"pinned":true,"userTypedValue":"Hello World!"}';
     ss.setTabState(tab1, newTabState);
 
     // Undo pinning
     gBrowser.unpinTab(tab1);
 
-    is(tab1.linkedBrowser.__SS_tabStillLoading, true,
-       "_tabStillLoading should be true.");
-
     // Close and restore tab
     gBrowser.removeTab(tab1);
     let savedState = JSON.parse(ss.getClosedTabData(window))[0].state;
     isnot(savedState.pinned, true, "Pinned should not be true");
     tab1 = ss.undoCloseTab(window, 0);
 
     isnot(tab1.pinned, true, "Should not be pinned");
     gBrowser.removeTab(tab1);
--- a/browser/components/sessionstore/test/browser_625257.js
+++ b/browser/components/sessionstore/test/browser_625257.js
@@ -34,17 +34,16 @@ function firstOnLoad(aEvent) {
 
   let uri = aEvent.target.location;
   is(uri, "about:blank", "first load should be for about:blank");
 
   // Trigger a save state.
   ss.getBrowserState();
 
   is(gBrowser.tabs[1], tab, "newly created tab should exist by now");
-  ok(tab.linkedBrowser.__SS_data, "newly created tab should be in save state");
 
   tab.linkedBrowser.loadURI(URI_TO_LOAD);
 }
 
 let tabsListener = {
   onLocationChange: function onLocationChange(aBrowser) {
     gBrowser.removeTabsProgressListener(tabsListener);
 
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_dying_cache.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+  TestRunner.run();
+}
+
+/**
+ * This test ensures that after closing a window we keep its state data around
+ * as long as something keeps a reference to it. It should only be possible to
+ * read data after closing - writing should fail.
+ */
+
+function runTests() {
+  // Open a new window.
+  let win = OpenBrowserWindow();
+  yield whenWindowLoaded(win);
+
+  // Load some URL in the current tab.
+  win.gBrowser.selectedBrowser.loadURI("about:robots");
+  yield whenBrowserLoaded(win.gBrowser.selectedBrowser);
+
+  // Open a second tab and close the first one.
+  let tab = win.gBrowser.addTab("about:mozilla");
+  yield whenBrowserLoaded(tab.linkedBrowser);
+  win.gBrowser.removeTab(win.gBrowser.tabs[0]);
+
+  // Make sure our window is still tracked by sessionstore
+  // and the window state is as expected.
+  ok("__SSi" in win, "window is being tracked by sessionstore");
+  ss.setWindowValue(win, "foo", "bar");
+  checkWindowState(win);
+
+  let state = ss.getWindowState(win);
+  let closedTabData = ss.getClosedTabData(win);
+
+  // Close our window and wait a tick.
+  whenWindowClosed(win);
+  yield win.close();
+
+  // SessionStore should no longer track our window
+  // but it should still report the same state.
+  ok(!("__SSi" in win), "sessionstore does no longer track our window");
+  checkWindowState(win);
+
+  // Make sure we're not allowed to modify state data.
+  ok(shouldThrow(() => ss.setWindowState(win, {})),
+     "we're not allowed to modify state data anymore");
+  ok(shouldThrow(() => ss.setWindowValue(win, "foo", "baz")),
+     "we're not allowed to modify state data anymore");
+}
+
+function checkWindowState(window) {
+  let {windows: [{tabs}]} = JSON.parse(ss.getWindowState(window));
+  is(tabs.length, 1, "the window has a single tab");
+  is(tabs[0].entries[0].url, "about:mozilla", "the tab is about:mozilla");
+
+  is(ss.getClosedTabCount(window), 1, "the window has one closed tab");
+  let [{state: {entries: [{url}]}}] = JSON.parse(ss.getClosedTabData(window));
+  is(url, "about:robots", "the closed tab is about:robots");
+
+  is(ss.getWindowValue(window, "foo"), "bar", "correct extData value");
+}
+
+function shouldThrow(f) {
+  try {
+    f();
+  } catch (e) {
+    return true;
+  }
+}
+
+function whenWindowClosed(window) {
+  window.addEventListener("SSWindowClosing", function onClosing() {
+    window.removeEventListener("SSWindowClosing", onClosing);
+    executeSoon(next);
+  });
+}
--- a/browser/components/sessionstore/test/head.js
+++ b/browser/components/sessionstore/test/head.js
@@ -182,24 +182,24 @@ function waitForSaveState(aSaveStateCall
       clearTimeout(timeout);
     }
   });
 
   observing = true;
   Services.obs.addObserver(observer, topic, false);
 };
 
-function whenBrowserLoaded(aBrowser, aCallback) {
+function whenBrowserLoaded(aBrowser, aCallback = next) {
   aBrowser.addEventListener("load", function onLoad() {
     aBrowser.removeEventListener("load", onLoad, true);
     executeSoon(aCallback);
   }, true);
 }
 
-function whenWindowLoaded(aWindow, aCallback) {
+function whenWindowLoaded(aWindow, aCallback = next) {
   aWindow.addEventListener("load", function windowLoadListener() {
     aWindow.removeEventListener("load", windowLoadListener, false);
     executeSoon(function executeWhenWindowLoaded() {
       aCallback(aWindow);
     });
   }, false);
 }
 
--- a/browser/devtools/commandline/BuiltinCommands.jsm
+++ b/browser/devtools/commandline/BuiltinCommands.jsm
@@ -16,16 +16,18 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource:///modules/devtools/shared/event-emitter.js");
 
 XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
                                   "resource:///modules/devtools/gDevTools.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "devtools",
                                   "resource:///modules/devtools/gDevTools.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
+                                  "resource:///modules/devtools/AppCacheUtils.jsm");
 
 /* CmdAddon ---------------------------------------------------------------- */
 
 (function(module) {
   XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                     "resource://gre/modules/AddonManager.jsm");
 
   // We need to use an object in which to store any flags because a primitive
@@ -2059,8 +2061,261 @@ XPCOMUtils.defineLazyModuleGetter(this, 
     function fireChange() {
       eventEmitter.emit("changed", tab);
     }
     var target = devtools.TargetFactory.forTab(tab);
     target.off("navigate", fireChange);
     target.once("navigate", fireChange);
   }
 }(this));
+
+/* CmdAppCache ------------------------------------------------------- */
+
+(function(module) {
+  /**
+  * 'appcache' command
+  */
+
+  gcli.addCommand({
+    name: 'appcache',
+    description: gcli.lookup('appCacheDesc')
+  });
+
+  gcli.addConverter({
+    from: "appcacheerrors",
+    to: "view",
+    exec: function([errors, manifestURI], context) {
+      if (errors.length == 0) {
+        return context.createView({
+          html: "<span>" + gcli.lookup("appCacheValidatedSuccessfully") + "</span>"
+        });
+      }
+
+      let appcacheValidateHtml =
+        "<h4>Manifest URI: ${manifestURI}</h4>" +
+        "<ol>" +
+        "  <li foreach='error in ${errors}'>" +
+        "    ${error.msg}" +
+        "  </li>" +
+        "</ol>";
+
+      return context.createView({
+        html: "<div>" + appcacheValidateHtml + "</div>",
+        data: {
+          errors: errors,
+          manifestURI: manifestURI
+        }
+      });
+    }
+  });
+
+  gcli.addCommand({
+    name: 'appcache validate',
+    description: gcli.lookup('appCacheValidateDesc'),
+    manual: gcli.lookup('appCacheValidateManual'),
+    returnType: 'appcacheerrors',
+    params: [{
+      group: "options",
+      params: [
+        {
+          type: "string",
+          name: "uri",
+          description: gcli.lookup("appCacheValidateUriDesc"),
+          defaultValue: null,
+        }
+      ]
+    }],
+    exec: function(args, context) {
+      let utils;
+      let promise = context.createPromise();
+
+      if (args.uri) {
+        utils = new AppCacheUtils(args.uri);
+      } else {
+        let doc = context.environment.contentDocument;
+        utils = new AppCacheUtils(doc);
+      }
+
+      utils.validateManifest().then(function(errors) {
+        promise.resolve([errors, utils.manifestURI || "-"]);
+      });
+
+      return promise;
+    }
+  });
+
+  gcli.addCommand({
+    name: 'appcache clear',
+    description: gcli.lookup('appCacheClearDesc'),
+    manual: gcli.lookup('appCacheClearManual'),
+    exec: function(args, context) {
+      let utils = new AppCacheUtils(args.uri);
+      utils.clearAll();
+
+      return gcli.lookup("appCacheClearCleared");
+    }
+  });
+
+  gcli.addConverter({
+    from: "appcacheentries",
+    to: "view",
+    exec: function(entries, context) {
+      if (!entries) {
+        return context.createView({
+          html: "<span>" + gcli.lookup("appCacheManifestContainsErrors") + "</span>"
+        });
+      }
+
+      if (entries.length == 0) {
+        return context.createView({
+          html: "<span>" + gcli.lookup("appCacheNoResults") + "</span>"
+        });
+      }
+
+      let appcacheListEntries = "" +
+        "<ul class='gcli-appcache-list'>" +
+        "  <li foreach='entry in ${entries}'>" +
+        "    <table class='gcli-appcache-detail'>" +
+        "      <tr>" +
+        "        <td>" + gcli.lookup("appCacheListKey") + "</td>" +
+        "        <td>${entry.key}</td>" +
+        "      </tr>" +
+        "      <tr>" +
+        "        <td>" + gcli.lookup("appCacheListFetchCount") + "</td>" +
+        "        <td>${entry.fetchCount}</td>" +
+        "      </tr>" +
+        "      <tr>" +
+        "        <td>" + gcli.lookup("appCacheListLastFetched") + "</td>" +
+        "        <td>${entry.lastFetched}</td>" +
+        "      </tr>" +
+        "      <tr>" +
+        "        <td>" + gcli.lookup("appCacheListLastModified") + "</td>" +
+        "        <td>${entry.lastModified}</td>" +
+        "      </tr>" +
+        "      <tr>" +
+        "        <td>" + gcli.lookup("appCacheListExpirationTime") + "</td>" +
+        "        <td>${entry.expirationTime}</td>" +
+        "      </tr>" +
+        "      <tr>" +
+        "        <td>" + gcli.lookup("appCacheListDataSize") + "</td>" +
+        "        <td>${entry.dataSize}</td>" +
+        "      </tr>" +
+        "      <tr>" +
+        "        <td>" + gcli.lookup("appCacheListDeviceID") + "</td>" +
+        "        <td>${entry.deviceID} <span class='gcli-out-shortcut' " +
+        "onclick='${onclick}' ondblclick='${ondblclick}' " +
+        "data-command='appcache viewentry ${entry.key}'" +
+        ">" + gcli.lookup("appCacheListViewEntry") + "</span>" +
+        "        </td>" +
+        "      </tr>" +
+        "    </table>" +
+        "  </li>" +
+        "</ul>";
+
+      return context.createView({
+        html: appcacheListEntries,
+        data: {
+          entries: entries,
+          onclick: createUpdateHandler(context),
+          ondblclick: createExecuteHandler(context),
+        }
+      });
+    }
+  });
+
+  gcli.addCommand({
+    name: 'appcache list',
+    description: gcli.lookup('appCacheListDesc'),
+    manual: gcli.lookup('appCacheListManual'),
+    returnType: "appcacheentries",
+    params: [{
+      group: "options",
+      params: [
+        {
+          type: "string",
+          name: "search",
+          description: gcli.lookup("appCacheListSearchDesc"),
+          defaultValue: null,
+        },
+      ]
+    }],
+    exec: function(args, context) {
+      let doc = context.environment.contentDocument;
+      let utils = new AppCacheUtils();
+
+      let entries = utils.listEntries(args.search);
+      return entries;
+    }
+  });
+
+  gcli.addCommand({
+    name: 'appcache viewentry',
+    description: gcli.lookup('appCacheViewEntryDesc'),
+    manual: gcli.lookup('appCacheViewEntryManual'),
+    params: [
+      {
+        type: "string",
+        name: "key",
+        description: gcli.lookup("appCacheViewEntryKey"),
+        defaultValue: null,
+      }
+    ],
+    exec: function(args, context) {
+      let doc = context.environment.contentDocument;
+      let utils = new AppCacheUtils();
+
+      let result = utils.viewEntry(args.key);
+      if (result) {
+        return result;
+      }
+    }
+  });
+
+  /**
+   * Helper to find the 'data-command' attribute and call some action on it.
+   * @see |updateCommand()| and |executeCommand()|
+   */
+  function withCommand(element, action) {
+    let command = element.getAttribute("data-command");
+    if (!command) {
+      command = element.querySelector("*[data-command]")
+              .getAttribute("data-command");
+    }
+
+    if (command) {
+      action(command);
+    }
+    else {
+      console.warn("Missing data-command for " + util.findCssSelector(element));
+    }
+  }
+
+  /**
+   * Create a handler to update the requisition to contain the text held in the
+   * first matching data-command attribute under the currentTarget of the event.
+   * @param context Either a Requisition or an ExecutionContext or another object
+   * that contains an |update()| function that follows a similar contract.
+   */
+  function createUpdateHandler(context) {
+    return function(ev) {
+      withCommand(ev.currentTarget, function(command) {
+        context.update(command);
+      });
+    }
+  }
+
+  /**
+   * Create a handler to execute the text held in the data-command attribute
+   * under the currentTarget of the event.
+   * @param context Either a Requisition or an ExecutionContext or another object
+   * that contains an |update()| function that follows a similar contract.
+   */
+  function createExecuteHandler(context) {
+    return function(ev) {
+      withCommand(ev.currentTarget, function(command) {
+        context.exec({
+          visible: true,
+          typed: command
+        });
+      });
+    }
+  }
+}(this));
--- a/browser/devtools/commandline/commandline.css
+++ b/browser/devtools/commandline/commandline.css
@@ -39,11 +39,21 @@
   padding-left: 0;
 }
 
 .gcli-cookielist-detail {
   padding-left: 20px;
   padding-bottom: 10px;
 }
 
+.gcli-appcache-list {
+  list-style-type: none;
+  padding-left: 0;
+}
+
+.gcli-appcache-detail {
+  padding-left: 20px;
+  padding-bottom: 10px;
+}
+
 .gcli-row-out .nowrap {
   white-space: nowrap;
 }
--- a/browser/devtools/commandline/test/Makefile.in
+++ b/browser/devtools/commandline/test/Makefile.in
@@ -10,16 +10,31 @@ VPATH     = @srcdir@
 relativesrcdir  = @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MOCHITEST_BROWSER_FILES = \
   browser_cmd_addon.js \
   $(browser_cmd_calllog.js disabled until bug 845831 is fixed) \
   $(browser_cmd_calllog_chrome.js disabled until bug 845831 is fixed) \
+  browser_cmd_appcache_invalid.js \
+  browser_cmd_appcache_invalid_appcache.appcache \
+  browser_cmd_appcache_invalid_appcache.appcache^headers^ \
+  browser_cmd_appcache_invalid_index.html \
+  browser_cmd_appcache_invalid_page1.html \
+  browser_cmd_appcache_invalid_page2.html \
+  browser_cmd_appcache_invalid_page3.html \
+  browser_cmd_appcache_invalid_page3.html^headers^ \
+  browser_cmd_appcache_valid.js \
+  browser_cmd_appcache_valid_appcache.appcache \
+  browser_cmd_appcache_valid_appcache.appcache^headers^ \
+  browser_cmd_appcache_valid_index.html \
+  browser_cmd_appcache_valid_page1.html \
+  browser_cmd_appcache_valid_page2.html \
+  browser_cmd_appcache_valid_page3.html \
   browser_cmd_commands.js \
   browser_cmd_cookie.js \
   browser_cmd_jsb.js \
   browser_cmd_jsb_script.jsi \
   browser_cmd_pagemod_export.html \
   browser_cmd_pagemod_export.js \
   browser_cmd_pref.js \
   browser_cmd_restart.js \
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_appcache_invalid.js
@@ -0,0 +1,102 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the appcache validate works as they should with an invalid
+// manifest.
+
+const TEST_URI = "http://sub1.test1.example.com/browser/browser/devtools/commandline/" +
+                 "test/browser_cmd_appcache_invalid_index.html";
+
+let tests = {};
+
+function test() {
+  helpers.addTabWithToolbar(TEST_URI, function(options) {
+    let deferred = Promise.defer();
+
+    // Wait for site to be cached.
+    gBrowser.contentWindow.applicationCache.addEventListener('error', function BCAI_error() {
+      gBrowser.contentWindow.applicationCache.removeEventListener('error', BCAI_error);
+
+      info("Site now cached, running tests.");
+
+      deferred.resolve(helpers.audit(options, [
+        {
+          setup: 'appcache validate',
+          check: {
+            input:  'appcache validate',
+            markup: 'VVVVVVVVVVVVVVVVV',
+            status: 'VALID',
+            args: {}
+          },
+          exec: {
+            completed: false,
+            output: [
+              /Manifest has a character encoding of ISO-8859-1\. Manifests must have the utf-8 character encoding\./,
+              /The first line of the manifest must be "CACHE MANIFEST" at line 1\./,
+              /"CACHE MANIFEST" is only valid on the first line at line 3\./,
+              /images\/sound-icon\.png points to a resource that is not available at line 9\./,
+              /images\/background\.png points to a resource that is not available at line 10\./,
+              /NETWORK section line 13 \(\/checking\.cgi\) prevents caching of line 13 \(\/checking\.cgi\) in the NETWORK section\./,
+              /\/checking\.cgi points to a resource that is not available at line 13\./,
+              /Asterisk used as a wildcard in the NETWORK section at line 14\. A single line containing an asterisk is called the online whitelist wildcard flag and is only valid in the NETWORK section\. Other uses of the \* character are prohibited\. The presence of this flag indicates that any URI not listed as cached is to be implicitly treated as being in the online whitelist namespaces\. If the flag is not present then the blocking state indicates that URIs not listed explicitly in the manifest are to be treated as unavailable\./,
+              /\.\.\/rel\.html points to a resource that is not available at line 17\./,
+              /\.\.\/\.\.\/rel\.html points to a resource that is not available at line 18\./,
+              /\.\.\/\.\.\/\.\.\/rel\.html points to a resource that is not available at line 19\./,
+              /\.\.\/\.\.\/\.\.\/\.\.\/rel\.html points to a resource that is not available at line 20\./,
+              /\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/rel\.html points to a resource that is not available at line 21\./,
+              /\/\.\.\/ is not a valid URI prefix at line 22\./,
+              /\/test\.css points to a resource that is not available at line 23\./,
+              /\/test\.js points to a resource that is not available at line 24\./,
+              /test\.png points to a resource that is not available at line 25\./,
+              /\/main\/features\.js points to a resource that is not available at line 27\./,
+              /\/main\/settings\/index\.css points to a resource that is not available at line 28\./,
+              /http:\/\/example\.com\/scene\.jpg points to a resource that is not available at line 29\./,
+              /\/section1\/blockedbyfallback\.html points to a resource that is not available at line 30\./,
+              /http:\/\/example\.com\/images\/world\.jpg points to a resource that is not available at line 31\./,
+              /\/section2\/blockedbyfallback\.html points to a resource that is not available at line 32\./,
+              /\/main\/home points to a resource that is not available at line 34\./,
+              /main\/app\.js points to a resource that is not available at line 35\./,
+              /\/settings\/home points to a resource that is not available at line 37\./,
+              /\/settings\/app\.js points to a resource that is not available at line 38\./,
+              /The file http:\/\/sub1\.test1\.example\.com\/browser\/browser\/devtools\/commandline\/test\/browser_cmd_appcache_invalid_page3\.html was modified after http:\/\/sub1\.test1\.example\.com\/browser\/browser\/devtools\/commandline\/test\/browser_cmd_appcache_invalid_appcache\.appcache\. Unless the text in the manifest file is changed the cached version will be used instead at line 39\./,
+              /browser_cmd_appcache_invalid_page3\.html has cache-control set to no-store\. This will prevent the application cache from storing the file at line 39\./,
+              /http:\/\/example\.com\/logo\.png points to a resource that is not available at line 40\./,
+              /http:\/\/example\.com\/check\.png points to a resource that is not available at line 41\./,
+              /Spaces in URIs need to be replaced with % at line 42\./,
+              /http:\/\/example\.com\/cr oss\.png points to a resource that is not available at line 42\./,
+              /Asterisk used as a wildcard in the CACHE section at line 43\. A single line containing an asterisk is called the online whitelist wildcard flag and is only valid in the NETWORK section\. Other uses of the \* character are prohibited\. The presence of this flag indicates that any URI not listed as cached is to be implicitly treated as being in the online whitelist namespaces\. If the flag is not present then the blocking state indicates that URIs not listed explicitly in the manifest are to be treated as unavailable\./,
+              /The SETTINGS section may only contain a single value, "prefer-online" or "fast" at line 47\./,
+              /FALLBACK section line 50 \(\/section1\/ \/offline1\.html\) prevents caching of line 30 \(\/section1\/blockedbyfallback\.html\) in the CACHE section\./,
+              /\/offline1\.html points to a resource that is not available at line 50\./,
+              /FALLBACK section line 51 \(\/section2\/ offline2\.html\) prevents caching of line 32 \(\/section2\/blockedbyfallback\.html\) in the CACHE section\./,
+              /offline2\.html points to a resource that is not available at line 51\./,
+              /Only two URIs separated by spaces are allowed in the FALLBACK section at line 52\./,
+              /Asterisk \(\*\) incorrectly used as a wildcard in a fallback namespace at line 53\. Namespaces simply need to match a path prefix\./,
+              /offline3\.html points to a resource that is not available at line 53\./,
+              /Invalid section name \(BLAH\) at line 55\./,
+              /Only two URIs separated by spaces are allowed in the FALLBACK section at line 55\./
+            ]
+          },
+        },
+      ]));
+    });
+
+    acceptOfflineCachePrompt();
+
+    return deferred.promise;
+  }).then(finish);
+
+  function acceptOfflineCachePrompt() {
+    // Pages containing an appcache the notification bar gives options to allow
+    // or deny permission for the app to save data offline. Let's click Allow.
+    let notificationID = "offline-app-requested-sub1.test1.example.com";
+    let notification = PopupNotifications.getNotification(notificationID, gBrowser.selectedBrowser);
+
+    if (notification) {
+      info("Authorizing offline storage.");
+      notification.mainAction.callback();
+    } else {
+      info("No notification box is available.");
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_appcache_invalid_appcache.appcache
@@ -0,0 +1,55 @@
+# some comment
+
+CACHE MANIFEST
+# the above is a required line
+# this is a comment
+# spaces are ignored
+# blank lines are ignored
+
+images/sound-icon.png
+images/background.png
+
+NETWORK:
+/checking.cgi
+/checking.*
+
+CACHE:
+../rel.html
+../../rel.html
+../../../rel.html
+../../../../rel.html
+../../../../../rel.html
+/../invalid.html
+/test.css
+/test.js
+test.png
+browser_cmd_appcache_invalid_index.html
+/main/features.js
+/main/settings/index.css
+http://example.com/scene.jpg
+/section1/blockedbyfallback.html
+http://example.com/images/world.jpg
+/section2/blockedbyfallback.html
+browser_cmd_appcache_invalid_page1.html
+/main/home
+main/app.js
+browser_cmd_appcache_invalid_page2.html
+/settings/home
+/settings/app.js
+browser_cmd_appcache_invalid_page3.html
+http://example.com/logo.png
+http://example.com/check.png
+http://example.com/cr oss.png
+/checking*.png
+
+SETTINGS:
+prefer-online
+fast
+
+FALLBACK:
+/section1/ /offline1.html
+/section2/ offline2.html
+dadsdsd
+* offline3.html
+
+BLAH:
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_appcache_invalid_appcache.appcache^headers^
@@ -0,0 +1,1 @@
+Content-Type: text/cache-manifest; charset=ISO-8859-1
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_appcache_invalid_index.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html manifest="browser_cmd_appcache_invalid_appcache.appcache">
+  <head>
+    <meta charset="UTF-8">
+  <head>
+  <body>
+    <h1>Example index.html</h1>
+    <br />
+    <a href="browser_cmd_appcache_invalid_index.html">Home</a> |
+    <a href="browser_cmd_appcache_invalid_page1.html">Page 1</a> |
+    <a href="browser_cmd_appcache_invalid_page2.html">Page 2</a> |
+    <a href="browser_cmd_appcache_invalid_page3.html">Page 3</a>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_appcache_invalid_page1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html manifest="browser_cmd_appcache_invalid_appcache.appcache">
+  <head>
+    <meta charset="UTF-8">
+  <head>
+  <body>
+    <h1>Example page1.html</h1>
+    <br />
+    <a href="browser_cmd_appcache_invalid_index.html">Home</a> |
+    <a href="browser_cmd_appcache_invalid_page1.html">Page 1</a> |
+    <a href="browser_cmd_appcache_invalid_page2.html">Page 2</a> |
+    <a href="browser_cmd_appcache_invalid_page3.html">Page 3</a>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_appcache_invalid_page2.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html manifest="browser_cmd_appcache_invalid_appcache.appcache">
+  <head>
+    <meta charset="UTF-8">
+  <head>
+  <body>
+    <h1>Example page2.html</h1>
+    <br />
+    <a href="browser_cmd_appcache_invalid_index.html">Home</a> |
+    <a href="browser_cmd_appcache_invalid_page1.html">Page 1</a> |
+    <a href="browser_cmd_appcache_invalid_page2.html">Page 2</a> |
+    <a href="browser_cmd_appcache_invalid_page3.html">Page 3</a>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_appcache_invalid_page3.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html manifest="browser_cmd_appcache_invalid_appcache.appcache">
+  <head>
+    <meta charset="UTF-8">
+  <head>
+  <body>
+    <h1>Example page3.html</h1>
+    <br />
+    <a href="browser_cmd_appcache_invalid_index.html">Home</a> |
+    <a href="browser_cmd_appcache_invalid_page1.html">Page 1</a> |
+    <a href="browser_cmd_appcache_invalid_page2.html">Page 2</a> |
+    <a href="browser_cmd_appcache_invalid_page3.html">Page 3</a>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_appcache_invalid_page3.html^headers^
@@ -0,0 +1,2 @@
+Cache-Control: no-store, no-cache
+Last-Modified: Tue, 23 Apr 9999 11:41:13 GMT
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_appcache_valid.js
@@ -0,0 +1,160 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the appcache commands works as they should
+
+const TEST_URI = "http://sub1.test2.example.com/browser/browser/devtools/" +
+                 "commandline/test/browser_cmd_appcache_valid_index.html";
+let tests = {};
+
+function test() {
+  helpers.addTabWithToolbar(TEST_URI, function(options) {
+    let deferred = Promise.defer();
+
+    info("adding cache listener.");
+
+    // Wait for site to be cached.
+    gBrowser.contentWindow.applicationCache.addEventListener('cached', function BCAV_cached() {
+      gBrowser.contentWindow.applicationCache.removeEventListener('cached', BCAV_cached);
+
+      info("Site now cached, running tests.");
+
+      deferred.resolve(helpers.audit(options, [
+        {
+          setup: 'appcache',
+          check: {
+            input:  'appcache',
+            markup: 'IIIIIIII',
+            status: 'ERROR',
+            args: {}
+          },
+        },
+
+        {
+          setup: 'appcache list',
+          check: {
+            input:  'appcache list',
+            markup: 'VVVVVVVVVVVVV',
+            status: 'VALID',
+            args: {},
+          },
+          exec: {
+            output: [ /index/, /page1/, /page2/, /page3/ ]
+          },
+        },
+
+        {
+          setup: 'appcache list page',
+          check: {
+            input:  'appcache list page',
+            markup: 'VVVVVVVVVVVVVVVVVV',
+            status: 'VALID',
+            args: {
+              search: { value: 'page' },
+            }
+          },
+          exec: {
+            output: [ /page1/, /page2/, /page3/ ]
+          },
+          post: function(output) {
+            ok(!output.contains("index"), "index is not contained in output");
+          }
+        },
+
+        {
+          setup: 'appcache validate',
+          check: {
+            input:  'appcache validate',
+            markup: 'VVVVVVVVVVVVVVVVV',
+            status: 'VALID',
+            args: {}
+          },
+          exec: {
+            completed: false,
+            output: [ /successfully/ ]
+          },
+        },
+
+        {
+          setup: 'appcache validate ' + TEST_URI,
+          check: {
+            input:  'appcache validate ' + TEST_URI,
+                  // appcache validate http://sub1.test2.example.com/browser/browser/devtools/commandline/test/browser_cmd_appcache_valid_index.html
+            markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
+            status: 'VALID',
+            args: {
+              uri: {
+                value: TEST_URI
+              },
+            }
+          },
+          exec: {
+            completed: false,
+            output: [ /successfully/ ]
+          },
+        },
+
+        {
+          setup: 'appcache clear',
+          check: {
+            input:  'appcache clear',
+            markup: 'VVVVVVVVVVVVVV',
+            status: 'VALID',
+            args: {},
+          },
+          exec: {
+            output: [ /successfully/ ]
+          },
+        },
+
+        {
+          setup: 'appcache list',
+          check: {
+            input:  'appcache list',
+            markup: 'VVVVVVVVVVVVV',
+            status: 'VALID',
+            args: {},
+          },
+          exec: {
+            output: [ /no results/ ]
+          },
+          post: function(output) {
+            ok(!output.contains("index"), "index is not contained in output");
+            ok(!output.contains("page1"), "page1 is not contained in output");
+            ok(!output.contains("page2"), "page1 is not contained in output");
+            ok(!output.contains("page3"), "page1 is not contained in output");
+          }
+        },
+
+        {
+          setup: 'appcache viewentry --key ' + TEST_URI,
+          check: {
+            input:  'appcache viewentry --key ' + TEST_URI,
+                  // appcache viewentry --key http://sub1.test2.example.com/browser/browser/devtools/commandline/test/browser_cmd_appcache_valid_index.html
+            markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
+            status: 'VALID',
+            args: {}
+          },
+        },
+      ]));
+    });
+
+    acceptOfflineCachePrompt();
+
+    return deferred.promise;
+  }).then(finish);
+
+  function acceptOfflineCachePrompt() {
+    // Pages containing an appcache the notification bar gives options to allow
+    // or deny permission for the app to save data offline. Let's click Allow.
+    let notificationID = "offline-app-requested-sub1.test2.example.com";
+    let notification = PopupNotifications.getNotification(notificationID, gBrowser.selectedBrowser);
+
+    if (notification) {
+      info("Authorizing offline storage.");
+      notification.mainAction.callback();
+    } else {
+      info("No notification box is available.");
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_appcache_valid_appcache.appcache
@@ -0,0 +1,5 @@
+CACHE MANIFEST
+browser_cmd_appcache_valid_index.html
+browser_cmd_appcache_valid_page1.html
+browser_cmd_appcache_valid_page2.html
+browser_cmd_appcache_valid_page3.html
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_appcache_valid_appcache.appcache^headers^
@@ -0,0 +1,1 @@
+Content-Type: text/cache-manifest
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_appcache_valid_index.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html manifest="browser_cmd_appcache_valid_appcache.appcache">
+  <head>
+    <meta charset="UTF-8">
+  <head>
+  <body>
+    <h1>Example index.html</h1>
+    <a href="browser_cmd_appcache_valid_index.html">Home</a> |
+    <a href="browser_cmd_appcache_valid_page1.html">Page 1</a> |
+    <a href="browser_cmd_appcache_valid_page2.html">Page 2</a> |
+    <a href="browser_cmd_appcache_valid_page3.html">Page 3</a>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_appcache_valid_page1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html manifest="browser_cmd_appcache_valid_appcache.appcache">
+  <head>
+    <meta charset="UTF-8">
+  <head>
+  <body>
+    <h1>Example page1.html</h1>
+    <a href="browser_cmd_appcache_valid_index.html">Home</a> |
+    <a href="browser_cmd_appcache_valid_page1.html">Page 1</a> |
+    <a href="browser_cmd_appcache_valid_page2.html">Page 2</a> |
+    <a href="browser_cmd_appcache_valid_page3.html">Page 3</a>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_appcache_valid_page2.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html manifest="browser_cmd_appcache_valid_appcache.appcache">
+  <head>
+    <meta charset="UTF-8">
+  <head>
+  <body>
+    <h1>Example page2.html</h1>
+    <a href="browser_cmd_appcache_valid_index.html">Home</a> |
+    <a href="browser_cmd_appcache_valid_page1.html">Page 1</a> |
+    <a href="browser_cmd_appcache_valid_page2.html">Page 2</a> |
+    <a href="browser_cmd_appcache_valid_page3.html">Page 3</a>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_appcache_valid_page3.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html manifest="browser_cmd_appcache_valid_appcache.appcache">
+  <head>
+    <meta charset="UTF-8">
+  <head>
+  <body>
+    <h1>Example page3.html</h1>
+    <a href="browser_cmd_appcache_valid_index.html">Home</a> |
+    <a href="browser_cmd_appcache_valid_page1.html">Page 1</a> |
+    <a href="browser_cmd_appcache_valid_page2.html">Page 2</a> |
+    <a href="browser_cmd_appcache_valid_page3.html">Page 3</a>
+  </body>
+</html>
--- a/browser/devtools/commandline/test/helpers.js
+++ b/browser/devtools/commandline/test/helpers.js
@@ -728,20 +728,23 @@ helpers._exec = function(options, name, 
     var div = options.window.document.createElement('div');
     var nodePromise = converters.convert(output.data, output.type, 'dom',
                                          options.display.requisition.context);
     nodePromise.then(function(node) {
       div.appendChild(node);
       var actualOutput = div.textContent.trim();
 
       var doTest = function(match, against) {
-        if (!match.test(against)) {
-          assert.ok(false, 'html output for ' + name + ' against ' + match.source);
-          log('Actual textContent');
-          log(against);
+        if (match.test(against)) {
+          assert.ok(true, 'html output for ' + name + ' should match ' +
+                          match.source);
+        } else {
+          assert.ok(false, 'html output for ' + name + ' should match ' +
+                           match.source +
+                           '. Actual textContent: "' + against + '"');
         }
       };
 
       if (typeof expected.output === 'string') {
         assert.is(actualOutput,
                   expected.output,
                   'html output for ' + name);
       }
@@ -749,17 +752,17 @@ helpers._exec = function(options, name, 
         expected.output.forEach(function(match) {
           doTest(match, actualOutput);
         });
       }
       else {
         doTest(expected.output, actualOutput);
       }
 
-      deferred.resolve();
+      deferred.resolve(actualOutput);
     });
   };
 
   if (output.completed !== false) {
     checkOutput();
   }
   else {
     var changed = function() {
@@ -787,19 +790,19 @@ helpers._setup = function(options, name,
   }
 
   return Promise.reject('setup must be a string or a function');
 };
 
 /**
  * Helper to shutdown the test
  */
-helpers._post = function(name, action) {
+helpers._post = function(name, action, output) {
   if (typeof action === 'function') {
-    return Promise.resolve(action());
+    return Promise.resolve(action(output));
   }
   return Promise.resolve(action);
 };
 
 /*
  * We do some basic response time stats so we can see if we're getting slow
  */
 var totalResponseTime = 0;
@@ -946,18 +949,18 @@ helpers.audit = function(options, audits
         maxResponseTime = responseTime;
         maxResponseCulprit = assert.currentTest + '/' + name;
       }
       averageOver++;
 
       var checkDone = helpers._check(options, name, audit.check);
       return checkDone.then(function() {
         var execDone = helpers._exec(options, name, audit.exec);
-        return execDone.then(function() {
-          return helpers._post(name, audit.post).then(function() {
+        return execDone.then(function(output) {
+          return helpers._post(name, audit.post, output).then(function() {
             if (assert.testLogging) {
               log('- END \'' + name + '\' in ' + assert.currentTest);
             }
           });
         });
       });
     });
   }).then(null, function(ex) {
--- a/browser/devtools/debugger/DebuggerUI.jsm
+++ b/browser/devtools/debugger/DebuggerUI.jsm
@@ -483,23 +483,26 @@ ChromeDebuggerProcess.prototype = {
   },
 
   /**
    * Closes the remote debugger, removing the profile and killing the process.
    */
   close: function RDP_close() {
     dumpn("Closing chrome debugging process");
     if (!this.globalUI) {
+      dumpn("globalUI is missing");
       return;
     }
     delete this.globalUI._chromeDebugger;
 
     if (this._dbgProcess.isRunning) {
+      dumpn("Killing chrome debugging process...");
       this._dbgProcess.kill();
     }
+    dumpn("...done.");
     if (typeof this._closeCallback == "function") {
       this._closeCallback.call({}, this);
     }
 
     this._dbgProcess = null;
     this._dbgProfile = null;
     this._win = null;
 
--- a/browser/devtools/debugger/test/browser_dbg_createChrome.js
+++ b/browser/devtools/debugger/test/browser_dbg_createChrome.js
@@ -43,33 +43,34 @@ function testSimpleCall() {
       "The remote debugger profile wasn't created properly!");
     ok(gProcess._dbgProfile.localDir,
       "The remote debugger profile doesn't have a localDir...");
     ok(gProcess._dbgProfile.rootDir,
       "The remote debugger profile doesn't have a rootDir...");
     ok(gProcess._dbgProfile.name,
       "The remote debugger profile doesn't have a name...");
 
-    info("profile localDir: " + gProcess._dbgProfile.localDir);
-    info("profile rootDir: " + gProcess._dbgProfile.rootDir);
+    info("profile localDir: " + gProcess._dbgProfile.localDir.path);
+    info("profile rootDir: " + gProcess._dbgProfile.rootDir.path);
     info("profile name: " + gProcess._dbgProfile.name);
 
     let profileService = Cc["@mozilla.org/toolkit/profile-service;1"]
       .createInstance(Ci.nsIToolkitProfileService);
 
     let profile = profileService.getProfileByName(gProcess._dbgProfile.name);
 
     ok(profile,
       "The remote debugger profile wasn't *actually* created properly!");
     is(profile.localDir.path, gProcess._dbgProfile.localDir.path,
       "The remote debugger profile doesn't have the correct localDir!");
     is(profile.rootDir.path, gProcess._dbgProfile.rootDir.path,
       "The remote debugger profile doesn't have the correct rootDir!");
 
-    DebuggerUI.toggleChromeDebugger();
+    let chromeDebug = DebuggerUI.toggleChromeDebugger();
+    info("toggleChromeDebugger() returned " + chromeDebug);
   }}, 0);
 }
 
 function aOnClosing() {
   ok(!gProcess._dbgProcess.isRunning,
     "The remote debugger process isn't closed as it should be!");
   is(gProcess._dbgProcess.exitValue, (Services.appinfo.OS == "WINNT" ? 0 : 256),
     "The remote debugger process didn't die cleanly.");
--- a/browser/devtools/debugger/test/helpers.js
+++ b/browser/devtools/debugger/test/helpers.js
@@ -728,20 +728,23 @@ helpers._exec = function(options, name, 
     var div = options.window.document.createElement('div');
     var nodePromise = converters.convert(output.data, output.type, 'dom',
                                          options.display.requisition.context);
     nodePromise.then(function(node) {
       div.appendChild(node);
       var actualOutput = div.textContent.trim();
 
       var doTest = function(match, against) {
-        if (!match.test(against)) {
-          assert.ok(false, 'html output for ' + name + ' against ' + match.source);
-          log('Actual textContent');
-          log(against);
+        if (match.test(against)) {
+          assert.ok(true, 'html output for ' + name + ' should match ' +
+                          match.source);
+        } else {
+          assert.ok(false, 'html output for ' + name + ' should match ' +
+                           match.source +
+                           '. Actual textContent: "' + against + '"');
         }
       };
 
       if (typeof expected.output === 'string') {
         assert.is(actualOutput,
                   expected.output,
                   'html output for ' + name);
       }
@@ -749,17 +752,17 @@ helpers._exec = function(options, name, 
         expected.output.forEach(function(match) {
           doTest(match, actualOutput);
         });
       }
       else {
         doTest(expected.output, actualOutput);
       }
 
-      deferred.resolve();
+      deferred.resolve(actualOutput);
     });
   };
 
   if (output.completed !== false) {
     checkOutput();
   }
   else {
     var changed = function() {
@@ -787,19 +790,19 @@ helpers._setup = function(options, name,
   }
 
   return Promise.reject('setup must be a string or a function');
 };
 
 /**
  * Helper to shutdown the test
  */
-helpers._post = function(name, action) {
+helpers._post = function(name, action, output) {
   if (typeof action === 'function') {
-    return Promise.resolve(action());
+    return Promise.resolve(action(output));
   }
   return Promise.resolve(action);
 };
 
 /*
  * We do some basic response time stats so we can see if we're getting slow
  */
 var totalResponseTime = 0;
@@ -946,18 +949,18 @@ helpers.audit = function(options, audits
         maxResponseTime = responseTime;
         maxResponseCulprit = assert.currentTest + '/' + name;
       }
       averageOver++;
 
       var checkDone = helpers._check(options, name, audit.check);
       return checkDone.then(function() {
         var execDone = helpers._exec(options, name, audit.exec);
-        return execDone.then(function() {
-          return helpers._post(name, audit.post).then(function() {
+        return execDone.then(function(output) {
+          return helpers._post(name, audit.post, output).then(function() {
             if (assert.testLogging) {
               log('- END \'' + name + '\' in ' + assert.currentTest);
             }
           });
         });
       });
     });
   }).then(null, function(ex) {
--- a/browser/devtools/inspector/test/helpers.js
+++ b/browser/devtools/inspector/test/helpers.js
@@ -19,17 +19,17 @@
 
 this.EXPORTED_SYMBOLS = [ 'helpers' ];
 var helpers = {};
 this.helpers = helpers;
 let require = (Cu.import("resource://gre/modules/devtools/Require.jsm", {})).require;
 Components.utils.import("resource:///modules/devtools/gcli.jsm", {});
 
 let console = (Cu.import("resource://gre/modules/devtools/Console.jsm", {})).console;
-let {devtools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
+let devtools = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools;
 let TargetFactory = devtools.TargetFactory;
 
 let Promise = (Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {})).Promise;
 let assert = { ok: ok, is: is, log: info };
 
 var util = require('util/util');
 
 var converters = require('gcli/converters');
@@ -728,20 +728,23 @@ helpers._exec = function(options, name, 
     var div = options.window.document.createElement('div');
     var nodePromise = converters.convert(output.data, output.type, 'dom',
                                          options.display.requisition.context);
     nodePromise.then(function(node) {
       div.appendChild(node);
       var actualOutput = div.textContent.trim();
 
       var doTest = function(match, against) {
-        if (!match.test(against)) {
-          assert.ok(false, 'html output for ' + name + ' against ' + match.source);
-          log('Actual textContent');
-          log(against);
+        if (match.test(against)) {
+          assert.ok(true, 'html output for ' + name + ' should match ' +
+                          match.source);
+        } else {
+          assert.ok(false, 'html output for ' + name + ' should match ' +
+                           match.source +
+                           '. Actual textContent: "' + against + '"');
         }
       };
 
       if (typeof expected.output === 'string') {
         assert.is(actualOutput,
                   expected.output,
                   'html output for ' + name);
       }
@@ -749,17 +752,17 @@ helpers._exec = function(options, name, 
         expected.output.forEach(function(match) {
           doTest(match, actualOutput);
         });
       }
       else {
         doTest(expected.output, actualOutput);
       }
 
-      deferred.resolve();
+      deferred.resolve(actualOutput);
     });
   };
 
   if (output.completed !== false) {
     checkOutput();
   }
   else {
     var changed = function() {
@@ -787,19 +790,19 @@ helpers._setup = function(options, name,
   }
 
   return Promise.reject('setup must be a string or a function');
 };
 
 /**
  * Helper to shutdown the test
  */
-helpers._post = function(name, action) {
+helpers._post = function(name, action, output) {
   if (typeof action === 'function') {
-    return Promise.resolve(action());
+    return Promise.resolve(action(output));
   }
   return Promise.resolve(action);
 };
 
 /*
  * We do some basic response time stats so we can see if we're getting slow
  */
 var totalResponseTime = 0;
@@ -946,18 +949,18 @@ helpers.audit = function(options, audits
         maxResponseTime = responseTime;
         maxResponseCulprit = assert.currentTest + '/' + name;
       }
       averageOver++;
 
       var checkDone = helpers._check(options, name, audit.check);
       return checkDone.then(function() {
         var execDone = helpers._exec(options, name, audit.exec);
-        return execDone.then(function() {
-          return helpers._post(name, audit.post).then(function() {
+        return execDone.then(function(output) {
+          return helpers._post(name, audit.post, output).then(function() {
             if (assert.testLogging) {
               log('- END \'' + name + '\' in ' + assert.currentTest);
             }
           });
         });
       });
     });
   }).then(null, function(ex) {
--- a/browser/devtools/responsivedesign/test/helpers.js
+++ b/browser/devtools/responsivedesign/test/helpers.js
@@ -728,20 +728,23 @@ helpers._exec = function(options, name, 
     var div = options.window.document.createElement('div');
     var nodePromise = converters.convert(output.data, output.type, 'dom',
                                          options.display.requisition.context);
     nodePromise.then(function(node) {
       div.appendChild(node);
       var actualOutput = div.textContent.trim();
 
       var doTest = function(match, against) {
-        if (!match.test(against)) {
-          assert.ok(false, 'html output for ' + name + ' against ' + match.source);
-          log('Actual textContent');
-          log(against);
+        if (match.test(against)) {
+          assert.ok(true, 'html output for ' + name + ' should match ' +
+                          match.source);
+        } else {
+          assert.ok(false, 'html output for ' + name + ' should match ' +
+                           match.source +
+                           '. Actual textContent: "' + against + '"');
         }
       };
 
       if (typeof expected.output === 'string') {
         assert.is(actualOutput,
                   expected.output,
                   'html output for ' + name);
       }
@@ -749,17 +752,17 @@ helpers._exec = function(options, name, 
         expected.output.forEach(function(match) {
           doTest(match, actualOutput);
         });
       }
       else {
         doTest(expected.output, actualOutput);
       }
 
-      deferred.resolve();
+      deferred.resolve(actualOutput);
     });
   };
 
   if (output.completed !== false) {
     checkOutput();
   }
   else {
     var changed = function() {
@@ -787,19 +790,19 @@ helpers._setup = function(options, name,
   }
 
   return Promise.reject('setup must be a string or a function');
 };
 
 /**
  * Helper to shutdown the test
  */
-helpers._post = function(name, action) {
+helpers._post = function(name, action, output) {
   if (typeof action === 'function') {
-    return Promise.resolve(action());
+    return Promise.resolve(action(output));
   }
   return Promise.resolve(action);
 };
 
 /*
  * We do some basic response time stats so we can see if we're getting slow
  */
 var totalResponseTime = 0;
@@ -946,18 +949,18 @@ helpers.audit = function(options, audits
         maxResponseTime = responseTime;
         maxResponseCulprit = assert.currentTest + '/' + name;
       }
       averageOver++;
 
       var checkDone = helpers._check(options, name, audit.check);
       return checkDone.then(function() {
         var execDone = helpers._exec(options, name, audit.exec);
-        return execDone.then(function() {
-          return helpers._post(name, audit.post).then(function() {
+        return execDone.then(function(output) {
+          return helpers._post(name, audit.post, output).then(function() {
             if (assert.testLogging) {
               log('- END \'' + name + '\' in ' + assert.currentTest);
             }
           });
         });
       });
     });
   }).then(null, function(ex) {
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/AppCacheUtils.jsm
@@ -0,0 +1,623 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * validateManifest() warns of the following errors:
+ *  - No manifest specified in page
+ *  - Manifest is not utf-8
+ *  - Manifest mimetype not text/cache-manifest
+ *  - Manifest does not begin with "CACHE MANIFEST"
+ *  - Page modified since appcache last changed
+ *  - Duplicate entries
+ *  - Conflicting entries e.g. in both CACHE and NETWORK sections or in cache
+ *    but blocked by FALLBACK namespace
+ *  - Detect referenced files that are not available
+ *  - Detect referenced files that have cache-control set to no-store
+ *  - Wildcards used in a section other than NETWORK
+ *  - Spaces in URI not replaced with %20
+ *  - Completely invalid URIs
+ *  - Too many dot dot slash operators
+ *  - SETTINGS section is valid
+ *  - Invalid section name
+ *  - etc.
+ */
+
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+let { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+let { Services }   = Cu.import("resource://gre/modules/Services.jsm", {});
+let { Promise }    = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
+
+this.EXPORTED_SYMBOLS = ["AppCacheUtils"];
+
+function AppCacheUtils(documentOrUri) {
+  this._parseManifest = this._parseManifest.bind(this);
+
+  if (documentOrUri) {
+    if (typeof documentOrUri == "string") {
+      this.uri = documentOrUri;
+    }
+    if (/HTMLDocument/.test(documentOrUri.toString())) {
+      this.doc = documentOrUri;
+    }
+  }
+}
+
+AppCacheUtils.prototype = {
+  get cachePath() {
+    return "";
+  },
+
+  validateManifest: function ACU_validateManifest() {
+    let deferred = Promise.defer();
+    this.errors = [];
+    // Check for missing manifest.
+    this._getManifestURI().then(manifestURI => {
+      this.manifestURI = manifestURI;
+
+      if (!this.manifestURI) {
+        this._addError(0, "noManifest");
+        deferred.resolve(this.errors);
+      }
+
+      this._getURIInfo(this.manifestURI).then(uriInfo => {
+        this._parseManifest(uriInfo).then(() => {
+          // Sort errors by line number.
+          this.errors.sort(function(a, b) {
+            return a.line - b.line;
+          });
+          deferred.resolve(this.errors);
+        });
+      });
+    });
+
+    return deferred.promise;
+  },
+
+  _parseManifest: function ACU__parseManifest(uriInfo) {
+    let deferred = Promise.defer();
+    let manifestName = uriInfo.name;
+    let manifestLastModified = new Date(uriInfo.responseHeaders["Last-Modified"]);
+
+    if (uriInfo.charset.toLowerCase() != "utf-8") {
+      this._addError(0, "notUTF8", uriInfo.charset);
+    }
+
+    if (uriInfo.mimeType != "text/cache-manifest") {
+      this._addError(0, "badMimeType", uriInfo.mimeType);
+    }
+
+    let parser = new ManifestParser(uriInfo.text, this.manifestURI);
+    let parsed = parser.parse();
+
+    if (parsed.errors.length > 0) {
+      this.errors.push.apply(this.errors, parsed.errors);
+    }
+
+    // Check for duplicate entries.
+    let dupes = {};
+    for (let parsedUri of parsed.uris) {
+      dupes[parsedUri.uri] = dupes[parsedUri.uri] || [];
+      dupes[parsedUri.uri].push({
+        line: parsedUri.line,
+        section: parsedUri.section,
+        original: parsedUri.original
+      });
+    }
+    for (let [uri, value] of Iterator(dupes)) {
+      if (value.length > 1) {
+        this._addError(0, "duplicateURI", uri, JSON.stringify(value));
+      }
+    }
+
+    // Loop through network entries making sure that fallback and cache don't
+    // contain uris starting with the network uri.
+    for (let neturi of parsed.uris) {
+      if (neturi.section == "NETWORK") {
+        for (let parsedUri of parsed.uris) {
+          if (parsedUri.uri.startsWith(neturi.uri)) {
+            this._addError(neturi.line, "networkBlocksURI", neturi.line,
+                           neturi.original, parsedUri.line, parsedUri.original,
+                           parsedUri.section);
+          }
+        }
+      }
+    }
+
+    // Loop through fallback entries making sure that fallback and cache don't
+    // contain uris starting with the network uri.
+    for (let fb of parsed.fallbacks) {
+      for (let parsedUri of parsed.uris) {
+        if (parsedUri.uri.startsWith(fb.namespace)) {
+          this._addError(fb.line, "fallbackBlocksURI", fb.line,
+                         fb.original, parsedUri.line, parsedUri.original,
+                         parsedUri.section);
+        }
+      }
+    }
+
+    // Check that all resources exist and that their cach-control headers are
+    // not set to no-store.
+    let current = -1;
+    for (let i = 0, len = parsed.uris.length; i < len; i++) {
+      let parsedUri = parsed.uris[i];
+      this._getURIInfo(parsedUri.uri).then(uriInfo => {
+        current++;
+
+        if (uriInfo.success) {
+          // Check that the resource was not modified after the manifest was last
+          // modified. If it was then the manifest file should be refreshed.
+          let resourceLastModified =
+            new Date(uriInfo.responseHeaders["Last-Modified"]);
+
+          if (manifestLastModified < resourceLastModified) {
+            this._addError(parsedUri.line, "fileChangedButNotManifest",
+                           uriInfo.name, manifestName, parsedUri.line);
+          }
+
+          // If cache-control: no-store the file will not be added to the
+          // appCache.
+          if (uriInfo.nocache) {
+            this._addError(parsedUri.line, "cacheControlNoStore",
+                           parsedUri.original, parsedUri.line);
+          }
+        } else {
+          this._addError(parsedUri.line, "notAvailable",
+                         parsedUri.original, parsedUri.line);
+        }
+
+        if (current == len - 1) {
+          deferred.resolve();
+        }
+      });
+    }
+
+    return deferred.promise;
+  },
+
+  _getURIInfo: function ACU__getURIInfo(uri) {
+    let inputStream = Cc["@mozilla.org/scriptableinputstream;1"]
+                        .createInstance(Ci.nsIScriptableInputStream);
+    let deferred = Promise.defer();
+    let channelCharset = "";
+    let buffer = "";
+    let channel = Services.io.newChannel(uri, null, null);
+
+    // Avoid the cache:
+    channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+    channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
+
+    channel.asyncOpen({
+      onStartRequest: function (request, context) {
+        // This empty method is needed in order for onDataAvailable to be
+        // called.
+      },
+
+      onDataAvailable: function (request, context, stream, offset, count) {
+        request.QueryInterface(Ci.nsIHttpChannel);
+        inputStream.init(stream);
+        buffer = buffer.concat(inputStream.read(count));
+      },
+
+      onStopRequest: function onStartRequest(request, context, statusCode) {
+        if (statusCode == 0) {
+          request.QueryInterface(Ci.nsIHttpChannel);
+
+          let result = {
+            name: request.name,
+            success: request.requestSucceeded,
+            status: request.responseStatus + " - " + request.responseStatusText,
+            charset: request.contentCharset || "utf-8",
+            mimeType: request.contentType,
+            contentLength: request.contentLength,
+            nocache: request.isNoCacheResponse() || request.isNoStoreResponse(),
+            prePath: request.URI.prePath + "/",
+            text: buffer
+          };
+
+          result.requestHeaders = {};
+          request.visitRequestHeaders(function(header, value) {
+            result.requestHeaders[header] = value;
+          });
+
+          result.responseHeaders = {};
+          request.visitResponseHeaders(function(header, value) {
+            result.responseHeaders[header] = value;
+          });
+
+          deferred.resolve(result);
+        } else {
+          deferred.resolve({
+            name: request.name,
+            success: false
+          });
+        }
+      }
+    }, null);
+    return deferred.promise;
+  },
+
+  listEntries: function ACU_show(searchTerm) {
+    let entries = [];
+
+    Services.cache.visitEntries({
+      visitDevice: function(deviceID, deviceInfo) {
+        return true;
+      },
+
+      visitEntry: function(deviceID, entryInfo) {
+        if (entryInfo.deviceID == "offline") {
+          let entry = {};
+          let lowerKey = entryInfo.key.toLowerCase();
+
+          if (searchTerm && lowerKey.indexOf(searchTerm.toLowerCase()) == -1) {
+            return true;
+          }
+
+          for (let [key, value] of Iterator(entryInfo)) {
+            if (key == "QueryInterface") {
+              continue;
+            }
+            if (key == "clientID") {
+              entry.key = entryInfo.key;
+            }
+            if (key == "expirationTime" || key == "lastFetched" || key == "lastModified") {
+              value = new Date(value * 1000);
+            }
+            entry[key] = value;
+          }
+          entries.push(entry);
+        }
+        return true;
+      }
+    });
+
+    return entries;
+  },
+
+  viewEntry: function ACU_viewEntry(key) {
+    let uri;
+
+    Services.cache.visitEntries({
+      visitDevice: function(deviceID, deviceInfo) {
+        return true;
+      },
+
+      visitEntry: function(deviceID, entryInfo) {
+        if (entryInfo.deviceID == "offline" && entryInfo.key == key) {
+          uri = "about:cache-entry?client=" + entryInfo.clientID +
+                "&sb=1&key=" + entryInfo.key;
+          return false;
+        }
+        return true;
+      }
+    });
+
+    if (uri) {
+      let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
+                 .getService(Ci.nsIWindowMediator);
+      let win = wm.getMostRecentWindow("navigator:browser");
+      win.gBrowser.selectedTab = win.gBrowser.addTab(uri);
+    } else {
+      return l10n.GetStringFromName("entryNotFound");
+    }
+  },
+
+  clearAll: function ACU_clearAll() {
+    Services.cache.evictEntries(Ci.nsICache.STORE_OFFLINE);
+  },
+
+  _getManifestURI: function ACU__getManifestURI() {
+    let deferred = Promise.defer();
+
+    let getURI = node => {
+      let htmlNode = this.doc.querySelector("html[manifest]");
+      if (htmlNode) {
+        let pageUri = this.doc.location ? this.doc.location.href : this.uri;
+        let origin = pageUri.substr(0, pageUri.lastIndexOf("/") + 1);
+        return origin + htmlNode.getAttribute("manifest");
+      }
+    };
+
+    if (this.doc) {
+      let uri = getURI(this.doc);
+      return Promise.resolve(uri);
+    } else {
+      this._getURIInfo(this.uri).then(uriInfo => {
+        if (uriInfo.success) {
+          let html = uriInfo.text;
+          let parser = _DOMParser;
+          this.doc = parser.parseFromString(html, "text/html");
+          let uri = getURI(this.doc);
+          deferred.resolve(uri);
+        } else {
+          this.errors.push({
+            line: 0,
+            msg: "The URI passed to AppCacheUtils is invalid."
+          });
+        }
+      });
+    }
+    return deferred.promise;
+  },
+
+  _addError: function ACU__addError(line, l10nString, ...params) {
+    let msg;
+
+    if (params) {
+      msg = l10n.formatStringFromName(l10nString, params, params.length);
+    } else {
+      msg = l10n.GetStringFromName(l10nString);
+    }
+
+    this.errors.push({
+      line: line,
+      msg: msg
+    });
+  },
+};
+
+/**
+ * We use our own custom parser because we need far more detailed information
+ * than the system manifest parser provides.
+ *
+ * @param {String} manifestText
+ *        The text content of the manifest file.
+ * @param {String} manifestURI
+ *        The URI of the manifest file. This is used in calculating the path of
+ *        relative URIs.
+ */
+function ManifestParser(manifestText, manifestURI) {
+  this.manifestText = manifestText;
+  this.origin = manifestURI.substr(0, manifestURI.lastIndexOf("/") + 1)
+                           .replace(" ", "%20");
+}
+
+ManifestParser.prototype = {
+  parse: function OCIMP_parse() {
+    let lines = this.manifestText.split(/\r?\n/);
+    let fallbacks = this.fallbacks = [];
+    let settings = this.settings = [];
+    let errors = this.errors = [];
+    let uris = this.uris = [];
+
+    this.currSection = "CACHE";
+
+    for (let i = 0; i < lines.length; i++) {
+      let text = this.text = lines[i].replace(/^\s+|\s+$/g);
+      this.currentLine = i + 1;
+
+      if (i == 0 && text != "CACHE MANIFEST") {
+        this._addError(1, "firstLineMustBeCacheManifest", 1);
+      }
+
+      // Ignore comments
+      if (/^#/.test(text) || !text.length) {
+        continue;
+      }
+
+      if (text == "CACHE MANIFEST") {
+        if (this.currentLine != 1) {
+          this._addError(this.currentLine, "cacheManifestOnlyFirstLine",
+                         this.currentLine);
+        }
+        continue;
+      }
+
+      if (this._maybeUpdateSectionName()) {
+        continue;
+      }
+
+      switch (this.currSection) {
+        case "CACHE":
+        case "NETWORK":
+          this.parseLine();
+          break;
+        case "FALLBACK":
+          this.parseFallbackLine();
+          break;
+        case "SETTINGS":
+          this.parseSettingsLine();
+          break;
+      }
+    }
+
+    return {
+      uris: uris,
+      fallbacks: fallbacks,
+      settings: settings,
+      errors: errors
+    };
+  },
+
+  parseLine: function OCIMP_parseLine() {
+    let text = this.text;
+
+    if (text.indexOf("*") != -1) {
+      if (this.currSection != "NETWORK" || text.length != 1) {
+        this._addError(this.currentLine, "asteriskInWrongSection",
+                       this.currSection, this.currentLine);
+        return;
+      }
+    }
+
+    if (/\s/.test(text)) {
+      this._addError(this.currentLine, "escapeSpaces", this.currentLine);
+      text = text.replace(/\s/g, "%20")
+    }
+
+    if (text[0] == "/") {
+      if (text.substr(0, 4) == "/../") {
+        this._addError(this.currentLine, "slashDotDotSlashBad", this.currentLine);
+      } else {
+        this.uris.push(this._wrapURI(this.origin + text.substring(1)));
+      }
+    } else if (text.substr(0, 2) == "./") {
+      this.uris.push(this._wrapURI(this.origin + text.substring(2)));
+    } else if (text.substr(0, 4) == "http") {
+      this.uris.push(this._wrapURI(text));
+    } else {
+      let origin = this.origin;
+      let path = text;
+
+      while (path.substr(0, 3) == "../" && /^https?:\/\/.*?\/.*?\//.test(origin)) {
+        let trimIdx = origin.substr(0, origin.length - 1).lastIndexOf("/") + 1;
+        origin = origin.substr(0, trimIdx);
+        path = path.substr(3);
+      }
+
+      if (path.substr(0, 3) == "../") {
+        this._addError(this.currentLine, "tooManyDotDotSlashes", this.currentLine);
+        return;
+      }
+
+      if (/^https?:\/\//.test(path)) {
+        this.uris.push(this._wrapURI(path));
+        return;
+      }
+      this.uris.push(this._wrapURI(origin + path));
+    }
+  },
+
+  parseFallbackLine: function OCIMP_parseFallbackLine() {
+    let split = this.text.split(/\s+/);
+    let origURI = this.text;
+
+    if (split.length != 2) {
+      this._addError(this.currentLine, "fallbackUseSpaces", this.currentLine);
+      return;
+    }
+
+    let [ namespace, fallback ] = split;
+
+    if (namespace.indexOf("*") != -1) {
+      this._addError(this.currentLine, "fallbackAsterisk", this.currentLine);
+    }
+
+    if (/\s/.test(namespace)) {
+      this._addError(this.currentLine, "escapeSpaces", this.currentLine);
+      namespace = namespace.replace(/\s/g, "%20")
+    }
+
+    if (namespace.substr(0, 4) == "/../") {
+      this._addError(this.currentLine, "slashDotDotSlashBad", this.currentLine);
+    }
+
+    if (namespace.substr(0, 2) == "./") {
+      namespace = this.origin + namespace.substring(2);
+    }
+
+    if (namespace.substr(0, 4) != "http") {
+      let origin = this.origin;
+      let path = namespace;
+
+      while (path.substr(0, 3) == "../" && /^https?:\/\/.*?\/.*?\//.test(origin)) {
+        let trimIdx = origin.substr(0, origin.length - 1).lastIndexOf("/") + 1;
+        origin = origin.substr(0, trimIdx);
+        path = path.substr(3);
+      }
+
+      if (path.substr(0, 3) == "../") {
+        this._addError(this.currentLine, "tooManyDotDotSlashes", this.currentLine);
+      }
+
+      if (/^https?:\/\//.test(path)) {
+        namespace = path;
+      } else {
+        if (path[0] == "/") {
+          path = path.substring(1);
+        }
+        namespace = origin + path;
+      }
+    }
+
+    this.text = fallback;
+    this.parseLine();
+
+    this.fallbacks.push({
+      line: this.currentLine,
+      original: origURI,
+      namespace: namespace,
+      fallback: fallback
+    });
+  },
+
+  parseSettingsLine: function OCIMP_parseSettingsLine() {
+    let text = this.text;
+
+    if (this.settings.length == 1 || !/prefer-online|fast/.test(text)) {
+      this._addError(this.currentLine, "settingsBadValue", this.currentLine);
+      return;
+    }
+
+    switch (text) {
+      case "prefer-online":
+        this.settings.push(this._wrapURI(text));
+        break;
+      case "fast":
+        this.settings.push(this._wrapURI(text));
+        break;
+    }
+  },
+
+  _wrapURI: function OCIMP__wrapURI(uri) {
+    return {
+      section: this.currSection,
+      line: this.currentLine,
+      uri: uri,
+      original: this.text
+    };
+  },
+
+  _addError: function OCIMP__addError(line, l10nString, ...params) {
+    let msg;
+
+    if (params) {
+      msg = l10n.formatStringFromName(l10nString, params, params.length);
+    } else {
+      msg = l10n.GetStringFromName(l10nString);
+    }
+
+    this.errors.push({
+      line: line,
+      msg: msg
+    });
+  },
+
+  _maybeUpdateSectionName: function OCIMP__maybeUpdateSectionName() {
+    let text = this.text;
+
+    if (text == text.toUpperCase() && text.charAt(text.length - 1) == ":") {
+      text = text.substr(0, text.length - 1);
+
+      switch (text) {
+        case "CACHE":
+        case "NETWORK":
+        case "FALLBACK":
+        case "SETTINGS":
+          this.currSection = text;
+          return true;
+        default:
+          this._addError(this.currentLine,
+                         "invalidSectionName", text, this.currentLine);
+          return false;
+      }
+    }
+  },
+};
+
+XPCOMUtils.defineLazyGetter(this, "l10n", function() Services.strings
+  .createBundle("chrome://browser/locale/devtools/appcacheutils.properties"));
+
+XPCOMUtils.defineLazyGetter(this, "appcacheservice", function() {
+  return Cc["@mozilla.org/network/application-cache-service;1"]
+           .getService(Ci.nsIApplicationCacheService);
+
+});
+
+XPCOMUtils.defineLazyGetter(this, "_DOMParser", function() {
+  return Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
+});
--- a/browser/devtools/styleeditor/test/helpers.js
+++ b/browser/devtools/styleeditor/test/helpers.js
@@ -728,20 +728,23 @@ helpers._exec = function(options, name, 
     var div = options.window.document.createElement('div');
     var nodePromise = converters.convert(output.data, output.type, 'dom',
                                          options.display.requisition.context);
     nodePromise.then(function(node) {
       div.appendChild(node);
       var actualOutput = div.textContent.trim();
 
       var doTest = function(match, against) {
-        if (!match.test(against)) {
-          assert.ok(false, 'html output for ' + name + ' against ' + match.source);
-          log('Actual textContent');
-          log(against);
+        if (match.test(against)) {
+          assert.ok(true, 'html output for ' + name + ' should match ' +
+                          match.source);
+        } else {
+          assert.ok(false, 'html output for ' + name + ' should match ' +
+                           match.source +
+                           '. Actual textContent: "' + against + '"');
         }
       };
 
       if (typeof expected.output === 'string') {
         assert.is(actualOutput,
                   expected.output,
                   'html output for ' + name);
       }
@@ -749,17 +752,17 @@ helpers._exec = function(options, name, 
         expected.output.forEach(function(match) {
           doTest(match, actualOutput);
         });
       }
       else {
         doTest(expected.output, actualOutput);
       }
 
-      deferred.resolve();
+      deferred.resolve(actualOutput);
     });
   };
 
   if (output.completed !== false) {
     checkOutput();
   }
   else {
     var changed = function() {
@@ -787,19 +790,19 @@ helpers._setup = function(options, name,
   }
 
   return Promise.reject('setup must be a string or a function');
 };
 
 /**
  * Helper to shutdown the test
  */
-helpers._post = function(name, action) {
+helpers._post = function(name, action, output) {
   if (typeof action === 'function') {
-    return Promise.resolve(action());
+    return Promise.resolve(action(output));
   }
   return Promise.resolve(action);
 };
 
 /*
  * We do some basic response time stats so we can see if we're getting slow
  */
 var totalResponseTime = 0;
@@ -946,18 +949,18 @@ helpers.audit = function(options, audits
         maxResponseTime = responseTime;
         maxResponseCulprit = assert.currentTest + '/' + name;
       }
       averageOver++;
 
       var checkDone = helpers._check(options, name, audit.check);
       return checkDone.then(function() {
         var execDone = helpers._exec(options, name, audit.exec);
-        return execDone.then(function() {
-          return helpers._post(name, audit.post).then(function() {
+        return execDone.then(function(output) {
+          return helpers._post(name, audit.post, output).then(function() {
             if (assert.testLogging) {
               log('- END \'' + name + '\' in ' + assert.currentTest);
             }
           });
         });
       });
     });
   }).then(null, function(ex) {
--- a/browser/devtools/webconsole/test/Makefile.in
+++ b/browser/devtools/webconsole/test/Makefile.in
@@ -117,30 +117,32 @@ MOCHITEST_BROWSER_FILES = \
 	browser_repeated_messages_accuracy.js \
 	browser_webconsole_bug_821877_csp_errors.js \
 	browser_eval_in_debugger_stackframe.js \
 	browser_console_variables_view.js \
 	browser_console_variables_view_while_debugging.js \
 	browser_console.js \
 	browser_longstring_hang.js \
 	browser_console_consolejsm_output.js \
-  browser_webconsole_bug_837351_securityerrors.js \
+	browser_webconsole_bug_837351_securityerrors.js \
+	browser_bug_865871_variables_view_close_on_esc_key.js \
+	browser_bug_865288_repeat_different_objects.js \
 	head.js \
 	$(NULL)
 
 ifeq ($(OS_ARCH), Darwin)
 MOCHITEST_BROWSER_FILES += \
-        browser_webconsole_bug_804845_ctrl_key_nav.js \
+	browser_webconsole_bug_804845_ctrl_key_nav.js \
         $(NULL)
 endif
 
 ifeq ($(OS_ARCH),WINNT)
 MOCHITEST_BROWSER_FILES += \
-		browser_webconsole_bug_623749_ctrl_a_select_all_winnt.js \
-		$(NULL)
+	browser_webconsole_bug_623749_ctrl_a_select_all_winnt.js \
+	$(NULL)
 endif
 
 MOCHITEST_BROWSER_FILES += \
 	test-console.html \
 	test-network.html \
 	test-network-request.html \
 	test-mutation.html \
 	testscript.js \
@@ -220,12 +222,12 @@ MOCHITEST_BROWSER_FILES += \
 	test-repeated-messages.html \
 	test-bug-766001-console-log.js \
 	test-bug-766001-js-console-links.html \
 	test-bug-766001-js-errors.js \
 	test-bug-821877-csperrors.html \
 	test-bug-821877-csperrors.html^headers^ \
 	test-eval-in-stackframe.html \
 	test-bug-859170-longstring-hang.html \
-  test-bug-837351-security-errors.html \
+	test-bug-837351-security-errors.html \
 	$(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_bug_865288_repeat_different_objects.js
@@ -0,0 +1,82 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests that makes sure messages are not considered repeated when console.log()
+// is invoked with different objects, see bug 865288.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-repeated-messages.html";
+
+let hud = null;
+
+function test() {
+  addTab(TEST_URI);
+  browser.addEventListener("load", function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    openConsole(null, consoleOpened);
+  }, true);
+}
+
+function consoleOpened(aHud) {
+  hud = aHud;
+
+  // Check that css warnings are not coalesced if they come from different lines.
+  info("waiting for 3 console.log objects");
+
+  hud.jsterm.clearOutput(true);
+  content.wrappedJSObject.testConsoleObjects();
+
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      name: "3 console.log messages",
+      text: "abba",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+      count: 3,
+      repeats: 1,
+      objects: true,
+    }],
+  }).then(checkMessages);
+}
+
+function checkMessages(aResults)
+{
+  let result = aResults[0];
+  let msgs = [...result.matched];
+  is(msgs.length, 3, "3 message elements");
+  let m = -1;
+
+  function nextMessage()
+  {
+    let msg = msgs[++m];
+    if (msg) {
+      ok(msg, "message element #" + m);
+
+      let clickable = msg.querySelector(".hud-clickable");
+      ok(clickable, "clickable object #" + m);
+
+      scrollOutputToNode(msg);
+      clickObject(clickable);
+    }
+    else {
+      finishTest();
+    }
+  }
+
+  nextMessage();
+
+  function clickObject(aObject)
+  {
+    hud.jsterm.once("variablesview-fetched", onObjectFetch);
+    EventUtils.synthesizeMouse(aObject, 2, 2, {}, hud.iframeWindow);
+  }
+
+  function onObjectFetch(aEvent, aVar)
+  {
+    findVariableViewProperties(aVar, [
+      { name: "id", value: "abba" + m },
+    ], { webconsole: hud }).then(nextMessage);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_bug_865871_variables_view_close_on_esc_key.js
@@ -0,0 +1,98 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Check that the variables view sidebar can be closed by pressing Escape in the
+// web console.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-eval-in-stackframe.html";
+
+let gWebConsole, gJSTerm, gVariablesView;
+
+function test()
+{
+  registerCleanupFunction(() => {
+    gWebConsole = gJSTerm = gVariablesView = null;
+  });
+
+  addTab(TEST_URI);
+  browser.addEventListener("load", function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    openConsole(null, consoleOpened);
+  }, true);
+}
+
+function consoleOpened(hud)
+{
+  gWebConsole = hud;
+  gJSTerm = hud.jsterm;
+  gJSTerm.execute("fooObj", onExecuteFooObj);
+}
+
+function onExecuteFooObj()
+{
+  let msg = gWebConsole.outputNode.querySelector(".webconsole-msg-output");
+  ok(msg, "output message found");
+  isnot(msg.textContent.indexOf("[object Object]"), -1, "message text check");
+
+  gJSTerm.once("variablesview-fetched", onFooObjFetch);
+  EventUtils.synthesizeMouse(msg, 2, 2, {}, gWebConsole.iframeWindow)
+}
+
+function onFooObjFetch(aEvent, aVar)
+{
+  gVariablesView = aVar._variablesView;
+  ok(gVariablesView, "variables view object");
+
+  findVariableViewProperties(aVar, [
+    { name: "testProp", value: "testValue" },
+  ], { webconsole: gWebConsole }).then(onTestPropFound);
+}
+
+function onTestPropFound(aResults)
+{
+  let prop = aResults[0].matchedProp;
+  ok(prop, "matched the |testProp| property in the variables view");
+
+  is(content.wrappedJSObject.fooObj.testProp, aResults[0].value,
+     "|fooObj.testProp| value is correct");
+
+  gVariablesView.window.focus();
+  gJSTerm.once("sidebar-closed", onSidebarClosed);
+  EventUtils.synthesizeKey("VK_ESCAPE", {}, gVariablesView.window);
+}
+
+function onSidebarClosed()
+{
+  gJSTerm.clearOutput();
+  gJSTerm.execute("window", onExecuteWindow);
+}
+
+function onExecuteWindow()
+{
+  let msg = gWebConsole.outputNode.querySelector(".webconsole-msg-output");
+  ok(msg, "output message found");
+  isnot(msg.textContent.indexOf("[object Window]"), -1, "message text check");
+
+  gJSTerm.once("variablesview-fetched", onWindowFetch);
+  EventUtils.synthesizeMouse(msg, 2, 2, {}, gWebConsole.iframeWindow)
+}
+
+function onWindowFetch(aEvent, aVar)
+{
+  gVariablesView = aVar._variablesView;
+  ok(gVariablesView, "variables view object");
+
+  findVariableViewProperties(aVar, [
+    { name: "foo", value: "globalFooBug783499" },
+  ], { webconsole: gWebConsole }).then(onFooFound);
+}
+
+function onFooFound(aResults)
+{
+  gVariablesView.window.focus();
+  gJSTerm.once("sidebar-closed", finishTest);
+  EventUtils.synthesizeKey("VK_ESCAPE", {}, gVariablesView.window);
+}
+
--- a/browser/devtools/webconsole/test/browser_console.js
+++ b/browser/devtools/webconsole/test/browser_console.js
@@ -1,16 +1,16 @@
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Test the basic features of the Browser Console, bug 587757.
 
-const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-eval-in-stackframe.html";
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html?" + Date.now();
 
 function test()
 {
   HUDConsoleUI.toggleBrowserConsole().then(consoleOpened);
 }
 
 function consoleOpened(hud)
 {
--- a/browser/devtools/webconsole/test/browser_console_variables_view.js
+++ b/browser/devtools/webconsole/test/browser_console_variables_view.js
@@ -1,15 +1,14 @@
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-// Test that makes sure web console eval happens in the user-selected stackframe
-// from the js debugger.
+// Check that variables view works as expected in the web console.
 
 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-eval-in-stackframe.html";
 
 let gWebConsole, gJSTerm, gVariablesView;
 
 function test()
 {
   addTab(TEST_URI);
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -864,26 +864,51 @@ function getMessageElementText(aElement)
  *
  * @param object aOptions
  *        Options for what you want to wait for:
  *        - webconsole: the webconsole instance you work with.
  *        - messages: an array of objects that tells which messages to wait for.
  *        Properties:
  *            - text: string or RegExp to match the textContent of each new
  *            message.
+ *            - noText: string or RegExp that must not match in the message
+ *            textContent.
  *            - repeats: the number of message repeats, as displayed by the Web
  *            Console.
  *            - category: match message category. See CATEGORY_* constants at
  *            the top of this file.
  *            - severity: match message severity. See SEVERITY_* constants at
  *            the top of this file.
  *            - count: how many unique web console messages should be matched by
  *            this rule.
+ *            - consoleTrace: boolean, set to |true| to match a console.trace()
+ *            message. Optionally this can be an object of the form
+ *            { file, fn, line } that can match the specified file, function
+ *            and/or line number in the trace message.
+ *            - consoleTime: string that matches a console.time() timer name.
+ *            Provide this if you want to match a console.time() message.
+ *            - consoleTimeEnd: same as above, but for console.timeEnd().
+ *            - consoleDir: boolean, set to |true| to match a console.dir()
+ *            message.
+ *            - longString: boolean, set to |true} to match long strings in the
+ *            message.
+ *            - objects: boolean, set to |true| if you expect inspectable
+ *            objects in the message.
  * @return object
  *         A Promise object is returned once the messages you want are found.
+ *         The promise is resolved with the array of rule objects you give in
+ *         the |messages| property. Each objects is the same as provided, with
+ *         additional properties:
+ *         - matched: a Set of web console messages that matched the rule.
+ *         - clickableElements: a list of inspectable objects. This is available
+ *         if any of the following properties are present in the rule:
+ *         |consoleTrace| or |objects|.
+ *         - longStrings: a list of long string ellipsis elements you can click
+ *         in the message element, to expand a long string. This is available
+ *         only if |longString| is present in the matching rule.
  */
 function waitForMessages(aOptions)
 {
   gPendingOutputTest++;
   let webconsole = aOptions.webconsole;
   let rules = WebConsoleUtils.cloneObject(aOptions.messages, true);
   let rulesMatched = 0;
   let listenerAdded = false;
--- a/browser/devtools/webconsole/test/test-repeated-messages.html
+++ b/browser/devtools/webconsole/test/test-repeated-messages.html
@@ -1,18 +1,24 @@
 <!DOCTYPE HTML>
 <html dir="ltr" xml:lang="en-US" lang="en-US">
   <head>
     <meta charset="utf8">
-    <title>Test for bugs 720180 and 800510</title>
+    <title>Test for bugs 720180, 800510 and 865288</title>
     <script>
       function testConsole() {
         console.log("foo repeat"); console.log("foo repeat");
         console.log("foo repeat"); console.error("foo repeat");
       }
+      function testConsoleObjects() {
+        for (var i = 0; i < 3; i++) {
+          var o = { id: "abba" + i };
+          console.log("abba", o);
+        }
+      }
     </script>
     <style>
       body {
         background-image: foobarz;
       }
       p {
         background-image: foobarz;
       }
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -1057,17 +1057,18 @@ WebConsoleFrame.prototype = {
         break;
       }
 
       case "timeEnd": {
         let timer = aMessage.timer;
         if (!timer) {
           return;
         }
-        body = l10n.getFormatStr("timeEnd", [timer.name, timer.duration]);
+        let duration = Math.round(timer.duration * 100) / 100;
+        body = l10n.getFormatStr("timeEnd", [timer.name, duration]);
         clipboardText = body;
         break;
       }
 
       default:
         Cu.reportError("Unknown Console API log level: " + level);
         return;
     }
@@ -1092,25 +1093,31 @@ WebConsoleFrame.prototype = {
     }
 
     let node = this.createMessageNode(CATEGORY_WEBDEV, LEVELS[level], body,
                                       sourceURL, sourceLine, clipboardText,
                                       level, aMessage.timeStamp);
 
     if (objectActors.size > 0) {
       node._objectActors = objectActors;
+
+      let repeatNode = node.querySelector(".webconsole-msg-repeat");
+      repeatNode._uid += [...objectActors].join("-");
     }
 
     // Make the node bring up the variables view, to allow the user to inspect
     // the stack trace.
     if (level == "trace") {
       node._stacktrace = aMessage.stacktrace;
 
       this.makeOutputMessageLink(node, () =>
-        this.jsterm.openVariablesView({ rawObject: node._stacktrace }));
+        this.jsterm.openVariablesView({
+          rawObject: node._stacktrace,
+          autofocus: true,
+        }));
     }
 
     return node;
   },
 
   /**
    * Handle ConsoleAPICall objects received from the server. This method outputs
    * the window.console API call.
@@ -1131,21 +1138,21 @@ WebConsoleFrame.prototype = {
    * @param nsIDOMNode aAnchor
    *        The object inspector anchor element. This is the clickable element
    *        in the console.log message we display.
    * @param object aObjectActor
    *        The object actor grip.
    */
   _consoleLogClick: function WCF__consoleLogClick(aAnchor, aObjectActor)
   {
-    let options = {
+    this.jsterm.openVariablesView({
       label: aAnchor.textContent,
       objectActor: aObjectActor,
-    };
-    this.jsterm.openVariablesView(options);
+      autofocus: true,
+    });
   },
 
   /**
    * Reports an error in the page source, either JavaScript or CSS.
    *
    * @param nsIScriptError aScriptError
    *        The error message to report.
    * @return nsIDOMElement|undefined
@@ -2643,16 +2650,17 @@ function JSTerm(aWebConsoleFrame)
   this.historyIndex = 0;
   this.historyPlaceHolder = 0;  // this.history.length;
   this._objectActorsInVariablesViews = new Map();
 
   this._keyPress = this.keyPress.bind(this);
   this._inputEventHandler = this.inputEventHandler.bind(this);
   this._fetchVarProperties = this._fetchVarProperties.bind(this);
   this._fetchVarLongString = this._fetchVarLongString.bind(this);
+  this._onKeypressInVariablesView = this._onKeypressInVariablesView.bind(this);
 
   EventEmitter.decorate(this);
 }
 
 JSTerm.prototype = {
   SELECTED_FRAME: -1,
 
   /**
@@ -3001,16 +3009,18 @@ JSTerm.prototype = {
    *        - rawObject: the raw object you want to show in the variables view.
    *        - label: label to display in the variables view for inspected
    *        object.
    *        - hideFilterInput: optional boolean, |true| if you want to hide the
    *        variables view filter input.
    *        - targetElement: optional nsIDOMElement to append the variables view
    *        to. An iframe element is used as a container for the view. If this
    *        option is not used, then the variables view opens in the sidebar.
+   *        - autofocus: optional boolean, |true| if you want to give focus to
+   *        the variables view window after open, |false| otherwise.
    * @return object
    *         A Promise object that is resolved when the variables view has
    *         opened. The new variables view instance is given to the callbacks.
    */
   openVariablesView: function JST_openVariablesView(aOptions)
   {
     let onContainerReady = (aWindow) => {
       let container = aWindow.document.querySelector("#variables");
@@ -3018,20 +3028,25 @@ JSTerm.prototype = {
       if (!view || aOptions.targetElement) {
         let viewOptions = {
           container: container,
           hideFilterInput: aOptions.hideFilterInput,
         };
         view = this._createVariablesView(viewOptions);
         if (!aOptions.targetElement) {
           this._variablesView = view;
+          aWindow.addEventListener("keypress", this._onKeypressInVariablesView);
         }
       }
       aOptions.view = view;
       this._updateVariablesView(aOptions);
+
+      this.sidebar.show();
+      aOptions.autofocus && aWindow.focus();
+
       this.emit("variablesview-open", view, aOptions);
       return view;
     };
 
     let promise;
     if (aOptions.targetElement) {
       let deferred = Promise.defer();
       promise = deferred.promise;
@@ -3043,36 +3058,36 @@ JSTerm.prototype = {
         deferred.resolve(iframe.contentWindow);
       }, true);
 
       iframe.flex = 1;
       iframe.setAttribute("src", VARIABLES_VIEW_URL);
       aOptions.targetElement.appendChild(iframe);
     }
     else {
-      this._createSidebar();
+      if (!this.sidebar) {
+        this._createSidebar();
+      }
       promise = this._addVariablesViewSidebarTab();
     }
 
     return promise.then(onContainerReady);
   },
 
   /**
    * Create the Web Console sidebar.
    *
    * @see devtools/framework/sidebar.js
    * @private
    */
   _createSidebar: function JST__createSidebar()
   {
-    if (!this.sidebar) {
-      let tabbox = this.hud.document.querySelector("#webconsole-sidebar");
-      let ToolSidebar = devtools.require("devtools/framework/sidebar").ToolSidebar;
-      this.sidebar = new ToolSidebar(tabbox, this);
-    }
+    let tabbox = this.hud.document.querySelector("#webconsole-sidebar");
+    let ToolSidebar = devtools.require("devtools/framework/sidebar").ToolSidebar;
+    this.sidebar = new ToolSidebar(tabbox, this);
     this.sidebar.show();
   },
 
   /**
    * Add the variables view tab to the sidebar.
    *
    * @private
    * @return object
@@ -3101,16 +3116,37 @@ JSTerm.prototype = {
       this.sidebar.once("variablesview-ready", onTabReady);
       this.sidebar.addTab("variablesview", VARIABLES_VIEW_URL, true);
     }
 
     return deferred.promise;
   },
 
   /**
+   * The keypress event handler for the Variables View sidebar. Currently this
+   * is used for removing the sidebar when Escape is pressed.
+   *
+   * @private
+   * @param nsIDOMEvent aEvent
+   *        The keypress DOM event object.
+   */
+  _onKeypressInVariablesView: function JST__onKeypressInVariablesView(aEvent)
+  {
+    let tag = aEvent.target.nodeName;
+    if (aEvent.keyCode != Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE || aEvent.shiftKey ||
+        aEvent.altKey || aEvent.ctrlKey || aEvent.metaKey ||
+        ["input", "textarea", "select", "textbox"].indexOf(tag) > -1) {
+        return;
+    }
+
+    this._sidebarDestroy();
+    this.inputNode.focus();
+  },
+
+  /**
    * Create a variables view instance.
    *
    * @private
    * @param object aOptions
    *        Options for the new Variables View instance:
    *        - container: the DOM element where the variables view is inserted.
    *        - hideFilterInput: boolean, if true the variables filter input is
    *        hidden.
@@ -3757,16 +3793,19 @@ JSTerm.prototype = {
     let inputUpdated = false;
 
     switch(aEvent.keyCode) {
       case Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE:
         if (this.autocompletePopup.isOpen) {
           this.clearCompletion();
           aEvent.preventDefault();
         }
+        else if (this.sidebar) {
+          this._sidebarDestroy();
+        }
         break;
 
       case Ci.nsIDOMKeyEvent.DOM_VK_RETURN:
         if (this.autocompletePopup.isOpen && this.autocompletePopup.selectedIndex > -1) {
           this.acceptProposedCompletion();
         }
         else {
           this.execute();
@@ -4162,38 +4201,51 @@ JSTerm.prototype = {
    * @param object aResponse
    *        The JavaScript evaluation response received from the server.
    */
   _evalOutputClick: function JST__evalOutputClick(aResponse)
   {
     this.openVariablesView({
       label: VariablesView.getString(aResponse.result),
       objectActor: aResponse.result,
+      autofocus: true,
     });
   },
 
   /**
-   * Destroy the JSTerm object. Call this method to avoid memory leaks.
+   * Destroy the sidebar.
+   * @private
    */
-  destroy: function JST_destroy()
+  _sidebarDestroy: function JST__sidebarDestroy()
   {
     if (this._variablesView) {
       let actors = this._objectActorsInVariablesViews.get(this._variablesView);
       for (let actor of actors) {
         this.hud._releaseObject(actor);
       }
       actors.clear();
       this._variablesView = null;
     }
 
     if (this.sidebar) {
+      this.sidebar.hide();
       this.sidebar.destroy();
       this.sidebar = null;
     }
 
+    this.emit("sidebar-closed");
+  },
+
+  /**
+   * Destroy the JSTerm object. Call this method to avoid memory leaks.
+   */
+  destroy: function JST_destroy()
+  {
+    this._sidebarDestroy();
+
     this.clearCompletion();
     this.clearOutput();
 
     this.autocompletePopup.destroy();
     this.autocompletePopup = null;
 
     let popup = this.hud.owner.chromeWindow.document
                 .getElementById("webConsole_autocompletePopup");
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/devtools/appcacheutils.properties
@@ -0,0 +1,112 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# LOCALIZATION NOTE These strings are used inside the Inspector
+# which is available from the Web Developer sub-menu -> 'Inspect'.
+#
+# The correct localization of this file might be to keep it in
+# English, or another language commonly spoken among web developers.
+# You want to make that choice consistent across the developer tools.
+# A good criteria is the language in which you'd find the best
+# documentation on web development on the web.
+
+# LOCALIZATION NOTE (noManifest): Used when an attempt is made to validate a
+# page using AppCacheUtils.jsm that has no cache manifest.
+noManifest=The specified page has no manifest.
+
+# LOCALIZATION NOTE (notUTF8): Used when an attempt is made to validate a
+# cache manifest using AppCacheUtils.jsm having a character encoding that is not
+# UTF-8.
+notUTF8=Manifest has a character encoding of %S. Manifests must have the utf-8 character encoding.
+
+# LOCALIZATION NOTE (badMimeType): Used when an attempt is made to validate a
+# cache manifest using AppCacheUtils.jsm having a mimetype that is not
+# text/cache-manifest.
+badMimeType=Manifest has a mimetype of %S. Manifests must have a mimetype of text/cache-manifest.
+
+# LOCALIZATION NOTE (duplicateURI): Used when an attempt is made to validate a
+# page using AppCacheUtils.jsm that has a cache manifest which references the
+# same URI from multiple locations.
+duplicateURI=URI %S is referenced in multiple locations. This is not allowed: %S.
+
+# LOCALIZATION NOTE (networkBlocksURI): Used when an attempt is made to validate
+# a page using AppCacheUtils.jsm that has a cache manifest which references the
+# same URI in the NETWORK section as it does in other sections.
+networkBlocksURI=NETWORK section line %S (%S) prevents caching of line %S (%S) in the %S section.
+
+# LOCALIZATION NOTE (fallbackBlocksURI): Used when an attempt is made to
+# validate a page using AppCacheUtils.jsm that has a cache manifest which
+# references the same URI in the FALLBACK section as it does in other sections.
+fallbackBlocksURI=FALLBACK section line %S (%S) prevents caching of line %S (%S) in the %S section.
+
+# LOCALIZATION NOTE (fileChangedButNotManifest): Used when an attempt is made to
+# validate a page using AppCacheUtils.jsm that has a cache manifest which
+# references a URI that has a file modified after the cache manifest.
+fileChangedButNotManifest=The file %S was modified after %S. Unless the text in the manifest file is changed the cached version will be used instead at line %S.
+
+# LOCALIZATION NOTE (cacheControlNoStore): Used when an attempt is made to
+# validate a page using AppCacheUtils.jsm that has a header preventing caching
+# or storing information.
+cacheControlNoStore=%S has cache-control set to no-store. This will prevent the application cache from storing the file at line %S.
+
+# LOCALIZATION NOTE (notAvailable): Used when an attempt is made to validate a
+# page using AppCacheUtils.jsm that is not available.
+notAvailable=%S points to a resource that is not available at line %S.
+
+# LOCALIZATION NOTE (firstLineMustBeCacheManifest): Used when an attempt is made
+# to validate a cache manifest using AppCacheUtils.jsm having a first line that
+# is not "CACHE MANIFEST."
+firstLineMustBeCacheManifest=The first line of the manifest must be "CACHE MANIFEST" at line %S.
+
+# LOCALIZATION NOTE (cacheManifestOnlyFirstLine): Used when an attempt is made
+# to validate a cache manifest using AppCacheUtils.jsm having "CACHE MANIFEST"
+# on a line other than the first line.
+cacheManifestOnlyFirstLine="CACHE MANIFEST" is only valid on the first line at line %S.
+
+# LOCALIZATION NOTE (asteriskInWrongSection): Used when an attempt is made
+# to validate a cache manifest using AppCacheUtils.jsm having an asterisk (*) in
+# a section other than the network section.
+asteriskInWrongSection=Asterisk used as a wildcard in the %S section at line %S. A single line containing an asterisk is called the online whitelist wildcard flag and is only valid in the NETWORK section. Other uses of the * character are prohibited. The presence of this flag indicates that any URI not listed as cached is to be implicitly treated as being in the online whitelist namespaces. If the flag is not present then the blocking state indicates that URIs not listed explicitly in the manifest are to be treated as unavailable.
+
+# LOCALIZATION NOTE (escapeSpaces): Used when an attempt is made to validate a
+# cache manifest using AppCacheUtils.jsm having a space in a URI. Spaces are to
+# be replaced with %20.
+escapeSpaces=Spaces in URIs need to be replaced with %20 at line %S.
+
+# LOCALIZATION NOTE (slashDotDotSlashBad): Used when an attempt is made to
+# validate a cache manifest using AppCacheUtils.jsm having a URI containing
+# /../, which is invalid.
+slashDotDotSlashBad=/../ is not a valid URI prefix at line %S.
+
+# LOCALIZATION NOTE (tooManyDotDotSlashes): Used when an attempt is made to
+# validate a cache manifest using AppCacheUtils.jsm having a URI containing
+# too many ../ operators. Too many of these operaters mean that the file would
+# be below the root of the site, which is not possible.
+tooManyDotDotSlashes=Too many dot dot slash operators (../) at line %S.
+
+# LOCALIZATION NOTE (fallbackUseSpaces): Used when an attempt is made to
+# validate a cache manifest using AppCacheUtils.jsm having a FALLBACK section
+# containing more or less than the standard two URIs seperated by a single
+# space.
+fallbackUseSpaces=Only two URIs separated by spaces are allowed in the FALLBACK section at line %S.
+
+# LOCALIZATION NOTE (fallbackAsterisk): Used when an attempt is made to validate
+# a cache manifest using AppCacheUtils.jsm having a FALLBACK section that
+# attempts to use an asterisk (*) as a wildcard. In this section the URI is
+# simply a path prefix.
+fallbackAsterisk=Asterisk (*) incorrectly used as a wildcard in a fallback namespace at line %S. Namespaces simply need to match a path prefix.
+
+# LOCALIZATION NOTE (settingsBadValue): Used when an attempt is made to validate
+# a cache manifest using AppCacheUtils.jsm having a SETTINGS section containing
+# something other than the valid "prefer-online" or "fast."
+settingsBadValue=The SETTINGS section may only contain a single value, "prefer-online" or "fast" at line %S.
+
+# LOCALIZATION NOTE (invalidSectionName): Used when an attempt is made to
+# validate a cache manifest using AppCacheUtils.jsm containing an invalid
+# section name.
+invalidSectionName=Invalid section name (%S) at line %S.
+
+# LOCALIZATION NOTE (entryNotFound): Used when an attempt is made to view a
+# cache entry that does not exist.
+entryNotFound=Entry not found.
--- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
@@ -1120,16 +1120,95 @@ paintflashingChromeDesc=chrome frames
 # LOCALIZATION NOTE (paintflashingManual) A longer description describing the
 # set of commands that control paint flashing.
 paintflashingManual=Draw repainted areas in different colors
 
 # LOCALIZATION NOTE (paintflashingTooltip) A string displayed as the
 # tooltip of button in devtools toolbox which toggles paint flashing.
 paintflashingTooltip=Highlight painted area
 
+# LOCALIZATION NOTE (appCacheDesc) A very short string used to describe the
+# function of the "appcache" command
+appCacheDesc=Application cache utilities
+
+# LOCALIZATION NOTE (appCacheValidateDesc) A very short string used to describe
+# the function of the "appcache validate" command.
+appCacheValidateDesc=Validate cache manifest
+
+# LOCALIZATION NOTE (appCacheValidateManual) A fuller description of the
+# 'validate' parameter to the 'appcache' command, displayed when the user asks
+# for help on what it does.
+appCacheValidateManual=Find issues relating to a cache manifest and the files that it references
+
+# LOCALIZATION NOTE (appCacheValidateUriDesc) A very short string used to describe
+# the function of the "uri" parameter of the appcache validate" command.
+appCacheValidateUriDesc=URI to check
+
+# LOCALIZATION NOTE (appCacheValidated) Displayed by the "appcache validate"
+# command when it has been successfully validated.
+appCacheValidatedSuccessfully=Appcache validated successfully.
+
+# LOCALIZATION NOTE (appCacheClearDesc) A very short string used to describe
+# the function of the "appcache clear" command.
+appCacheClearDesc=Clear entries from the application cache
+
+# LOCALIZATION NOTE (appCacheClearManual) A fuller description of the
+# 'appcache clear' command, displayed when the user asks for help on what it does.
+appCacheClearManual=Clear one or more entries from the application cache
+
+# LOCALIZATION NOTE (appCacheClearCleared) Displayed by the "appcache clear"
+# command when entries are successfully cleared.
+appCacheClearCleared=Entries cleared successfully.
+
+# LOCALIZATION NOTE (AppCacheListDesc) A very short string used to describe
+# the function of the "appcache list" command.
+appCacheListDesc=Display a list of application cache entries.
+
+# LOCALIZATION NOTE (AppCacheListManual) A fuller description of the
+# 'appcache list' command, displayed when the user asks for help on what it does.
+appCacheListManual=Display a list of all application cache entries. If the search parameter is used then the table displays the entries containing the search term.
+
+# LOCALIZATION NOTE (AppCacheListSearchDesc) A very short string used to describe
+# the function of the "search" parameter of the appcache list" command.
+appCacheListSearchDesc=Filter results using a search term.
+
+# LOCALIZATION NOTE (AppCacheList*) Row headers for the 'appcache list' command.
+appCacheListKey=Key:
+appCacheListDataSize=Data size:
+appCacheListDeviceID=Device ID:
+appCacheListExpirationTime=Expires:
+appCacheListFetchCount=Fetch count:
+appCacheListLastFetched=Last fetched:
+appCacheListLastModified=Last modified:
+
+# LOCALIZATION NOTE (appCacheListViewEntry) The text for the view entry button
+# of the 'appcache list' command.
+appCacheListViewEntry=View Entry
+
+# LOCALIZATION NOTE (appCacheManifestContainsErrors) Displayed by the
+# "appcache list" command when the manifest is not valid.
+appCacheManifestContainsErrors=The manifest contains errors. Please run appcache validate and resolve the errors before trying again.
+
+# LOCALIZATION NOTE (appCacheNoResults) Displayed by the "appcache list" command
+# when a search returns no results.
+appCacheNoResults=Your search returned no results.
+
+# LOCALIZATION NOTE (appCacheViewEntryDesc) A very short string used to describe
+# the function of the "appcache viewentry" command.
+appCacheViewEntryDesc=Open a new tab containing the specified cache entry information.
+
+# LOCALIZATION NOTE (appCacheViewEntryManual) A fuller description of the
+# 'appcache viewentry' command, displayed when the user asks for help on what it
+# does.
+appCacheViewEntryManual=Open a new tab containing the specified cache entry information.
+
+# LOCALIZATION NOTE (appCacheViewEntryKey) A very short string used to describe
+# the function of the "key" parameter of the 'appcache viewentry' command.
+appCacheViewEntryKey=The key for the entry to display.
+
 # LOCALIZATION NOTE (profilerDesc) A very short string used to describe the
 # function of the profiler command.
 profilerDesc=Manage profiler
 
 # LOCALIZATION NOTE (profilerManual) A longer description describing the
 # set of commands that control the profiler.
 profilerManual=Commands to start or stop a JavaScript profiler
 
@@ -1184,9 +1263,9 @@ profilerNotStarted2=This profile has not
 profilerStarting2=Starting…
 
 # LOCALIZATION NOTE (profilerStopping) A very short string that indicates that
 # we're stopping the profiler.
 profilerStopping2=Stopping…
 
 # LOCALIZATION NOTE (profilerNotReady) A message that is displayed whenever
 # an operation cannot be completed because the profiler has not been opened yet.
-profilerNotReady=For this command to work you need to open the profiler first
\ No newline at end of file
+profilerNotReady=For this command to work you need to open the profiler first
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -17,16 +17,17 @@
     locale/browser/aboutSessionRestore.dtd         (%chrome/browser/aboutSessionRestore.dtd)
 #ifdef MOZ_SERVICES_SYNC
     locale/browser/syncProgress.dtd                (%chrome/browser/syncProgress.dtd)
     locale/browser/aboutSyncTabs.dtd               (%chrome/browser/aboutSyncTabs.dtd)
 #endif
     locale/browser/browser.dtd                     (%chrome/browser/browser.dtd)
     locale/browser/baseMenuOverlay.dtd             (%chrome/browser/baseMenuOverlay.dtd)
     locale/browser/browser.properties              (%chrome/browser/browser.properties)
+    locale/browser/devtools/appcacheutils.properties  (%chrome/browser/devtools/appcacheutils.properties)
     locale/browser/devtools/debugger.dtd              (%chrome/browser/devtools/debugger.dtd)
     locale/browser/devtools/debugger.properties       (%chrome/browser/devtools/debugger.properties)
     locale/browser/devtools/netmonitor.dtd            (%chrome/browser/devtools/netmonitor.dtd)
     locale/browser/devtools/netmonitor.properties     (%chrome/browser/devtools/netmonitor.properties)
     locale/browser/devtools/gcli.properties           (%chrome/browser/devtools/gcli.properties)
     locale/browser/devtools/gclicommands.properties   (%chrome/browser/devtools/gclicommands.properties)
     locale/browser/devtools/webconsole.properties     (%chrome/browser/devtools/webconsole.properties)
     locale/browser/devtools/inspector.properties      (%chrome/browser/devtools/inspector.properties)
--- a/dom/apps/src/PermissionsTable.jsm
+++ b/dom/apps/src/PermissionsTable.jsm
@@ -114,16 +114,21 @@ this.PermissionsTable =  { geolocation: 
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            mobileconnection: {
                              app: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
+                           mobilenetwork: {
+                             app: DENY_ACTION,
+                             privileged: ALLOW_ACTION,
+                             certified: ALLOW_ACTION
+                           },
                            power: {
                              app: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            push: {
                             app: ALLOW_ACTION,
                             privileged: ALLOW_ACTION,
--- a/dom/base/ConsoleAPI.js
+++ b/dom/base/ConsoleAPI.js
@@ -175,51 +175,56 @@ ConsoleAPI.prototype = {
     };
 
     Object.defineProperties(contentObj, properties);
     Cu.makeObjectPropsNormal(contentObj);
 
     this._queuedCalls = [];
     this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
     this._window = Cu.getWeakReference(aWindow);
-    this.timerRegistry = {};
+    this.timerRegistry = new Map();
 
     return contentObj;
   },
 
   observe: function CA_observe(aSubject, aTopic, aData)
   {
     if (aTopic == "inner-window-destroyed") {
       let innerWindowID = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
       if (innerWindowID == this._innerID) {
         Services.obs.removeObserver(this, "inner-window-destroyed");
         this._windowDestroyed = true;
         if (!this._timerInitialized) {
-          this.timerRegistry = {};
+          this.timerRegistry.clear();
         }
       }
     }
   },
 
   /**
    * Queue a call to a console method. See the CALL_DELAY constant.
    *
    * @param string aMethod
    *        The console method the code has invoked.
    * @param object aArguments
    *        The arguments passed to the console method.
    */
   queueCall: function CA_queueCall(aMethod, aArguments)
   {
+    let window = this._window.get();
     let metaForCall = {
-      isPrivate: PrivateBrowsingUtils.isWindowPrivate(this._window.get()),
+      isPrivate: PrivateBrowsingUtils.isWindowPrivate(window),
       timeStamp: Date.now(),
       stack: this.getStackTrace(aMethod != "trace" ? 1 : null),
     };
 
+    if (aMethod == "time" || aMethod == "timeEnd") {
+      metaForCall.monotonicTimer = window.performance.now();
+    }
+
     this._queuedCalls.push([aMethod, aArguments, metaForCall]);
 
     if (!this._timerInitialized) {
       this._timer.initWithCallback(this._timerCallback.bind(this), CALL_DELAY,
                                    Ci.nsITimer.TYPE_REPEATING_SLACK);
       this._timerInitialized = true;
     }
   },
@@ -234,17 +239,17 @@ ConsoleAPI.prototype = {
       .forEach(this._processQueuedCall, this);
 
     if (!this._queuedCalls.length) {
       this._timerInitialized = false;
       this._timer.cancel();
 
       if (this._windowDestroyed) {
         ConsoleAPIStorage.clearEvents(this._innerID);
-        this.timerRegistry = {};
+        this.timerRegistry.clear();
       }
     }
   },
 
   /**
    * Process a queued call to a console method.
    *
    * @private
@@ -288,20 +293,20 @@ ConsoleAPI.prototype = {
           Cu.reportError(ex);
           Cu.reportError(ex.stack);
           return;
         }
         break;
       case "dir":
         break;
       case "time":
-        consoleEvent.timer = this.startTimer(args[0], meta.timeStamp);
+        consoleEvent.timer = this.startTimer(args[0], meta.monotonicTimer);
         break;
       case "timeEnd":
-        consoleEvent.timer = this.stopTimer(args[0], meta.timeStamp);
+        consoleEvent.timer = this.stopTimer(args[0], meta.monotonicTimer);
         break;
       default:
         // unknown console API method!
         return;
     }
 
     this.notifyObservers(method, consoleEvent, meta.isPrivate);
   },
@@ -412,68 +417,68 @@ ConsoleAPI.prototype = {
         }
       }
     }
 
     return stack;
   },
 
   /*
-   * A registry of started timers. Timer maps are key-value pairs of timer
-   * names to timer start times, for all timers defined in the page. Timer
-   * names are prepended with the inner window ID in order to avoid conflicts
-   * with Object.prototype functions.
+   * A registry of started timers.
+   * @type Map
    */
   timerRegistry: null,
 
   /**
    * Create a new timer by recording the current time under the specified name.
    *
    * @param string aName
    *        The name of the timer.
-   * @param number [aTimestamp=Date.now()]
-   *        Optional timestamp that tells when the timer was originally started.
+   * @param number aTimestamp
+   *        A monotonic strictly-increasing timing value that tells when the
+   *        timer was started.
    * @return object
    *        The name property holds the timer name and the started property
    *        holds the time the timer was started. In case of error, it returns
    *        an object with the single property "error" that contains the key
    *        for retrieving the localized error message.
    **/
   startTimer: function CA_startTimer(aName, aTimestamp) {
     if (!aName) {
-        return;
+      return;
     }
-    if (Object.keys(this.timerRegistry).length > MAX_PAGE_TIMERS - 1) {
-        return { error: "maxTimersExceeded" };
+    if (this.timerRegistry.size > MAX_PAGE_TIMERS - 1) {
+      return { error: "maxTimersExceeded" };
     }
-    let key = this._innerID + "-" + aName.toString();
-    if (!(key in this.timerRegistry)) {
-        this.timerRegistry[key] = aTimestamp || Date.now();
+    let key = aName.toString();
+    if (!this.timerRegistry.has(key)) {
+      this.timerRegistry.set(key, aTimestamp);
     }
-    return { name: aName, started: this.timerRegistry[key] };
+    return { name: aName, started: this.timerRegistry.get(key) };
   },
 
   /**
    * Stop the timer with the specified name and retrieve the elapsed time.
    *
    * @param string aName
    *        The name of the timer.
-   * @param number [aTimestamp=Date.now()]
-   *        Optional timestamp that tells when the timer was originally stopped.
+   * @param number aTimestamp
+   *        A monotonic strictly-increasing timing value that tells when the
+   *        timer was stopped.
    * @return object
    *        The name property holds the timer name and the duration property
    *        holds the number of milliseconds since the timer was started.
    **/
   stopTimer: function CA_stopTimer(aName, aTimestamp) {
     if (!aName) {
-        return;
+      return;
     }
-    let key = this._innerID + "-" + aName.toString();
-    if (!(key in this.timerRegistry)) {
-        return;
+    let key = aName.toString();
+    if (!this.timerRegistry.has(key)) {
+      return;
     }
-    let duration = (aTimestamp || Date.now()) - this.timerRegistry[key];
-    delete this.timerRegistry[key];
+    let duration = aTimestamp - this.timerRegistry.get(key);
+    this.timerRegistry.delete(key);
     return { name: aName, duration: duration };
   }
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ConsoleAPI]);
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1354,17 +1354,18 @@ NS_IMETHODIMP
 Navigator::GetMozMobileConnection(nsIDOMMozMobileConnection** aMobileConnection)
 {
   *aMobileConnection = nullptr;
 
   if (!mMobileConnection) {
     nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
     NS_ENSURE_TRUE(window, NS_OK);
 
-    if (!CheckPermission("mobileconnection")) {
+    if (!CheckPermission("mobileconnection") &&
+        !CheckPermission("mobilenetwork")) {
       return NS_OK;
     }
 
     mMobileConnection = new network::MobileConnection();
     mMobileConnection->Init(window);
   }
 
   NS_ADDREF(*aMobileConnection = mMobileConnection);
--- a/dom/network/interfaces/nsIDOMMobileConnection.idl
+++ b/dom/network/interfaces/nsIDOMMobileConnection.idl
@@ -8,30 +8,37 @@ interface nsIDOMEventListener;
 interface nsIDOMDOMRequest;
 interface nsIDOMMozMobileICCInfo;
 interface nsIDOMMozMobileConnectionInfo;
 interface nsIDOMMozMobileNetworkInfo;
 interface nsIDOMMozMobileCellInfo;
 interface nsIDOMMozIccManager;
 interface nsIDOMMozMobileCFInfo;
 
-[scriptable, builtinclass, uuid(bde7e16c-ff1f-4c7f-b1cd-480984cbb206)]
+[scriptable, builtinclass, uuid(780de142-562c-4141-bd5c-5413fb1952d2)]
 interface nsIDOMMozMobileConnection : nsIDOMEventTarget
 {
   const long ICC_SERVICE_CLASS_VOICE = (1 << 0);
   const long ICC_SERVICE_CLASS_DATA = (1 << 1);
   const long ICC_SERVICE_CLASS_FAX = (1 << 2);
   const long ICC_SERVICE_CLASS_SMS = (1 << 3);
   const long ICC_SERVICE_CLASS_DATA_SYNC = (1 << 4);
   const long ICC_SERVICE_CLASS_DATA_ASYNC = (1 << 5);
   const long ICC_SERVICE_CLASS_PACKET = (1 << 6);
   const long ICC_SERVICE_CLASS_PAD = (1 << 7);
   const long ICC_SERVICE_CLASS_MAX = (1 << 7);
 
   /**
+   * These two fields can be accessed by privileged applications with the
+   * 'mobilenetwork' permission.
+   */
+  readonly attribute DOMString lastKnownNetwork;
+  readonly attribute DOMString lastKnownHomeNetwork;
+
+  /**
    * Indicates the state of the device's ICC card.
    *
    * Possible values: null, 'unknown', 'absent', 'pinRequired', 'pukRequired',
    * 'networkLocked', 'corporateLocked', 'serviceProviderLocked', 'ready'.
    */
   readonly attribute DOMString cardState;
 
   /**
--- a/dom/network/src/MobileConnection.cpp
+++ b/dom/network/src/MobileConnection.cpp
@@ -6,16 +6,18 @@
 #include "nsIDOMDOMRequest.h"
 #include "nsIDOMClassInfo.h"
 #include "nsDOMEvent.h"
 #include "nsIDOMUSSDReceivedEvent.h"
 #include "nsIDOMDataErrorEvent.h"
 #include "nsIDOMCFStateChangeEvent.h"
 #include "nsIDOMICCCardLockErrorEvent.h"
 #include "GeneratedEvents.h"
+#include "mozilla/Preferences.h"
+#include "nsIPermissionManager.h"
 
 #include "nsContentUtils.h"
 #include "nsJSUtils.h"
 #include "nsJSON.h"
 #include "jsapi.h"
 #include "mozilla/Services.h"
 #include "IccManager.h"
 
@@ -76,37 +78,44 @@ NS_IMPL_EVENT_HANDLER(MobileConnection, 
 NS_IMPL_EVENT_HANDLER(MobileConnection, ussdreceived)
 NS_IMPL_EVENT_HANDLER(MobileConnection, dataerror)
 NS_IMPL_EVENT_HANDLER(MobileConnection, icccardlockerror)
 NS_IMPL_EVENT_HANDLER(MobileConnection, cfstatechange)
 
 MobileConnection::MobileConnection()
 {
   mProvider = do_GetService(NS_RILCONTENTHELPER_CONTRACTID);
+  mWindow = nullptr;
 
   // Not being able to acquire the provider isn't fatal since we check
   // for it explicitly below.
   if (!mProvider) {
     NS_WARNING("Could not acquire nsIMobileConnectionProvider!");
     return;
   }
-
-  mListener = new Listener(this);
-  DebugOnly<nsresult> rv = mProvider->RegisterMobileConnectionMsg(mListener);
-  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
-                   "Failed registering mobile connection messages with provider");
 }
 
 void
 MobileConnection::Init(nsPIDOMWindow* aWindow)
 {
   BindToOwner(aWindow);
 
-  mIccManager = new icc::IccManager();
-  mIccManager->Init(aWindow);
+  mWindow = do_GetWeakReference(aWindow);
+  mListener = new Listener(this);
+
+  if (!CheckPermission("mobilenetwork") &&
+      CheckPermission("mobileconnection")) {
+    DebugOnly<nsresult> rv = mProvider->RegisterMobileConnectionMsg(mListener);
+    NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
+                     "Failed registering mobile connection messages with provider");
+
+    mIccManager = new icc::IccManager();
+    mIccManager->Init(aWindow);
+    printf_stderr("MobileConnection & IccManager initialized");
+  }
 }
 
 void
 MobileConnection::Shutdown()
 {
   if (mProvider && mListener) {
     mListener->Disconnect();
     mProvider->UnregisterMobileConnectionMsg(mListener);
@@ -118,278 +127,408 @@ MobileConnection::Shutdown()
     mIccManager->Shutdown();
     mIccManager = nullptr;
   }
 }
 
 // nsIDOMMozMobileConnection
 
 NS_IMETHODIMP
+MobileConnection::GetLastKnownNetwork(nsAString& network)
+{
+  network.SetIsVoid(true);
+
+  if (!CheckPermission("mobilenetwork")) {
+    return NS_OK;
+  }
+
+  network = mozilla::Preferences::GetString("ril.lastKnownNetwork");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+MobileConnection::GetLastKnownHomeNetwork(nsAString& network)
+{
+  network.SetIsVoid(true);
+
+  if (!CheckPermission("mobilenetwork")) {
+    return NS_OK;
+  }
+
+  network = mozilla::Preferences::GetString("ril.lastKnownHomeNetwork");
+  return NS_OK;
+}
+
+// All fields below require the "mobileconnection" permission.
+
+bool
+MobileConnection::CheckPermission(const char* type)
+{
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
+  NS_ENSURE_TRUE(window, false);
+
+  nsCOMPtr<nsIPermissionManager> permMgr =
+    do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
+  NS_ENSURE_TRUE(permMgr, false);
+
+  uint32_t permission = nsIPermissionManager::DENY_ACTION;
+  permMgr->TestPermissionFromWindow(window, type, &permission);
+  return permission == nsIPermissionManager::ALLOW_ACTION;
+}
+
+NS_IMETHODIMP
 MobileConnection::GetCardState(nsAString& cardState)
 {
-  if (!mProvider) {
-    cardState.SetIsVoid(true);
+  cardState.SetIsVoid(true);
+
+  if (!mProvider || !CheckPermission("mobileconnection")) {
     return NS_OK;
   }
   return mProvider->GetCardState(cardState);
 }
 
 NS_IMETHODIMP
 MobileConnection::GetIccInfo(nsIDOMMozMobileICCInfo** aIccInfo)
 {
-  if (!mProvider) {
-    *aIccInfo = nullptr;
+  *aIccInfo = nullptr;
+
+  if (!mProvider || !CheckPermission("mobileconnection")) {
     return NS_OK;
   }
   return mProvider->GetIccInfo(aIccInfo);
 }
 
 NS_IMETHODIMP
 MobileConnection::GetVoice(nsIDOMMozMobileConnectionInfo** voice)
 {
-  if (!mProvider) {
-    *voice = nullptr;
+  *voice = nullptr;
+
+  if (!mProvider || !CheckPermission("mobileconnection")) {
     return NS_OK;
   }
   return mProvider->GetVoiceConnectionInfo(voice);
 }
 
 NS_IMETHODIMP
 MobileConnection::GetData(nsIDOMMozMobileConnectionInfo** data)
 {
-  if (!mProvider) {
-    *data = nullptr;
+  *data = nullptr;
+
+  if (!mProvider || !CheckPermission("mobileconnection")) {
     return NS_OK;
   }
   return mProvider->GetDataConnectionInfo(data);
 }
 
 NS_IMETHODIMP
 MobileConnection::GetNetworkSelectionMode(nsAString& networkSelectionMode)
 {
-  if (!mProvider) {
-    networkSelectionMode.SetIsVoid(true);
-    return NS_OK;
+  networkSelectionMode.SetIsVoid(true);
+
+  if (!mProvider || !CheckPermission("mobileconnection")) {
+     return NS_OK;
   }
   return mProvider->GetNetworkSelectionMode(networkSelectionMode);
 }
 
 NS_IMETHODIMP
 MobileConnection::GetIcc(nsIDOMMozIccManager** aIcc)
 {
+  *aIcc = nullptr;
+
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   NS_IF_ADDREF(*aIcc = mIccManager);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 MobileConnection::GetNetworks(nsIDOMDOMRequest** request)
 {
   *request = nullptr;
 
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   if (!mProvider) {
     return NS_ERROR_FAILURE;
   }
 
   return mProvider->GetNetworks(GetOwner(), request);
 }
 
 NS_IMETHODIMP
 MobileConnection::SelectNetwork(nsIDOMMozMobileNetworkInfo* network, nsIDOMDOMRequest** request)
 {
   *request = nullptr;
 
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   if (!mProvider) {
     return NS_ERROR_FAILURE;
   }
 
   return mProvider->SelectNetwork(GetOwner(), network, request);
 }
 
 NS_IMETHODIMP
 MobileConnection::SelectNetworkAutomatically(nsIDOMDOMRequest** request)
 {
   *request = nullptr;
 
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   if (!mProvider) {
     return NS_ERROR_FAILURE;
   }
 
   return mProvider->SelectNetworkAutomatically(GetOwner(), request);
 }
 
 NS_IMETHODIMP
 MobileConnection::GetCardLock(const nsAString& aLockType, nsIDOMDOMRequest** aDomRequest)
 {
   *aDomRequest = nullptr;
 
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   if (!mProvider) {
     return NS_ERROR_FAILURE;
   }
 
   return mProvider->GetCardLock(GetOwner(), aLockType, aDomRequest);
 }
 
 NS_IMETHODIMP
 MobileConnection::UnlockCardLock(const JS::Value& aInfo,
                                  nsIDOMDOMRequest** aDomRequest)
 {
   *aDomRequest = nullptr;
 
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   if (!mProvider) {
     return NS_ERROR_FAILURE;
   }
 
   return mProvider->UnlockCardLock(GetOwner(), aInfo, aDomRequest);
 }
 
 NS_IMETHODIMP
 MobileConnection::SetCardLock(const JS::Value& aInfo,
                               nsIDOMDOMRequest** aDomRequest)
 {
   *aDomRequest = nullptr;
 
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   if (!mProvider) {
     return NS_ERROR_FAILURE;
   }
 
   return mProvider->SetCardLock(GetOwner(), aInfo, aDomRequest);
 }
 
 NS_IMETHODIMP
 MobileConnection::SendMMI(const nsAString& aMMIString,
                           nsIDOMDOMRequest** request)
 {
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   if (!mProvider) {
     return NS_ERROR_FAILURE;
   }
 
   return mProvider->SendMMI(GetOwner(), aMMIString, request);
 }
 
 NS_IMETHODIMP
 MobileConnection::CancelMMI(nsIDOMDOMRequest** request)
 {
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   if (!mProvider) {
     return NS_ERROR_FAILURE;
   }
 
   return mProvider->CancelMMI(GetOwner(), request);
 }
 
 NS_IMETHODIMP
 MobileConnection::GetCallForwardingOption(uint16_t aReason,
                                           nsIDOMDOMRequest** aRequest)
 {
   *aRequest = nullptr;
 
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   if (!mProvider) {
     return NS_ERROR_FAILURE;
   }
 
   return mProvider->GetCallForwardingOption(GetOwner(), aReason, aRequest);
 }
 
 NS_IMETHODIMP
 MobileConnection::SetCallForwardingOption(nsIDOMMozMobileCFInfo* aCFInfo,
                                           nsIDOMDOMRequest** aRequest)
 {
   *aRequest = nullptr;
 
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   if (!mProvider) {
     return NS_ERROR_FAILURE;
   }
 
   return mProvider->SetCallForwardingOption(GetOwner(), aCFInfo, aRequest);
 }
 
 NS_IMETHODIMP
 MobileConnection::GetCallWaitingOption(nsIDOMDOMRequest** aRequest)
 {
   *aRequest = nullptr;
 
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   if (!mProvider) {
     return NS_ERROR_FAILURE;
   }
 
   return mProvider->GetCallWaitingOption(GetOwner(), aRequest);
 }
 
 NS_IMETHODIMP
 MobileConnection::SetCallWaitingOption(bool aEnabled,
                                        nsIDOMDOMRequest** aRequest)
 {
   *aRequest = nullptr;
 
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   if (!mProvider) {
     return NS_ERROR_FAILURE;
   }
 
   return mProvider->SetCallWaitingOption(GetOwner(), aEnabled, aRequest);
 }
 
 // nsIMobileConnectionListener
 
 NS_IMETHODIMP
 MobileConnection::NotifyVoiceChanged()
 {
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   return DispatchTrustedEvent(NS_LITERAL_STRING("voicechange"));
 }
 
 NS_IMETHODIMP
 MobileConnection::NotifyDataChanged()
 {
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   return DispatchTrustedEvent(NS_LITERAL_STRING("datachange"));
 }
 
 NS_IMETHODIMP
 MobileConnection::NotifyCardStateChanged()
 {
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   return DispatchTrustedEvent(NS_LITERAL_STRING("cardstatechange"));
 }
 
 NS_IMETHODIMP
 MobileConnection::NotifyIccInfoChanged()
 {
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   return DispatchTrustedEvent(NS_LITERAL_STRING("iccinfochange"));
 }
 
 NS_IMETHODIMP
 MobileConnection::NotifyUssdReceived(const nsAString& aMessage,
                                      bool aSessionEnded)
 {
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   nsCOMPtr<nsIDOMEvent> event;
   NS_NewDOMUSSDReceivedEvent(getter_AddRefs(event), this, nullptr, nullptr);
 
   nsCOMPtr<nsIDOMUSSDReceivedEvent> ce = do_QueryInterface(event);
   nsresult rv = ce->InitUSSDReceivedEvent(NS_LITERAL_STRING("ussdreceived"),
                                           false, false,
                                           aMessage, aSessionEnded);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return DispatchTrustedEvent(ce);
 }
 
 NS_IMETHODIMP
 MobileConnection::NotifyDataError(const nsAString& aMessage)
 {
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   nsCOMPtr<nsIDOMEvent> event;
   NS_NewDOMDataErrorEvent(getter_AddRefs(event), this, nullptr, nullptr);
 
   nsCOMPtr<nsIDOMDataErrorEvent> ce = do_QueryInterface(event);
   nsresult rv = ce->InitDataErrorEvent(NS_LITERAL_STRING("dataerror"),
                                        false, false, aMessage);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return DispatchTrustedEvent(ce);
 }
 
 NS_IMETHODIMP
 MobileConnection::NotifyIccCardLockError(const nsAString& aLockType,
                                          uint32_t aRetryCount)
 {
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   nsCOMPtr<nsIDOMEvent> event;
   NS_NewDOMICCCardLockErrorEvent(getter_AddRefs(event), this, nullptr, nullptr);
 
   nsCOMPtr<nsIDOMICCCardLockErrorEvent> ce = do_QueryInterface(event);
   nsresult rv =
     ce->InitICCCardLockErrorEvent(NS_LITERAL_STRING("icccardlockerror"),
                                   false, false, aLockType, aRetryCount);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -400,16 +539,20 @@ MobileConnection::NotifyIccCardLockError
 NS_IMETHODIMP
 MobileConnection::NotifyCFStateChange(bool aSuccess,
                                       unsigned short aAction,
                                       unsigned short aReason,
                                       const nsAString& aNumber,
                                       unsigned short aSeconds,
                                       unsigned short aServiceClass)
 {
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
   nsCOMPtr<nsIDOMEvent> event;
   NS_NewDOMCFStateChangeEvent(getter_AddRefs(event), this, nullptr, nullptr);
 
   nsCOMPtr<nsIDOMCFStateChangeEvent> ce = do_QueryInterface(event);
   nsresult rv = ce->InitCFStateChangeEvent(NS_LITERAL_STRING("cfstatechange"),
                                            false, false,
                                            aSuccess, aAction, aReason, aNumber,
                                            aSeconds, aServiceClass);
--- a/dom/network/src/MobileConnection.h
+++ b/dom/network/src/MobileConnection.h
@@ -46,15 +46,18 @@ public:
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MobileConnection,
                                            nsDOMEventTargetHelper)
 
 private:
   nsCOMPtr<nsIMobileConnectionProvider> mProvider;
   nsRefPtr<Listener> mListener;
   nsRefPtr<icc::IccManager> mIccManager;
+  nsWeakPtr mWindow;
+
+  bool CheckPermission(const char* type);
 };
 
 } // namespace network
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_network_MobileConnection_h
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -253,17 +253,18 @@ function RadioInterfaceLayer() {
                      lastKnownMcc: null,
                      cell: null,
                      type: null,
                      signalStrength: null,
                      relSignalStrength: null},
   };
 
   try {
-    this.rilContext.voice.lastKnownMcc = Services.prefs.getCharPref("ril.lastKnownMcc");
+    this.rilContext.voice.lastKnownMcc =
+      Services.prefs.getCharPref("ril.lastKnownMcc");
   } catch (e) {}
 
   this.voicemailInfo = {
     number: null,
     displayName: null
   };
 
   // Read the 'ril.radio.disabled' setting in order to start with a known
@@ -1075,16 +1076,24 @@ RadioInterfaceLayer.prototype = {
         // !voice.network is in case voice.network is still null.
         if (!voice.network || voice.network.mcc != message.mcc) {
           try {
             Services.prefs.setCharPref("ril.lastKnownMcc", message.mcc);
           } catch (e) {}
         }
       }
 
+      // Update lastKnownNetwork
+      if (message.mcc && message.mnc) {
+        try {
+          Services.prefs.setCharPref("ril.lastKnownNetwork",
+                                     message.mcc + "-" + message.mnc);
+        } catch (e) {}
+      }
+
       voice.network = message;
       if (!message.batch) {
         this._sendMobileConnectionMessage("RIL:VoiceInfoChanged", voice);
       }
     }
 
     if (this.networkChanged(message, data.network)) {
       data.network = message;
@@ -1784,16 +1793,24 @@ RadioInterfaceLayer.prototype = {
                           oldIccInfo.msisdn != message.msisdn;
     if (!iccInfoChanged) {
       return;
     }
     // RIL:IccInfoChanged corresponds to a DOM event that gets fired only
     // when the MCC or MNC codes have changed.
     this._sendMobileConnectionMessage("RIL:IccInfoChanged", message);
 
+    // Update lastKnownHomeNetwork.
+    if (message.mcc && message.mnc) {
+      try {
+        Services.prefs.setCharPref("ril.lastKnownHomeNetwork",
+                                   message.mcc + "-" + message.mnc);
+      } catch (e) {}
+    }
+
     // If spn becomes available, we should check roaming again.
     let oldSpn = oldIccInfo ? oldIccInfo.spn : null;
     if (!oldSpn && message.spn) {
       let voice = this.rilContext.voice;
       let data = this.rilContext.data;
       let voiceRoaming = voice.roaming;
       let dataRoaming = data.roaming;
       this.checkRoamingBetweenOperators(voice);
@@ -2674,17 +2691,24 @@ RadioInterfaceLayer.prototype = {
       strict7BitEncoding = Services.prefs.getBoolPref("dom.sms.strict7BitEncoding");
     } catch (e) {
       strict7BitEncoding = false;
     }
 
     let options = this._fragmentText(message, null, strict7BitEncoding);
     options.rilMessageType = "sendSMS";
     options.number = PhoneNumberUtils.normalize(number);
-    options.requestStatusReport = true;
+    let requestStatusReport;
+    try {
+      requestStatusReport =
+        Services.prefs.getBoolPref("dom.sms.requestStatusReport");
+    } catch (e) {
+      requestStatusReport = false;
+    }
+    options.requestStatusReport = requestStatusReport;
     if (options.segmentMaxSeq > 1) {
       options.segmentRef16Bit = this.segmentRef16Bit;
       options.segmentRef = this.nextSegmentRef;
     }
 
     let sendingMessage = {
       type: "sms",
       receiver: number,
--- a/hal/gonk/GonkHal.cpp
+++ b/hal/gonk/GonkHal.cpp
@@ -492,16 +492,21 @@ bool WriteToFile(const char *filename, c
 bool sScreenEnabled = true;
 
 // We can read wakeLockFilename to find out whether the cpu wake lock
 // is already acquired, but reading and parsing it is a lot more work
 // than tracking it ourselves, and it won't be accurate anyway (kernel
 // internal wake locks aren't counted here.)
 bool sCpuSleepAllowed = true;
 
+// Some CPU wake locks may be acquired internally in HAL. We use a counter to
+// keep track of these needs. Note we have to hold |sInternalLockCpuMonitor|
+// when reading or writing this variable to ensure thread-safe.
+int32_t sInternalLockCpuCount = 0;
+
 } // anonymous namespace
 
 bool
 GetScreenEnabled()
 {
   return sScreenEnabled;
 }
 
@@ -544,27 +549,52 @@ SetScreenBrightness(double brightness)
   aConfig.mode() = hal::eHalLightMode_User;
   aConfig.flash() = hal::eHalLightFlash_None;
   aConfig.flashOnMS() = aConfig.flashOffMS() = 0;
   aConfig.color() = color;
   hal::SetLight(hal::eHalLightID_Backlight, aConfig);
   hal::SetLight(hal::eHalLightID_Buttons, aConfig);
 }
 
+static Monitor* sInternalLockCpuMonitor = nullptr;
+
+static void
+UpdateCpuSleepState()
+{
+  sInternalLockCpuMonitor->AssertCurrentThreadOwns();
+  bool allowed = sCpuSleepAllowed && !sInternalLockCpuCount;
+  WriteToFile(allowed ? wakeUnlockFilename : wakeLockFilename, "gecko");
+}
+
+static void
+InternalLockCpu() {
+  MonitorAutoLock monitor(*sInternalLockCpuMonitor);
+  ++sInternalLockCpuCount;
+  UpdateCpuSleepState();
+}
+
+static void
+InternalUnlockCpu() {
+  MonitorAutoLock monitor(*sInternalLockCpuMonitor);
+  --sInternalLockCpuCount;
+  UpdateCpuSleepState();
+}
+
 bool
 GetCpuSleepAllowed()
 {
   return sCpuSleepAllowed;
 }
 
 void
 SetCpuSleepAllowed(bool aAllowed)
 {
-  WriteToFile(aAllowed ? wakeUnlockFilename : wakeLockFilename, "gecko");
+  MonitorAutoLock monitor(*sInternalLockCpuMonitor);
   sCpuSleepAllowed = aAllowed;
+  UpdateCpuSleepState();
 }
 
 static light_device_t* sLights[hal::eHalLightID_Count];	// will be initialized to NULL
 
 light_device_t* GetDevice(hw_module_t* module, char const* name)
 {
   int err;
   hw_device_t* device;
@@ -727,17 +757,17 @@ void
 SetTimezone(const nsCString& aTimezoneSpec)
 {
   if (aTimezoneSpec.Equals(GetTimezone())) {
     return;
   }
 
   int32_t oldTimezoneOffsetMinutes = GetTimezoneOffset();
   property_set("persist.sys.timezone", aTimezoneSpec.get());
-  // this function is automatically called by the other time conversion
+  // This function is automatically called by the other time conversion
   // functions that depend on the timezone. To be safe, we call it manually.
   tzset();
   int32_t newTimezoneOffsetMinutes = GetTimezoneOffset();
   hal::NotifySystemTimezoneChange(
     hal::SystemTimezoneChangeInformation(
       oldTimezoneOffsetMinutes, newTimezoneOffsetMinutes));
 }
 
@@ -794,48 +824,51 @@ LockScreenOrientation(const dom::ScreenO
 }
 
 void
 UnlockScreenOrientation()
 {
   OrientationObserver::GetInstance()->UnlockScreenOrientation();
 }
 
-
+// This thread will wait for the alarm firing by a blocking IO.
 static pthread_t sAlarmFireWatcherThread;
 
-// If |sAlarmData| is non-null, it's owned by the watcher thread.
-typedef struct AlarmData {
-
+// If |sAlarmData| is non-null, it's owned by the alarm-watcher thread.
+struct AlarmData {
 public:
-  AlarmData(int aFd) : mFd(aFd), mGeneration(sNextGeneration++), mShuttingDown(false) {}
+  AlarmData(int aFd) : mFd(aFd),
+                       mGeneration(sNextGeneration++),
+                       mShuttingDown(false) {}
   ScopedClose mFd;
   int mGeneration;
   bool mShuttingDown;
 
   static int sNextGeneration;
 
-} AlarmData;
+};
 
 int AlarmData::sNextGeneration = 0;
 
 AlarmData* sAlarmData = NULL;
 
 class AlarmFiredEvent : public nsRunnable {
-
 public:
   AlarmFiredEvent(int aGeneration) : mGeneration(aGeneration) {}
 
   NS_IMETHOD Run() {
     // Guard against spurious notifications caused by an alarm firing
     // concurrently with it being disabled.
-    if (sAlarmData && !sAlarmData->mShuttingDown && mGeneration == sAlarmData->mGeneration) {
+    if (sAlarmData && !sAlarmData->mShuttingDown &&
+        mGeneration == sAlarmData->mGeneration) {
       hal::NotifyAlarmFired();
     }
-
+    // The fired alarm event has been delivered to the observer (if needed);
+    // we can now release a CPU wake lock.
+    InternalUnlockCpu();
     return NS_OK;
   }
 
 private:
   int mGeneration;
 };
 
 // Runs on alarm-watcher thread.
@@ -866,21 +899,28 @@ WaitForAlarm(void* aData)
     int alarmTypeFlags = 0;
 
     // ALARM_WAIT apparently will block even if an alarm hasn't been
     // programmed, although this behavior doesn't seem to be
     // documented.  We rely on that here to avoid spinning the CPU
     // while awaiting an alarm to be programmed.
     do {
       alarmTypeFlags = ioctl(alarmData->mFd, ANDROID_ALARM_WAIT);
-    } while (alarmTypeFlags < 0 && errno == EINTR && !alarmData->mShuttingDown);
+    } while (alarmTypeFlags < 0 && errno == EINTR &&
+             !alarmData->mShuttingDown);
 
-    if (!alarmData->mShuttingDown &&
-        alarmTypeFlags >= 0 && (alarmTypeFlags & ANDROID_ALARM_RTC_WAKEUP_MASK)) {
-      NS_DispatchToMainThread(new AlarmFiredEvent(alarmData->mGeneration));
+    if (!alarmData->mShuttingDown && alarmTypeFlags >= 0 &&
+        (alarmTypeFlags & ANDROID_ALARM_RTC_WAKEUP_MASK)) {
+      // To make sure the observer can get the alarm firing notification
+      // *on time* (the system won't sleep during the process in any way),
+      // we need to acquire a CPU wake lock before firing the alarm event.
+      InternalLockCpu();
+      nsRefPtr<AlarmFiredEvent> event =
+        new AlarmFiredEvent(alarmData->mGeneration);
+      NS_DispatchToMainThread(event);
     }
   }
 
   pthread_cleanup_pop(1);
   return NULL;
 }
 
 bool
@@ -905,20 +945,25 @@ EnableAlarm()
     HAL_LOG(("Failed to set SIGUSR1 signal for alarm-watcher thread."));
     return false;
   }
 
   pthread_attr_t attr;
   pthread_attr_init(&attr);
   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
 
-  int status = pthread_create(&sAlarmFireWatcherThread, &attr, WaitForAlarm, alarmData.get());
+  // Initialize the monitor for internally locking CPU to ensure thread-safe
+  // before running the alarm-watcher thread.
+  sInternalLockCpuMonitor = new Monitor("sInternalLockCpuMonitor");
+  int status = pthread_create(&sAlarmFireWatcherThread, &attr, WaitForAlarm,
+                              alarmData.get());
   if (status) {
     alarmData = NULL;
-    HAL_LOG(("Failed to create alarm watcher thread. Status: %d.", status));
+    delete sInternalLockCpuMonitor;
+    HAL_LOG(("Failed to create alarm-watcher thread. Status: %d.", status));
     return false;
   }
 
   pthread_attr_destroy(&attr);
 
   // The thread owns this now.  We only hold a pointer.
   sAlarmData = alarmData.forget();
   return true;
@@ -931,32 +976,35 @@ DisableAlarm()
 
   // NB: this must happen-before the thread cancellation.
   sAlarmData = NULL;
 
   // The cancel will interrupt the thread and destroy it, freeing the
   // data pointed at by sAlarmData.
   DebugOnly<int> err = pthread_kill(sAlarmFireWatcherThread, SIGUSR1);
   MOZ_ASSERT(!err);
+
+  delete sInternalLockCpuMonitor;
 }
 
 bool
 SetAlarm(int32_t aSeconds, int32_t aNanoseconds)
 {
   if (!sAlarmData) {
     HAL_LOG(("We should have enabled the alarm."));
     return false;
   }
 
   struct timespec ts;
   ts.tv_sec = aSeconds;
   ts.tv_nsec = aNanoseconds;
 
-  // currently we only support RTC wakeup alarm type
-  const int result = ioctl(sAlarmData->mFd, ANDROID_ALARM_SET(ANDROID_ALARM_RTC_WAKEUP), &ts);
+  // Currently we only support RTC wakeup alarm type.
+  const int result = ioctl(sAlarmData->mFd,
+                           ANDROID_ALARM_SET(ANDROID_ALARM_RTC_WAKEUP), &ts);
 
   if (result < 0) {
     HAL_LOG(("Unable to set alarm: %s.", strerror(errno)));
     return false;
   }
 
   return true;
 }
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -4063,16 +4063,17 @@ pref("dom.vibrator.max_vibrate_list_len"
 // Battery API
 pref("dom.battery.enabled", true);
 
 // WebSMS
 pref("dom.sms.enabled", false);
 // Enable Latin characters replacement with corresponding ones in GSM SMS
 // 7-bit default alphabet.
 pref("dom.sms.strict7BitEncoding", false);
+pref("dom.sms.requestStatusReport", false);
 
 // WebContacts
 pref("dom.mozContacts.enabled", false);
 
 // WebAlarms
 pref("dom.mozAlarms.enabled", false);
 
 // SimplePush
--- a/toolkit/devtools/webconsole/WebConsoleUtils.jsm
+++ b/toolkit/devtools/webconsole/WebConsoleUtils.jsm
@@ -1585,17 +1585,30 @@ this.JSTermHelpers = function JSTermHelp
    *         The DOM element currently selected in the highlighter.
    */
    Object.defineProperty(aOwner.sandbox, "$0", {
     get: function() {
       let window = aOwner.chromeWindow();
       if (!window) {
         return null;
       }
-      let target = devtools.TargetFactory.forTab(window.gBrowser.selectedTab);
+
+      let target = null;
+      try {
+        target = devtools.TargetFactory.forTab(window.gBrowser.selectedTab);
+      }
+      catch (ex) {
+        // If we report this exception the user will get it in the Browser
+        // Console every time when she evaluates any string.
+      }
+
+      if (!target) {
+        return null;
+      }
+
       let toolbox = gDevTools.getToolbox(target);
       let panel = toolbox ? toolbox.getPanel("inspector") : null;
       let node = panel ? panel.selection.node : null;
 
       return node ? aOwner.makeDebuggeeValue(node) : null;
     },
     enumerable: true,
     configurable: false
--- a/toolkit/mozapps/extensions/AddonUpdateChecker.jsm
+++ b/toolkit/mozapps/extensions/AddonUpdateChecker.jsm
@@ -407,16 +407,18 @@ function UpdateParser(aId, aUpdateKey, a
 
   LOG("Requesting " + aUrl);
   try {
     this.request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
                    createInstance(Ci.nsIXMLHttpRequest);
     this.request.open("GET", this.url, true);
     this.request.channel.notificationCallbacks = new CertUtils.BadCertHandler(!requireBuiltIn);
     this.request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+    // Prevent the request from writing to cache.
+    this.request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
     this.request.overrideMimeType("text/xml");
     var self = this;
     this.request.addEventListener("load", function loadEventListener(event) { self.onLoad() }, false);
     this.request.addEventListener("error", function errorEventListener(event) { self.onError() }, false);
     this.request.send(null);
   }
   catch (e) {
     ERROR("Failed to request update manifest", e);