Merge from mozilla-central.
authorDavid Anderson <danderson@mozilla.com>
Mon, 10 Sep 2012 12:16:38 -0700
changeset 113082 28bfdee5702699b0e9313837ff125b55a9f90189
parent 113081 8bd7ad214f7a6e9cd93ad99407f43ef97da97ef3 (current diff)
parent 110996 ebd88961b3c3810a37412ea147d468f0f865badc (diff)
child 113083 9425b626563a19d5ee8b3e37e13cc09e1bcd27f8
push id1708
push userakeybl@mozilla.com
push dateMon, 19 Nov 2012 21:10:21 +0000
treeherdermozilla-beta@27b14fe50103 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone18.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 from mozilla-central.
b2g/installer/package-manifest.in
browser/base/content/browser.js
browser/base/content/browser.xul
browser/base/content/tabbrowser.xml
browser/themes/winstripe/browser.css
build/mobile/b2gemulator.py
build/mobile/devicemanager-utils.py
build/mobile/devicemanager.py
build/mobile/devicemanagerADB.py
build/mobile/devicemanagerSUT.py
build/mobile/droid.py
build/mobile/emulator.py
build/mobile/emulator_battery.py
caps/include/nsScriptSecurityManager.h
caps/src/nsScriptSecurityManager.cpp
chrome/src/nsChromeRegistry.cpp
configure.in
content/base/src/nsContentSink.cpp
content/base/src/nsContentSink.h
content/base/src/nsContentUtils.cpp
content/base/src/nsEventSource.cpp
content/base/src/nsFrameMessageManager.cpp
content/base/src/nsGkAtomList.h
content/base/src/nsPropertyTable.h
content/base/src/nsWebSocket.cpp
content/base/src/nsXMLHttpRequest.cpp
content/base/src/nsXMLHttpRequest.h
content/canvas/src/WebGLContext.cpp
content/html/content/src/nsHTMLOListElement.cpp
content/svg/content/src/nsSVGSVGElement.cpp
content/svg/content/src/nsSVGSVGElement.h
content/xbl/src/nsXBLDocumentInfo.cpp
content/xml/document/src/nsXMLDocument.cpp
content/xul/templates/src/nsContentSupportMap.h
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/base/nsDOMWindowUtils.cpp
dom/base/nsGlobalWindow.cpp
dom/base/nsJSEnvironment.cpp
dom/bindings/Codegen.py
dom/indexedDB/IndexedDatabaseManager.cpp
dom/ipc/ContentParent.cpp
dom/ipc/TabChild.cpp
dom/workers/File.cpp
dom/workers/WorkerPrivate.cpp
editor/libeditor/html/nsHTMLDataTransfer.cpp
gfx/gl/GLContext.h
gfx/gl/GLContextProviderOSMesa.cpp
gfx/layers/d3d10/LayerManagerD3D10.cpp
gfx/thebes/gfxPlatform.cpp
image/src/Decoder.cpp
js/ipc/ObjectWrapperParent.cpp
js/src/Makefile.in
js/src/gc/Root.h
js/src/ion/CodeGenerator.cpp
js/src/js.msg
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsatom.h
js/src/jsdbgapi.cpp
js/src/jsexn.cpp
js/src/jsfriendapi.h
js/src/jsfun.cpp
js/src/jsgc.cpp
js/src/jsinterp.cpp
js/src/jsinterp.h
js/src/jsiter.cpp
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/jsproxy.cpp
js/src/jsproxy.h
js/src/jstypedarray.cpp
js/src/jstypedarray.h
js/src/jswrapper.cpp
js/src/jswrapper.h
js/src/jsxml.cpp
js/src/methodjit/StubCalls.cpp
js/src/shell/js.cpp
js/src/vm/Debugger.cpp
js/src/vm/GlobalObject.cpp
js/src/vm/ScopeObject.cpp
js/src/vm/Stack.cpp
js/xpconnect/loader/mozJSComponentLoader.cpp
js/xpconnect/src/XPCComponents.cpp
js/xpconnect/src/XPCConvert.cpp
js/xpconnect/src/XPCQuickStubs.cpp
js/xpconnect/src/XPCQuickStubs.h
js/xpconnect/src/XPCWrappedJSClass.cpp
js/xpconnect/src/XPCWrappedNativeJSOps.cpp
js/xpconnect/src/XPCWrapper.cpp
js/xpconnect/src/nsXPConnect.cpp
js/xpconnect/src/xpcprivate.h
js/xpconnect/wrappers/AccessCheck.cpp
js/xpconnect/wrappers/AccessCheck.h
js/xpconnect/wrappers/FilteringWrapper.cpp
js/xpconnect/wrappers/WrapperFactory.cpp
js/xpconnect/wrappers/WrapperFactory.h
js/xpconnect/wrappers/XrayWrapper.cpp
layout/base/nsCSSFrameConstructor.cpp
layout/base/nsChangeHint.h
layout/base/nsFrameManager.cpp
layout/base/nsFrameManager.h
layout/generic/nsBlockFrame.cpp
layout/generic/nsBlockFrame.h
layout/generic/nsBulletFrame.cpp
layout/generic/nsBulletFrame.h
layout/generic/nsFrame.cpp
layout/generic/nsInlineFrame.cpp
layout/generic/nsTextFrameThebes.cpp
layout/mathml/nsMathMLmtableFrame.cpp
layout/reftests/bugs/reftest.list
layout/reftests/canvas/reftest.list
layout/reftests/svg/as-image/reftest.list
layout/reftests/svg/reftest.list
layout/reftests/svg/smil/reftest.list
layout/reftests/svg/smil/transform/reftest.list
layout/style/nsCSSScanner.cpp
layout/style/nsFontFaceLoader.cpp
layout/style/nsStyleContext.cpp
layout/style/nsStyleContext.h
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/ua.css
layout/svg/base/src/nsSVGEffects.cpp
layout/svg/base/src/nsSVGOuterSVGFrame.cpp
layout/tables/BasicTableLayoutStrategy.cpp
layout/tables/SpanningCellSorter.h
layout/tables/nsTableFrame.cpp
layout/tools/reftest/reftest.js
mobile/android/base/GeckoApp.java
mobile/android/chrome/content/browser.js
modules/libpref/src/init/all.js
parser/htmlparser/src/nsExpatDriver.cpp
testing/mozbase/manifestdestiny/tests/test.py
testing/mozbase/manifestdestiny/tests/test_expressionparser.txt
testing/mozbase/manifestdestiny/tests/test_manifestparser.txt
testing/mozbase/manifestdestiny/tests/test_testmanifest.txt
uriloader/prefetch/nsPrefetchService.cpp
widget/nsIWidget.h
widget/windows/nsClipboard.cpp
widget/windows/nsDataObj.cpp
widget/windows/nsImageClipboard.cpp
xpcom/base/nsCycleCollector.cpp
xpcom/build/nsXPComInit.cpp
xpcom/ds/nsHashtable.h
xpcom/glue/nsCycleCollectionParticipant.h
xpcom/glue/nsISupportsImpl.h
xpcom/glue/nsTHashtable.h
xpcom/io/SpecialSystemDirectory.cpp
xpcom/io/nsDirectoryService.cpp
xpcom/io/nsDirectoryService.h
xpcom/string/public/nsXPCOMStrings.h
--- a/b2g/Makefile.in
+++ b/b2g/Makefile.in
@@ -10,12 +10,16 @@ VPATH      = @srcdir@
 include $(DEPTH)/config/autoconf.mk
 
 DIRS = chrome components locales
 
 ifeq ($(OS_ARCH),WINNT)
 DIRS += $(DEPTH)/xulrunner/tools/redit
 endif
 
+ifneq ($(GAIADIR),)
+DIRS += gaia
+endif
+
 DIRS += app
 
 include $(topsrcdir)/config/rules.mk
 include $(topsrcdir)/testing/testsuite-targets.mk
--- a/b2g/app/Makefile.in
+++ b/b2g/app/Makefile.in
@@ -11,17 +11,21 @@ include $(DEPTH)/config/autoconf.mk
 
 PREF_JS_EXPORTS = $(srcdir)/b2g.js
 
 ifdef ENABLE_MARIONETTE
 DEFINES += -DENABLE_MARIONETTE=1
 endif
 
 ifndef LIBXUL_SDK
+ifneq ($(GAIADIR),)
+PROGRAM=$(MOZ_APP_NAME)-bin$(BIN_SUFFIX)
+else
 PROGRAM=$(MOZ_APP_NAME)$(BIN_SUFFIX)
+endif
 
 CPPSRCS = nsBrowserApp.cpp
 
 LOCAL_INCLUDES += -I$(topsrcdir)/toolkit/xre
 LOCAL_INCLUDES += -I$(topsrcdir)/xpcom/base
 LOCAL_INCLUDES += -I$(topsrcdir)/xpcom/build
 LOCAL_INCLUDES += -I$(DEPTH)/build
 
--- a/b2g/app/macbuild/Contents/Info.plist.in
+++ b/b2g/app/macbuild/Contents/Info.plist.in
@@ -1,138 +1,32 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
 	<key>CFBundleDevelopmentRegion</key>
 	<string>English</string>
-	<key>CFBundleDocumentTypes</key>
-	<array>
-		<dict>
-			<key>CFBundleTypeExtensions</key>
-			<array>
-				<string>html</string>
-				<string>htm</string>
-				<string>shtml</string>
-				<string>xht</string>
-				<string>xhtml</string>
-			</array>
-			<key>CFBundleTypeIconFile</key>
-			<string>document.icns</string>
-			<key>CFBundleTypeName</key>
-			<string>HTML Document</string>
-			<key>CFBundleTypeOSTypes</key>
-			<array>
-				<string>HTML</string>
-			</array>
-			<key>CFBundleTypeRole</key>
-			<string>Viewer</string>
-		</dict>
-		<dict>
-			<key>CFBundleTypeExtensions</key>
-			<array>
-			  <string>text</string>
-				<string>txt</string>
-				<string>js</string>
-				<string>log</string>
-				<string>css</string>
-				<string>xul</string>
-				<string>rdf</string>
-			</array>
-			<key>CFBundleTypeIconFile</key>
-			<string>document.icns</string>
-			<key>CFBundleTypeName</key>
-			<string>Text Document</string>
-			<key>CFBundleTypeOSTypes</key>
-			<array>
-				<string>TEXT</string>
-				<string>utxt</string>
-			</array>
-			<key>CFBundleTypeRole</key>
-			<string>Viewer</string>
-		</dict>
-		<dict>
-			<key>CFBundleTypeExtensions</key>
-			<array>
-				<string>jpeg</string>
-				<string>jpg</string>
-				<string>png</string>
-				<string>gif</string>
-			</array>
-			<key>CFBundleTypeIconFile</key>
-			<string>fileBookmark.icns</string>
-			<key>CFBundleTypeName</key>
-			<string>document.icns</string>
-			<key>CFBundleTypeOSTypes</key>
-			<array>
-				<string>GIFf</string>
-				<string>JPEG</string>
-				<string>PNGf</string>
-			</array>
-			<key>CFBundleTypeRole</key>
-			<string>Viewer</string>
-		</dict>
-	</array>
 	<key>CFBundleExecutable</key>
-	<string>%MOZ_APP_NAME%</string>
+	<string>b2g</string>
 	<key>CFBundleGetInfoString</key>
 	<string>%APP_NAME% %APP_VERSION%</string>
 	<key>CFBundleIconFile</key>
 	<string>%MOZ_APP_NAME%</string>
 	<key>CFBundleIdentifier</key>
 	<string>org.mozilla.b2g</string>
 	<key>CFBundleInfoDictionaryVersion</key>
 	<string>%MOZ_APP_VERSION%</string>
 	<key>CFBundleName</key>
 	<string>%APP_NAME%</string>
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
 	<string>%APP_VERSION%</string>
 	<key>CFBundleSignature</key>
 	<string>MOZB</string>
-	<key>CFBundleURLTypes</key>
-	<array>
-		<dict>
-			<key>CFBundleURLIconFile</key>
-			<string>document.icns</string>
-			<key>CFBundleURLName</key>
-			<string>http URL</string>
-			<key>CFBundleURLSchemes</key>
-			<array>
-				<string>http</string>
-			</array>
-		</dict>
-		<dict>
-			<key>CFBundleURLIconFile</key>
-			<string>document.icns</string>
-			<key>CFBundleURLName</key>
-			<string>https URL</string>
-			<key>CFBundleURLSchemes</key>
-			<array>
-				<string>https</string>
-			</array>
-		</dict>
-		<dict>
-			<key>CFBundleURLName</key>
-			<string>ftp URL</string>
-			<key>CFBundleURLSchemes</key>
-			<array>
-				<string>ftp</string>
-			</array>
-		</dict>
-		<dict>
-			<key>CFBundleURLName</key>
-			<string>file URL</string>
-			<key>CFBundleURLSchemes</key>
-			<array>
-				<string>file</string>
-			</array>
-		</dict>
-	</array>
 	<key>CFBundleVersion</key>
 	<string>%APP_VERSION%</string>
 	<key>NSAppleScriptEnabled</key>
 	<true/>
 	<key>CGDisableCoalescedUpdates</key>
 	<true/>
 </dict>
 </plist>
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -347,18 +347,17 @@ var shell = {
                    ObjectWrapper.wrap(details, getContentWindow()));
   },
 
   sendSystemMessage: function shell_sendSystemMessage(msg) {
     let origin = Services.io.newURI(msg.manifest, null, null).prePath;
     this.sendChromeEvent({
       type: 'open-app',
       url: msg.uri,
-      origin: origin,
-      manifest: msg.manifest,
+      manifestURL: msg.manifest,
       isActivity: (msg.type == 'activity'),
       target: msg.target
     });
   },
 
   receiveMessage: function shell_receiveMessage(message) {
     if (message.name != 'content-handler') {
       return;
@@ -596,17 +595,17 @@ var WebappsHelper = {
         DOMApplicationRegistry.getManifestFor(json.origin, function(aManifest) {
           if (!aManifest)
             return;
 
           let manifest = new DOMApplicationManifest(aManifest, json.origin);
           shell.sendChromeEvent({
             "type": "webapps-launch",
             "url": manifest.fullLaunchPath(json.startPoint),
-            "origin": json.origin
+            "manifestURL": json.manifestURL
           });
         });
         break;
       case "webapps-ask-install":
         let id = this.registerInstaller(json);
         shell.sendChromeEvent({
           type: "webapps-ask-install",
           id: id,
new file mode 100644
--- /dev/null
+++ b/b2g/gaia/Makefile.in
@@ -0,0 +1,42 @@
+# 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/.
+
+DEPTH     = @DEPTH@
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+GAIA_PATH := gaia/profile
+
+
+# We don't have a wrapper script on Windows yet
+ifneq ($(OS_ARCH),WINNT)
+PROGRAM = $(MOZ_APP_NAME)$(BIN_SUFFIX)
+CSRCS = run-b2g.c
+
+DEFINES += \
+  -DB2G_NAME=\"$(MOZ_APP_NAME)-bin$(BIN_SUFFIX)\" \
+  -DGAIA_PATH=\"$(GAIA_PATH)\" \
+  $(NULL)
+
+# This is needed to avoid making run-b2g depend on mozglue
+WRAP_LDFLAGS :=
+endif
+
+GENERATED_DIRS += $(DIST)/bin/$(GAIA_PATH)
+
+include $(topsrcdir)/config/rules.mk
+
+
+libs::
+	# Below here is how Gaia gets built
+	# The Gaia build system freaks out when N > 1 for -jN
+	$(MAKE) -j1 -C $(GAIADIR) clean
+	$(MAKE) -j1 -C $(GAIADIR) profile GAIA_DOMAIN=desktop-builds.$(MOZ_APP_NAME).mozilla.org
+	(cd $(GAIADIR)/profile && tar $(TAR_CREATE_FLAGS) - .) | (cd $(abspath $(DIST))/bin/$(GAIA_PATH) && tar -xf -)
+
+
+
new file mode 100644
--- /dev/null
+++ b/b2g/gaia/run-b2g.c
@@ -0,0 +1,47 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <libgen.h>
+
+#ifndef B2G_NAME
+#define B2G_NAME "b2g-bin"
+#endif
+#ifndef GAIA_PATH
+#define GAIA_PATH "gaia/profile"
+#endif
+#define NOMEM "Could not allocate enough memory"
+
+void error(char* msg){
+    fprintf(stderr, "ERROR: %s\n", msg);
+}
+
+int main(int argc, char* argv[], char* envp[]){
+    char* cwd = NULL;
+    char* full_path = NULL;
+    char* full_profile_path = NULL;
+    printf("Starting %s\n", B2G_NAME);
+    cwd = realpath(dirname(argv[0]), NULL);
+    full_path = (char*) malloc(strlen(cwd) + strlen(B2G_NAME) + 2);
+    if (!full_path) {
+        error(NOMEM);
+        return -2;
+    }
+    full_profile_path = (char*) malloc(strlen(cwd) + strlen(GAIA_PATH) + 2);
+    if (!full_profile_path) {
+        error(NOMEM);
+        return -2;
+    }
+    sprintf(full_path, "%s/%s", cwd, B2G_NAME);
+    sprintf(full_profile_path, "%s/%s", cwd, GAIA_PATH);
+    free(cwd);
+    printf("Running: %s -profile %s\n", full_path, full_profile_path);
+    fflush(stdout);
+    fflush(stderr);
+    execle(full_path, full_path, "-profile", full_profile_path, NULL, envp);
+    error("unable to start");
+    perror(argv[0]);
+    free(full_path);
+    free(full_profile_path);
+    return -1;
+}
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -696,8 +696,14 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DL
 @BINPATH@/components/ActivitiesGlue.js
 @BINPATH@/components/ProcessGlobal.js
 @BINPATH@/components/ContentHandler.js
 @BINPATH@/components/PaymentGlue.js
 
 #ifdef XP_MACOSX
 @BINPATH@/@DLL_PREFIX@plugin_child_interpose@DLL_SUFFIX@
 #endif
+
+#ifdef PACKAGE_GAIA
+[gaia]
+@BINPATH@/gaia/*
+@BINPATH@/b2g-bin@BIN_SUFFIX@
+#endif
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -84,17 +84,17 @@
     <command id="cmd_fullZoomEnlarge" oncommand="FullZoom.enlarge()"/>
     <command id="cmd_fullZoomReset"   oncommand="FullZoom.reset()"/>
     <command id="cmd_fullZoomToggle"  oncommand="ZoomManager.toggleZoom();"/>
     <command id="Browser:OpenLocation" oncommand="openLocation();"/>
 
     <command id="Tools:Search" oncommand="BrowserSearch.webSearch();"/>
     <command id="Tools:Downloads" oncommand="BrowserDownloadsUI();"/>
     <command id="Tools:DevToolbar" oncommand="DeveloperToolbar.toggle();" disabled="true" hidden="true"/>
-    <command id="Tools:DevToolbarFocus" oncommand="DeveloperToolbar.focus();" disabled="true"/>
+    <command id="Tools:DevToolbarFocus" oncommand="DeveloperToolbar.focusToggle();" disabled="true"/>
     <command id="Tools:WebConsole" oncommand="HUDConsoleUI.toggleHUD();"/>
     <command id="Tools:Inspect" oncommand="InspectorUI.toggleInspectorUI();"/>
     <command id="Tools:Debugger" oncommand="DebuggerUI.toggleDebugger();" disabled="true" hidden="true"/>
     <command id="Tools:RemoteDebugger" oncommand="DebuggerUI.toggleRemoteDebugger();" disabled="true" hidden="true"/>
     <command id="Tools:ChromeDebugger" oncommand="DebuggerUI.toggleChromeDebugger();" disabled="true" hidden="true"/>
     <command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();" disabled="true" hidden="true"/>
     <command id="Tools:StyleEditor" oncommand="StyleEditor.toggle();" disabled="true" hidden="true"/>
     <command id="Tools:ResponsiveUI" oncommand="ResponsiveUI.toggle();" disabled="true" hidden="true"/>
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -215,16 +215,17 @@ let SocialFlyout = {
 
   _createFrame: function() {
     let panel = this.panel;
     if (!Social.provider || panel.firstChild)
       return;
     // create and initialize the panel for this window
     let iframe = document.createElement("iframe");
     iframe.setAttribute("type", "content");
+    iframe.setAttribute("class", "social-panel-frame");
     iframe.setAttribute("flex", "1");
     iframe.setAttribute("origin", Social.provider.origin);
     panel.appendChild(iframe);
   },
 
   unload: function() {
     let panel = this.panel;
     if (!panel.firstChild)
@@ -531,16 +532,17 @@ var SocialToolbar = {
     for each(let name in iconNames) {
       let icon = provider.ambientNotificationIcons[name];
 
       let notificationFrameId = "social-status-" + icon.name;
       let notificationFrame = document.getElementById(notificationFrameId);
       if (!notificationFrame) {
         notificationFrame = document.createElement("iframe");
         notificationFrame.setAttribute("type", "content");
+        notificationFrame.setAttribute("class", "social-panel-frame");
         notificationFrame.setAttribute("id", notificationFrameId);
         notificationFrame.setAttribute("mozbrowser", "true");
         notificationFrames.appendChild(notificationFrame);
       }
       notificationFrame.setAttribute("origin", provider.origin);
       if (notificationFrame.getAttribute("src") != icon.contentPanel)
         notificationFrame.setAttribute("src", icon.contentPanel);
 
--- a/browser/base/content/browser-tabPreviews.js
+++ b/browser/base/content/browser-tabPreviews.js
@@ -653,17 +653,17 @@ var allTabs = {
       var matches = 0;
       if (filter.length && !tab.hidden) {
         let tabstring = tab.linkedBrowser.currentURI.spec;
         try {
           tabstring = decodeURI(tabstring);
         } catch (e) {}
         tabstring = tab.label + " " + tab.label.toLocaleLowerCase() + " " + tabstring;
         for (let i = 0; i < filter.length; i++)
-          matches += tabstring.indexOf(filter[i]) > -1;
+          matches += tabstring.contains(filter[i]);
       }
       if (matches < filter.length || tab.hidden) {
         preview.hidden = true;
       }
       else {
         this._visible++;
         this._updatePreview(preview);
         preview.hidden = false;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1044,17 +1044,17 @@ var gBrowserInit = {
           .QueryInterface(Ci.nsIInterfaceRequestor)
           .getInterface(Ci.nsIXULWindow)
           .XULBrowserWindow = window.XULBrowserWindow;
     window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
       new nsBrowserAccess();
 
     // set default character set if provided
     if ("arguments" in window && window.arguments.length > 1 && window.arguments[1]) {
-      if (window.arguments[1].indexOf("charset=") != -1) {
+      if (window.arguments[1].startsWith("charset=")) {
         var arrayArgComponents = window.arguments[1].split("=");
         if (arrayArgComponents) {
           //we should "inherit" the charset menu setting in a new window
           getMarkupDocumentViewer().defaultCharacterSet = arrayArgComponents[1];
         }
       }
     }
 
@@ -2753,17 +2753,17 @@ function getMeOutOfHere() {
   // Get the start page from the *default* pref branch, not the user's
   var prefs = Cc["@mozilla.org/preferences-service;1"]
              .getService(Ci.nsIPrefService).getDefaultBranch(null);
   var url = BROWSER_NEW_TAB_URL;
   try {
     url = prefs.getComplexValue("browser.startup.homepage",
                                 Ci.nsIPrefLocalizedString).data;
     // If url is a pipe-delimited set of pages, just take the first one.
-    if (url.indexOf("|") != -1)
+    if (url.contains("|"))
       url = url.split("|")[0];
   } catch(e) {
     Components.utils.reportError("Couldn't get homepage pref: " + e);
   }
   content.location = url;
 }
 
 function BrowserFullScreen()
@@ -3462,17 +3462,17 @@ function FillHistoryMenu(aParent) {
 
     aParent.appendChild(item);
   }
   return true;
 }
 
 function addToUrlbarHistory(aUrlToAdd) {
   if (aUrlToAdd &&
-      aUrlToAdd.indexOf(" ") == -1 &&
+      !aUrlToAdd.contains(" ") &&
       !/[\x00-\x1F]/.test(aUrlToAdd))
     PlacesUIUtils.markPageAsTyped(aUrlToAdd);
 }
 
 function toJavaScriptConsole()
 {
   toOpenWindowByType("global:console", "chrome://global/content/console.xul");
 }
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -263,20 +263,22 @@
                 class="editSharePopupBottomButton"
                 label="&social.sharePopup.undo.label;"
                 accesskey="&social.sharePopup.undo.accesskey;"
                 command="Social:UnsharePage"/>
 #endif
       </hbox>
     </panel>
 
-    <panel id="social-notification-panel" type="arrow" hidden="true" noautofocus="true">
+    <panel id="social-notification-panel" class="social-panel"
+           type="arrow" hidden="true" noautofocus="true">
       <box id="social-notification-box" flex="1"></box>
     </panel>
     <panel id="social-flyout-panel"
+           class="social-panel"
            onpopupshown="SocialFlyout.onShown()"
            onpopuphidden="SocialFlyout.onHidden()"
            side="right"
            type="arrow"
            hidden="true"
            noautofocus="true"
            position="topcenter topright"/>
 
--- a/browser/base/content/pageinfo/pageInfo.xul
+++ b/browser/base/content/pageinfo/pageInfo.xul
@@ -59,17 +59,17 @@
     <command id="cmd_cookieToggle"  oncommand="onRadioClick('cookie');"/>
     <command id="cmd_installToggle" oncommand="onRadioClick('install');"/>
     <command id="cmd_fullscreenToggle" oncommand="onRadioClick('fullscreen');"/>
     <command id="cmd_geoToggle"     oncommand="onRadioClick('geo');"/>
     <command id="cmd_indexedDBToggle" oncommand="onRadioClick('indexedDB');"/>
     <command id="cmd_pluginsToggle" oncommand="onRadioClick('plugins');"/>
   </commandset>
 
-  <keyset>
+  <keyset id="pageInfoKeySet">
     <key key="&closeWindow.key;" modifiers="accel" command="cmd_close"/>
     <key keycode="VK_ESCAPE"                       command="cmd_close"/>
 #ifdef XP_MACOSX
     <key key="."                 modifiers="meta"  command="cmd_close"/>
 #else
     <key keycode="VK_F1"                           command="cmd_help"/>
 #endif
     <key key="&copy.key;"        modifiers="accel" command="cmd_copy"/>
@@ -97,60 +97,60 @@
       <!-- Others added by overlay -->
     </radiogroup>
   </windowdragbox>
 
   <deck id="mainDeck" flex="1">
     <!-- General page information -->
     <vbox id="generalPanel">
       <textbox class="header" readonly="true" id="titletext"/>
-      <grid>
+      <grid id="generalGrid">
         <columns>
           <column/>
           <column class="gridSeparator"/>
           <column flex="1"/>
         </columns>
-        <rows>
-          <row>
+        <rows id="generalRows">
+          <row id="generalURLRow">
             <label control="urltext" value="&generalURL;"/>
             <separator/>
             <textbox readonly="true" id="urltext"/>
           </row>
-          <row>
+          <row id="generalSeparatorRow1">
             <separator class="thin"/>
           </row>
-          <row>
+          <row id="generalTypeRow">
             <label control="typetext" value="&generalType;"/>
             <separator/>
             <textbox readonly="true" id="typetext"/>
           </row>
-          <row>
+          <row id="generalModeRow">
             <label control="modetext" value="&generalMode;"/>
             <separator/>
             <textbox readonly="true" crop="end" id="modetext"/>
           </row>
-          <row>
+          <row id="generalEncodingRow">
             <label control="encodingtext" value="&generalEncoding;"/>
             <separator/>
             <textbox readonly="true" id="encodingtext"/>
           </row>
-          <row>
+          <row id="generalSizeRow">
             <label control="sizetext" value="&generalSize;"/>
             <separator/>
             <textbox readonly="true" id="sizetext"/>
           </row>
-          <row>
+          <row id="generalReferrerRow">
             <label control="refertext" value="&generalReferrer;"/>
             <separator/>
             <textbox readonly="true" id="refertext"/>
           </row>
-          <row>
+          <row id="generalSeparatorRow2">
             <separator class="thin"/>
           </row>
-          <row>
+          <row id="generalModifiedRow">
             <label control="modifiedtext" value="&generalModified;"/>
             <separator/>
             <textbox readonly="true" id="modifiedtext"/>
           </row>
         </rows>
       </grid>
       <separator class="thin"/>
       <groupbox id="metaTags" flex="1" class="collapsable treebox">
@@ -160,24 +160,24 @@
             <treecol id="meta-name"    label="&generalMetaName;"
                      persist="width" flex="1"
                      onclick="gMetaView.onPageMediaSort('meta-name');"/>
             <splitter class="tree-splitter"/>
             <treecol id="meta-content" label="&generalMetaContent;"
                      persist="width" flex="4"
                      onclick="gMetaView.onPageMediaSort('meta-content');"/>
           </treecols>
-          <treechildren flex="1"/>
+          <treechildren id="metatreechildren" flex="1"/>
         </tree>        
       </groupbox>
       <groupbox id="securityBox">
         <caption id="securityBoxCaption" label="&securityHeader;"/>
         <description id="general-security-identity" class="header"/>
         <description id="general-security-privacy"  class="header"/>
-        <hbox align="right">
+        <hbox id="securityDetailsButtonBox" align="right">
           <button id="security-view-details" label="&generalSecurityDetails;"
                   accesskey="&generalSecurityDetails.accesskey;"
                   oncommand="onClickMore();"/>
         </hbox>
       </groupbox>
     </vbox>
 
     <!-- Media information -->
@@ -200,199 +200,200 @@
           <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="4"
                         width="4"  id="image-alt"    label="&mediaAltHeader;"
                         onclick="gImageView.onPageMediaSort('image-alt');"/>
           <splitter class="tree-splitter"/>
           <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="1"
                         width="1"  id="image-count"    label="&mediaCount;"
                         onclick="gImageView.onPageMediaSort('image-count');"/>
         </treecols>
-        <treechildren flex="1"/>
+        <treechildren id="imagetreechildren" flex="1"/>
       </tree>
       <splitter orient="vertical" id="mediaSplitter"/>
       <vbox flex="1" id="mediaPreviewBox" collapsed="true">
         <grid id="mediaGrid">
           <columns>
             <column id="mediaLabelColumn"/>
             <column class="gridSeparator"/>
             <column flex="1"/>
           </columns>
-          <rows>
-            <row>
+          <rows id="mediaRows">
+            <row id="mediaLocationRow">
               <label control="imageurltext" value="&mediaLocation;"/>
               <separator/>
               <textbox readonly="true" id="imageurltext"/>
             </row>
-            <row>
+            <row id="mediaTypeRow">
               <label control="imagetypetext" value="&generalType;"/>
               <separator/>
               <textbox readonly="true" id="imagetypetext"/>
             </row>
-            <row>
+            <row id="mediaSizeRow">
               <label control="imagesizetext" value="&generalSize;"/>
               <separator/>
               <textbox readonly="true" id="imagesizetext"/>
             </row>
-            <row>
+            <row id="mediaDimensionRow">
               <label control="imagedimensiontext" value="&mediaDimension;"/>
               <separator/>
               <textbox readonly="true" id="imagedimensiontext"/>
             </row>
-            <row>
+            <row id="mediaTextRow">
               <label control="imagetext" value="&mediaText;"/>
               <separator/>
               <textbox readonly="true" id="imagetext"/>
             </row>
-            <row>
+            <row id="mediaLongdescRow">
               <label control="imagelongdesctext" value="&mediaLongdesc;"/>
               <separator/>
               <textbox readonly="true" id="imagelongdesctext"/>
             </row>
           </rows>
         </grid>
-        <hbox align="end">
-          <vbox>
+        <hbox id="imageSaveBox" align="end">
+          <vbox id="blockImageBox">
             <checkbox id="blockImage" hidden="true" oncommand="onBlockImage()"
                       accesskey="&mediaBlockImage.accesskey;"/>
             <label control="thepreviewimage" value="&mediaPreview;" class="header"/>
           </vbox>
-          <spacer flex="1"/>
+          <spacer id="imageSaveBoxSpacer" flex="1"/>
           <button label="&mediaSaveAs;" accesskey="&mediaSaveAs.accesskey;"
                   icon="save" id="imagesaveasbutton"
                   oncommand="saveMedia();"/>
         </hbox>
-        <vbox class="inset iframe" flex="1" pack="center">
+        <vbox id="imagecontainerbox" class="inset iframe" flex="1" pack="center">
           <hbox id="theimagecontainer" pack="center">
             <image id="thepreviewimage"/>
           </hbox>
           <hbox id="brokenimagecontainer" pack="center" collapsed="true">
             <image id="brokenimage" src="resource://gre-resources/broken-image.png"/>
           </hbox>
         </vbox>
       </vbox>
       <hbox id="mediaSaveBox" collapsed="true">
-        <spacer flex="1"/>
+        <spacer id="mediaSaveBoxSpacer" flex="1"/>
         <button label="&mediaSaveAs;" accesskey="&mediaSaveAs2.accesskey;"
-                icon="save" oncommand="saveMedia();"/>
+                icon="save" id="mediasaveasbutton"
+                oncommand="saveMedia();"/>
       </hbox>
     </vbox>
 
     <!-- Feeds -->
     <vbox id="feedPanel">
       <richlistbox id="feedListbox" flex="1"/>
     </vbox>
 
     <!-- Permissions -->
     <vbox id="permPanel">
-      <hbox>
+      <hbox id="permHostBox">
         <label value="&permissionsFor;" control="hostText" />
         <textbox id="hostText" class="header" readonly="true"
                  crop="end" flex="1"/>
       </hbox>
 
       <vbox id="permList" flex="1">
-        <vbox class="permission">
+        <vbox class="permission" id="permImageRow">
           <label class="permissionLabel" id="permImageLabel"
                  value="&permImage;" control="imageRadioGroup"/>
-          <hbox role="group" aria-labelledby="permImageLabel">
+          <hbox id="permImageBox" role="group" aria-labelledby="permImageLabel">
             <checkbox id="imageDef" command="cmd_imageDef" label="&permUseDefault;"/>
             <spacer flex="1"/>
             <radiogroup id="imageRadioGroup" orient="horizontal">
               <radio id="image#1" command="cmd_imageToggle" label="&permAllow;"/>
               <radio id="image#2" command="cmd_imageToggle" label="&permBlock;"/>
             </radiogroup>
           </hbox>
         </vbox>
-        <vbox class="permission">
+        <vbox class="permission" id="permPopupRow">
           <label class="permissionLabel" id="permPopupLabel"
                  value="&permPopup;" control="popupRadioGroup"/>
-          <hbox role="group" aria-labelledby="permPopupLabel">
+          <hbox id="permPopupBox" role="group" aria-labelledby="permPopupLabel">
             <checkbox id="popupDef" command="cmd_popupDef" label="&permUseDefault;"/>
             <spacer flex="1"/>
             <radiogroup id="popupRadioGroup" orient="horizontal">
               <radio id="popup#1" command="cmd_popupToggle" label="&permAllow;"/>
               <radio id="popup#2" command="cmd_popupToggle" label="&permBlock;"/>
             </radiogroup>
           </hbox>
         </vbox>
-        <vbox class="permission">
+        <vbox class="permission" id="permCookieRow">
           <label class="permissionLabel" id="permCookieLabel"
                  value="&permCookie;" control="cookieRadioGroup"/>
-          <hbox role="group" aria-labelledby="permCookieLabel">
+          <hbox id="permCookieBox" role="group" aria-labelledby="permCookieLabel">
             <checkbox id="cookieDef" command="cmd_cookieDef" label="&permUseDefault;"/>
             <spacer flex="1"/>
             <radiogroup id="cookieRadioGroup" orient="horizontal">
               <radio id="cookie#1" command="cmd_cookieToggle" label="&permAllow;"/>
               <radio id="cookie#8" command="cmd_cookieToggle" label="&permAllowSession;"/>
               <radio id="cookie#2" command="cmd_cookieToggle" label="&permBlock;"/>
             </radiogroup>
           </hbox>
         </vbox>
-        <vbox class="permission">
+        <vbox class="permission" id="permInstallRow">
           <label class="permissionLabel" id="permInstallLabel"
                  value="&permInstall;" control="installRadioGroup"/>
-          <hbox role="group" aria-labelledby="permInstallLabel">
+          <hbox id="permInstallBox" role="group" aria-labelledby="permInstallLabel">
             <checkbox id="installDef" command="cmd_installDef" label="&permUseDefault;"/>
             <spacer flex="1"/>
             <radiogroup id="installRadioGroup" orient="horizontal">
               <radio id="install#1" command="cmd_installToggle" label="&permAllow;"/>
               <radio id="install#2" command="cmd_installToggle" label="&permBlock;"/>
             </radiogroup>
           </hbox>
         </vbox>
-        <vbox class="permission">
+        <vbox class="permission" id="permGeoRow" >
           <label class="permissionLabel" id="permGeoLabel"
                  value="&permGeo;" control="geoRadioGroup"/>
-          <hbox role="group" aria-labelledby="permGeoLabel">
+          <hbox id="permGeoBox" role="group" aria-labelledby="permGeoLabel">
             <checkbox id="geoDef" command="cmd_geoDef" label="&permAskAlways;"/>
             <spacer flex="1"/>
             <radiogroup id="geoRadioGroup" orient="horizontal">
               <radio id="geo#1" command="cmd_geoToggle" label="&permAllow;"/>
               <radio id="geo#2" command="cmd_geoToggle" label="&permBlock;"/>
             </radiogroup>
           </hbox>
         </vbox>
-        <vbox class="permission">
+        <vbox class="permission" id="permIndexedDBRow">
           <label class="permissionLabel" id="permIndexedDBLabel"
                  value="&permIndexedDB;" control="indexedDBRadioGroup"/>
-          <hbox role="group" aria-labelledby="permIndexedDBLabel">
+          <hbox id="permIndexedDBBox" role="group" aria-labelledby="permIndexedDBLabel">
             <checkbox id="indexedDBDef" command="cmd_indexedDBDef" label="&permUseDefault;"/>
             <spacer flex="1"/>
             <radiogroup id="indexedDBRadioGroup" orient="horizontal">
               <!-- Ask and Allow are purposefully reversed here! -->
               <radio id="indexedDB#1" command="cmd_indexedDBToggle" label="&permAskAlways;"/>
               <radio id="indexedDB#0" command="cmd_indexedDBToggle" label="&permAllow;"/>
               <radio id="indexedDB#2" command="cmd_indexedDBToggle" label="&permBlock;"/>
             </radiogroup>
           </hbox>
-          <hbox>
+          <hbox id="permIndexedDBBox2">
             <spacer flex="1"/>
-            <vbox pack="center">
+            <vbox id="permIndexedDBStatusBox" pack="center">
               <label id="indexedDBStatus" control="indexedDBClear" hidden="true"/>
             </vbox>
             <button id="indexedDBClear" label="&permClearStorage;" hidden="true"
                     accesskey="&permClearStorage.accesskey;" onclick="onIndexedDBClear();"/>
           </hbox>
         </vbox>
         <vbox class="permission" id="permPluginsRow">
           <label class="permissionLabel" id="permPluginsLabel"
                  value="&permPlugins;" control="pluginsRadioGroup"/>
-          <hbox role="group" aria-labelledby="permPluginsLabel">
+          <hbox id="permPluginsBox" role="group" aria-labelledby="permPluginsLabel">
             <checkbox id="pluginsDef" command="cmd_pluginsDef" label="&permAskAlways;"/>
             <spacer flex="1"/>
             <radiogroup id="pluginsRadioGroup" orient="horizontal">
               <radio id="plugins#1" command="cmd_pluginsToggle" label="&permAllow;"/>
               <radio id="plugins#2" command="cmd_pluginsToggle" label="&permBlock;"/>
             </radiogroup>
           </hbox>
         </vbox>
-        <vbox class="permission">
+        <vbox class="permission" id="permFullscreenRow">
           <label class="permissionLabel" id="permFullscreenLabel"
                  value="&permFullscreen;" control="fullscreenRadioGroup"/>
-          <hbox role="group" aria-labelledby="permFullscreenLabel">
+          <hbox id="permFullscreenBox" role="group" aria-labelledby="permFullscreenLabel">
             <checkbox id="fullscreenDef" command="cmd_fullscreenDef" label="&permUseDefault;"/>
             <spacer flex="1"/>
             <radiogroup id="fullscreenRadioGroup" orient="horizontal">
               <radio id="fullscreen#0" command="cmd_fullscreenToggle" label="&permAskAlways;"/>
               <radio id="fullscreen#1" command="cmd_fullscreenToggle" label="&permAllow;"/>
               <radio id="fullscreen#2" command="cmd_fullscreenToggle" label="&permBlock;"/>
             </radiogroup>
           </hbox>
@@ -400,95 +401,102 @@
       </vbox>
     </vbox>
 
     <!-- Security & Privacy -->
     <vbox id="securityPanel">
       <!-- Identity Section -->
       <groupbox id="security-identity-groupbox" flex="1">
         <caption id="security-identity" label="&securityView.identity.header;"/>
-        <grid flex="1">
+        <grid id="security-identity-grid" flex="1">
           <columns>
             <column/>
             <column flex="1"/>
           </columns>
-          <rows>
-            <row><!-- Domain -->
+          <rows id="security-identity-rows">
+            <!-- Domain -->
+            <row id="security-identity-domain-row">
               <label id="security-identity-domain-label"
                      class="fieldLabel"
                      value="&securityView.identity.domain;"
                      control="security-identity-domain-value"/>
               <textbox id="security-identity-domain-value"
                        class="fieldValue" readonly="true"/>
             </row>
-            <row><!-- Owner -->
+            <!-- Owner -->
+            <row id="security-identity-owner-row">
               <label id="security-identity-owner-label"
                      class="fieldLabel"
                      value="&securityView.identity.owner;"
                      control="security-identity-owner-value"/>
               <textbox id="security-identity-owner-value"
                        class="fieldValue" readonly="true"/>
             </row>
-            <row><!-- Verifier -->
+            <!-- Verifier -->
+            <row id="security-identity-verifier-row">
               <label id="security-identity-verifier-label"
                      class="fieldLabel"
                      value="&securityView.identity.verifier;"
                      control="security-identity-verifier-value"/>
               <textbox id="security-identity-verifier-value"
                        class="fieldValue" readonly="true" />
             </row>
           </rows>
         </grid>
         <spacer flex="1"/>
-        <hbox pack="end"><!-- Cert button -->
+        <!-- Cert button -->
+        <hbox id="security-view-cert-box" pack="end">
           <button id="security-view-cert" label="&securityView.certView;"
                   accesskey="&securityView.accesskey;"
                   oncommand="security.viewCert();"/>
         </hbox>
       </groupbox>
       
       <!-- Privacy & History section -->
       <groupbox id="security-privacy-groupbox" flex="1">
         <caption id="security-privacy" label="&securityView.privacy.header;" />
-        <grid>
+        <grid id="security-privacy-grid">
           <columns>
             <column flex="1"/>
             <column flex="1"/>
           </columns>
-          <rows>
-            <row><!-- History -->
+          <rows id="security-privacy-rows">
+            <!-- History -->
+            <row id="security-privacy-history-row">
               <label id="security-privacy-history-label"
                            control="security-privacy-history-value"
                            class="fieldLabel">&securityView.privacy.history;</label>
               <textbox id="security-privacy-history-value"
                        class="fieldValue"
                        value="&securityView.unknown;"
                        readonly="true"/>
             </row>
-            <row><!-- Cookies -->
+            <!-- Cookies -->
+            <row id="security-privacy-cookies-row">
               <label id="security-privacy-cookies-label"
                            control="security-privacy-cookies-value"
                            class="fieldLabel">&securityView.privacy.cookies;</label>
-              <hbox align="center">
+              <hbox id="security-privacy-cookies-box" align="center">
                 <textbox id="security-privacy-cookies-value"
                          class="fieldValue"
                          value="&securityView.unknown;"
                          flex="1"
                          readonly="true"/>
                 <button id="security-view-cookies"
                         label="&securityView.privacy.viewCookies;"
                         accesskey="&securityView.privacy.viewCookies.accessKey;"
                         oncommand="security.viewCookies();"/>
               </hbox>
             </row>
-            <row><!-- Passwords -->
+            <!-- Passwords -->
+            <row id="security-privacy-passwords-row">
               <label id="security-privacy-passwords-label"
                             control="security-privacy-passwords-value"
                             class="fieldLabel">&securityView.privacy.passwords;</label>
-              <hbox align="center">
+              <hbox id="security-privacy-passwords-box" align="center">
                 <textbox id="security-privacy-passwords-value"
                          class="fieldValue"
                          value="&securityView.unknown;"
                          flex="1"
                          readonly="true"/>
                 <button id="security-view-password"
                         label="&securityView.privacy.viewPasswords;"
                         accesskey="&securityView.privacy.viewPasswords.accessKey;"
@@ -497,17 +505,17 @@
             </row>
           </rows>
         </grid>
       </groupbox>
       
       <!-- Technical Details section -->
       <groupbox id="security-technical-groupbox" flex="1">
         <caption id="security-technical" label="&securityView.technical.header;" />
-        <vbox flex="1">
+        <vbox id="security-technical-box" flex="1">
           <label id="security-technical-shortform" class="fieldValue"/>
           <description id="security-technical-longform1" class="fieldLabel"/>
           <description id="security-technical-longform2" class="fieldLabel"/>
         </vbox>
       </groupbox>
     </vbox>
     <!-- Others added by overlay -->
   </deck>
--- a/browser/base/content/socialchat.xml
+++ b/browser/base/content/socialchat.xml
@@ -2,22 +2,20 @@
 
 <bindings id="socialChatBindings"
     xmlns="http://www.mozilla.org/xbl"
     xmlns:xbl="http://www.mozilla.org/xbl"
     xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <binding id="chatbox">
     <content orient="vertical" mousethrough="never">
-      <xul:hbox class="chat-titlebar" xbl:inherits="minimized">
+      <xul:hbox class="chat-titlebar" xbl:inherits="minimized,selected"
+                onclick="document.getBindingParent(this).toggle();">
         <xul:image class="chat-status-icon" xbl:inherits="src=image"/>
         <xul:label class="chat-title" flex="1" xbl:inherits="value=label,crop"/>
-        <xul:toolbarbutton class="chat-toggle-button chat-toolbarbutton"
-                           xbl:inherits="minimized"
-                           oncommand="document.getBindingParent(this).toggle();"/>
         <xul:toolbarbutton class="chat-close-button chat-toolbarbutton"
                            oncommand="document.getBindingParent(this).close();"/>
       </xul:hbox>
       <xul:iframe anonid="iframe" class="chat-frame" flex="1"
                   xbl:inherits="src,origin,collapsed=minimized" type="content"/>
     </content>
 
     <implementation implements="nsIDOMEventListener">
@@ -127,17 +125,29 @@
       </field>
 
       <property name="emptyWidth">
         <getter>
           return document.getAnonymousElementByAttribute(this, "anonid", "spacer").boxObject.width;
         </getter>
       </property>
 
-      <field name="selectedChat"/>
+      <property name="selectedChat">
+        <getter><![CDATA[
+          return this._selectedChat;
+        ]]></getter>
+        <setter><![CDATA[
+          if (this._selectedChat)
+            this._selectedChat.removeAttribute("selected");
+          this._selectedChat = val;
+          if (val) {
+            this._selectedChat.setAttribute("selected", "true");
+          }
+        ]]></setter>
+      </property>
 
       <field name="menuitemMap">new WeakMap()</field>
       <field name="chatboxForURL">new Map();</field>
 
       <property name="firstCollapsedChild">
         <getter><![CDATA[
           let child = this.lastChild;
           while (child && !child.collapsed) {
--- a/browser/base/content/sync/aboutSyncTabs.js
+++ b/browser/base/content/sync/aboutSyncTabs.js
@@ -52,18 +52,18 @@ let RemoteTabViewer = {
     let val = event.target.value.toLowerCase();
     let numTabs = this._tabsList.getRowCount();
     let clientTabs = 0;
     let currentClient = null;
     for (let i = 0;i < numTabs;i++) {
       let item = this._tabsList.getItemAtIndex(i);
       let hide = false;
       if (item.getAttribute("type") == "tab") {
-        if (item.getAttribute("url").toLowerCase().indexOf(val) == -1 &&
-            item.getAttribute("title").toLowerCase().indexOf(val) == -1)
+        if (!item.getAttribute("url").toLowerCase().contains(val) && 
+            !item.getAttribute("title").toLowerCase().contains(val))
           hide = true;
         else
           clientTabs++;
       }
       else if (item.getAttribute("type") == "client") {
         if (currentClient) {
           if (clientTabs == 0)
             currentClient.hidden = true;
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -790,17 +790,17 @@
                 newTitle += sep;
             }
             newTitle += modifier;
 
             // If location bar is hidden and the URL type supports a host,
             // add the scheme and host to the title to prevent spoofing.
             // XXX https://bugzilla.mozilla.org/show_bug.cgi?id=22183#c239
             try {
-              if (docElement.getAttribute("chromehidden").indexOf("location") != -1) {
+              if (docElement.getAttribute("chromehidden").contains("location")) {
                 var uri = this.mURIFixup.createExposableURI(
                             aBrowser.currentURI);
                 if (uri.scheme == "about")
                   newTitle = uri.spec + sep + newTitle;
                 else
                   newTitle = uri.prePath + sep + newTitle;
               }
             } catch (e) {}
@@ -3745,17 +3745,17 @@
         } else {
           // Pass true to disallow dropping javascript: or data: urls
           let url;
           try {
             url = browserDragAndDrop.drop(event, { }, true);
           } catch (ex) {}
 
           // valid urls don't contain spaces ' '; if we have a space it isn't a valid url.
-          if (!url || url.indexOf(" ") != -1)
+          if (!url || url.contains(" "))
             return;
 
           let bgLoad = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
 
           if (event.shiftKey)
             bgLoad = !bgLoad;
 
           let tab = this._getDragTargetTab(event);
--- a/browser/base/content/test/browser_bug555224.js
+++ b/browser/base/content/test/browser_bug555224.js
@@ -14,17 +14,17 @@ function afterZoomAndLoad(aCallback, aTa
       executeSoon(aCallback);
   }, true);
   let oldAPTS = FullZoom._applyPrefToSetting;
   FullZoom._applyPrefToSetting = function(value, browser) {
     if (!value)
       value = undefined;
     oldAPTS.call(FullZoom, value, browser);
     // Don't reset _applyPrefToSetting until we've seen the about:blank load(s)
-    if (browser && (browser.currentURI.spec.indexOf("http:") != -1)) {
+    if (browser && browser.currentURI.spec.startsWith("http:")) {
       FullZoom._applyPrefToSetting = oldAPTS;
       didZoom = true;
     }
     if (didLoad && didZoom)
       executeSoon(aCallback);
   };
 }
 
--- a/browser/base/content/test/browser_homeDrop.js
+++ b/browser/base/content/test/browser_homeDrop.js
@@ -23,17 +23,17 @@ function test() {
     ok(true, "dialog appeared in response to home button drop");
     domwindow.document.documentElement.cancelDialog();
     Services.wm.removeListener(dialogListener);
 
     // Now trigger the invalid URI test
     executeSoon(function () {
       let consoleListener = {
         observe: function (m) {
-          if (m.message.indexOf("NS_ERROR_DOM_BAD_URI") > -1) {
+          if (m.message.contains("NS_ERROR_DOM_BAD_URI")) {
             ok(true, "drop was blocked");
             executeSoon(finish);
           }
         }
       }
       Services.console.registerListener(consoleListener);
       registerCleanupFunction(function () {
         Services.console.unregisterListener(consoleListener);
--- a/browser/base/content/test/browser_popupNotification.js
+++ b/browser/base/content/test/browser_popupNotification.js
@@ -751,17 +751,17 @@ function triggerSecondaryCommand(popup, 
     for (let i = 0; i <= index; i++)
       EventUtils.synthesizeKey("VK_DOWN", {});
 
     // Activate
     EventUtils.synthesizeKey("VK_ENTER", {});
   }, false);
 
   // One down event to open the popup
-  EventUtils.synthesizeKey("VK_DOWN", { altKey: (navigator.platform.indexOf("Mac") == -1) });
+  EventUtils.synthesizeKey("VK_DOWN", { altKey: !navigator.platform.contains("Mac") });
 }
 
 function loadURI(uri, callback) {
   if (callback) {
     gBrowser.addEventListener("load", function() {
       // Ignore the about:blank load
       if (gBrowser.currentURI.spec != uri)
         return;
--- a/browser/base/content/test/browser_social_chatwindow.js
+++ b/browser/base/content/test/browser_social_chatwindow.js
@@ -86,27 +86,29 @@ var tests = {
       }
     }
     let num = numToOpen;
     while (num-- > 0) {
       port.postMessage({topic: "test-chatbox-open", data: { id: num }});
     }
   },
   testWorkerChatWindow: function(next) {
+    const chatUrl = "https://example.com/browser/browser/base/content/test/social_chat.html";
     let port = Social.provider.port;
     ok(port, "provider has a port");
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "got-chatbox-message":
           ok(true, "got a chat window opened");
           let chats = document.getElementById("pinnedchats");
           while (chats.selectedChat) {
             chats.selectedChat.close();
           }
           ok(!chats.selectedChat, "chats are all closed");
+          ensureSocialUrlNotRemembered(chatUrl);
           next();
           break;
       }
     }
-    port.postMessage({topic: "test-worker-chat" });
+    port.postMessage({topic: "test-worker-chat", data: chatUrl});
   }
 }
--- a/browser/base/content/test/browser_social_shareButton.js
+++ b/browser/base/content/test/browser_social_shareButton.js
@@ -138,25 +138,25 @@ function checkShortcutWorked(keyTarget) 
 
 function checkOKButton() {
   let okButton = document.getElementById("editSharePopupOkButton");
   let undoButton = document.getElementById("editSharePopupUndoButton");
   is(document.activeElement, okButton, "ok button should be focused by default");
 
   // This rest of particular test doesn't really apply on Mac, since buttons
   // aren't focusable by default.
-  if (navigator.platform.indexOf("Mac") != -1) {
+  if (navigator.platform.contains("Mac")) {
     executeSoon(testCloseBySpace);
     return;
   }
 
   let displayName = document.getElementById("socialUserDisplayName");
 
   // Linux has the buttons in the [unshare] [ok] order, so displayName will come first.
-  if (navigator.platform.indexOf("Linux") != -1) {
+  if (navigator.platform.contains("Linux")) {
     checkNextInTabOrder(displayName, function () {
       checkNextInTabOrder(undoButton, function () {
         checkNextInTabOrder(okButton, testCloseBySpace);
       });
     });
   } else {
     checkNextInTabOrder(undoButton, function () {
       checkNextInTabOrder(displayName, function () {
--- a/browser/base/content/test/social_worker.js
+++ b/browser/base/content/test/social_worker.js
@@ -59,17 +59,17 @@ onconnect = function(e) {
         break;
       case "flyout-message":
         testPort.postMessage({topic:"got-flyout-message", result: event.data.result});
         break;
       case "flyout-visibility":
         testPort.postMessage({topic:"got-flyout-visibility", result: event.data.result});
         break;
       case "test-worker-chat":
-        apiPort.postMessage({topic: "social.request-chat", data: "https://example.com/browser/browser/base/content/test/social_chat.html" });
+        apiPort.postMessage({topic: "social.request-chat", data: event.data.data });
         break;
       case "social.initialize":
         // This is the workerAPI port, respond and set up a notification icon.
         apiPort = port;
         port.postMessage({topic: "social.initialize-response"});
         let profile = {
           portrait: "https://example.com/portrait.jpg",
           userName: "trickster",
--- a/browser/base/content/test/test_offline_gzip.html
+++ b/browser/base/content/test/test_offline_gzip.html
@@ -71,17 +71,17 @@ function handleMessageEvents(event) {
         // Sometimes document.body may not exist, and trying to access
         // it will throw an exception, so handle this case.
         try {
           var bodyInnerHTML = frames.testFrame.document.body.innerHTML;
         }
         catch (e) {
           var bodyInnerHTML = "";
         }
-        if (cacheCount == 2 || (bodyInnerHTML.indexOf("error") != -1)) {
+        if (cacheCount == 2 || bodyInnerHTML.contains("error")) {
           clearInterval(intervalID);
           is(cacheCount, 2, "frame not reloaded successfully");
           if (cacheCount != 2) {
             finishTest();
           }
         }
       }, 100);
       break;
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -461,17 +461,17 @@
           var selectedVal = inputVal.substring(this.selectionStart, this.selectionEnd);
 
           // If the selection doesn't start at the beginning or doesn't span the full domain or
           // the URL bar is modified, nothing else to do here.
           if (this.selectionStart > 0 || this.valueIsTyped)
             return selectedVal;
           // The selection doesn't span the full domain if it doesn't contain a slash and is
           // followed by some character other than a slash.
-          if (selectedVal.indexOf("/") == -1) {
+          if (!selectedVal.contains("/")) {
             let remainder = inputVal.replace(selectedVal, "");
             if (remainder != "" && remainder[0] != "/")
               return selectedVal;
           }
 
           let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup);
 
           let uri;
--- a/browser/components/thumbnails/PageThumbs.jsm
+++ b/browser/components/thumbnails/PageThumbs.jsm
@@ -1,15 +1,15 @@
 /* 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/. */
 
 "use strict";
 
-let EXPORTED_SYMBOLS = ["PageThumbs", "PageThumbsStorage", "PageThumbsCache"];
+let EXPORTED_SYMBOLS = ["PageThumbs", "PageThumbsStorage"];
 
 const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
 const PREF_STORAGE_VERSION = "browser.pagethumbnails.storage_version";
 const LATEST_STORAGE_VERSION = 2;
@@ -528,68 +528,8 @@ let PageThumbsHistoryObserver = {
   onEndUpdateBatch: function () {},
   onVisit: function () {},
   onBeforeDeleteURI: function () {},
   onPageChanged: function () {},
   onDeleteVisits: function () {},
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver])
 };
-
-/**
- * A singleton handling the storage of page thumbnails.
- */
-let PageThumbsCache = {
-  /**
-   * Calls the given callback with a cache entry opened for reading.
-   * @param aKey The key identifying the desired cache entry.
-   * @param aCallback The callback that is called when the cache entry is ready.
-   */
-  getReadEntry: function Cache_getReadEntry(aKey, aCallback) {
-    // Try to open the desired cache entry.
-    this._openCacheEntry(aKey, Ci.nsICache.ACCESS_READ, aCallback);
-  },
-
-  /**
-   * Opens the cache entry identified by the given key.
-   * @param aKey The key identifying the desired cache entry.
-   * @param aAccess The desired access mode (see nsICache.ACCESS_* constants).
-   * @param aCallback The function to be called when the cache entry was opened.
-   */
-  _openCacheEntry: function Cache_openCacheEntry(aKey, aAccess, aCallback) {
-    function onCacheEntryAvailable(aEntry, aAccessGranted, aStatus) {
-      let validAccess = aAccess == aAccessGranted;
-      let validStatus = Components.isSuccessCode(aStatus);
-
-      // Check if a valid entry was passed and if the
-      // access we requested was actually granted.
-      if (aEntry && !(validAccess && validStatus)) {
-        aEntry.close();
-        aEntry = null;
-      }
-
-      aCallback(aEntry);
-    }
-
-    let listener = this._createCacheListener(onCacheEntryAvailable);
-    this._cacheSession.asyncOpenCacheEntry(aKey, aAccess, listener);
-  },
-
-  /**
-   * Returns a cache listener implementing the nsICacheListener interface.
-   * @param aCallback The callback to be called when the cache entry is available.
-   * @return The new cache listener.
-   */
-  _createCacheListener: function Cache_createCacheListener(aCallback) {
-    return {
-      onCacheEntryAvailable: aCallback,
-      QueryInterface: XPCOMUtils.generateQI([Ci.nsICacheListener])
-    };
-  }
-};
-
-/**
- * Define a lazy getter for the cache session.
- */
-XPCOMUtils.defineLazyGetter(PageThumbsCache, "_cacheSession", function () {
-  return Services.cache.createSession(PageThumbs.scheme,
-                                     Ci.nsICache.STORE_ON_DISK, true);
-});
--- a/browser/components/thumbnails/PageThumbsProtocol.js
+++ b/browser/components/thumbnails/PageThumbsProtocol.js
@@ -69,331 +69,33 @@ Protocol.prototype = {
   /**
    * Constructs a new channel from the given URI for this protocol handler.
    * @param aURI The URI for which to construct a channel.
    * @return The newly created channel.
    */
   newChannel: function Proto_newChannel(aURI) {
     let {url} = parseURI(aURI);
     let file = PageThumbsStorage.getFileForURL(url);
-
-    if (file.exists()) {
-      let fileuri = Services.io.newFileURI(file);
-      return Services.io.newChannelFromURI(fileuri);
-    }
-
-    return new Channel(aURI);
+    let fileuri = Services.io.newFileURI(file);
+    return Services.io.newChannelFromURI(fileuri);
   },
 
   /**
    * Decides whether to allow a blacklisted port.
    * @return Always false, we'll never allow ports.
    */
   allowPort: function () false,
 
   classID: Components.ID("{5a4ae9b5-f475-48ae-9dce-0b4c1d347884}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler])
 };
 
 let NSGetFactory = XPCOMUtils.generateNSGetFactory([Protocol]);
 
 /**
- * A channel implementation responsible for delivering cached thumbnails.
- */
-function Channel(aURI) {
-  this._uri = aURI;
-
-  // nsIChannel
-  this.originalURI = aURI;
-
-  // nsIHttpChannel
-  this._responseHeaders = {"content-type": PageThumbs.contentType};
-}
-
-Channel.prototype = {
-  _uri: null,
-  _referrer: null,
-  _canceled: false,
-  _status: Cr.NS_OK,
-  _isPending: false,
-  _wasOpened: false,
-  _responseText: "OK",
-  _responseStatus: 200,
-  _responseHeaders: null,
-  _requestMethod: "GET",
-  _requestStarted: false,
-  _allowPipelining: true,
-  _requestSucceeded: true,
-
-  /* :::::::: nsIChannel ::::::::::::::: */
-
-  get URI() this._uri,
-  owner: null,
-  notificationCallbacks: null,
-  get securityInfo() null,
-
-  contentType: PageThumbs.contentType,
-  contentCharset: null,
-  contentLength: -1,
-
-  get contentDisposition() {
-    throw (Components.returnCode = Cr.NS_ERROR_NOT_AVAILABLE);
-  },
-
-  get contentDispositionFilename() {
-    throw (Components.returnCode = Cr.NS_ERROR_NOT_AVAILABLE);
-  },
-
-  get contentDispositionHeader() {
-    throw (Components.returnCode = Cr.NS_ERROR_NOT_AVAILABLE);
-  },
-
-  open: function Channel_open() {
-    throw (Components.returnCode = Cr.NS_ERROR_NOT_IMPLEMENTED);
-  },
-
-  asyncOpen: function Channel_asyncOpen(aListener, aContext) {
-    if (this._isPending)
-      throw (Components.returnCode = Cr.NS_ERROR_IN_PROGRESS);
-
-    if (this._wasOpened)
-      throw (Components.returnCode = Cr.NS_ERROR_ALREADY_OPENED);
-
-    if (this._canceled)
-      return (Components.returnCode = this._status);
-
-    this._isPending = true;
-    this._wasOpened = true;
-
-    this._listener = aListener;
-    this._context = aContext;
-
-    if (this.loadGroup)
-      this.loadGroup.addRequest(this, null);
-
-    if (this._canceled)
-      return;
-
-    let {url} = parseURI(this._uri);
-    if (!url) {
-      this._serveThumbnailNotFound();
-      return;
-    }
-
-    PageThumbsCache.getReadEntry(url, function (aEntry) {
-      let inputStream = aEntry && aEntry.openInputStream(0);
-      if (!inputStream || !inputStream.available()) {
-        if (aEntry)
-          aEntry.close();
-        this._serveThumbnailNotFound();
-        return;
-      }
-
-      this._entry = aEntry;
-      this._pump = Cc["@mozilla.org/network/input-stream-pump;1"].
-                   createInstance(Ci.nsIInputStreamPump);
-
-      this._pump.init(inputStream, -1, -1, 0, 0, true);
-      this._pump.asyncRead(this, null);
-
-      this._trackThumbnailHitOrMiss(true);
-    }.bind(this));
-  },
-
-  /**
-   * Serves a "404 Not Found" if we didn't find the requested thumbnail.
-   */
-  _serveThumbnailNotFound: function Channel_serveThumbnailNotFound() {
-    this._responseStatus = 404;
-    this._responseText = "Not Found";
-    this._requestSucceeded = false;
-
-    this.onStartRequest(this, null);
-    this.onStopRequest(this, null, Cr.NS_OK);
-
-    this._trackThumbnailHitOrMiss(false);
-  },
-
-  /**
-   * Implements telemetry tracking for thumbnail cache hits and misses.
-   * @param aFound Whether the thumbnail was found.
-   */
-  _trackThumbnailHitOrMiss: function Channel_trackThumbnailHitOrMiss(aFound) {
-    Services.telemetry.getHistogramById("FX_THUMBNAILS_HIT_OR_MISS")
-      .add(aFound);
-  },
-
-  /* :::::::: nsIStreamListener ::::::::::::::: */
-
-  onStartRequest: function Channel_onStartRequest(aRequest, aContext) {
-    if (!this.canceled && Components.isSuccessCode(this._status))
-      this._status = aRequest.status;
-
-    this._requestStarted = true;
-    this._listener.onStartRequest(this, this._context);
-  },
-
-  onDataAvailable: function Channel_onDataAvailable(aRequest, aContext,
-                                                    aInStream, aOffset, aCount) {
-    this._listener.onDataAvailable(this, this._context, aInStream, aOffset, aCount);
-  },
-
-  onStopRequest: function Channel_onStopRequest(aRequest, aContext, aStatus) {
-    this._isPending = false;
-    this._status = aStatus;
-
-    this._listener.onStopRequest(this, this._context, aStatus);
-    this._listener = null;
-    this._context = null;
-
-    if (this._entry)
-      this._entry.close();
-
-    if (this.loadGroup)
-      this.loadGroup.removeRequest(this, null, aStatus);
-  },
-
-  /* :::::::: nsIRequest ::::::::::::::: */
-
-  get status() this._status,
-  get name() this._uri.spec,
-  isPending: function Channel_isPending() this._isPending,
-
-  loadFlags: Ci.nsIRequest.LOAD_NORMAL,
-  loadGroup: null,
-
-  cancel: function Channel_cancel(aStatus) {
-    if (this._canceled)
-      return;
-
-    this._canceled = true;
-    this._status = aStatus;
-
-    if (this._pump)
-      this._pump.cancel(aStatus);
-  },
-
-  suspend: function Channel_suspend() {
-    if (this._pump)
-      this._pump.suspend();
-  },
-
-  resume: function Channel_resume() {
-    if (this._pump)
-      this._pump.resume();
-  },
-
-  /* :::::::: nsIHttpChannel ::::::::::::::: */
-
-  get referrer() this._referrer,
-
-  set referrer(aReferrer) {
-    if (this._wasOpened)
-      throw (Components.returnCode = Cr.NS_ERROR_IN_PROGRESS);
-
-    this._referrer = aReferrer;
-  },
-
-  get requestMethod() this._requestMethod,
-
-  set requestMethod(aMethod) {
-    if (this._wasOpened)
-      throw (Components.returnCode = Cr.NS_ERROR_IN_PROGRESS);
-
-    this._requestMethod = aMethod.toUpperCase();
-  },
-
-  get allowPipelining() this._allowPipelining,
-
-  set allowPipelining(aAllow) {
-    if (this._wasOpened)
-      throw (Components.returnCode = Cr.NS_ERROR_FAILURE);
-
-    this._allowPipelining = aAllow;
-  },
-
-  redirectionLimit: 10,
-
-  get responseStatus() {
-    if (this._requestStarted)
-      throw (Components.returnCode = Cr.NS_ERROR_NOT_AVAILABLE);
-
-    return this._responseStatus;
-  },
-
-  get responseStatusText() {
-    if (this._requestStarted)
-      throw (Components.returnCode = Cr.NS_ERROR_NOT_AVAILABLE);
-
-    return this._responseText;
-  },
-
-  get requestSucceeded() {
-    if (this._requestStarted)
-      throw (Components.returnCode = Cr.NS_ERROR_NOT_AVAILABLE);
-
-    return this._requestSucceeded;
-  },
-
-  isNoCacheResponse: function Channel_isNoCacheResponse() false,
-  isNoStoreResponse: function Channel_isNoStoreResponse() false,
-
-  getRequestHeader: function Channel_getRequestHeader() {
-    throw (Components.returnCode = Cr.NS_ERROR_NOT_AVAILABLE);
-  },
-
-  setRequestHeader: function Channel_setRequestHeader() {
-    if (this._wasOpened)
-      throw (Components.returnCode = Cr.NS_ERROR_IN_PROGRESS);
-  },
-
-  visitRequestHeaders: function Channel_visitRequestHeaders() {},
-
-  getResponseHeader: function Channel_getResponseHeader(aHeader) {
-    let name = aHeader.toLowerCase();
-    if (name in this._responseHeaders)
-      return this._responseHeaders[name];
-
-    throw (Components.returnCode = Cr.NS_ERROR_NOT_AVAILABLE);
-  },
-
-  setResponseHeader: function Channel_setResponseHeader(aHeader, aValue, aMerge) {
-    let name = aHeader.toLowerCase();
-    if (!aValue && !aMerge)
-      delete this._responseHeaders[name];
-    else
-      this._responseHeaders[name] = aValue;
-  },
-
-  visitResponseHeaders: function Channel_visitResponseHeaders(aVisitor) {
-    for (let name in this._responseHeaders) {
-      let value = this._responseHeaders[name];
-
-      try {
-        aVisitor.visitHeader(name, value);
-      } catch (e) {
-        // The visitor can throw to stop the iteration.
-        return;
-      }
-    }
-  },
-
-  /* :::::::: nsIHttpChannelInternal ::::::::::::::: */
-
-  documentURI: null,
-  get canceled() this._canceled,
-  allowSpdy: false,
-
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel,
-                                         Ci.nsIHttpChannel,
-                                         Ci.nsIHttpChannelInternal,
-                                         Ci.nsIRequest])
-}
-
-/**
  * Parses a given URI and extracts all parameters relevant to this protocol.
  * @param aURI The URI to parse.
  * @return The parsed parameters.
  */
 function parseURI(aURI) {
   let {scheme, staticHost} = PageThumbs;
   let re = new RegExp("^" + scheme + "://" + staticHost + ".*?\\?");
   let query = aURI.spec.replace(re, "");
--- a/browser/devtools/commandline/CmdScreenshot.jsm
+++ b/browser/devtools/commandline/CmdScreenshot.jsm
@@ -36,17 +36,17 @@ gcli.addCommand({
     },
     {
       name: "fullpage",
       type: "boolean",
       description: gcli.lookup("screenshotFullPageDesc"),
       manual: gcli.lookup("screenshotFullPageManual")
     },
     {
-      name: "node",
+      name: "selector",
       type: "node",
       defaultValue: null,
       description: gcli.lookup("inspectNodeDesc"),
       manual: gcli.lookup("inspectNodeManual")
     }
   ],
   exec: function Command_screenshot(args, context) {
     var document = context.environment.contentDocument;
@@ -54,17 +54,17 @@ gcli.addCommand({
       var promise = context.createPromise();
       document.defaultView.setTimeout(function Command_screenshotDelay() {
         let reply = this.grabScreen(document, args.filename);
         promise.resolve(reply);
       }.bind(this), args.delay * 1000);
       return promise;
     }
     else {
-      return this.grabScreen(document, args.filename, args.fullpage, args.node);
+      return this.grabScreen(document, args.filename, args.fullpage, args.selector);
     }
   },
   grabScreen:
   function Command_screenshotGrabScreen(document, filename, fullpage, node) {
     let window = document.defaultView;
     let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
     let left = 0;
     let top = 0;
--- a/browser/devtools/commandline/gcli.jsm
+++ b/browser/devtools/commandline/gcli.jsm
@@ -1702,17 +1702,17 @@ exports.ArrayArgument = ArrayArgument;
 define('gcli/types/selection', ['require', 'exports', 'module' , 'gcli/l10n', 'gcli/types', 'gcli/types/spell'], function(require, exports, module) {
 
 
 var l10n = require('gcli/l10n');
 var types = require('gcli/types');
 var Type = require('gcli/types').Type;
 var Status = require('gcli/types').Status;
 var Conversion = require('gcli/types').Conversion;
-var Speller = require('gcli/types/spell').Speller;
+var spell = require('gcli/types/spell');
 
 
 /**
  * Registration and de-registration.
  */
 exports.startup = function() {
   types.registerType(SelectionType);
 };
@@ -1883,23 +1883,24 @@ SelectionType.prototype._findPredictions
         if (predictions.indexOf(option) === -1) {
           this._addToPredictions(predictions, option, arg);
         }
       }
     }
   }
 
   // Try fuzzy matching if we don't get a prefix match
-  if (false && predictions.length === 0) {
-    var speller = new Speller();
-    var names = lookup.map(function(opt) {
-      return opt.name;
+  if (predictions.length === 0) {
+    var names = [];
+    lookup.forEach(function(opt) {
+      if (!opt.value.hidden) {
+        names.push(opt.name);
+      }
     });
-    speller.train(names);
-    var corrected = speller.correct(match);
+    var corrected = spell.correct(match, names);
     if (corrected) {
       lookup.forEach(function(opt) {
         if (opt.name === corrected) {
           predictions.push(opt);
         }
       }, this);
     }
   }
@@ -2000,163 +2001,126 @@ SelectionType.prototype._findValue = fun
 
 SelectionType.prototype.name = 'selection';
 
 exports.SelectionType = SelectionType;
 
 
 });
 /*
- * Copyright (c) 2009 Panagiotis Astithas
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
- * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
- * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 define('gcli/types/spell', ['require', 'exports', 'module' ], function(require, exports, module) {
 
-
-/**
- * A spell-checker based on the statistical algorithm described by Peter Norvig
- * in http://norvig.com/spell-correct.html, and converted to JavaScript by Past
- * http://past.github.com/speller/
- *
- * Usage requires a two-step process:
- * 1) call speller.train() one or more times with a large text to train the
- *    language model
- * 2) call speller.correct(word) to retrieve the correction for the specified
- *    word
- */
-function Speller() {
-  // A map of words to the count of times they were encountered during training.
-  this._nWords = {};
-}
-
-Speller.letters = "abcdefghijklmnopqrstuvwxyz".split("");
-
-/**
- * A function that trains the language model with the words in the supplied
- * text. Multiple invocation of this function can extend the training of the
- * model.
- */
-Speller.prototype.train = function(words) {
-  words.forEach(function(word) {
-    word = word.toLowerCase();
-    this._nWords[word] = this._nWords.hasOwnProperty(word) ?
-            this._nWords[word] + 1 :
-            1;
-  }, this);
+/*
+ * A spell-checker based on Damerau-Levenshtein distance.
+ */
+
+var INSERTION_COST = 1;
+var DELETION_COST = 1;
+var SWAP_COST = 1;
+var SUBSTITUTION_COST = 2;
+var MAX_EDIT_DISTANCE = 4;
+
+/**
+ * Compute Damerau-Levenshtein Distance
+ * @see http://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance
+ */
+function damerauLevenshteinDistance(wordi, wordj) {
+  var N = wordi.length;
+  var M = wordj.length;
+
+  // We only need to store three rows of our dynamic programming matrix.
+  // (Without swap, it would have been two.)
+  var row0 = new Array(N+1);
+  var row1 = new Array(N+1);
+  var row2 = new Array(N+1);
+  var tmp;
+
+  var i, j;
+
+  // The distance between the empty string and a string of size i is the cost
+  // of i insertions.
+  for (i = 0; i <= N; i++) {
+    row1[i] = i * INSERTION_COST;
+  }
+
+  // Row-by-row, we're computing the edit distance between substrings wordi[0..i]
+  // and wordj[0..j].
+  for (j = 1; j <= M; j++)
+  {
+    // Edit distance between wordi[0..0] and wordj[0..j] is the cost of j
+    // insertions.
+    row0[0] = j * INSERTION_COST;
+
+    for (i = 1; i <= N; i++)
+    {
+      // Handle deletion, insertion and substitution: we can reach each cell
+      // from three other cells corresponding to those three operations. We
+      // want the minimum cost.
+      row0[i] = Math.min(
+          row0[i-1] + DELETION_COST,
+          row1[i] + INSERTION_COST,
+          row1[i-1] + (wordi[i-1] === wordj[j-1] ? 0 : SUBSTITUTION_COST));
+      // We handle swap too, eg. distance between help and hlep should be 1. If
+      // we find such a swap, there's a chance to update row0[1] to be lower.
+      if (i > 1 && j > 1 && wordi[i-1] === wordj[j-2] && wordj[j-1] === wordi[i-2]) {
+        row0[i] = Math.min(row0[i], row2[i-2] + SWAP_COST);
+      }
+    }
+
+    tmp = row2;
+    row2 = row1;
+    row1 = row0;
+    row0 = tmp;
+  }
+
+  return row1[N];
 };
 
 /**
  * A function that returns the correction for the specified word.
  */
-Speller.prototype.correct = function(word) {
-  if (this._nWords.hasOwnProperty(word)) {
-    return word;
-  }
-
-  var candidates = {};
-  var list = this._edits(word);
-  list.forEach(function(edit) {
-    if (this._nWords.hasOwnProperty(edit)) {
-      candidates[this._nWords[edit]] = edit;
-    }
-  }, this);
-
-  if (this._countKeys(candidates) > 0) {
-    return candidates[this._max(candidates)];
-  }
-
-  list.forEach(function(edit) {
-    this._edits(edit).forEach(function(w) {
-      if (this._nWords.hasOwnProperty(w)) {
-        candidates[this._nWords[w]] = w;
-      }
-    }, this);
-  }, this);
-
-  return this._countKeys(candidates) > 0 ?
-      candidates[this._max(candidates)] :
-      null;
-};
-
-/**
- * A helper function that counts the keys in the supplied object.
- */
-Speller.prototype._countKeys = function(object) {
-  // return Object.keys(object).length; ?
-  var count = 0;
-  for (var attr in object) {
-    if (object.hasOwnProperty(attr)) {
-      count++;
-    }
-  }
-  return count;
-};
-
-/**
- * A helper function that returns the word with the most occurrences in the
- * language model, among the supplied candidates.
- * @param candidates
- */
-Speller.prototype._max = function(candidates) {
-  var arr = [];
-  for (var candidate in candidates) {
-    if (candidates.hasOwnProperty(candidate)) {
-      arr.push(candidate);
-    }
-  }
-  return Math.max.apply(null, arr);
-};
-
-/**
- * A function that returns the set of possible corrections of the specified
- * word. The edits can be deletions, insertions, alterations or transpositions.
- */
-Speller.prototype._edits = function(word) {
-  var results = [];
-
-  // Deletion
-  for (var i = 0; i < word.length; i++) {
-    results.push(word.slice(0, i) + word.slice(i + 1));
-  }
-
-  // Transposition
-  for (i = 0; i < word.length - 1; i++) {
-    results.push(word.slice(0, i) + word.slice(i + 1, i + 2)
-            + word.slice(i, i + 1) + word.slice(i + 2));
-  }
-
-  // Alteration
-  for (i = 0; i < word.length; i++) {
-    Speller.letters.forEach(function(l) {
-      results.push(word.slice(0, i) + l + word.slice(i + 1));
-    }, this);
-  }
-
-  // Insertion
-  for (i = 0; i <= word.length; i++) {
-    Speller.letters.forEach(function(l) {
-      results.push(word.slice(0, i) + l + word.slice(i));
-    }, this);
-  }
-
-  return results;
-};
-
-exports.Speller = Speller;
+exports.correct = function(word, names) {
+  var distance = {};
+  var sorted_candidates;
+
+  names.forEach(function(candidate) {
+    distance[candidate] = damerauLevenshteinDistance(word, candidate);
+  });
+
+  sorted_candidates = names.sort(function(worda, wordb) {
+    if (distance[worda] !== distance[wordb]) {
+      return distance[worda] - distance[wordb];
+    } else {
+      // if the score is the same, always return the first string
+      // in the lexicographical order
+      return worda < wordb;
+    }
+  });
+
+  if (distance[sorted_candidates[0]] <= MAX_EDIT_DISTANCE) {
+    return sorted_candidates[0];
+  } else {
+    return undefined;
+  }
+};
 
 
 });
 /*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -4063,17 +4027,19 @@ if (typeof document !== 'undefined') {
  */
 exports._empty = [];
 
 /**
  * Setter for the document that contains the nodes we're matching
  */
 exports.setDocument = function(document) {
   doc = document;
-  exports._empty = doc.querySelectorAll('x>:root');
+  if (doc != null) {
+    exports._empty = doc.querySelectorAll('x>:root');
+  }
 };
 
 /**
  * Undo the effects of setDocument()
  */
 exports.unsetDocument = function() {
   doc = undefined;
 };
@@ -5122,17 +5088,17 @@ define('gcli/ui/domtemplate', ['require'
 define('gcli/cli', ['require', 'exports', 'module' , 'gcli/util', 'gcli/ui/view', 'gcli/l10n', 'gcli/canon', 'gcli/promise', 'gcli/types', 'gcli/types/basic', 'gcli/argument'], function(require, exports, module) {
 
 
 var util = require('gcli/util');
 var view = require('gcli/ui/view');
 var l10n = require('gcli/l10n');
 
 var canon = require('gcli/canon');
-var Promise = require('gcli/promise').Promise;
+var Q = require('gcli/promise');
 
 var Status = require('gcli/types').Status;
 var Conversion = require('gcli/types').Conversion;
 var ArrayType = require('gcli/types/basic').ArrayType;
 var StringType = require('gcli/types/basic').StringType;
 var BooleanType = require('gcli/types/basic').BooleanType;
 var NumberType = require('gcli/types/basic').NumberType;
 
@@ -5185,18 +5151,16 @@ function Assignment(param, paramIndex) {
   this.conversion = undefined;
 
   // The index of this parameter in the parent Requisition. paramIndex === -1
   // is the command assignment although this should not be relied upon, it is
   // better to test param instanceof CommandAssignment
   this.paramIndex = paramIndex;
 
   this.onAssignmentChange = util.createEvent('Assignment.onAssignmentChange');
-
-  this.setBlank();
 }
 
 /**
  * Easy accessor for conversion.arg.
  * This is a read-only property because writes to arg should be done through
  * the 'conversion' property.
  */
 Object.defineProperty(Assignment.prototype, 'arg', {
@@ -5271,49 +5235,16 @@ Assignment.prototype.getPredictionAt = f
  * to take this into account.
  */
 Assignment.prototype.isInName = function() {
   return this.conversion.arg.type === 'NamedArgument' &&
          this.conversion.arg.prefix.slice(-1) !== ' ';
 };
 
 /**
- * Report on the status of the last parse() conversion.
- * We force mutations to happen through this method rather than have
- * setValue and setArgument functions to help maintain integrity when we
- * have ArrayArguments and don't want to get confused. This way assignments
- * are just containers for a conversion rather than things that store
- * a connection between an arg/value.
- * @see types.Conversion
- */
-Assignment.prototype.setConversion = function(conversion) {
-  var oldConversion = this.conversion;
-
-  this.conversion = conversion;
-  this.conversion.assign(this);
-
-  if (this.conversion.equals(oldConversion)) {
-    return;
-  }
-
-  this.onAssignmentChange({
-    assignment: this,
-    conversion: this.conversion,
-    oldConversion: oldConversion
-  });
-};
-
-/**
- * Setup an empty value for the conversion by parsing an empty argument.
- */
-Assignment.prototype.setBlank = function() {
-  this.setConversion(this.param.type.getBlank());
-};
-
-/**
  * Make sure that there is some content for this argument by using an
  * Argument of '' if needed.
  */
 Assignment.prototype.ensureVisibleArgument = function() {
   // It isn't clear if we should be sending events from this method.
   // It should only be called when structural changes are happening in which
   // case we're going to ignore the event anyway. But on the other hand
   // perhaps this function shouldn't need to know how it is used, and should
@@ -5449,18 +5380,16 @@ function CommandAssignment() {
           value.description :
           'The command to execute';
     },
     enumerable: true
   });
   this.param = new canon.Parameter(commandParamMetadata);
   this.paramIndex = -1;
   this.onAssignmentChange = util.createEvent('CommandAssignment.onAssignmentChange');
-
-  this.setBlank();
 }
 
 CommandAssignment.prototype = Object.create(Assignment.prototype);
 
 CommandAssignment.prototype.getStatus = function(arg) {
   return Status.combine(
     Assignment.prototype.getStatus.call(this, arg),
     this.conversion.value && this.conversion.value.exec ?
@@ -5535,16 +5464,17 @@ function Requisition(environment, doc) {
     catch (ex) {
       // Ignore
     }
   }
 
   // The command that we are about to execute.
   // @see setCommandConversion()
   this.commandAssignment = new CommandAssignment();
+  this._setAssignment(this.commandAssignment, null, true);
 
   // The object that stores of Assignment objects that we are filling out.
   // The Assignment objects are stored under their param.name for named
   // lookup. Note: We make use of the property of Javascript objects that
   // they are not just hashmaps, but linked-list hashmaps which iterate in
   // insertion order.
   // _assignments excludes the commandAssignment.
   this._assignments = {};
@@ -5621,16 +5551,17 @@ Requisition.prototype._commandAssignment
 
   this._assignments = {};
 
   var command = this.commandAssignment.value;
   if (command) {
     for (var i = 0; i < command.params.length; i++) {
       var param = command.params[i];
       var assignment = new Assignment(param, i);
+      this._setAssignment(assignment, null, true);
       assignment.onAssignmentChange.add(this._assignmentChanged, this);
       this._assignments[param.name] = assignment;
     }
   }
   this.assignmentCount = Object.keys(this._assignments).length;
 };
 
 /**
@@ -5743,58 +5674,99 @@ Requisition.prototype.getAssignments = f
   }
   Object.keys(this._assignments).forEach(function(name) {
     assignments.push(this.getAssignment(name));
   }, this);
   return assignments;
 };
 
 /**
- * Alter the given assignment using the given arg. This function is better than
- * calling assignment.setConversion(assignment.param.type.parse(arg)) because
- * it adjusts the args in this requisition to keep things up to date
+ * Alter the given assignment using the given arg.
+ * @param assignment The assignment to alter
+ * @param arg The new value for the assignment. An instance of Argument, or an
+ * instance of Conversion, or null to set the blank value.
  */
 Requisition.prototype.setAssignment = function(assignment, arg) {
-  var originalArgs = assignment.arg.getArgs();
-  var conversion = assignment.param.type.parse(arg);
-  assignment.setConversion(conversion);
-
-  var replacementArgs = arg.getArgs();
-  var maxLen = Math.max(originalArgs.length, replacementArgs.length);
-  for (var i = 0; i < maxLen; i++) {
-    // If there are no more original args, or if the original arg was blank
-    // (i.e. not typed by the user), we'll just need to add at the end
-    if (i >= originalArgs.length || originalArgs[i].type === 'BlankArgument') {
-      this._args.push(replacementArgs[i]);
-      continue;
-    }
-
-    var index = this._args.indexOf(originalArgs[i]);
-    if (index === -1) {
-      console.error('Couldn\'t find ', originalArgs[i], ' in ', this._args);
-      throw new Error('Couldn\'t find ' + originalArgs[i]);
-    }
-
-    // If there are no more replacement args, we just remove the original args
-    // Otherwise swap original args and replacements
-    if (i >= replacementArgs.length) {
-      this._args.splice(index, 1);
-    }
-    else {
-      this._args[index] = replacementArgs[i];
-    }
-  }
+  this._setAssignment(assignment, arg, false);
+};
+
+/**
+ * Internal function to alter the given assignment using the given arg.
+ * @param assignment The assignment to alter
+ * @param arg The new value for the assignment. An instance of Argument, or an
+ * instance of Conversion, or null to set the blank value.
+ * @param skipArgUpdate (default=false) Adjusts the args in this requisition to
+ * keep things up to date. Args should only be skipped when setAssignment is
+ * being called as part of the update process.
+ */
+Requisition.prototype._setAssignment = function(assignment, arg, skipArgUpdate) {
+  if (!skipArgUpdate) {
+    var originalArgs = assignment.arg.getArgs();
+
+    // Update the args array
+    var replacementArgs = arg.getArgs();
+    var maxLen = Math.max(originalArgs.length, replacementArgs.length);
+    for (var i = 0; i < maxLen; i++) {
+      // If there are no more original args, or if the original arg was blank
+      // (i.e. not typed by the user), we'll just need to add at the end
+      if (i >= originalArgs.length || originalArgs[i].type === 'BlankArgument') {
+        this._args.push(replacementArgs[i]);
+        continue;
+      }
+
+      var index = this._args.indexOf(originalArgs[i]);
+      if (index === -1) {
+        console.error('Couldn\'t find ', originalArgs[i], ' in ', this._args);
+        throw new Error('Couldn\'t find ' + originalArgs[i]);
+      }
+
+      // If there are no more replacement args, we just remove the original args
+      // Otherwise swap original args and replacements
+      if (i >= replacementArgs.length) {
+        this._args.splice(index, 1);
+      }
+      else {
+        this._args[index] = replacementArgs[i];
+      }
+    }
+  }
+
+  var conversion;
+  if (arg == null) {
+    conversion = assignment.param.type.getBlank();
+  }
+  else if (typeof arg.getStatus === 'function') {
+    conversion = arg;
+  }
+  else {
+    conversion = assignment.param.type.parse(arg);
+  }
+
+  var oldConversion = assignment.conversion;
+
+  assignment.conversion = conversion;
+  assignment.conversion.assign(assignment);
+
+  if (assignment.conversion.equals(oldConversion)) {
+    return;
+  }
+
+  assignment.onAssignmentChange({
+    assignment: assignment,
+    conversion: assignment.conversion,
+    oldConversion: oldConversion
+  });
 };
 
 /**
  * Reset all the assignments to their default values
  */
 Requisition.prototype.setBlankArguments = function() {
   this.getAssignments().forEach(function(assignment) {
-    assignment.setBlank();
+    this._setAssignment(assignment, null, true);
   }, this);
 };
 
 /**
  * Complete the argument at <tt>cursor</tt>.
  * Basically the same as:
  *   assignment = getAssignmentAt(cursor);
  *   assignment.value = assignment.conversion.predictions[0];
@@ -6143,18 +6115,18 @@ Requisition.prototype.getAssignmentAt = 
  *    argument values as passed to command.exec. This method is significantly
  *    faster, and designed for use from keyboard shortcuts.
  * In addition to these properties, the input parameter can contain a 'hidden'
  * property which can be set to true to hide the output from the
  * CommandOutputManager.
  * @param input (optional) The command to execute. See above.
  */
 Requisition.prototype.exec = function(input) {
-  var command;
-  var args;
+  var command = null;
+  var args = null;
   var hidden = false;
   if (input && input.hidden) {
     hidden = true;
   }
 
   if (input) {
     if (typeof input === 'string') {
       this.update(input);
@@ -6195,44 +6167,69 @@ Requisition.prototype.exec = function(in
     args: args,
     typed: typed,
     canonical: this.toCanonicalString(),
     hidden: hidden
   });
 
   this.commandOutputManager.onOutput({ output: output });
 
+  var onDone = function(data) {
+    output.complete(data);
+  };
+
+  var onError = function(error) {
+    console.error(error);
+    output.error = true;
+    output.complete(error);
+  };
+
   try {
     var context = exports.createExecutionContext(this);
     var reply = command.exec(args, context);
 
-    if (reply != null && typeof reply.then === 'function') {
-      reply.then(
-          function(data) { output.complete(data); },
-          function(error) { output.error = true; output.complete(error); });
-
-      output.promise = reply;
-      // Add progress to our promise and add a handler for it here
-      // See bug 659300
-    }
-    else {
-      output.complete(reply);
-    }
+    this._then(reply, onDone, onError);
   }
   catch (ex) {
-    console.error(ex);
-    output.error = true;
-    output.complete(ex);
+    onError(ex);
   }
 
   this.update('');
   return output;
 };
 
 /**
+ * Different types of promise have different ways of doing 'then'. This is a
+ * catch-all so we can ignore the differences. It also handles concrete values
+ * and calls onDone directly if thing is not a promise.
+ * @param thing The value to test for 'promiseness'
+ * @param onDone The action to take if thing is resolved
+ * @param onError The action to take if thing is rejected
+ */
+Requisition.prototype._then = function(thing, onDone, onError) {
+  var then = null;
+  if (thing != null && typeof thing.then === 'function') {
+    // Old GCLI style / simple promises with a then function
+    then = thing.then;
+  }
+  else if (thing != null && thing.promise != null &&
+                typeof thing.promise.then === 'function') {
+    // Q / Mozilla add-ons style
+    then = thing.promise.then;
+  }
+
+  if (then != null) {
+    then(onDone, onError);
+  }
+  else {
+    onDone(thing);
+  }
+};
+
+/**
  * Called by the UI when ever the user interacts with a command line input
  * @param typed The contents of the input field
  */
 Requisition.prototype.update = function(typed) {
   this._structuralChangeInProgress = true;
 
   this._args = this._tokenize(typed);
   var args = this._args.slice(0); // i.e. clone
@@ -6512,26 +6509,26 @@ Requisition.prototype._split = function(
   // Handle the special case of the user typing { javascript(); }
   // We use the hidden 'eval' command directly rather than shift()ing one of
   // the parameters, and parse()ing it.
   var conversion;
   if (args[0].type === 'ScriptArgument') {
     // Special case: if the user enters { console.log('foo'); } then we need to
     // use the hidden 'eval' command
     conversion = new Conversion(evalCommand, new ScriptArgument());
-    this.commandAssignment.setConversion(conversion);
+    this._setAssignment(this.commandAssignment, conversion, true);
     return;
   }
 
   var argsUsed = 1;
 
   while (argsUsed <= args.length) {
     var arg = (argsUsed === 1) ?
-      args[0] :
-      new MergedArgument(args, 0, argsUsed);
+              args[0] :
+              new MergedArgument(args, 0, argsUsed);
     conversion = this.commandAssignment.param.type.parse(arg);
 
     // We only want to carry on if this command is a parent command,
     // which means that there is a commandAssignment, but not one with
     // an exec function.
     if (!conversion.value || conversion.value.exec) {
       break;
     }
@@ -6539,17 +6536,17 @@ Requisition.prototype._split = function(
     // Previously we needed a way to hide commands depending context.
     // We have not resurrected that feature yet, but if we do we should
     // insert code here to ignore certain commands depending on the
     // context/environment
 
     argsUsed++;
   }
 
-  this.commandAssignment.setConversion(conversion);
+  this._setAssignment(this.commandAssignment, conversion, true);
 
   for (var i = 0; i < argsUsed; i++) {
     args.shift();
   }
 
   // This could probably be re-written to consume args as we go
 };
 
@@ -6585,21 +6582,18 @@ Requisition.prototype._assign = function
     return;
   }
 
   // Special case: if there is only 1 parameter, and that's of type
   // text, then we put all the params into the first param
   if (this.assignmentCount === 1) {
     var assignment = this.getAssignment(0);
     if (assignment.param.type instanceof StringType) {
-      var arg = (args.length === 1) ?
-        args[0] :
-        new MergedArgument(args);
-      var conversion = assignment.param.type.parse(arg);
-      assignment.setConversion(conversion);
+      var arg = (args.length === 1) ? args[0] : new MergedArgument(args);
+      this._setAssignment(assignment, arg, true);
       return;
     }
   }
 
   // Positional arguments can still be specified by name, but if they are
   // then we need to ignore them when working them out positionally
   var unassignedParams = this.getParameterNames();
 
@@ -6634,77 +6628,74 @@ Requisition.prototype._assign = function
           var arrayArg = arrayArgs[assignment.param.name];
           if (!arrayArg) {
             arrayArg = new ArrayArgument();
             arrayArgs[assignment.param.name] = arrayArg;
           }
           arrayArg.addArgument(arg);
         }
         else {
-          var conversion = assignment.param.type.parse(arg);
-          assignment.setConversion(conversion);
+          this._setAssignment(assignment, arg, true);
         }
       }
       else {
         // Skip this parameter and handle as a positional parameter
         i++;
       }
     }
   }, this);
 
   // What's left are positional parameters assign in order
   unassignedParams.forEach(function(name) {
     var assignment = this.getAssignment(name);
 
     // If not set positionally, and we can't set it non-positionally,
     // we have to default it to prevent previous values surviving
     if (!assignment.param.isPositionalAllowed) {
-      assignment.setBlank();
+      this._setAssignment(assignment, null, true);
       return;
     }
 
     // If this is a positional array argument, then it swallows the
     // rest of the arguments.
     if (assignment.param.type instanceof ArrayType) {
       var arrayArg = arrayArgs[assignment.param.name];
       if (!arrayArg) {
         arrayArg = new ArrayArgument();
         arrayArgs[assignment.param.name] = arrayArg;
       }
       arrayArg.addArguments(args);
       args = [];
     }
     else {
       if (args.length === 0) {
-        assignment.setBlank();
+        this._setAssignment(assignment, null, true);
       }
       else {
         var arg = args.splice(0, 1)[0];
         // --foo and -f are named parameters, -4 is a number. So '-' is either
         // the start of a named parameter or a number depending on the context
         var isIncompleteName = assignment.param.type instanceof NumberType ?
             /-[-a-zA-Z_]/.test(arg.text) :
             arg.text.charAt(0) === '-';
 
         if (isIncompleteName) {
           this._unassigned.push(new UnassignedAssignment(this, arg));
         }
         else {
-          var conversion = assignment.param.type.parse(arg);
-          assignment.setConversion(conversion);
+          this._setAssignment(assignment, arg, true);
         }
       }
     }
   }, this);
 
   // Now we need to assign the array argument (if any)
   Object.keys(arrayArgs).forEach(function(name) {
     var assignment = this.getAssignment(name);
-    var conversion = assignment.param.type.parse(arrayArgs[name]);
-    assignment.setConversion(conversion);
+    this._setAssignment(assignment, arrayArgs[name], true);
   }, this);
 
   // What's left is can't be assigned, but we need to extract
   this._addUnassignedArgs(args);
 };
 
 exports.Requisition = Requisition;
 
@@ -6724,27 +6715,41 @@ function Output(options) {
   this.error = false;
   this.start = new Date();
 
   this.onClose = util.createEvent('Output.onClose');
   this.onChange = util.createEvent('Output.onChange');
 }
 
 /**
- * Called when there is data to display
- * @param data
- */
-Output.prototype.complete = function(data) {
+ * Called when there is data to display, but the command is still executing
+ * @param data The new data. If the data structure has been altered but the
+ * root object is still the same, The same root object should be passed in the
+ * data parameter.
+ * @param ev Optional additional event data, for example to explain how the
+ * data structure has changed
+ */
+Output.prototype.changed = function(data, ev) {
   this.data = data;
 
+  ev = ev || {};
+  ev.output = this;
+  this.onChange(ev);
+};
+
+/**
+ * Called when there is data to display, and the command has finished executing
+ * See changed() for details on parameters.
+ */
+Output.prototype.complete = function(data, ev) {
   this.end = new Date();
   this.duration = this.end.getTime() - this.start.getTime();
   this.completed = true;
 
-  this.onChange({ output: this });
+  this.changed(data, ev);
 };
 
 /**
  * Convert to a DOM element for display.
  * @param element The DOM node to which the data should be written. Existing
  * content of 'element' will be removed before 'outputData' is added.
  */
 Output.prototype.toDom = function(element) {
@@ -6825,18 +6830,25 @@ exports.Output = Output;
  */
 exports.createExecutionContext = function(requisition) {
   return {
     exec: requisition.exec.bind(requisition),
     update: requisition.update.bind(requisition),
     document: requisition.document,
     environment: requisition.environment,
     createView: view.createView,
+    defer: function() {
+      return Q.defer();
+    },
+    /**
+     * @deprecated Use defer() instead, which does the same thing, but is not
+     * confusingly named
+     */
     createPromise: function() {
-      return new Promise();
+      return Q.defer();
     }
   };
 };
 
 
 });
 /*
  * Copyright 2012, Mozilla Foundation and contributors
@@ -6851,18 +6863,23 @@ exports.createExecutionContext = functio
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 define('gcli/promise', ['require', 'exports', 'module' ], function(require, exports, module) {
 
-  Components.utils.import("resource:///modules/devtools/Promise.jsm");
-  exports.Promise = Promise;
+  var imported = {};
+  Components.utils.import("resource://gre/modules/commonjs/promise/core.js",
+                          imported);
+
+  exports.defer = imported.Promise.defer;
+  exports.resolve = imported.Promise.resolve;
+  exports.reject = imported.Promise.reject;
 
 });
 define("text!gcli/ui/intro.html", [], "\n" +
   "<div>\n" +
   "  <p>${l10n.introTextOpening}</p>\n" +
   "\n" +
   "  <p>\n" +
   "    ${l10n.introTextCommands}\n" +
@@ -7232,17 +7249,17 @@ FocusManager.prototype._checkShow = func
   var showOutput = this._shouldShowOutput();
   if (this.isOutputVisible !== showOutput.visible) {
     ev.outputVisible = this.isOutputVisible = showOutput.visible;
     fire = true;
   }
 
   if (fire) {
     if (this._debug) {
-      console.debug('FocusManager.onVisibilityChange', ev);
+      console.log('FocusManager.onVisibilityChange', ev);
     }
     this.onVisibilityChange(ev);
   }
 };
 
 /**
  * Calculate if we should be showing or hidden taking into account all the
  * available inputs
@@ -7730,16 +7747,19 @@ Field.prototype.element = undefined;
 
 /**
  * Indicates that this field should drop any resources that it has created
  */
 Field.prototype.destroy = function() {
   delete this.messageElement;
 };
 
+// Note: We could/should probably change Fields from working with Conversions
+// to working with Arguments (Tokens), which makes for less calls to parse()
+
 /**
  * Update this field display with the value from this conversion.
  * Subclasses should provide an implementation of this function.
  */
 Field.prototype.setConversion = function(conversion) {
   throw new Error('Field should not be used directly');
 };
 
@@ -9388,17 +9408,19 @@ Inputter.prototype.textChanged = functio
   }
 
   var newStr = this.requisition.toString();
   var input = this.getInputState();
 
   input.typed = newStr;
   this._processCaretChange(input);
 
-  this.element.value = newStr;
+  if (this.element.value !== newStr) {
+    this.element.value = newStr;
+  }
   this.onInputChange({ inputState: input });
 };
 
 /**
  * Various ways in which we need to manipulate the caret/selection position.
  * A value of null means we're not expecting a change
  */
 var Caret = {
@@ -9464,18 +9486,22 @@ Inputter.prototype._processCaretChange =
   start = (start > input.typed.length) ? input.typed.length : start;
   end = (end > input.typed.length) ? input.typed.length : end;
 
   var newInput = {
     typed: input.typed,
     cursor: { start: start, end: end }
   };
 
-  this.element.selectionStart = start;
-  this.element.selectionEnd = end;
+  if (this.element.selectionStart !== start) {
+    this.element.selectionStart = start;
+  }
+  if (this.element.selectionEnd !== end) {
+    this.element.selectionEnd = end;
+  }
 
   this._checkAssignment(start);
 
   this._caretChange = null;
   return newInput;
 };
 
 /**
@@ -9600,17 +9626,17 @@ Inputter.prototype.onKeyUp = function(ev
     else if (this.element.value === '' || this._scrollingThroughHistory) {
       this._scrollingThroughHistory = true;
       this.requisition.update(this.history.backward());
     }
     else {
       // If the user is on a valid value, then we increment the value, but if
       // they've typed something that's not right we page through predictions
       if (this.assignment.getStatus() === Status.VALID) {
-        this.requisition.increment(assignment);
+        this.requisition.increment(this.assignment);
         // See notes on focusManager.onInputChange in onKeyDown
         if (this.focusManager) {
           this.focusManager.onInputChange();
         }
       }
       else {
         this.changeChoice(-1);
       }
@@ -9624,17 +9650,17 @@ Inputter.prototype.onKeyUp = function(ev
     }
     else if (this.element.value === '' || this._scrollingThroughHistory) {
       this._scrollingThroughHistory = true;
       this.requisition.update(this.history.forward());
     }
     else {
       // See notes above for the UP key
       if (this.assignment.getStatus() === Status.VALID) {
-        this.requisition.decrement(assignment);
+        this.requisition.decrement(this.assignment);
         // See notes on focusManager.onInputChange in onKeyDown
         if (this.focusManager) {
           this.focusManager.onInputChange();
         }
       }
       else {
         this.changeChoice(+1);
       }
@@ -10309,17 +10335,17 @@ Tooltip.prototype.selectChoice = functio
   }
   return false;
 };
 
 /**
  * Called by the onFieldChange event on the current Field
  */
 Tooltip.prototype.fieldChanged = function(ev) {
-  this.assignment.setConversion(ev.conversion);
+  this.requisition.setAssignment(this.assignment, ev.conversion.arg);
 
   var isError = ev.conversion.message != null && ev.conversion.message !== '';
   this.focusManager.setError(isError);
 
   // Nasty hack, the inputter won't know about the text change yet, so it will
   // get it's calculations wrong. We need to wait until the current set of
   // changes has had a chance to propagate
   this.document.defaultView.setTimeout(function() {
--- a/browser/devtools/commandline/test/browser_cmd_pref.js
+++ b/browser/devtools/commandline/test/browser_cmd_pref.js
@@ -158,18 +158,18 @@ function testPrefStatus() {
       setting: { arg: ' devtools.editor.tabsize' },
       value: { value: 4 },
     }
   });
 
   helpers.setInput('pref list');
   helpers.check({
     input:  'pref list',
-    hints:           '',
-    markup: 'EEEEVEEEE',
+    hints:           ' -> pref set',
+    markup: 'IIIIVIIII',
     status: 'ERROR'
   });
 }
 
 function testPrefSetEnable() {
   DeveloperToolbarTest.exec({
     typed: "pref set devtools.editor.tabsize 9",
     args: {
--- a/browser/devtools/commandline/test/browser_gcli_web.js
+++ b/browser/devtools/commandline/test/browser_gcli_web.js
@@ -103,33 +103,33 @@ define('gclitest/index', ['require', 'ex
   };
 
   /**
    * A simple proxy to examiner.run, for convenience - this is run from the
    * top level.
    * @param options Lookup of options that customize test running. Includes:
    * - window (default=undefined) A reference to the DOM window. If left
    *   undefined then a reduced set of tests will run.
-   * - isNode (default=false) Are we running under NodeJS, specifically, are we
-   *   using JSDom, which isn't a 100% complete DOM implementation.
+   * - isJsdom (default=false) Are we running under JSDom, specifically, which
+   *   isn't a 100% complete DOM implementation.
    *   Some tests are skipped when using NodeJS.
    * - display (default=undefined) A reference to a Display implementation.
    *   A reduced set of tests will run if left undefined
    * - detailedResultLog (default=false) do we output a test summary to
    *   |console.log| on test completion.
    * - hideExec (default=false) Set the |hidden| property in calls to
    *   |requisition.exec()| which prevents the display from becoming messed up,
    *   however use of hideExec restricts the set of tests that are run
    */
-  exports.run = function(options) {
+  exports.runAsync = function(options, callback) {
     options = options || {};
     examiner.mergeDefaultOptions(options);
 
     examiner.reset();
-    examiner.run(options);
+    examiner.runAsync(options, callback);
 
     // A better set of default than those specified above, come from the set
     // that are passed to run().
     examiner.defaultOptions = {
       window: options.window,
       display: options.display,
       hideExec: options.hideExec
     };
@@ -150,23 +150,16 @@ define('gclitest/index', ['require', 'ex
       settings.setDefaults(options.settings);
     }
 
     window.display = new Display(options);
     var requisition = window.display.requisition;
 
     // setTimeout keeps stack traces clear of RequireJS frames
     window.setTimeout(function() {
-      var options = {
-        window: window,
-        display: window.display,
-        hideExec: true
-      };
-      exports.run(options);
-
       window.createDebugCheck = function() {
         require([ 'gclitest/helpers' ], function(helpers) {
           helpers.setup(options);
           console.log(helpers._createDebugCheck());
           helpers.shutdown(options);
         });
       };
 
@@ -193,17 +186,25 @@ define('gclitest/index', ['require', 'ex
         }
       }, true);
 
       window.testCommands = function() {
         require([ 'gclitest/mockCommands' ], function(mockCommands) {
           mockCommands.setup();
         });
       };
-      window.testCommands();
+
+      var options = {
+        window: window,
+        display: window.display,
+        hideExec: true
+      };
+      exports.runAsync(options, function() {
+        window.testCommands();
+      });
     }, 10);
 
     return {
       /**
        * The exact shape of the object returned by exec is likely to change in
        * the near future. If you do use it, please expect your code to break.
        */
       exec: requisition.exec.bind(requisition),
@@ -322,35 +323,16 @@ examiner.mergeDefaultOptions = function(
   Object.keys(examiner.defaultOptions).forEach(function(name) {
     if (options[name] == null) {
       options[name] = examiner.defaultOptions[name];
     }
   });
 };
 
 /**
- * Run the tests defined in the test suite synchronously
- */
-examiner.run = function(options) {
-  Object.keys(examiner.suites).forEach(function(suiteName) {
-    var suite = examiner.suites[suiteName];
-    suite.run(options);
-  }.bind(this));
-
-  if (options.detailedResultLog) {
-    examiner.detailedResultLog();
-  }
-  else {
-    console.log('Completed test suite');
-  }
-
-  return examiner.suites;
-};
-
-/**
  * Run all the tests asynchronously
  */
 examiner.runAsync = function(options, callback) {
   this._runAsyncInternal(0, options, callback);
 };
 
 /**
  * Run all the test suits asynchronously
@@ -473,31 +455,16 @@ function Suite(suiteName, suite) {
  */
 Suite.prototype.reset = function() {
   Object.keys(this.tests).forEach(function(testName) {
     this.tests[testName].reset();
   }, this);
 };
 
 /**
- * Run all the tests in this suite synchronously
- */
-Suite.prototype.run = function(options) {
-  if (!this._setup(options)) {
-    return;
-  }
-
-  Object.keys(this.tests).forEach(function(testName) {
-    this.tests[testName].run(options);
-  }, this);
-
-  this._shutdown(options);
-};
-
-/**
  * Run all the tests in this suite asynchronously
  */
 Suite.prototype.runAsync = function(options, callback) {
   if (!this._setup(options)) {
     if (typeof callback === 'function') {
       callback();
     }
     return;
@@ -631,35 +598,42 @@ Suite.prototype._logToAllTests = functio
  * A test represents data about a single test function
  */
 function Test(suite, name, func) {
   this.suite = suite;
   this.name = name;
   this.func = func;
   this.title = name.replace(/^test/, '').replace(/([A-Z])/g, ' $1');
 
+  this.outstanding = [];
+  this.callback = undefined;
+
   this.failures = [];
   this.status = stati.notrun;
   this.checks = 0;
 }
 
 /**
  * Reset the test to its original state
  */
 Test.prototype.reset = function() {
+  this.outstanding = [];
+  this.callback = undefined;
+
   this.failures = [];
   this.status = stati.notrun;
   this.checks = 0;
 };
 
 /**
- * Run just a single test
+ * Run all the tests in this suite asynchronously
  */
-Test.prototype.run = function(options) {
+Test.prototype.runAsync = function(options, callback) {
   assert.currentTest = this;
+  this.callback = callback;
   this.status = stati.executing;
   this.failures = [];
   this.checks = 0;
 
   try {
     this.func.apply(this.suite, [ options ]);
   }
   catch (ex) {
@@ -670,28 +644,30 @@ Test.prototype.run = function(options) {
     }
   }
 
   if (this.status === stati.executing) {
     this.status = stati.pass;
   }
 
   assert.currentTest = null;
+
+  this.checkFinish();
 };
 
 /**
- * Run all the tests in this suite asynchronously
+ * Check to see if the currently executing test is completed (i.e. the list of
+ * outstanding tasks has all been completed)
  */
-Test.prototype.runAsync = function(options, callback) {
-  setTimeout(function() {
-    this.run(options);
-    if (typeof callback === 'function') {
-      callback();
+Test.prototype.checkFinish = function() {
+  if (this.outstanding.length == 0) {
+    if (typeof this.callback === 'function') {
+      this.callback();
     }
-  }.bind(this), delay);
+  }
 };
 
 /**
  * Create a JSON object suitable for serialization
  */
 Test.prototype.toRemote = function() {
   return {
     name: this.name,
@@ -887,31 +863,35 @@ define('gclitest/testCanon', ['require',
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gclitest/helpers', ['require', 'exports', 'module' , 'test/assert', 'gcli/util'], function(require, exports, module) {
+define('gclitest/helpers', ['require', 'exports', 'module' , 'test/assert'], function(require, exports, module) {
 
 
 var test = require('test/assert');
-var util = require('gcli/util');
-
+// A copy of this code exists in firefox mochitests; when updated here it
+// should be updated there too. Hence the use of an exports synonym for non
+// AMD contexts.
 var helpers = exports;
 
 helpers._display = undefined;
+helpers._options = undefined;
 
 helpers.setup = function(options) {
+  helpers._options = options;
   helpers._display = options.display;
 };
 
 helpers.shutdown = function(options) {
+  helpers._options = undefined;
   helpers._display = undefined;
 };
 
 /**
  * Various functions to return the actual state of the command line
  */
 helpers._actual = {
   input: function() {
@@ -1020,16 +1000,26 @@ helpers._createDebugCheck = function() {
  * and check() which ensures that things are in the right place afterwards.
  */
 helpers.setInput = function(typed, cursor) {
   helpers._display.inputter.setInput(typed);
 
   if (cursor) {
     helpers._display.inputter.setCursor({ start: cursor, end: cursor });
   }
+  else {
+    // This is a hack because jsdom appears to not handle cursor updates
+    // in the same way as most browsers.
+    if (helpers._options.isJsdom) {
+      helpers._display.inputter.setCursor({
+        start: typed.length,
+        end: typed.length
+      });
+    }
+  }
 
   helpers._display.focusManager.onInputChange();
 };
 
 /**
  * Simulate focusing the input field
  */
 helpers.focusInput = function() {
@@ -1584,17 +1574,17 @@ exports.testSingleNumber = function() {
 exports.testElement = function(options) {
   update({ typed: 'tse', cursor: { start: 3, end: 3 } });
   test.is(        'VVV', statuses);
   test.is(Status.ERROR, status);
   test.is('tse', requ.commandAssignment.value.name);
   test.ok(assign1.arg.type === 'BlankArgument');
   test.is(undefined, assign1.value);
 
-  if (!options.isNode) {
+  if (!options.isJsdom) {
     update({ typed: 'tse :root', cursor: { start: 9, end: 9 } });
     test.is(        'VVVVVVVVV', statuses);
     test.is(Status.VALID, status);
     test.is('tse', requ.commandAssignment.value.name);
     test.is(':root', assign1.arg.text);
     if (!options.window.isFake) {
       test.is(options.window.document.documentElement, assign1.value);
     }
@@ -1620,17 +1610,17 @@ exports.testElement = function(options) 
     // digging into the CSS engine we can't tell that so we default to incomplete
     test.is(        'VVVVIIIIIIIIIIIII', statuses);
     test.is(Status.ERROR, status);
     test.is('tse', requ.commandAssignment.value.name);
     test.is('#gcli-nomatch', assign1.arg.text);
     test.is(undefined, assign1.value);
   }
   else {
-    test.log('Skipping :root test due to jsdom (from isNode)');
+    test.log('Skipping :root test due to jsdom');
   }
 
   update({ typed: 'tse #', cursor: { start: 5, end: 5 } });
   test.is(        'VVVVE', statuses);
   test.is(Status.ERROR, status);
   test.is('tse', requ.commandAssignment.value.name);
   test.is('#', assign1.arg.text);
   test.is(undefined, assign1.value);
@@ -2355,17 +2345,17 @@ exports.testActivate = function(options)
 
   helpers.setInput('tsg b');
   helpers.check({
     hints: 'bb [options]'
   });
 
   helpers.setInput('tsg d');
   helpers.check({
-    hints: ' [options]'
+    hints: ' [options] -> ccc'
   });
 
   helpers.setInput('tsg aa');
   helpers.check({
     hints: 'a [options]'
   });
 
   helpers.setInput('tsg aaa');
@@ -2769,33 +2759,39 @@ var mockDoc = {
 
 });
 /*
  * Copyright 2009-2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE.txt or:
  * http://opensource.org/licenses/BSD-3-Clause
  */
 
-define('gclitest/testFocus', ['require', 'exports', 'module' , 'gclitest/helpers', 'gclitest/mockCommands'], function(require, exports, module) {
-
-
+define('gclitest/testFocus', ['require', 'exports', 'module' , 'test/assert', 'gclitest/helpers', 'gclitest/mockCommands'], function(require, exports, module) {
+
+
+var test = require('test/assert');
 var helpers = require('gclitest/helpers');
 var mockCommands = require('gclitest/mockCommands');
 
 exports.setup = function(options) {
   mockCommands.setup();
   helpers.setup(options);
 };
 
 exports.shutdown = function(options) {
   mockCommands.shutdown();
   helpers.shutdown(options);
 };
 
 exports.testBasic = function(options) {
+  if (options.isJsdom) {
+    test.log('jsdom does not pass on focus events properly, skipping testBasic');
+    return;
+  }
+
   helpers.focusInput();
   helpers.exec(options, 'help');
 
   helpers.setInput('tsn deep');
   helpers.check({
     input:  'tsn deep',
     hints:          '',
     markup: 'IIIVIIII',
@@ -3338,19 +3334,18 @@ exports.testIncomplete = function(option
   test.is(requisition._unassigned[0].param.type.isIncompleteName, true,
           'unassigned.isIncompleteName: tsg -');
 };
 
 exports.testHidden = function(options) {
   helpers.setInput('tshidde');
   helpers.check({
     input:  'tshidde',
-    markup: 'EEEEEEE',
-    status: 'ERROR',
-    hints:  '',
+    hints:         ' -> tse',
+    status: 'ERROR'
   });
 
   helpers.setInput('tshidden');
   helpers.check({
     input:  'tshidden',
     hints:          ' [options]',
     markup: 'VVVVVVVV',
     status: 'VALID',
@@ -3465,32 +3460,33 @@ exports.testHidden = function(options) {
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gclitest/testIntro', ['require', 'exports', 'module' , 'gclitest/helpers', 'test/assert'], function(require, exports, module) {
+define('gclitest/testIntro', ['require', 'exports', 'module' , 'gclitest/helpers', 'test/assert', 'gcli/canon'], function(require, exports, module) {
 
   var helpers = require('gclitest/helpers');
   var test = require('test/assert');
+  var canon = require('gcli/canon');
 
   exports.setup = function(options) {
     helpers.setup(options);
   };
 
   exports.shutdown = function(options) {
     helpers.shutdown(options);
   };
 
   exports.testIntroStatus = function(options) {
-    if (options.isFirefox) {
-      test.log('Skipping testIntroStatus in Firefox.');
+    if (canon.getCommand('intro') == null) {
+      test.log('Skipping testIntroStatus; missing intro command.');
       return;
     }
 
     helpers.setInput('intro');
     helpers.check({
       typed:  'intro',
       markup: 'VVVVV',
       status: 'VALID',
@@ -3502,18 +3498,18 @@ define('gclitest/testIntro', ['require',
       typed:  'intro foo',
       markup: 'VVVVVVEEE',
       status: 'ERROR',
       hints: ''
     });
   };
 
   exports.testIntroExec = function(options) {
-    if (options.isFirefox) {
-      test.log('Skipping testIntroExec in Firefox.');
+    if (canon.getCommand('intro') == null) {
+      test.log('Skipping testIntroStatus; missing intro command.');
       return;
     }
 
     helpers.exec(options, {
       typed: 'intro',
       args: { },
       outputMatch: [
         /command\s*line/,
@@ -3678,17 +3674,20 @@ exports.testBasic = function(options) {
   check('VVVVVVVVVVVVVVVVVVVVVVV', Status.VALID, 'window.document.title', 0);
 
   input('{ d');
   check('VVI', Status.ERROR, 'd', 'document');
 
   input('{ document.title');
   check('VVVVVVVVVVVVVVVV', Status.VALID, 'document.title', 0);
 
-  test.ok('donteval' in options.window, 'donteval exists');
+  if (!options.isJsdom) {
+    // jsdom causes an eval here, maybe that's node/v8?
+    test.ok('donteval' in options.window, 'donteval exists');
+  }
 
   input('{ don');
   check('VVIII', Status.ERROR, 'don', 'donteval');
 
   input('{ donteval');
   check('VVVVVVVVVV', Status.VALID, 'donteval', 0);
 
   /*
@@ -3827,22 +3826,22 @@ exports.testComplete = function(options)
 
   if (!canon.getCommand('{')) {
     test.log('Skipping exec tests because { is not registered');
   }
   else {
     check('{ wind', COMPLETES_TO, '{ window', 0);
     check('{ window.docum', COMPLETES_TO, '{ window.document', 0);
 
-    // Bug 717228: This fails under node
-    if (!options.isNode) {
+    // Bug 717228: This fails under jsdom
+    if (!options.isJsdom) {
       check('{ window.document.titl', COMPLETES_TO, '{ window.document.title ', 0);
     }
     else {
-      test.log('Running under Node. Skipping tests due to bug 717228.');
+      test.log('Skipping tests due to jsdom and bug 717228.');
     }
   }
 };
 
 exports.testInternalComplete = function(options) {
   // Bug 664377
   // check('tsela 1', COMPLETES_TO, 'tselarr 1', 0, 3, 8);
 };
@@ -4003,71 +4002,76 @@ exports.testNode = function(options) {
         status: 'ERROR',
         message: 'Syntax error in CSS query'
       },
       nodes: { status: 'VALID' },
       nodes2: { status: 'VALID' }
     }
   });
 
-  helpers.setInput('tse :root');
-  helpers.check({
-    input:  'tse :root',
-    hints:           ' [options]',
-    markup: 'VVVVVVVVV',
-    cursor: 9,
-    current: 'node',
-    status: 'VALID',
-    args: {
-      command: { name: 'tse' },
-      node: { arg: ' :root', status: 'VALID' },
-      nodes: { status: 'VALID' },
-      nodes2: { status: 'VALID' }
-    }
-  });
-
-  helpers.setInput('tse :root ');
-  helpers.check({
-    input:  'tse :root ',
-    hints:            '[options]',
-    markup: 'VVVVVVVVVV',
-    cursor: 10,
-    current: 'node',
-    status: 'VALID',
-    args: {
-      command: { name: 'tse' },
-      node: { arg: ' :root ', status: 'VALID' },
-      nodes: { status: 'VALID' },
-      nodes2: { status: 'VALID' }
-    }
-  });
-  test.is(requisition.getAssignment('node').value.tagName,
-          'HTML',
-          'root id');
-
-  helpers.setInput('tse #gcli-nomatch');
-  helpers.check({
-    input:  'tse #gcli-nomatch',
-    hints:                   ' [options]',
-    markup: 'VVVVIIIIIIIIIIIII',
-    cursor: 17,
-    current: 'node',
-    status: 'ERROR',
-    args: {
-      command: { name: 'tse' },
-      node: {
-        value: undefined,
-        arg: ' #gcli-nomatch',
-        status: 'INCOMPLETE',
-        message: 'No matches'
-      },
-      nodes: { status: 'VALID' },
-      nodes2: { status: 'VALID' }
-    }
-  });
+  if (options.isJsdom) {
+    test.log('skipping node tests because jsdom');
+  }
+  else {
+    helpers.setInput('tse :root');
+    helpers.check({
+      input:  'tse :root',
+      hints:           ' [options]',
+      markup: 'VVVVVVVVV',
+      cursor: 9,
+      current: 'node',
+      status: 'VALID',
+      args: {
+        command: { name: 'tse' },
+        node: { arg: ' :root', status: 'VALID' },
+        nodes: { status: 'VALID' },
+        nodes2: { status: 'VALID' }
+      }
+    });
+
+    helpers.setInput('tse :root ');
+    helpers.check({
+      input:  'tse :root ',
+      hints:            '[options]',
+      markup: 'VVVVVVVVVV',
+      cursor: 10,
+      current: 'node',
+      status: 'VALID',
+      args: {
+        command: { name: 'tse' },
+        node: { arg: ' :root ', status: 'VALID' },
+        nodes: { status: 'VALID' },
+        nodes2: { status: 'VALID' }
+      }
+    });
+    test.is(requisition.getAssignment('node').value.tagName,
+            'HTML',
+            'root id');
+
+    helpers.setInput('tse #gcli-nomatch');
+    helpers.check({
+      input:  'tse #gcli-nomatch',
+      hints:                   ' [options]',
+      markup: 'VVVVIIIIIIIIIIIII',
+      cursor: 17,
+      current: 'node',
+      status: 'ERROR',
+      args: {
+        command: { name: 'tse' },
+        node: {
+          value: undefined,
+          arg: ' #gcli-nomatch',
+          status: 'INCOMPLETE',
+          message: 'No matches'
+        },
+        nodes: { status: 'VALID' },
+        nodes2: { status: 'VALID' }
+      }
+    });
+  }
 
   helpers.setInput('tse #');
   helpers.check({
     input:  'tse #',
     hints:       ' [options]',
     markup: 'VVVVE',
     cursor: 5,
     current: 'node',
@@ -4126,16 +4130,21 @@ exports.testNode = function(options) {
       nodes2: { status: 'VALID' }
     }
   });
 };
 
 exports.testNodes = function(options) {
   var requisition = options.display.requisition;
 
+  if (options.isJsdom) {
+    test.log('skipping node tests because jsdom');
+    return;
+  }
+
   helpers.setInput('tse :root --nodes *');
   helpers.check({
     input:  'tse :root --nodes *',
     hints:                       ' [options]',
     markup: 'VVVVVVVVVVVVVVVVVVV',
     current: 'nodes',
     status: 'VALID',
     args: {
@@ -4231,23 +4240,24 @@ exports.testNodes = function(options) {
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gclitest/testPref', ['require', 'exports', 'module' , 'gcli/commands/pref', 'gclitest/helpers', 'gclitest/mockSettings', 'test/assert'], function(require, exports, module) {
+define('gclitest/testPref', ['require', 'exports', 'module' , 'gcli/commands/pref', 'gclitest/helpers', 'gclitest/mockSettings', 'test/assert', 'gcli/canon'], function(require, exports, module) {
 
 
 var pref = require('gcli/commands/pref');
 var helpers = require('gclitest/helpers');
 var mockSettings = require('gclitest/mockSettings');
 var test = require('test/assert');
+var canon = require('gcli/canon');
 
 
 exports.setup = function(options) {
   helpers.setup(options);
 
   if (!options.isFirefox) {
     mockSettings.setup();
   }
@@ -4265,16 +4275,21 @@ exports.shutdown = function(options) {
 };
 
 exports.testPrefShowStatus = function(options) {
   if (options.isFirefox) {
     test.log('Skipping testPrefShowStatus in Firefox.');
     return;
   }
 
+  if (canon.getCommand('intro') == null) {
+    test.log('Skipping testIntroStatus; missing intro command.');
+    return;
+  }
+
   helpers.setInput('pref s');
   helpers.check({
     typed:  'pref s',
     hints:        'et',
     markup: 'IIIIVI',
     status: 'ERROR'
   });
 
@@ -4328,16 +4343,21 @@ exports.testPrefShowStatus = function(op
 };
 
 exports.testPrefSetStatus = function(options) {
   if (options.isFirefox) {
     test.log('Skipping testPrefSetStatus in Firefox.');
     return;
   }
 
+  if (canon.getCommand('intro') == null) {
+    test.log('Skipping testIntroStatus; missing intro command.');
+    return;
+  }
+
   helpers.setInput('pref s');
   helpers.check({
     typed:  'pref s',
     hints:        'et',
     markup: 'IIIIVI',
     status: 'ERROR',
   });
 
@@ -4347,17 +4367,17 @@ exports.testPrefSetStatus = function(opt
     hints:          ' <setting> <value>',
     markup: 'VVVVVVVV',
     status: 'ERROR'
   });
 
   helpers.setInput('pref xxx');
   helpers.check({
     typed:  'pref xxx',
-    markup: 'EEEEVEEE',
+    markup: 'IIIIVIII',
     status: 'ERROR'
   });
 
   helpers.setInput('pref set ');
   helpers.check({
     typed:  'pref set ',
     hints:           'allowSet <value>',
     markup: 'VVVVVVVVV',
@@ -4390,16 +4410,21 @@ exports.testPrefSetStatus = function(opt
 };
 
 exports.testPrefExec = function(options) {
   if (options.isFirefox) {
     test.log('Skipping testPrefExec in Firefox.');
     return;
   }
 
+  if (canon.getCommand('intro') == null) {
+    test.log('Skipping testIntroStatus; missing intro command.');
+    return;
+  }
+
   var initialAllowSet = pref.allowSet.value;
   pref.allowSet.value = false;
 
   test.is(mockSettings.tempNumber.value, 42, 'set to 42');
 
   helpers.exec(options, {
     typed: 'pref set tempNumber 4',
     args: {
@@ -4739,22 +4764,21 @@ exports.testPredictions = function(optio
   test.ok(options2.length > 1, 'have resources');
   options2.forEach(function(prediction) {
     checkPrediction(resource2, prediction);
   });
 
   var resource3 = types.getType({ name: 'resource', include: 'text/css' });
   var options3 = resource3.getLookup();
   // jsdom fails to support digging into stylesheets
-  if (!options.isNode) {
+  if (!options.isJsdom) {
     test.ok(options3.length >= 1, 'have resources');
   }
   else {
-    test.log('Running under Node. ' +
-             'Skipping checks due to jsdom document.stylsheets support.');
+    test.log('Skipping checks due to jsdom document.stylsheets support.');
   }
   options3.forEach(function(prediction) {
     checkPrediction(resource3, prediction);
   });
 
   var resource4 = types.getType({ name: 'resource' });
   var options4 = resource4.getLookup();
 
@@ -5013,39 +5037,38 @@ exports.testChange = function(options) {
  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
 define('gclitest/testSpell', ['require', 'exports', 'module' , 'test/assert', 'gcli/types/spell'], function(require, exports, module) {
 
 var test = require('test/assert');
-var Speller = require('gcli/types/spell').Speller;
+var spell = require('gcli/types/spell');
 
 exports.setup = function() {
 };
 
 exports.shutdown = function() {
 };
 
 exports.testSpellerSimple = function(options) {
   if (options.isFirefox) {
     test.log('Skipping testPref in Firefox.');
     return;
   }
 
-  var speller = new Speller();
-  speller.train(Object.keys(options.window));
-
-  test.is(speller.correct('document'), 'document');
-  test.is(speller.correct('documen'), 'document');
-  test.is(speller.correct('ocument'), 'document');
-  test.is(speller.correct('odcument'), 'document');
-
-  test.is(speller.correct('========='), null);
+  var alternatives = Object.keys(options.window);
+
+  test.is(spell.correct('document', alternatives), 'document');
+  test.is(spell.correct('documen', alternatives), 'document');
+  test.is(spell.correct('ocument', alternatives), 'document');
+  test.is(spell.correct('odcument', alternatives), 'document');
+
+  test.is(spell.correct('=========', alternatives), undefined);
 };
 
 
 });
 /*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -5453,17 +5476,17 @@ function type(typed, tests, options) {
   var inputter = options.display.inputter;
   var tooltip = options.display.tooltip;
 
   inputter.setInput(typed);
   if (tests.cursor) {
     inputter.setCursor({ start: tests.cursor, end: tests.cursor });
   }
 
-  if (!options.isNode) {
+  if (!options.isJsdom) {
     if (tests.important) {
       test.ok(tooltip.field.isImportant, 'Important for ' + typed);
     }
     else {
       test.ok(!tooltip.field.isImportant, 'Not important for ' + typed);
     }
 
     if (tests.options) {
@@ -5483,39 +5506,37 @@ function type(typed, tests, options) {
 }
 
 exports.testActivate = function(options) {
   if (!options.display) {
     test.log('No display. Skipping activate tests');
     return;
   }
 
-  if (options.isNode) {
-    test.log('Running under Node. Reduced checks due to JSDom.textContent');
+  if (options.isJsdom) {
+    test.log('Reduced checks due to JSDom.textContent');
   }
 
   type(' ', { }, options);
 
   type('tsb ', {
     important: true,
     options: [ 'false', 'true' ]
   }, options);
 
   type('tsb t', {
     important: true,
     options: [ 'true' ]
   }, options);
 
   type('tsb tt', {
     important: true,
-    options: [ ],
-    error: 'Can\'t use \'tt\'.'
+    options: [ 'true' ]
   }, options);
 
-
   type('asdf', {
     important: false,
     options: [ ],
     error: 'Can\'t use \'asdf\'.'
   }, options);
 
   type('', { }, options);
 };
@@ -5567,19 +5588,18 @@ function forEachType(options, callback) 
     }
 
     var type = types.getType(options);
     callback(type);
   });
 }
 
 exports.testDefault = function(options) {
-  if (options.isNode) {
-    test.log('Running under Node. ' +
-             'Skipping tests due to issues with resource type.');
+  if (options.isJsdom) {
+    test.log('Skipping tests due to issues with resource type.');
     return;
   }
 
   forEachType({}, function(type) {
     var blank = type.getBlank().value;
 
     // boolean and array types are exempt from needing undefined blank values
     if (type.name === 'boolean') {
--- a/browser/devtools/debugger/test/browser_dbg_displayName.html
+++ b/browser/devtools/debugger/test/browser_dbg_displayName.html
@@ -9,18 +9,21 @@ var a = function() {
   return function() {
     debugger;
   }
 }
 
 var anon = a();
 anon.displayName = "anonFunc";
 
+var inferred = a();
+
 function evalCall() {
   eval("anon();");
+  eval("inferred();");
 }
 
 </script>
 </head>
 
 <body></body>
 
 </html>
--- a/browser/devtools/debugger/test/browser_dbg_displayName.js
+++ b/browser/devtools/debugger/test/browser_dbg_displayName.js
@@ -1,13 +1,16 @@
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
+// Tests that anonymous functions appear in the stack frame list with either
+// their displayName property or a SpiderMonkey-inferred name.
+
 var gPane = null;
 var gTab = null;
 var gDebuggee = null;
 var gDebugger = null;
 
 const TAB_URL = EXAMPLE_URL + "browser_dbg_displayName.html";
 
 function test() {
@@ -31,21 +34,43 @@ function testAnonCall() {
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 3,
         "Should have three frames.");
 
       is(frames.querySelector("#stackframe-0 .dbg-stackframe-name").getAttribute("value"),
         "anonFunc", "Frame name should be anonFunc");
 
+      testInferredName();
+    }}, 0);
+  });
+
+  gDebuggee.evalCall();
+}
+
+function testInferredName() {
+  gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
+    Services.tm.currentThread.dispatch({ run: function() {
+
+      let frames = gDebugger.DebuggerView.StackFrames._frames;
+
+      is(gDebugger.DebuggerController.activeThread.state, "paused",
+        "Should only be getting stack frames while paused.");
+
+      is(frames.querySelectorAll(".dbg-stackframe").length, 3,
+        "Should have three frames.");
+
+      is(frames.querySelector("#stackframe-0 .dbg-stackframe-name").getAttribute("value"),
+        "a/<", "Frame name should be a/<");
+
       resumeAndFinish();
     }}, 0);
   });
 
-  gDebuggee.evalCall();
+  gDebugger.DebuggerController.activeThread.resume();
 }
 
 function resumeAndFinish() {
   gDebugger.DebuggerController.activeThread.resume(function() {
     removeTab(gTab);
     gPane = null;
     gDebuggee = null;
     finish();
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -1,19 +1,19 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 let tempScope = {};
-Cu.import("resource:///modules/devtools/dbg-server.jsm", tempScope);
-Cu.import("resource:///modules/devtools/dbg-client.jsm", tempScope);
-Cu.import("resource:///modules/Services.jsm", tempScope);
+Cu.import("resource://gre/modules/devtools/dbg-server.jsm", tempScope);
+Cu.import("resource://gre/modules/devtools/dbg-client.jsm", tempScope);
+Cu.import("resource://gre/modules/Services.jsm", tempScope);
 let DebuggerServer = tempScope.DebuggerServer;
 let DebuggerTransport = tempScope.DebuggerTransport;
 let DebuggerClient = tempScope.DebuggerClient;
 let Services = tempScope.Services;
 
 const EXAMPLE_URL = "http://example.com/browser/browser/devtools/debugger/test/";
 
 const TAB1_URL = EXAMPLE_URL + "browser_dbg_tab1.html";
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -30,16 +30,17 @@ Cu.import("resource://gre/modules/jsdebu
 const SCRATCHPAD_CONTEXT_CONTENT = 1;
 const SCRATCHPAD_CONTEXT_BROWSER = 2;
 const SCRATCHPAD_L10N = "chrome://browser/locale/devtools/scratchpad.properties";
 const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
 const PREF_RECENT_FILES_MAX = "devtools.scratchpad.recentFilesMax";
 const BUTTON_POSITION_SAVE = 0;
 const BUTTON_POSITION_CANCEL = 1;
 const BUTTON_POSITION_DONT_SAVE = 2;
+const BUTTON_POSITION_REVERT=0;
 
 let keysbundle = Services.strings.createBundle("chrome://global-platform/locale/platformKeys.properties");
 
 
 function SP_Pretty_Key(aElemKey) {
 
   let elemString = "";
   let elemMod = aElemKey.getAttribute("modifiers");
@@ -918,16 +919,82 @@ var Scratchpad = {
         if (aCallback) {
           aCallback(aStatus);
         }
       });
     }
   },
 
   /**
+   * Restore content from saved version of current file.
+   *
+   * @param function aCallback
+   *        Optional function you want to call when file is saved
+   */
+  revertFile: function SP_revertFile(aCallback)
+  {
+    
+    let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+    file.initWithPath(this.filename);
+
+    if (!file.exists()) {
+      return;
+    }
+
+    this.importFromFile(file, false, function(aStatus, aContent) {
+      if (aCallback) {
+        aCallback(aStatus);
+      }
+    });
+  },
+
+  /**
+   * Prompt to revert scratchpad if it has unsaved changes.
+   *
+   * @param function aCallback
+   *        Optional function you want to call when file is saved. The callback
+   *        receives three arguments:
+   *          - aRevert (boolean) - tells if the file has been reverted.
+   *          - status (number) - the file revert status result (if the file was
+   *          saved).
+   */
+  promptRevert: function SP_promptRervert(aCallback)
+  {
+    if (this.filename) {
+      let ps = Services.prompt;
+      let flags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_REVERT +
+                  ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL;
+
+      let button = ps.confirmEx(window,
+                          this.strings.GetStringFromName("confirmRevert.title"),
+                          this.strings.GetStringFromName("confirmRevert"),
+                          flags, null, null, null, null, {});
+      if (button == BUTTON_POSITION_CANCEL) {
+        if (aCallback) {
+          aCallback(false);
+        }
+
+        return;
+      }
+      if (button == BUTTON_POSITION_REVERT) {
+        this.revertFile(function(aStatus) {
+          if(aCallback){
+            aCallback(true, aStatus);
+          }
+        });
+
+        return;
+      }
+    }
+    if (aCallback) {
+      aCallback(false);
+    }
+  },
+
+  /**
    * Open the Error Console.
    */
   openErrorConsole: function SP_openErrorConsole()
   {
     this.browserWindow.toJavaScriptConsole();
   },
 
   /**
@@ -1102,16 +1169,24 @@ var Scratchpad = {
    * @private
    * @see SourceEditor.EVENTS.DIRTY_CHANGED
    * @param object aEvent
    *        The DirtyChanged event object.
    */
   _onDirtyChanged: function SP__onDirtyChanged(aEvent)
   {
     Scratchpad._updateTitle();
+    if (Scratchpad.filename) {
+      if (Scratchpad.editor.dirty) {
+        document.getElementById("sp-cmd-revert").removeAttribute("disabled");
+      }
+      else {
+        document.getElementById("sp-cmd-revert").setAttribute("disabled", true);
+      }
+    }
   },
 
   /**
    * Undo the last action of the user.
    */
   undo: function SP_undo()
   {
     this.editor.undo();
--- a/browser/devtools/scratchpad/scratchpad.xul
+++ b/browser/devtools/scratchpad/scratchpad.xul
@@ -28,16 +28,17 @@
 <commandset id="sourceEditorCommands"/>
 
 <commandset id="sp-commandset">
   <command id="sp-cmd-newWindow" oncommand="Scratchpad.openScratchpad();"/>
   <command id="sp-cmd-openFile" oncommand="Scratchpad.openFile();"/>
   <command id="sp-cmd-clearRecentFiles" oncommand="Scratchpad.clearRecentFiles();"/>
   <command id="sp-cmd-save" oncommand="Scratchpad.saveFile();"/>
   <command id="sp-cmd-saveas" oncommand="Scratchpad.saveFileAs();"/>
+  <command id="sp-cmd-revert" oncommand="Scratchpad.promptRevert();" disabled="true"/>
 
   <!-- TODO: bug 650340 - implement printFile()
   <command id="sp-cmd-printFile" oncommand="Scratchpad.printFile();" disabled="true"/>
  -->
 
   <command id="sp-cmd-close" oncommand="Scratchpad.close();"/>
   <command id="sp-cmd-run" oncommand="Scratchpad.run();"/>
   <command id="sp-cmd-inspect" oncommand="Scratchpad.inspect();"/>
@@ -127,16 +128,20 @@
                 label="&saveFileCmd.label;"
                 accesskey="&saveFileCmd.accesskey;"
                 key="sp-key-save"
                 command="sp-cmd-save"/>
       <menuitem id="sp-menu-saveas"
                 label="&saveFileAsCmd.label;"
                 accesskey="&saveFileAsCmd.accesskey;"
                 command="sp-cmd-saveas"/>
+      <menuitem id="sp-menu-revert"
+                label="&revertCmd.label;"
+                accesskey="&revertCmd.accesskey;"
+                command="sp-cmd-revert"/>
       <menuseparator/>
 
       <!-- TODO: bug 650340 - implement printFile
       <menuitem id="sp-menu-print"
                 label="&printCmd.label;"
                 accesskey="&printCmd.accesskey;"
                 command="sp-cmd-printFile"/>
       <menuseparator/>
--- a/browser/devtools/scratchpad/test/Makefile.in
+++ b/browser/devtools/scratchpad/test/Makefile.in
@@ -28,11 +28,12 @@ MOCHITEST_BROWSER_FILES = \
 		browser_scratchpad_bug_653427_confirm_close.js \
 		browser_scratchpad_bug684546_reset_undo.js \
 		browser_scratchpad_bug690552_display_outputs_errors.js \
 		browser_scratchpad_bug650345_find_ui.js \
 		browser_scratchpad_bug714942_goto_line_ui.js \
 		browser_scratchpad_bug_650760_help_key.js \
 		browser_scratchpad_bug_651942_recent_files.js \
 		browser_scratchpad_bug756681_display_non_error_exceptions.js \
+		browser_scratchpad_bug_751744_revert_to_saved.js \
 		head.js \
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_751744_revert_to_saved.js
@@ -0,0 +1,137 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let tempScope = {};
+Cu.import("resource://gre/modules/NetUtil.jsm", tempScope);
+Cu.import("resource://gre/modules/FileUtils.jsm", tempScope);
+let NetUtil = tempScope.NetUtil;
+let FileUtils = tempScope.FileUtils;
+
+// Reference to the Scratchpad object.
+let gScratchpad;
+
+// Reference to the temporary nsIFiles.
+let gFile;
+
+// Temporary file name.
+let gFileName = "testFileForBug751744.tmp"
+
+
+// Content for the temporary file.
+let gFileContent = "/* this file is already saved */\n" +
+                   "function foo() { alert('bar') }";
+let gLength = gFileContent.length;
+
+// Reference to the menu entry.
+let menu;
+
+function startTest()
+{
+  gScratchpad = gScratchpadWindow.Scratchpad;
+  menu = gScratchpadWindow.document.getElementById("sp-menu-revert");
+  createAndLoadTemporaryFile();
+}
+
+function testAfterSaved() {
+  // Check if the revert menu is disabled as the file is at saved state.
+  ok(menu.hasAttribute("disabled"), "The revert menu entry is disabled.");
+
+  // chancging the text in the file
+  gScratchpad.setText("\nfoo();", gLength, gLength);
+  // Checking the text got changed
+  is(gScratchpad.getText(), gFileContent + "\nfoo();",
+     "The text changed the first time.");
+
+  // Revert menu now should be enabled.
+  ok(!menu.hasAttribute("disabled"),
+     "The revert menu entry is enabled after changing text first time");
+
+  // reverting back to last saved state.
+  gScratchpad.revertFile(testAfterRevert);
+}
+
+function testAfterRevert() {
+  // Check if the file's text got reverted
+  is(gScratchpad.getText(), gFileContent,
+     "The text reverted back to original text.");
+  // The revert menu should be disabled again.
+  ok(menu.hasAttribute("disabled"),
+     "The revert menu entry is disabled after reverting.");
+
+  // chancging the text in the file again
+  gScratchpad.setText("\nalert(foo.toSource());", gLength, gLength);
+  // Saving the file.
+  gScratchpad.saveFile(testAfterSecondSave);
+}
+
+function testAfterSecondSave() {
+  // revert menu entry should be disabled.
+  ok(menu.hasAttribute("disabled"),
+     "The revert menu entry is disabled after saving.");
+
+  // changing the text.
+  gScratchpad.setText("\nfoo();", gLength + 23, gLength + 23);
+
+  // revert menu entry should get enabled yet again.
+  ok(!menu.hasAttribute("disabled"),
+     "The revert menu entry is enabled after changing text third time");
+
+  // reverting back to last saved state.
+  gScratchpad.revertFile(testAfterSecondRevert);
+}
+
+function testAfterSecondRevert() {
+  // Check if the file's text got reverted
+  is(gScratchpad.getText(), gFileContent + "\nalert(foo.toSource());",
+     "The text reverted back to the changed saved text.");
+  // The revert menu should be disabled again.
+  ok(menu.hasAttribute("disabled"),
+     "Revert menu entry is disabled after reverting to changed saved state.");
+  gFile.remove(false);
+  gFile = null;
+  gScratchpad = null;
+}
+
+function createAndLoadTemporaryFile()
+{
+  // Create a temporary file.
+  gFile = FileUtils.getFile("TmpD", [gFileName]);
+  gFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0666);
+
+  // Write the temporary file.
+  let fout = Cc["@mozilla.org/network/file-output-stream;1"].
+             createInstance(Ci.nsIFileOutputStream);
+  fout.init(gFile.QueryInterface(Ci.nsILocalFile), 0x02 | 0x08 | 0x20,
+            0644, fout.DEFER_OPEN);
+
+  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+                  createInstance(Ci.nsIScriptableUnicodeConverter);
+  converter.charset = "UTF-8";
+  let fileContentStream = converter.convertToInputStream(gFileContent);
+
+  NetUtil.asyncCopy(fileContentStream, fout, tempFileSaved);
+}
+
+function tempFileSaved(aStatus)
+{
+  ok(Components.isSuccessCode(aStatus),
+     "the temporary file was saved successfully");
+
+  // Import the file into Scratchpad.
+  gScratchpad.setFilename(gFile.path);
+  gScratchpad.importFromFile(gFile.QueryInterface(Ci.nsILocalFile),  true,
+                             testAfterSaved);
+}
+
+function test()
+{
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
+    gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
+    openScratchpad(startTest);
+  }, true);
+
+  content.location = "data:text/html,<p>test reverting to last saved state of" +
+                     " a file </p>";
+}
--- a/browser/devtools/shared/DeveloperToolbar.jsm
+++ b/browser/devtools/shared/DeveloperToolbar.jsm
@@ -10,16 +10,18 @@ const NS_XHTML = "http://www.w3.org/1999
 
 const WEBCONSOLE_CONTENT_SCRIPT_URL =
   "chrome://browser/content/devtools/HUDService-content.js";
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource:///modules/devtools/Commands.jsm");
 
+const Node = Components.interfaces.nsIDOMNode;
+
 XPCOMUtils.defineLazyModuleGetter(this, "console",
                                   "resource://gre/modules/devtools/Console.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "gcli",
                                   "resource:///modules/devtools/gcli.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "CmdCommands",
                                   "resource:///modules/devtools/CmdCmd.jsm");
@@ -133,16 +135,38 @@ DeveloperToolbar.prototype.focus = funct
   if (this.visible) {
     this._input.focus();
   } else {
     this.show(true);
   }
 };
 
 /**
+ * Called from browser.xul in response to menu-click or keyboard shortcut to
+ * toggle the toolbar
+ */
+DeveloperToolbar.prototype.focusToggle = function DT_focusToggle()
+{
+  if (this.visible) {
+    // If we have focus then the active element is the HTML input contained
+    // inside the xul input element
+    var active = this._chromeWindow.document.activeElement;
+    var position = this._input.compareDocumentPosition(active);
+    if (position & Node.DOCUMENT_POSITION_CONTAINED_BY) {
+      this.hide();
+    }
+    else {
+      this._input.focus();
+    }
+  } else {
+    this.show(true);
+  }
+};
+
+/**
  * Even if the user has not clicked on 'Got it' in the intro, we only show it
  * once per session.
  * Warning this is slightly messed up because this.DeveloperToolbar is not the
  * same as this.DeveloperToolbar when in browser.js context.
  */
 DeveloperToolbar.introShownThisSession = false;
 
 /**
--- a/browser/devtools/styleeditor/StyleEditor.jsm
+++ b/browser/devtools/styleeditor/StyleEditor.jsm
@@ -754,16 +754,97 @@ StyleEditor.prototype = {
         break;
       default:
         this._loadSourceFromCache(this.styleSheet.href);
         break;
     }
   },
 
   /**
+   * Decode a CSS source string to unicode according to the character set rules
+   * defined in <http://www.w3.org/TR/CSS2/syndata.html#charset>.
+   *
+   * @param string aString
+   *        Source of a CSS stylesheet, loaded from file or cache.
+   * @param string aChannelCharset
+   *        Charset of the source string if set by the HTTP channel.
+   * @return string
+   *         The CSS string, in unicode.
+   */
+  _decodeCSSCharset: function SE__decodeCSSCharset(aString, aChannelCharset)
+  {
+    // StyleSheet's charset can be specified from multiple sources
+
+    if (aChannelCharset.length > 0) {
+      // step 1 of syndata.html: charset given in HTTP header.
+      return this._convertToUnicode(aString, aChannelCharset);
+    }
+
+    let sheet = this.styleSheet;
+    if (sheet) {
+      // Do we have a @charset rule in the stylesheet?
+      // step 2 of syndata.html (without the BOM check).
+      if (sheet.cssRules) {
+        let rules = sheet.cssRules;
+        if (rules.length
+            && rules.item(0).type == Ci.nsIDOMCSSRule.CHARSET_RULE) {
+          return this._convertToUnicode(aString, rules.item(0).encoding);
+        }
+      }
+
+      if (sheet.ownerNode) {
+        // step 3: see <link charset="…">
+        let linkCharset = sheet.ownerNode.getAttribute("charset");
+        if (linkCharset != null) {
+          return this._convertToUnicode(aString, linkCharset);
+        }
+      }
+
+      // step 4 (1 of 2): charset of referring stylesheet.
+      let parentSheet = sheet.parentStyleSheet;
+      if (parentSheet && parentSheet.cssRules &&
+          parentSheet.cssRules[0].type == Ci.nsIDOMCSSRule.CHARSET_RULE) {
+        return this._convertToUnicode(aString,
+            parentSheet.cssRules[0].encoding);
+      }
+
+      // step 4 (2 of 2): charset of referring document.
+      if (sheet.ownerNode && sheet.ownerNode.ownerDocument.characterSet) {
+        return this._convertToUnicode(aString,
+            sheet.ownerNode.ownerDocument.characterSet);
+      }
+    }
+
+    // step 5: default to utf-8.
+    return this._convertToUnicode(aString, "UTF-8");
+  },
+
+  /**
+   * Convert a given string, encoded in a given character set, to unicode.
+   * @param string aString
+   *        A string.
+   * @param string aCharset
+   *        A character set.
+   * @return string
+   *         A unicode string.
+   */
+  _convertToUnicode: function SE__convertToUnicode(aString, aCharset) {
+    // Decoding primitives.
+    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+        .createInstance(Ci.nsIScriptableUnicodeConverter);
+
+    try {
+      converter.charset = aCharset;
+      return converter.ConvertToUnicode(aString);
+    } catch(e) {
+      return aString;
+    }
+  },
+
+  /**
    * Load source from a file or file-like resource.
    *
    * @param string aHref
    *        URL for the stylesheet.
    */
   _loadSourceFromFile: function SE__loadSourceFromFile(aHref)
   {
     try {
@@ -785,45 +866,55 @@ StyleEditor.prototype = {
    *
    * @param string aHref
    *        URL for the stylesheet.
    */
   _loadSourceFromCache: function SE__loadSourceFromCache(aHref)
   {
     let channel = Services.io.newChannel(aHref, null, null);
     let chunks = [];
+    let channelCharset = "";
     let streamListener = { // nsIStreamListener inherits nsIRequestObserver
       onStartRequest: function (aRequest, aContext, aStatusCode) {
         if (!Components.isSuccessCode(aStatusCode)) {
           return this._signalError(LOAD_ERROR);
         }
       }.bind(this),
       onDataAvailable: function (aRequest, aContext, aStream, aOffset, aCount) {
+        let channel = aRequest.QueryInterface(Ci.nsIChannel);
+        if (!channelCharset) {
+          channelCharset = channel.contentCharset;
+        }
         chunks.push(NetUtil.readInputStreamToString(aStream, aCount));
       },
       onStopRequest: function (aRequest, aContext, aStatusCode) {
         if (!Components.isSuccessCode(aStatusCode)) {
           return this._signalError(LOAD_ERROR);
         }
 
-        this._onSourceLoad(chunks.join(""));
+        this._onSourceLoad(chunks.join(""), channelCharset);
       }.bind(this)
     };
 
     channel.loadFlags = channel.LOAD_FROM_CACHE;
     channel.asyncOpen(streamListener, null);
   },
 
   /**
    * Called when source has been loaded.
    *
    * @param string aSourceText
+   * @param string aCharset
+   *        Optional. The character set to use. The default is to detect the
+   *        character set following the standard (see
+   *        <http://www.w3.org/TR/CSS2/syndata.html#charset>).
    */
-  _onSourceLoad: function SE__onSourceLoad(aSourceText)
+  _onSourceLoad: function SE__onSourceLoad(aSourceText, aCharset)
   {
+    aSourceText = this._decodeCSSCharset(aSourceText, aCharset || "");
     this._restoreExpando();
     this._state.text = prettifyCSS(aSourceText);
     this._loaded = true;
     this._triggerAction("Load");
   },
 
   /**
    * Create a new style sheet and append it to the content document.
--- a/browser/devtools/styleeditor/test/browser_styleeditor_init.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_init.js
@@ -66,16 +66,20 @@ function testEditorAdded(aChrome, aEdito
 
   if (gEditorAddedCount == 2) {
     finish();
   }
 }
 
 function testFirstStyleSheetEditor(aChrome, aEditor)
 {
+  // Note: the html <link> contains charset="UTF-8".
+  ok(aEditor._state.text.indexOf("\u263a") >= 0,
+     "stylesheet is unicode-aware.");
+
   //testing TESTCASE's simple.css stylesheet
   is(aEditor.styleSheetIndex, 0,
      "first stylesheet is at index 0");
 
   is(aEditor, aChrome.editors[0],
      "first stylesheet corresponds to StyleEditorChrome.editors[0]");
 
   ok(!aEditor.hasFlag("inline"),
--- a/browser/devtools/styleeditor/test/simple.css
+++ b/browser/devtools/styleeditor/test/simple.css
@@ -1,8 +1,9 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
+/* ☺ */
 
 body {
   margin: 0;
 }
 
--- a/browser/devtools/styleeditor/test/simple.html
+++ b/browser/devtools/styleeditor/test/simple.html
@@ -1,13 +1,13 @@
 <!doctype html>
 <html>
 <head>
   <title>simple testcase</title>
-  <link rel="stylesheet" type="text/css" media="screen" href="simple.css"/>
+  <link rel="stylesheet" charset="UTF-8" type="text/css" media="screen" href="simple.css"/>
   <style type="text/css">
   body {
     background: white;
   }
 
   div {
     font-size: 4em;
   }
--- a/browser/devtools/styleinspector/test/browser_ruleview_734259_style_editor_link.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_734259_style_editor_link.js
@@ -2,17 +2,17 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 let win;
 let doc;
 let contentWindow;
 
 let tempScope = {};
-Cu.import("resource:///modules/Services.jsm", tempScope);
+Cu.import("resource://gre/modules/Services.jsm", tempScope);
 let Services = tempScope.Services;
 
 function createDocument()
 {
   doc.body.innerHTML = '<style type="text/css"> ' +
     'html { color: #000000; } ' +
     'span { font-variant: small-caps; color: #000000; } ' +
     '.nomatches {color: #ff0000;}</style> <div id="first" style="margin: 10em; ' +
--- a/browser/devtools/styleinspector/test/head.js
+++ b/browser/devtools/styleinspector/test/head.js
@@ -1,17 +1,17 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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/. */
 
 let tempScope = {};
 Cu.import("resource:///modules/devtools/CssLogic.jsm", tempScope);
 Cu.import("resource:///modules/devtools/CssHtmlTree.jsm", tempScope);
-Cu.import("resource://gre/modules/HUDService.jsm", tempScope);
+Cu.import("resource:///modules/HUDService.jsm", tempScope);
 let HUDService = tempScope.HUDService;
 let ConsoleUtils = tempScope.ConsoleUtils;
 let CssLogic = tempScope.CssLogic;
 let CssHtmlTree = tempScope.CssHtmlTree;
 
 function log(aMsg)
 {
   dump("*** WebConsoleTest: " + aMsg + "\n");
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_601667_filter_buttons.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_601667_filter_buttons.js
@@ -43,22 +43,24 @@ function testMenuFilterButton(aCategory)
     }
     menuItem = menuItem.nextSibling;
   }
 
   // Turn all the filters on; make sure the button gets checked.
   menuItem = firstMenuItem;
   let prefKey;
   while (menuItem) {
-    prefKey = menuItem.getAttribute("prefKey");
-    chooseMenuItem(menuItem);
-    ok(isChecked(menuItem), "menu item " + prefKey + " for category " +
-       aCategory + " is checked after clicking it");
-    ok(hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
-       "on after clicking the appropriate menu item");
+    if (menuItem.hasAttribute("prefKey")) {
+      prefKey = menuItem.getAttribute("prefKey");
+      chooseMenuItem(menuItem);
+      ok(isChecked(menuItem), "menu item " + prefKey + " for category " +
+         aCategory + " is checked after clicking it");
+      ok(hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
+         "on after clicking the appropriate menu item");
+    }
     menuItem = menuItem.nextSibling;
   }
   ok(isChecked(button), "the button for category " + aCategory + " is " +
      "checked after turning on all its menu items");
 
   // Turn one filter off; make sure the button is still checked.
   prefKey = firstMenuItem.getAttribute("prefKey");
   chooseMenuItem(firstMenuItem);
@@ -98,21 +100,23 @@ function testMenuFilterButton(aCategory)
   // Turn all the filters on by clicking the main part of the button.
   clickButton(subbutton);
 
   ok(isChecked(button), "the button for category " + aCategory + " is " +
      "checked after clicking its main part");
 
   menuItem = firstMenuItem;
   while (menuItem) {
-    let prefKey = menuItem.getAttribute("prefKey");
-    ok(isChecked(menuItem), "menu item " + prefKey + " for category " +
-       aCategory + " is checked after clicking the button");
-    ok(hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
-       "on after clicking the button");
+    if (menuItem.hasAttribute("prefKey")) {
+      let prefKey = menuItem.getAttribute("prefKey");
+      ok(isChecked(menuItem), "menu item " + prefKey + " for category " +
+         aCategory + " is checked after clicking the button");
+      ok(hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
+         "on after clicking the button");
+    }
     menuItem = menuItem.nextSibling;
   }
 
   // Uncheck the main button by unchecking all the filters
   menuItem = firstMenuItem;
   while (menuItem) {
     chooseMenuItem(menuItem);
     menuItem = menuItem.nextSibling;
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_602572_log_bodies_checkbox.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_602572_log_bodies_checkbox.js
@@ -3,17 +3,17 @@
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  *
  * Contributor(s):
  *   Mihai Șucan <mihai.sucan@gmail.com>
  *
  * ***** END LICENSE BLOCK ***** */
 
-let menuitems = [], menupopups = [], huds = [], tabs = [];
+let menuitems = [], menupopups = [], huds = [], tabs = [], runCount = 0;
 
 function test()
 {
   // open tab 1
   addTab("data:text/html;charset=utf-8,Web Console test for bug 602572: log bodies checkbox. tab 1");
   tabs.push(tab);
 
   browser.addEventListener("load", function onLoad1(aEvent) {
@@ -38,23 +38,28 @@ function test()
       }, true);
     });
   }, true);
 }
 
 function startTest()
 {
   // Find the relevant elements in the Web Console of tab 2.
-  let win2 = tabs[1].linkedBrowser.contentWindow;
+  let win2 = tabs[runCount*2 + 1].linkedBrowser.contentWindow;
   let hudId2 = HUDService.getHudIdByWindow(win2);
   huds[1] = HUDService.hudReferences[hudId2];
   HUDService.disableAnimation(hudId2);
 
-  menuitems[1] = huds[1].ui.rootElement.querySelector("#saveBodies");
-  menupopups[1] = huds[1].ui.rootElement.querySelector("menupopup");
+  if (runCount == 0) {
+    menuitems[1] = huds[1].ui.rootElement.querySelector("#saveBodies");
+  }
+  else {
+    menuitems[1] = huds[1].ui.rootElement.querySelector("#saveBodiesContextMenu");
+  }
+  menupopups[1] = menuitems[1].parentNode;
 
   // Open the context menu from tab 2.
   menupopups[1].addEventListener("popupshown", onpopupshown2, false);
   executeSoon(function() {
     menupopups[1].openPopup();
   });
 }
 
@@ -90,33 +95,33 @@ function onpopupshown2b(aEvent)
 {
   menupopups[1].removeEventListener(aEvent.type, onpopupshown2b, false);
   is(menuitems[1].getAttribute("checked"), "true", "menuitems[1] is checked");
 
   menupopups[1].addEventListener("popuphidden", function _onhidden(aEvent) {
     menupopups[1].removeEventListener(aEvent.type, _onhidden, false);
 
     // Switch to tab 1 and open the Web Console context menu from there.
-    gBrowser.selectedTab = tabs[0];
+    gBrowser.selectedTab = tabs[runCount*2];
     waitForFocus(function() {
       // Find the relevant elements in the Web Console of tab 1.
-      let win1 = tabs[0].linkedBrowser.contentWindow;
+      let win1 = tabs[runCount*2].linkedBrowser.contentWindow;
       let hudId1 = HUDService.getHudIdByWindow(win1);
       huds[0] = HUDService.hudReferences[hudId1];
       HUDService.disableAnimation(hudId1);
 
       info("iframe1 height " + huds[0].iframe.clientHeight);
       info("iframe1 root height " + huds[0].ui.rootElement.clientHeight);
 
       menuitems[0] = huds[0].ui.rootElement.querySelector("#saveBodies");
       menupopups[0] = huds[0].ui.rootElement.querySelector("menupopup");
 
       menupopups[0].addEventListener("popupshown", onpopupshown1, false);
       menupopups[0].openPopup();
-    }, tabs[0].linkedBrowser.contentWindow);
+    }, tabs[runCount*2].linkedBrowser.contentWindow);
   }, false);
 
   executeSoon(function() {
     menupopups[1].hidePopup();
   });
 }
 
 function onpopupshown1(aEvent)
@@ -129,42 +134,55 @@ function onpopupshown1(aEvent)
 
   // Enable body logging for tab 1 as well.
   huds[0].ui.saveRequestAndResponseBodies = true;
 
   // Close the menu, and switch back to tab 2.
   menupopups[0].addEventListener("popuphidden", function _onhidden(aEvent) {
     menupopups[0].removeEventListener(aEvent.type, _onhidden, false);
 
-    gBrowser.selectedTab = tabs[1];
+    gBrowser.selectedTab = tabs[runCount*2 + 1];
     waitForFocus(function() {
       // Reopen the context menu from tab 2.
       menupopups[1].addEventListener("popupshown", onpopupshown2c, false);
       menupopups[1].openPopup();
-    }, tabs[1].linkedBrowser.contentWindow);
+    }, tabs[runCount*2 + 1].linkedBrowser.contentWindow);
   }, false);
 
   executeSoon(function() {
     menupopups[0].hidePopup();
   });
 }
 
 function onpopupshown2c(aEvent)
 {
   menupopups[1].removeEventListener(aEvent.type, onpopupshown2c, false);
 
   is(menuitems[1].getAttribute("checked"), "true", "menuitems[1] is checked");
 
   menupopups[1].addEventListener("popuphidden", function _onhidden(aEvent) {
     menupopups[1].removeEventListener(aEvent.type, _onhidden, false);
 
-    // Done!
-    huds = menuitems = menupopups = tabs = null;
+    // Done if on second run
     closeConsole(gBrowser.selectedTab, function() {
-      gBrowser.removeCurrentTab();
-      executeSoon(finishTest);
+      if (runCount == 0) {
+        runCount++;
+        executeSoon(test);
+      }
+      else {
+        gBrowser.removeCurrentTab();
+        gBrowser.selectedTab = tabs[2];
+        gBrowser.removeCurrentTab();
+        gBrowser.selectedTab = tabs[1];
+        gBrowser.removeCurrentTab();
+        gBrowser.selectedTab = tabs[0];
+        gBrowser.removeCurrentTab();
+        huds = menuitems = menupopups = tabs = null;
+        executeSoon(finishTest);
+      }
     });
+
   }, false);
 
   executeSoon(function() {
     menupopups[1].hidePopup();
   });
 }
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -338,21 +338,40 @@ WebConsoleFrame.prototype = {
       this.inputNode.style.fontSize = fontSize + "px";
     }
 
     let saveBodies = doc.getElementById("saveBodies");
     saveBodies.addEventListener("command", function() {
       this.saveRequestAndResponseBodies = !this.saveRequestAndResponseBodies;
     }.bind(this));
     saveBodies.setAttribute("checked", this.saveRequestAndResponseBodies);
-
-    let contextMenuId = this.outputNode.getAttribute("context");
-    let contextMenu = doc.getElementById(contextMenuId);
-    contextMenu.addEventListener("popupshowing", function() {
+    saveBodies.disabled = !this.getFilterState("networkinfo") &&
+                          !this.getFilterState("network");
+
+    saveBodies.parentNode.addEventListener("popupshowing", function() {
       saveBodies.setAttribute("checked", this.saveRequestAndResponseBodies);
+      saveBodies.disabled = !this.getFilterState("networkinfo") &&
+                            !this.getFilterState("network");
+    }.bind(this));
+
+    // Remove this part when context menu entry is removed.
+    let saveBodiesContextMenu = doc.getElementById("saveBodiesContextMenu");
+    saveBodiesContextMenu.addEventListener("command", function() {
+      this.saveRequestAndResponseBodies = !this.saveRequestAndResponseBodies;
+    }.bind(this));
+    saveBodiesContextMenu.setAttribute("checked",
+                                       this.saveRequestAndResponseBodies);
+    saveBodiesContextMenu.disabled = !this.getFilterState("networkinfo") &&
+                                     !this.getFilterState("network");
+
+    saveBodiesContextMenu.parentNode.addEventListener("popupshowing", function() {
+      saveBodiesContextMenu.setAttribute("checked",
+                                         this.saveRequestAndResponseBodies);
+      saveBodiesContextMenu.disabled = !this.getFilterState("networkinfo") &&
+                                       !this.getFilterState("network");
     }.bind(this));
 
     this.closeButton = doc.getElementById("webconsole-close-button");
     this.closeButton.addEventListener("command",
                                       this.owner.onCloseButton.bind(this.owner));
 
     let clearButton = doc.getElementsByClassName("webconsole-clear-console-button")[0];
     clearButton.addEventListener("command",
@@ -681,23 +700,32 @@ WebConsoleFrame.prototype = {
 
       case "menuitem": {
         let state = target.getAttribute("checked") !== "true";
         target.setAttribute("checked", state);
 
         let prefKey = target.getAttribute("prefKey");
         this.setFilterState(prefKey, state);
 
+        // Disable the log response and request body if network logging is off.
+        if (prefKey == "networkinfo" || prefKey == "network") {
+          let checkState = !this.getFilterState("networkinfo") &&
+                           !this.getFilterState("network");
+          this.document.getElementById("saveBodies").disabled = checkState;
+          this.document.getElementById("saveBodiesContextMenu").disabled = checkState;
+        }
+
         // Adjust the state of the button appropriately.
         let menuPopup = target.parentNode;
 
         let someChecked = false;
         let menuItem = menuPopup.firstChild;
         while (menuItem) {
-          if (menuItem.getAttribute("checked") === "true") {
+          if (menuItem.hasAttribute("prefKey") &&
+              menuItem.getAttribute("checked") === "true") {
             someChecked = true;
             break;
           }
           menuItem = menuItem.nextSibling;
         }
         let toolbarButton = menuPopup.parentNode;
         toolbarButton.setAttribute("checked", someChecked);
         break;
--- a/browser/devtools/webconsole/webconsole.xul
+++ b/browser/devtools/webconsole/webconsole.xul
@@ -34,17 +34,17 @@
     <key key="&fullZoomEnlargeCmd.commandkey3;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
     <key id="key_fullZoomReset" key="&fullZoomResetCmd.commandkey;" command="cmd_fullZoomReset" modifiers="accel"/>
     <key key="&fullZoomResetCmd.commandkey2;" command="cmd_fullZoomReset" modifiers="accel"/>
   </keyset>
   <keyset id="editMenuKeys"/>
 
   <popupset id="mainPopupSet">
     <menupopup id="output-contextmenu">
-      <menuitem id="saveBodies" type="checkbox" label="&saveBodies.label;"
+      <menuitem id="saveBodiesContextMenu" type="checkbox" label="&saveBodies.label;"
                 accesskey="&saveBodies.accesskey;"/>
       <menuitem id="menu_copy"/>
       <menuitem id="menu_selectAll"/>
     </menupopup>
   </popupset>
 
   <vbox class="hud-outer-wrapper" flex="1">
     <vbox class="hud-console-wrapper" flex="1">
@@ -58,16 +58,19 @@
         <toolbarbutton label="&btnPageNet.label;" type="menu-button"
                        category="net" class="devtools-toolbarbutton webconsole-filter-button"
                        tooltiptext="&btnPageNet.tooltip;">
           <menupopup>
             <menuitem label="&btnConsoleErrors;" type="checkbox" autocheck="false"
                       prefKey="network"/>
             <menuitem label="&btnConsoleLog;" type="checkbox" autocheck="false"
                       prefKey="networkinfo"/>
+            <menuseparator id="saveBodiesSeparator" />
+            <menuitem id="saveBodies" type="checkbox" label="&saveBodies.label;"
+                      accesskey="&saveBodies.accesskey;"/>
           </menupopup>
         </toolbarbutton>
         <toolbarbutton label="&btnPageCSS.label;" type="menu-button"
                        category="css" class="devtools-toolbarbutton webconsole-filter-button"
                        tooltiptext="&btnPageCSS.tooltip;">
           <menupopup>
             <menuitem label="&btnConsoleErrors;" type="checkbox" autocheck="false"
                       prefKey="csserror"/>
--- a/browser/locales/en-US/chrome/browser/devtools/scratchpad.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/scratchpad.dtd
@@ -31,16 +31,19 @@
 
 <!ENTITY openFileCmd.label            "Open File…">
 <!ENTITY openFileCmd.accesskey        "O">
 <!ENTITY openFileCmd.commandkey       "o">
 
 <!ENTITY openRecentMenu.label         "Open Recent">
 <!ENTITY openRecentMenu.accesskey     "R">
 
+<!ENTITY revertCmd.label              "Revert…">
+<!ENTITY revertCmd.accesskey          "t">
+
 <!ENTITY saveFileCmd.label            "Save">
 <!ENTITY saveFileCmd.accesskey        "S">
 <!ENTITY saveFileCmd.commandkey       "s">
 
 <!ENTITY saveFileAsCmd.label          "Save As…">
 <!ENTITY saveFileAsCmd.accesskey      "A">
 
 <!ENTITY closeCmd.label               "Close">
--- a/browser/locales/en-US/chrome/browser/devtools/scratchpad.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/scratchpad.properties
@@ -49,16 +49,24 @@ saveFile.failed=The file save operation 
 # LOCALIZATION NOTE  (confirmClose): This is message in the prompt dialog when
 # you try to close a scratchpad with unsaved changes.
 confirmClose=Do you want to save the changes you made to this scratchpad?
 
 # LOCALIZATION NOTE  (confirmClose.title): This is title of the prompt dialog when
 # you try to close a scratchpad with unsaved changes.
 confirmClose.title=Unsaved Changes
 
+# LOCALIZATION NOTE  (confirmRevert): This is message in the prompt dialog when
+# you try to revert unsaved content of scratchpad.
+confirmRevert=Do you want to revert the changes you made to this scratchpad?
+
+# LOCALIZATION NOTE  (confirmRevert.title): This is title of the prompt dialog when
+# you try to revert unsaved content of scratchpad.
+confirmRevert.title=Revert Changes
+
 # LOCALIZATION NOTE  (scratchpadIntro): This is a multi-line comment explaining
 # how to use the Scratchpad. Note that this should be a valid JavaScript
 # comment inside /* and */.
 scratchpadIntro1=/*\n * This is a JavaScript Scratchpad.\n *\n * Enter some JavaScript, then Right Click or choose from the Execute Menu:\n * 1. Run to evaluate the selected text (%1$S),\n * 2. Inspect to bring up an Object Inspector on the result (%2$S), or,\n * 3. Display to insert the result in a comment after the selection. (%3$S)\n */\n\n
 
 # LOCALIZATION NOTE  (notification.browserContext): This is the message displayed
 # over the top of the editor when the user has switched to browser context.
 browserContext.notification=This scratchpad executes in the Browser context.
--- a/browser/themes/gnomestripe/browser.css
+++ b/browser/themes/gnomestripe/browser.css
@@ -2483,18 +2483,17 @@ html|*#gcli-output-frame {
 
 .gcli-in-closebrace {
   color: hsl(0,0%,80%);
 }
 
 /* Responsive Mode */
 
 .browserContainer[responsivemode] {
-  background: #2a3643 url("chrome://browser/skin/devtools/responsive-background.png");
-  box-shadow: 0 0 7px black inset;
+  background: #222 url("chrome://browser/skin/devtools/responsive-background.png");
   padding: 0 20px 20px 20px;
 }
 
 .browserStack[responsivemode] {
   box-shadow: 0 0 7px black;
 }
 
 .devtools-responsiveui-toolbar {
@@ -2709,16 +2708,20 @@ html|*#gcli-output-frame {
   cursor: pointer;
   min-width: 0;
   margin: 0 6px;
 }
 #social-statusarea-username:hover {
   text-decoration: underline;
 }
 
+.social-panel > .panel-arrowcontainer > .panel-arrowcontent {
+  padding: 0;
+}
+
 .chat-status-icon {
   max-height: 16px;
   max-width: 16px;
   padding: 0;
 }
 
 .chat-toolbarbutton {
   -moz-appearance: none;
@@ -2728,87 +2731,75 @@ html|*#gcli-output-frame {
   background: none;
 }
 
 .chat-toolbarbutton > .toolbarbutton-text {
   display: none;
 }
 
 .chat-close-button {
-  list-style-image: url("chrome://global/skin/icons/close.png");
-  -moz-image-region: rect(0, 16px, 16px, 0);
-}
-
-.chat-close-button:hover {
-  -moz-image-region: rect(0, 32px, 16px, 16px);
+  list-style-image: url('chrome://browser/skin/social/chat-close.png');
+  -moz-image-region: rect(0, 14px, 14px, 0);
 }
 
 .chat-close-button:hover:active {
-  -moz-image-region: rect(0, 48px, 16px, 32px);
-}
-
-
-.chat-toggle-button {
-  /* XXX get a real image for this */
-  list-style-image: url("chrome://global/skin/icons/checkbox.png");
-  -moz-image-region: rect(0, 32px, 16px, 16px);
-}
-
-.chat-toggle-button:hover {
-  -moz-image-region: rect(0, 16px, 16px, 0);
-}
-
-.chat-toggle-button[minimized="true"] {
-  -moz-image-region: rect(0, 16px, 16px, 0);
-}
-
-.chat-toggle-button[minimized="true"]:hover {
-  -moz-image-region: rect(0, 32px, 16px, 16px);
+  -moz-image-region: rect(14px, 14px, 28px, 0);
+}
+
+.chat-close-button:hover {
+  -moz-image-region: rect(28px, 14px, 42px, 0);
 }
 
 .chat-title {
   font-weight: bold;
   color: -moz-dialogtext;
+  cursor: inherit;
 }
 
 .chat-titlebar {
-  background-image: linear-gradient(white, #ddd);
+  background-color: #d9d9d9;
   height: 20px;
   min-height: 20px;
   width: 100%;
   margin: 0;
   padding: 2px;
   border: none;
   border-bottom: 1px solid gray;
+  cursor: pointer;
 }
 
 .chat-titlebar[minimized="true"] {
   border-bottom: none;
 }
 
+.chat-titlebar[selected] {
+  background-color: #f0f0f0;
+}
+
 .chat-frame {
   padding: 0;
   margin: 0;
   overflow: hidden;
 }
 
 .chatbar-button {
-  /* XXX get a real image for this */
+  background-color: #d9d9d9;
   list-style-image: url("chrome://browser/skin/social/social.png");
   border: none;
   margin: 0;
   padding: 2px;
   height: 21px;
   width: 21px;
   border-top: 1px solid gray;
   -moz-border-end: 1px solid gray;
 }
 
 .chatbar-button[open="true"],
 .chatbar-button:active:hover {
+  background-color: #f0f0f0;
   box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2);
 }
 
 .chatbar-button > .toolbarbutton-text,
 .chatbar-button > .toolbarbutton-menu-dropmarker {
   display: none;
 }
 
--- a/browser/themes/gnomestripe/jar.mn
+++ b/browser/themes/gnomestripe/jar.mn
@@ -80,16 +80,18 @@ browser.jar:
   skin/classic/browser/preferences/Options.png        (preferences/Options.png)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/preferences/Options-sync.png   (preferences/Options-sync.png)
 #endif
 * skin/classic/browser/preferences/preferences.css    (preferences/preferences.css)
   skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
   skin/classic/browser/preferences/applications.css   (preferences/applications.css)
   skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
+  skin/classic/browser/social/social.png              (social/social.png)
+  skin/classic/browser/social/chat-close.png          (social/chat-close.png)
   skin/classic/browser/tabbrowser/alltabs.png          (tabbrowser/alltabs.png)
   skin/classic/browser/tabbrowser/connecting.png      (tabbrowser/connecting.png)
   skin/classic/browser/tabbrowser/loading.png         (tabbrowser/loading.png)
   skin/classic/browser/tabbrowser/tab.png             (tabbrowser/tab.png)
   skin/classic/browser/tabbrowser/tab-overflow-border.png (tabbrowser/tab-overflow-border.png)
   skin/classic/browser/tabbrowser/tabDragIndicator.png (tabbrowser/tabDragIndicator.png)
   skin/classic/browser/tabview/edit-light.png         (tabview/edit-light.png)
   skin/classic/browser/tabview/search.png             (tabview/search.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..cc773cbc1c45756ba57d191fa2c41afedd420287
GIT binary patch
literal 405
zc$@*10c!q<P)<h;3K|Lk000e1NJLTq000gE001fo1^@s6gLP{000048Nkl<ZNQuSL
zv2KGf5C-6T6~6+{<Iy`y9)Ju{mMmdO=15ggQ&sI7bgMhFm<hZC@`l86oh%vqOlVqB
zBrnFF?Emfn{_RsraUlekQgZ*Fl#*{Yn_wRR0E7@6061f8f~Az4F~$LaXIU0v_khLj
zUXmodEKB-UA1^`_UW_Q{XTFP)BndJvLS<QUs$c=M?eH_XnCzueil=Ex7Gib)*88fe
z!bjHcVO`f~j6tn+a7b&7UDwgj7=wt2<2X*R)*8ogL`1~4ZA0iDu-M%jh5?(Vp)>k8
zhREW$IEEHMKl9zE4#N;+CLvV_s$kJ}_?cYn_Hr@+eczLXm>q!iX7%Iq3m;j3125Mi
zda!tTrQzjT;C8pi!!Zxg;h5ugx5wMtJAI=);9|R%pU(3cT^vKv;#d$viy)PG_Nn>l
zOlF=fj{CeA4%$Iwve@nA<i@Ax+s_AJwORcJc}JrE9N5!t00000NkvXXu0mjf@*cTh
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..14916fb074c79b18c732f41661110027983c5d03
GIT binary patch
literal 1654
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4(FKV3x~tb`J1#c2)=|%1_J8No8QD
zU~CP!pM5z%<ln8-xf>pKTcrzf+GR}4O9?J<p0h1GeAn5iS7zH4J!UV<jhS^!<!#*t
z_M6@LR!7S}vfKS+=egOOUp<NUm`LJ}H62Pm-;_4bn{DE&9?ZP+`|kTc-&v|Ze-gLg
zXJOQ35q*A<&|v?ghyEOSB69x|W1Nh$_MZ&j?lv(mHFpR8%bP0SZ~n|DcPyvmUH<*m
z|B|oHn#v>iK}KQU6w@P04<i?3##|{8esHbx{ai!SLuY-8BbKvp%BNf8CpSLq=~bSV
zW@73vi}MisQ70?!9S#;VG9P^Ay>YHl<cYf4%1L_NHmhBO^q;1<ZsPg&LB6>8Q~ZV%
zQ(n$q`&^(mcb25z#hR{r@|Lv)J<k}f9<+~_*z34sZb5|E<He?RPG?ot399XV{;ni*
z#@p0c(<3#7RAp55B!#Rq&dGen&D=Tb+2yxi0w!&kZ?fOSIP&x3w*gaGUK<|$`pw(z
z%dWlGZ%>@e{@B!Rx#qQchktv&e=X{IS~=;%>n(D<SHDD0vI@I>`^nj5%7>4X2ih}x
zI5qXjq-Z>t=){vaEBVN(A3kD|N?uDYWtN0gZnC&$tucApTN(F?lkt2z;>5m2Z`$$g
z!S8u@Rj>E$5p6xXeesRJw_6)u?%E_gOS--^ap~f`hvqw<&X&$y&CL<`uj}bXkGRxR
z{!M|`OU<552%i3@l>5BVly|CSLJ>T>)_jQjp_Rks-zt7+`u-~?&fQxpc)Fob$8Pn!
z;Iqfxs9y59^g3n7{;sc1$0UAEFm#TZyHCY;PM77UTLIkaY~@X_pFN7vzs^2y_nFv-
zzjku0)hkl&J0$+qwMOy7KI46M@!Rt|5|`iJcRA(tV#BD{yDv}NwP<72l>Xc5-)#F<
z2}^F;xNl=!7>D+jMafxSn@ju|m{oKArhjIg{9dqyyQX8EZfF1Mr0@y;+FbcFdE%z?
zc&_)jskgzp|1I;j;A^qZGUJOaKWi1dl>U48wVrt>Tb$&9i^Y243ye#jaBirH(|Ij=
zTi#~g_J)bpx0Bb}<|gjywhLQ*yF@Qqj&V2t>Z|k5uey5o`ICC?ip77}Rw{E{JG3y~
z2$*3w3p^r=85p>QL70(Y)*J~21}3@8kcg59UmvUF{9L`nl>DSry^7od1`x2RumUo3
zQ%e#RDspr3imfVamB1>jfNYSkzLEl1NlCV?QiN}Sf^&XRs)C80iJpOy9hZWFf=y9M
znpKdC8&o@xXRDM^Qc_^0uU}qXu2*iXmtT~wZ)j<0sc&GUZ)BtkRH0j3nOBlnp_^B%
z3^4>|j!SBBa#3bMNoIbY0*IfOlwVq6tE2=~0|5|=Qj+2J6qFX_fNe=h)=$kz%}vcK
zDb_dCGt`G04l+L_*{ZlSDJwO(#16$cu*FV^>H5fu^)Xf3=%cxiT$cj_6Yli9VrYB-
zV@AIsu>k0715{@NRa-e1r6!i-7lq{K=h!)dlq-1VCZ?wbr6#6S+UTRJ!0TR!C@{Y4
zxPUrgac{>JuxDWiFgaZDba4!^5ZpS+k*nE2!1eg0&o^3yHs09FE&eET(-v_d@p%gu
zU164NUFp0s<xxlLrACKOm6e_+Dmpm!U#ZM54iq^P6FOCW<Ghk)oAZXJjgFc7GMC2Y
zt32YpWYStWZ)SthN|%EszCzk*{{<zIk6d2zL8Ue7wYBFP$C*dEnpkztS~S^e{EjN{
z7TV&!+%mPVq2=i^{&j^tUYhN-`oH(JyWidvm(ZlqWx&H!_*uP%kGUjvyVni#xElGs
zss@SOGY-79lfE;1v${^ePIjxGr*<;6TgUBVI3k(zKwqXI1(e=BUHx3vIVCg!0Cn%A
AApigX
--- a/browser/themes/pinstripe/browser.css
+++ b/browser/themes/pinstripe/browser.css
@@ -3201,18 +3201,17 @@ html|*#gcli-output-frame {
 
 .gcli-in-closebrace {
   color: hsl(0,0%,80%);
 }
 
 /* Responsive Mode */
 
 .browserContainer[responsivemode] {
-  background: #2a3643 url("chrome://browser/skin/devtools/responsive-background.png");
-  box-shadow: 0 0 7px black inset;
+  background: #222 url("chrome://browser/skin/devtools/responsive-background.png");
   padding: 0 20px 20px 20px;
 }
 
 .browserStack[responsivemode] {
   box-shadow: 0 0 7px black;
 }
 
 .devtools-responsiveui-toolbar {
@@ -3393,16 +3392,25 @@ html|*#gcli-output-frame {
   min-width: 0;
   margin: 0 6px;
 }
 
 #social-statusarea-username:hover {
   text-decoration: underline;
 }
 
+.social-panel > .panel-arrowcontainer > .panel-arrowcontent {
+  padding: 0;
+}
+
+#social-notification-box,
+.social-panel-frame {
+  border-radius: inherit;
+}
+
 /* === end of social toolbar provider menu === */
 
 .chat-status-icon {
   max-height: 16px;
   max-width: 16px;
   padding: 0;
 }
 
@@ -3414,88 +3422,75 @@ html|*#gcli-output-frame {
   background: none;
 }
 
 .chat-toolbarbutton > .toolbarbutton-text {
   display: none;
 }
 
 .chat-close-button {
-  list-style-image: url("chrome://global/skin/icons/close.png");
-  -moz-image-region: rect(0, 16px, 16px, 0);
-}
-
-.chat-close-button:hover {
-  -moz-image-region: rect(0, 32px, 16px, 16px);
+  list-style-image: url('chrome://browser/skin/social/chat-close.png');
+  -moz-image-region: rect(0, 14px, 14px, 0);
 }
 
 .chat-close-button:hover:active {
-  -moz-image-region: rect(0, 48px, 16px, 32px);
-}
-
-
-.chat-toggle-button {
-  /* XXX get a real image for this */
-  list-style-image: url("chrome://global/skin/icons/checkbox.png");
-  -moz-image-region: rect(0, 32px, 16px, 16px);
-}
-
-.chat-toggle-button:hover {
-  -moz-image-region: rect(0, 16px, 16px, 0);
-}
-
-.chat-toggle-button[minimized="true"] {
-  -moz-image-region: rect(0, 16px, 16px, 0);
-}
-
-.chat-toggle-button[minimized="true"]:hover {
-  -moz-image-region: rect(0, 32px, 16px, 16px);
+  -moz-image-region: rect(14px, 14px, 28px, 0);
+}
+
+.chat-close-button:hover {
+  -moz-image-region: rect(28px, 14px, 42px, 0);
 }
 
 .chat-title {
   font-weight: bold;
   color: -moz-dialogtext;
+  cursor: inherit;
 }
 
 .chat-titlebar {
-  background-image: linear-gradient(white, #ddd);
+  background-color: #d9d9d9;
   height: 20px;
   min-height: 20px;
   width: 100%;
   margin: 0;
   padding: 2px;
   border: none;
   border-bottom: 1px solid #404040;
+  cursor: pointer;
 }
 
 .chat-titlebar[minimized="true"] {
   border-bottom: none;
 }
 
+.chat-titlebar[selected] {
+  background-color: #f0f0f0;
+}
+
 .chat-frame {
   padding: 0;
   margin: 0;
   overflow: hidden;
 }
 
 .chatbar-button {
-  /* XXX get a real image for this */
-  background-image: linear-gradient(white, #ddd);
+  background-color: #d9d9d9;
   list-style-image: url("chrome://browser/skin/social/social.png");
   border: none;
   margin: 0;
   padding: 2px;
   height: 21px;
   width: 21px;
   border-top: 1px solid #404040;
   -moz-border-end: 1px solid #404040;
 }
 
 .chatbar-button[open="true"],
 .chatbar-button:active:hover {
+  background-color: #f0f0f0;
   box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2);
 }
 
 .chatbar-button > .toolbarbutton-text,
 .chatbar-button > .toolbarbutton-menu-dropmarker {
   display: none;
 }
 
--- a/browser/themes/pinstripe/jar.mn
+++ b/browser/themes/pinstripe/jar.mn
@@ -103,16 +103,17 @@ browser.jar:
   skin/classic/browser/preferences/Options-sync.png         (preferences/Options-sync.png)
 #endif
   skin/classic/browser/preferences/saveFile.png             (preferences/saveFile.png)
 * skin/classic/browser/preferences/preferences.css          (preferences/preferences.css)
   skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
   skin/classic/browser/preferences/applications.css         (preferences/applications.css)
   skin/classic/browser/preferences/aboutPermissions.css     (preferences/aboutPermissions.css)
   skin/classic/browser/social/social.png                    (social/social.png)
+  skin/classic/browser/social/chat-close.png                             (social/chat-close.png)
   skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon.png             (tabbrowser/alltabs-box-bkgnd-icon.png)
   skin/classic/browser/tabbrowser/newtab.png                             (tabbrowser/newtab.png)
   skin/classic/browser/tabbrowser/connecting.png                         (tabbrowser/connecting.png)
   skin/classic/browser/tabbrowser/loading.png                            (tabbrowser/loading.png)
   skin/classic/browser/tabbrowser/tab-arrow-left.png                     (tabbrowser/tab-arrow-left.png)
   skin/classic/browser/tabbrowser/tab-arrow-right.png                    (tabbrowser/tab-arrow-right.png)
   skin/classic/browser/tabbrowser/tabbar-bottom-bg-active.png            (tabbrowser/tabbar-bottom-bg-active.png)
   skin/classic/browser/tabbrowser/tabbar-bottom-bg-inactive.png          (tabbrowser/tabbar-bottom-bg-inactive.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..cc773cbc1c45756ba57d191fa2c41afedd420287
GIT binary patch
literal 405
zc$@*10c!q<P)<h;3K|Lk000e1NJLTq000gE001fo1^@s6gLP{000048Nkl<ZNQuSL
zv2KGf5C-6T6~6+{<Iy`y9)Ju{mMmdO=15ggQ&sI7bgMhFm<hZC@`l86oh%vqOlVqB
zBrnFF?Emfn{_RsraUlekQgZ*Fl#*{Yn_wRR0E7@6061f8f~Az4F~$LaXIU0v_khLj
zUXmodEKB-UA1^`_UW_Q{XTFP)BndJvLS<QUs$c=M?eH_XnCzueil=Ex7Gib)*88fe
z!bjHcVO`f~j6tn+a7b&7UDwgj7=wt2<2X*R)*8ogL`1~4ZA0iDu-M%jh5?(Vp)>k8
zhREW$IEEHMKl9zE4#N;+CLvV_s$kJ}_?cYn_Hr@+eczLXm>q!iX7%Iq3m;j3125Mi
zda!tTrQzjT;C8pi!!Zxg;h5ugx5wMtJAI=);9|R%pU(3cT^vKv;#d$viy)PG_Nn>l
zOlF=fj{CeA4%$Iwve@nA<i@Ax+s_AJwORcJc}JrE9N5!t00000NkvXXu0mjf@*cTh
--- a/browser/themes/winstripe/browser.css
+++ b/browser/themes/winstripe/browser.css
@@ -3163,18 +3163,17 @@ html|*#gcli-output-frame {
 
 .gcli-in-closebrace {
   color: hsl(0,0%,80%);
 }
 
 /* Responsive Mode */
 
 .browserContainer[responsivemode] {
-  background: #2a3643 url("chrome://browser/skin/devtools/responsive-background.png");
-  box-shadow: 0 0 7px black inset;
+  background: #222 url("chrome://browser/skin/devtools/responsive-background.png");
   padding: 0 20px 20px 20px;
 }
 
 .browserStack[responsivemode] {
   box-shadow: 0 0 7px black;
 }
 
 .devtools-responsiveui-toolbar {
@@ -3412,16 +3411,25 @@ html|*#gcli-output-frame {
   cursor: pointer;
   min-width: 0;
   margin: 0 6px;
 }
 #social-statusarea-username:hover {
   text-decoration: underline;
 }
 
+.social-panel > .panel-arrowcontainer > .panel-arrowcontent {
+  padding: 0;
+}
+
+#social-notification-box,
+.social-panel-frame {
+  border-radius: inherit;
+}
+
 .chat-status-icon {
   max-height: 16px;
   max-width: 16px;
   padding: 0;
 }
 
 .chat-toolbarbutton {
   -moz-appearance: none;
@@ -3431,77 +3439,65 @@ html|*#gcli-output-frame {
   background: none;
 }
 
 .chat-toolbarbutton > .toolbarbutton-text {
   display: none;
 }
 
 .chat-close-button {
-  list-style-image: url("chrome://global/skin/icons/Close.gif");
-  padding: 2px 4px;
-}
-
-.chat-close-button:hover {
-  box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2);
+  list-style-image: url('chrome://browser/skin/social/chat-close.png');
+  -moz-image-region: rect(0, 14px, 14px, 0);
 }
 
 .chat-close-button:hover:active {
-  box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2);
-}
-
-
-.chat-toggle-button {
-  /* XXX get a real image for this */
-  list-style-image: url("chrome://global/skin/icons/expand.png");
-}
-
-.chat-toggle-button:hover {
-  box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2);
-}
-
-.chat-toggle-button[minimized="true"] {
-  list-style-image: url("chrome://global/skin/icons/collapse.png");
-}
-
-.chat-toggle-button[minimized="true"]:hover {
-  box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2);
+  -moz-image-region: rect(14px, 14px, 28px, 0);
+}
+
+.chat-close-button:hover {
+  -moz-image-region: rect(28px, 14px, 42px, 0);
 }
 
 .chat-title {
   font-weight: bold;
   color: -moz-dialogtext;
+  cursor: inherit;
 }
 
 .chat-titlebar {
-  background-image: linear-gradient(white, #ddd);
+  background-color: #c4cfde;
   height: 20px;
   min-height: 20px;
   width: 100%;
   margin: 0;
   padding: 2px;
   border: none;
   border-bottom: 1px solid gray;
+  cursor: pointer;
 }
 
 .chat-titlebar[minimized="true"] {
   border-bottom: none;
 }
 
+.chat-titlebar[selected] {
+  background-color: #dae3f0;
+}
+
 .chat-frame {
   padding: 0;
   margin: 0;
   overflow: hidden;
 }
 
 .chatbar-button {
   /* XXX get a real image for this */
   -moz-appearance: none;
   list-style-image: url("chrome://browser/skin/social/social.png");
-  background-image: linear-gradient(white, #ddd);
+  background-color: #c4cfde;
   border: none;
   margin: 0;
   padding: 2px;
   height: 21px;
   width: 21px;
   border-top: 1px solid gray;
   -moz-border-end: 1px solid gray;
 }
@@ -3510,16 +3506,17 @@ html|*#gcli-output-frame {
   max-height: 16px;
   max-width: 16px;
   padding: 2px;
 }
 
 .chatbar-button[open="true"],
 .chatbar-button:hover,
 .chatbar-button:active:hover {
+  background-color: #dae3f0;
   box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2);
 }
 
 .chatbar-button > .toolbarbutton-text,
 .chatbar-button > .toolbarbutton-menu-dropmarker {
   display: none;
 }
 
--- a/browser/themes/winstripe/jar.mn
+++ b/browser/themes/winstripe/jar.mn
@@ -100,16 +100,17 @@ browser.jar:
         skin/classic/browser/preferences/Options-sync.png            (preferences/Options-sync.png)
 #endif
         skin/classic/browser/preferences/saveFile.png                (preferences/saveFile.png)
 *       skin/classic/browser/preferences/preferences.css             (preferences/preferences.css)
         skin/classic/browser/preferences/in-content/preferences.css  (preferences/in-content/preferences.css)
         skin/classic/browser/preferences/applications.css            (preferences/applications.css)
         skin/classic/browser/preferences/aboutPermissions.css        (preferences/aboutPermissions.css)
         skin/classic/browser/social/social.png                       (social/social.png)
+        skin/classic/browser/social/chat-close.png                   (social/chat-close.png)
         skin/classic/browser/tabbrowser/alltabs.png                  (tabbrowser/alltabs.png)
         skin/classic/browser/tabbrowser/newtab.png                   (tabbrowser/newtab.png)
         skin/classic/browser/tabbrowser/newtab-inverted.png          (tabbrowser/newtab-inverted.png)
         skin/classic/browser/tabbrowser/connecting.png               (tabbrowser/connecting.png)
         skin/classic/browser/tabbrowser/loading.png                  (tabbrowser/loading.png)
         skin/classic/browser/tabbrowser/tab.png                      (tabbrowser/tab.png)
         skin/classic/browser/tabbrowser/tab-arrow-left.png           (tabbrowser/tab-arrow-left.png)
         skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png  (tabbrowser/tab-arrow-left-inverted.png)
@@ -302,16 +303,17 @@ browser.jar:
         skin/classic/aero/browser/preferences/Options-sync.png       (preferences/Options-sync.png)
 #endif
         skin/classic/aero/browser/preferences/saveFile.png           (preferences/saveFile-aero.png)
 *       skin/classic/aero/browser/preferences/preferences.css        (preferences/preferences.css)
         skin/classic/aero/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
         skin/classic/aero/browser/preferences/applications.css       (preferences/applications.css)
         skin/classic/aero/browser/preferences/aboutPermissions.css   (preferences/aboutPermissions.css)
         skin/classic/aero/browser/social/social.png                  (social/social.png)
+        skin/classic/aero/browser/social/chat-close.png              (social/chat-close.png)
         skin/classic/aero/browser/tabbrowser/alltabs.png             (tabbrowser/alltabs.png)
         skin/classic/aero/browser/tabbrowser/newtab.png              (tabbrowser/newtab.png)
         skin/classic/aero/browser/tabbrowser/newtab-inverted.png     (tabbrowser/newtab-inverted.png)
         skin/classic/aero/browser/tabbrowser/connecting.png          (tabbrowser/connecting.png)
         skin/classic/aero/browser/tabbrowser/loading.png             (tabbrowser/loading.png)
         skin/classic/aero/browser/tabbrowser/tab.png                 (tabbrowser/tab.png)
         skin/classic/aero/browser/tabbrowser/tab-arrow-left.png      (tabbrowser/tab-arrow-left.png)
         skin/classic/aero/browser/tabbrowser/tab-arrow-left-inverted.png (tabbrowser/tab-arrow-left-inverted.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5260f1019668f8c9c6c64f1ec61f5346609cc5c3
GIT binary patch
literal 419
zc$@*F0bKrxP)<h;3K|Lk000e1NJLTq000gE001fo1^@s6gLP{00004MNkl<ZNQuSL
zu}T9$5C-6Rm3@YLopiY_fhz{C2x$(|q>)%C5>T+w7w{1jwowljlx+kthf#uPLdcP3
ztQPimXD?@z^I)63{n-6y2H@X5tu@ARj8aN;|4At^O;c|l0081RMgTw|L<LJJQ3!zm
zfLWIL*gjyey%$9hDy8^aUEBz9xH00On|UXSqR7kK2q~qIE0_mOJKRh*W_zi%#w1DD
zLevgGd*Ai_6YaBp4{sk{Xueodt_$x_t_zyIee!U=SP~J@rrcI=t_#|fTOuNQe*N&F
zeZXLQZ?!Jz?s3Lvba4o=#j$Y+HG*#Doljk@OD{7Exk7LS3%0|}WMjLRl>sQ;zt}?5
z4nTXedU<t2`>el#<H<C5@bKiEhvUhVI^90?aOj~P4yn`a)A8vAf1^2IW3z{YQGG@m
zhfuIM282)}$Yrj5Dh@_$=Gx-e&kJF%9b6`Z?Os-HJUBZ0`2e&wtKW~RZs^>=v#$UE
N002ovPDHLkV1mgzxiJ6$
--- a/build/autoconf/compiler-opts.m4
+++ b/build/autoconf/compiler-opts.m4
@@ -29,27 +29,23 @@ case "$target" in
     if test -z "$MIDL"; then MIDL=midl; fi
 
     # need override this flag since we don't use $(LDFLAGS) for this.
     if test -z "$HOST_LDFLAGS" ; then
         HOST_LDFLAGS=" "
     fi
     ;;
 *-darwin*)
-    # we prefer gcc-4.2 over gcc on older darwin, so
-    # use that specific version if it's available.
-    # On newer versions of darwin, gcc is llvm-gcc while gcc-4.2 is the plain
-    # one, so we also try that first. If that fails, we fall back to clang
-    # as llvm-gcc is an unsupported dead end.
-    MOZ_PATH_PROGS(CC, $CC gcc-4.2 clang gcc)
-    MOZ_PATH_PROGS(CXX, $CXX g++-4.2 clang++ g++)
-    IS_LLVM_GCC=$($CC -v 2>&1 | grep llvm-gcc)
-    if test -n "$IS_LLVM_GCC"
+    # GCC on darwin is based on gcc 4.2 and we don't support it anymore.
+    MOZ_PATH_PROGS(CC, $CC clang)
+    MOZ_PATH_PROGS(CXX, $CXX clang++)
+    IS_GCC=$($CC -v 2>&1 | grep gcc)
+    if test -n "$IS_GCC"
     then
-      echo llvm-gcc is known to be broken, please use gcc-4.2 or clang.
+      echo gcc is known to be broken on OS X, please use clang.
       exit 1
     fi
     ;;
 esac
 fi
 ])
 
 dnl ============================================================================
deleted file mode 100644
--- a/build/mobile/b2gemulator.py
+++ /dev/null
@@ -1,87 +0,0 @@
-# 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/.
-
-
-import os
-import platform
-
-from emulator import Emulator
-
-
-class B2GEmulator(Emulator):
-
-    def __init__(self, homedir=None, noWindow=False, logcat_dir=None, arch="x86",
-                 emulatorBinary=None, res='480x800', userdata=None,
-                 memory='512', partition_size='512'):
-        super(B2GEmulator, self).__init__(noWindow=noWindow, logcat_dir=logcat_dir,
-                                          arch=arch, emulatorBinary=emulatorBinary,
-                                          res=res, userdata=userdata,
-                                          memory=memory, partition_size=partition_size)
-        self.homedir = homedir
-        if self.homedir is not None:
-            self.homedir = os.path.expanduser(homedir)
-
-    def _check_file(self, filePath):
-        if not os.path.exists(filePath):
-            raise Exception(('File not found: %s; did you pass the B2G home '
-                             'directory as the homedir parameter, or set '
-                             'B2G_HOME correctly?') % filePath)
-
-    def _check_for_adb(self, host_dir):
-        if self._default_adb() == 0:
-            return
-        adb_paths = [os.path.join(self.homedir,'glue','gonk','out','host',
-                      host_dir ,'bin','adb'),os.path.join(self.homedir, 'out',
-                      'host', host_dir,'bin','adb'),os.path.join(self.homedir,
-                      'bin','adb')]
-        for option in adb_paths:
-            if os.path.exists(option):
-                self.adb = option
-                return
-        raise Exception('adb not found!')
-
-    def _locate_files(self):
-        if self.homedir is None:
-            self.homedir = os.getenv('B2G_HOME')
-        if self.homedir is None:
-            raise Exception('Must define B2G_HOME or pass the homedir parameter')
-        self._check_file(self.homedir)
-
-        if self.arch not in ("x86", "arm"):
-            raise Exception("Emulator architecture must be one of x86, arm, got: %s" %
-                            self.arch)
-
-        host_dir = "linux-x86"
-        if platform.system() == "Darwin":
-            host_dir = "darwin-x86"
-
-        host_bin_dir = os.path.join("out", "host", host_dir, "bin")
-
-        if self.arch == "x86":
-            binary = os.path.join(host_bin_dir, "emulator-x86")
-            kernel = "prebuilts/qemu-kernel/x86/kernel-qemu"
-            sysdir = "out/target/product/generic_x86"
-            self.tail_args = []
-        else:
-            binary = os.path.join(host_bin_dir, "emulator")
-            kernel = "prebuilts/qemu-kernel/arm/kernel-qemu-armv7"
-            sysdir = "out/target/product/generic"
-            self.tail_args = ["-cpu", "cortex-a8"]
-
-        self._check_for_adb(host_dir)
-
-        if not self.binary:
-            self.binary = os.path.join(self.homedir, binary)
-
-        self._check_file(self.binary)
-
-        self.kernelImg = os.path.join(self.homedir, kernel)
-        self._check_file(self.kernelImg)
-
-        self.sysDir = os.path.join(self.homedir, sysdir)
-        self._check_file(self.sysDir)
-
-        if not self.dataImg:
-            self.dataImg = os.path.join(self.sysDir, 'userdata.img')
-        self._check_file(self.dataImg)
deleted file mode 100644
--- a/build/mobile/devicemanager-utils.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# 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/.
-
-import devicemanager
-import sys
-import os
-
-def copy(dm, gre_path):
-    file = sys.argv[2]
-    if len(sys.argv) >= 4:
-        path = sys.argv[3]
-    else:
-        path = gre_path
-    if os.path.isdir(file):
-        dm.pushDir(file, path.replace('\\','/'))
-    else:
-        dm.pushFile(file, path.replace('\\','/'))
-
-def delete(dm, gre_path):
-    file = sys.argv[2]
-    dm.removeFile(file)
-
-def main():
-    ip_addr = os.environ.get("DEVICE_IP")
-    port = os.environ.get("DEVICE_PORT")
-    gre_path = os.environ.get("REMOTE_GRE_PATH").replace('\\','/')
-
-    if ip_addr == None:
-        print "Error: please define the environment variable DEVICE_IP before running this test"
-        sys.exit(1)
-
-    if port == None:
-        print "Error: please define the environment variable DEVICE_PORT before running this test"
-        sys.exit(1)
-
-    if gre_path == None:
-        print "Error: please define the environment variable REMOTE_GRE_PATH before running this test"
-        sys.exit(1)
-
-    dm = devicemanager.DeviceManager(ip_addr, int(port))
-    dm.sendCMD(['cd '+ gre_path], sleep = 1)
-    dm.debug = 0
-
-    if len(sys.argv) < 3:
-        print "usage: python devicemanager-utils.py <cmd> <path> [arg]"
-    cmd = sys.argv[1]
-    if (cmd == 'copy'):
-        sys.exit(copy(dm, gre_path))
-    if (cmd == 'delete'):
-        sys.exit(delete(dm, gre_path))
-    print "unrecognized command. supported commands are copy and delete"
-    sys.exit(-1)
-
-if __name__ == '__main__':
-    main()
deleted file mode 100755
--- a/build/mobile/devicemanager.py
+++ /dev/null
@@ -1,637 +0,0 @@
-# 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/.
-
-import hashlib
-import socket
-import os
-import re
-import StringIO
-
-class FileError(Exception):
-  " Signifies an error which occurs while doing a file operation."
-
-  def __init__(self, msg = ''):
-    self.msg = msg
-
-  def __str__(self):
-    return self.msg
-
-class DMError(Exception):
-  "generic devicemanager exception."
-
-  def __init__(self, msg= ''):
-    self.msg = msg
-
-  def __str__(self):
-    return self.msg
-
-def abstractmethod(method):
-  line = method.func_code.co_firstlineno
-  filename = method.func_code.co_filename
-  def not_implemented(*args, **kwargs):
-    raise NotImplementedError('Abstract method %s at File "%s", line %s '
-                              'should be implemented by a concrete class' %
-                              (repr(method), filename,line))
-  return not_implemented
-
-class DeviceManager:
-
-  @abstractmethod
-  def shell(self, cmd, outputfile, env=None, cwd=None, timeout=None):
-    """
-    executes shell command on device
-
-    timeout is specified in seconds, and if no timeout is given, 
-    we will run until the script returns
-    returns:
-    success: Return code from command
-    failure: None
-    """
-
-  @abstractmethod
-  def pushFile(self, localname, destname):
-    """
-    external function
-    returns:
-    success: True
-    failure: False
-    """
-
-  @abstractmethod
-  def mkDir(self, name):
-    """
-    external function
-    returns:
-    success: directory name
-    failure: None
-    """
-
-  def mkDirs(self, filename):
-    """
-    make directory structure on the device
-    WARNING: does not create last part of the path
-    external function
-    returns:
-    success: directory structure that we created
-    failure: None
-    """
-    parts = filename.split('/')
-    name = ""
-    for part in parts:
-        if (part == parts[-1]): break
-        if (part != ""):
-            name += '/' + part
-            if (not self.dirExists(name)):
-                if (self.mkDir(name) == None):
-                    print "failed making directory: " + str(name)
-                    return None
-    return name
-
-  @abstractmethod
-  def pushDir(self, localDir, remoteDir):
-    """
-    push localDir from host to remoteDir on the device
-    external function
-    returns:
-    success: remoteDir
-    failure: None
-    """
-
-  @abstractmethod
-  def dirExists(self, dirname):
-    """
-    external function
-    returns:
-    success: True
-    failure: False
-    """
-
-  @abstractmethod
-  def fileExists(self, filepath):
-    """
-    Because we always have / style paths we make this a lot easier with some
-    assumptions
-    external function
-    returns:
-    success: True
-    failure: False
-    """
-
-  @abstractmethod
-  def listFiles(self, rootdir):
-    """
-    list files on the device, requires cd to directory first
-    external function
-    returns:
-    success: array of filenames, ['file1', 'file2', ...]
-    failure: None
-    """
-
-  @abstractmethod
-  def removeFile(self, filename):
-    """
-    external function
-    returns:
-    success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
-    failure: None
-    """
-
-  @abstractmethod
-  def removeDir(self, remoteDir):
-    """
-    does a recursive delete of directory on the device: rm -Rf remoteDir
-    external function
-    returns:
-    success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
-    failure: None
-    """
-
-  @abstractmethod
-  def getProcessList(self):
-    """
-    external function
-    returns:
-    success: array of process tuples
-    failure: None
-    """
-
-  @abstractmethod
-  def fireProcess(self, appname, failIfRunning=False):
-    """
-    external function
-    DEPRECATED: Use shell() or launchApplication() for new code
-    returns:
-    success: pid
-    failure: None
-    """
-
-  @abstractmethod
-  def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = '', failIfRunning=False):
-    """
-    external function
-    DEPRECATED: Use shell() or launchApplication() for new code
-    returns:
-    success: output filename
-    failure: None
-    """
-
-  def processExist(self, appname):
-    """
-    iterates process list and returns pid if exists, otherwise None
-    external function
-    returns:
-    success: pid
-    failure: None
-    """
-
-    pid = None
-
-    #filter out extra spaces
-    parts = filter(lambda x: x != '', appname.split(' '))
-    appname = ' '.join(parts)
-
-    #filter out the quoted env string if it exists
-    #ex: '"name=value;name2=value2;etc=..." process args' -> 'process args'
-    parts = appname.split('"')
-    if (len(parts) > 2):
-      appname = ' '.join(parts[2:]).strip()
-
-    pieces = appname.split(' ')
-    parts = pieces[0].split('/')
-    app = parts[-1]
-
-    procList = self.getProcessList()
-    if (procList == []):
-      return None
-
-    for proc in procList:
-      procName = proc[1].split('/')[-1]
-      if (procName == app):
-        pid = proc[0]
-        break
-    return pid
-
-
-  @abstractmethod
-  def killProcess(self, appname, forceKill=False):
-    """
-    external function
-    returns:
-    success: True
-    failure: False
-    """
-
-  @abstractmethod
-  def catFile(self, remoteFile):
-    """
-    external function
-    returns:
-    success: filecontents
-    failure: None
-    """
-
-  @abstractmethod
-  def pullFile(self, remoteFile):
-    """
-    external function
-    returns:
-    success: output of pullfile, string
-    failure: None
-    """
-
-  @abstractmethod
-  def getFile(self, remoteFile, localFile = ''):
-    """
-    copy file from device (remoteFile) to host (localFile)
-    external function
-    returns:
-    success: output of pullfile, string
-    failure: None
-    """
-
-  @abstractmethod
-  def getDirectory(self, remoteDir, localDir, checkDir=True):
-    """
-    copy directory structure from device (remoteDir) to host (localDir)
-    external function
-    checkDir exists so that we don't create local directories if the
-    remote directory doesn't exist but also so that we don't call isDir
-    twice when recursing.
-    returns:
-    success: list of files, string
-    failure: None
-    """
-
-  @abstractmethod
-  def isDir(self, remotePath):
-    """
-    external function
-    returns:
-    success: True
-    failure: False
-    Throws a FileError exception when null (invalid dir/filename)
-    """
-
-  @abstractmethod
-  def validateFile(self, remoteFile, localFile):
-    """
-    true/false check if the two files have the same md5 sum
-    external function
-    returns:
-    success: True
-    failure: False
-    """
-
-  @abstractmethod
-  def getRemoteHash(self, filename):
-    """
-    return the md5 sum of a remote file
-    internal function
-    returns:
-    success: MD5 hash for given filename
-    failure: None
-    """
-
-  def getLocalHash(self, filename):
-    """
-    return the md5 sum of a file on the host
-    internal function
-    returns:
-    success: MD5 hash for given filename
-    failure: None
-    """
-
-    file = open(filename, 'rb')
-    if (file == None):
-      return None
-
-    try:
-      mdsum = hashlib.md5()
-    except:
-      return None
-
-    while 1:
-      data = file.read(1024)
-      if not data:
-        break
-      mdsum.update(data)
-
-    file.close()
-    hexval = mdsum.hexdigest()
-    if (self.debug >= 3): print "local hash returned: '" + hexval + "'"
-    return hexval
-
-  @abstractmethod
-  def getDeviceRoot(self):
-    """
-    Gets the device root for the testing area on the device
-    For all devices we will use / type slashes and depend on the device-agent
-    to sort those out.  The agent will return us the device location where we
-    should store things, we will then create our /tests structure relative to
-    that returned path.
-    Structure on the device is as follows:
-    /tests
-          /<fennec>|<firefox>  --> approot
-          /profile
-          /xpcshell
-          /reftest
-          /mochitest
-    external
-    returns:
-    success: path for device root
-    failure: None
-    """
-
-  @abstractmethod
-  def getAppRoot(self):
-    """
-    Either we will have /tests/fennec or /tests/firefox but we will never have
-    both.  Return the one that exists
-    TODO: ensure we can support org.mozilla.firefox
-    external function
-    returns:
-    success: path for app root
-    failure: None
-    """
-
-  def getTestRoot(self, type):
-    """
-    Gets the directory location on the device for a specific test type
-    Type is one of: xpcshell|reftest|mochitest
-    external function
-    returns:
-    success: path for test root
-    failure: None
-    """
-
-    devroot = self.getDeviceRoot()
-    if (devroot == None):
-      return None
-
-    if (re.search('xpcshell', type, re.I)):
-      self.testRoot = devroot + '/xpcshell'
-    elif (re.search('?(i)reftest', type)):
-      self.testRoot = devroot + '/reftest'
-    elif (re.search('?(i)mochitest', type)):
-      self.testRoot = devroot + '/mochitest'
-    return self.testRoot
-
-  def signal(self, processID, signalType, signalAction):
-    """
-    Sends a specific process ID a signal code and action.
-    For Example: SIGINT and SIGDFL to process x
-    """
-    #currently not implemented in device agent - todo
-
-    pass
-
-  def getReturnCode(self, processID):
-    """Get a return code from process ending -- needs support on device-agent"""
-    # TODO: make this real
-
-    return 0
-
-  @abstractmethod
-  def unpackFile(self, file_path, dest_dir=None):
-    """
-    external function
-    returns:
-    success: output of unzip command
-    failure: None
-    """
-
-  @abstractmethod
-  def reboot(self, ipAddr=None, port=30000):
-    """
-    external function
-    returns:
-    success: status from test agent
-    failure: None
-    """
-
-  def validateDir(self, localDir, remoteDir):
-    """
-    validate localDir from host to remoteDir on the device
-    external function
-    returns:
-    success: True
-    failure: False
-    """
-
-    if (self.debug >= 2): print "validating directory: " + localDir + " to " + remoteDir
-    for root, dirs, files in os.walk(localDir):
-      parts = root.split(localDir)
-      for file in files:
-        remoteRoot = remoteDir + '/' + parts[1]
-        remoteRoot = remoteRoot.replace('/', '/')
-        if (parts[1] == ""): remoteRoot = remoteDir
-        remoteName = remoteRoot + '/' + file
-        if (self.validateFile(remoteName, os.path.join(root, file)) <> True):
-            return False
-    return True
-
-  @abstractmethod
-  def getInfo(self, directive=None):
-    """
-    Returns information about the device:
-    Directive indicates the information you want to get, your choices are:
-    os - name of the os
-    id - unique id of the device
-    uptime - uptime of the device
-    uptimemillis - uptime of the device in milliseconds (NOT supported on all
-                   implementations)
-    systime - system time of the device
-    screen - screen resolution
-    memory - memory stats
-    process - list of running processes (same as ps)
-    disk - total, free, available bytes on disk
-    power - power status (charge, battery temp)
-    all - all of them - or call it with no parameters to get all the information
-    returns:
-    success: dict of info strings by directive name
-    failure: None
-    """
-
-  @abstractmethod
-  def installApp(self, appBundlePath, destPath=None):
-    """
-    external function
-    returns:
-    success: output from agent for inst command
-    failure: None
-    """
-
-  @abstractmethod
-  def uninstallAppAndReboot(self, appName, installPath=None):
-    """
-    external function
-    returns:
-    success: True
-    failure: None
-    """
-
-  @abstractmethod
-  def updateApp(self, appBundlePath, processName=None,
-                destPath=None, ipAddr=None, port=30000):
-    """
-    external function
-    returns:
-    success: text status from command or callback server
-    failure: None
-    """
-
-  @abstractmethod
-  def getCurrentTime(self):
-    """
-    external function
-    returns:
-    success: time in ms
-    failure: None
-    """
-
-  def recordLogcat(self):
-    """
-    external function
-    returns:
-    success: file is created in <testroot>/logcat.log
-    failure:
-    """
-    #TODO: spawn this off in a separate thread/process so we can collect all the logcat information
-
-    # Right now this is just clearing the logcat so we can only see what happens after this call.
-    buf = StringIO.StringIO()
-    self.shell(['/system/bin/logcat', '-c'], buf, root=True)
-
-  def getLogcat(self):
-    """
-    external function
-    returns: data from the local file
-    success: file is in 'filename'
-    failure: None
-    """
-    buf = StringIO.StringIO()
-    if self.shell(["/system/bin/logcat", "-d", "dalvikvm:S", "ConnectivityService:S", "WifiMonitor:S", "WifiStateTracker:S", "wpa_supplicant:S", "NetworkStateTracker:S"], buf, root=True) != 0:
-      return None
-
-    return str(buf.getvalue()[0:-1]).rstrip().split('\r')
-
-  @abstractmethod
-  def chmodDir(self, remoteDir):
-    """
-    external function
-    returns:
-    success: True
-    failure: False
-    """
-
-  @staticmethod
-  def _escapedCommandLine(cmd):
-    """ Utility function to return escaped and quoted version of command line """
-    quotedCmd = []
-
-    for arg in cmd:
-      arg.replace('&', '\&')
-
-      needsQuoting = False
-      for char in [ ' ', '(', ')', '"', '&' ]:
-        if arg.find(char) >= 0:
-          needsQuoting = True
-          break
-      if needsQuoting:
-        arg = '\'%s\'' % arg
-
-      quotedCmd.append(arg)
-
-    return " ".join(quotedCmd)
-
-
-class NetworkTools:
-  def __init__(self):
-    pass
-
-  # Utilities to get the local ip address
-  def getInterfaceIp(self, ifname):
-    if os.name != "nt":
-      import fcntl
-      import struct
-      s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-      return socket.inet_ntoa(fcntl.ioctl(
-                              s.fileno(),
-                              0x8915,  # SIOCGIFADDR
-                              struct.pack('256s', ifname[:15])
-                              )[20:24])
-    else:
-      return None
-
-  def getLanIp(self):
-    try:
-      ip = socket.gethostbyname(socket.gethostname())
-    except socket.gaierror:
-      ip = socket.gethostbyname(socket.gethostname() + ".local") # for Mac OS X
-    if ip.startswith("127.") and os.name != "nt":
-      interfaces = ["eth0","eth1","eth2","wlan0","wlan1","wifi0","ath0","ath1","ppp0"]
-      for ifname in interfaces:
-        try:
-          ip = self.getInterfaceIp(ifname)
-          break;
-        except IOError:
-          pass
-    return ip
-
-  # Gets an open port starting with the seed by incrementing by 1 each time
-  def findOpenPort(self, ip, seed):
-    try:
-      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-      s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-      connected = False
-      if isinstance(seed, basestring):
-        seed = int(seed)
-      maxportnum = seed + 5000 # We will try at most 5000 ports to find an open one
-      while not connected:
-        try:
-          s.bind((ip, seed))
-          connected = True
-          s.close()
-          break
-        except:
-          if seed > maxportnum:
-            print "Could not find open port after checking 5000 ports"
-          raise
-        seed += 1
-    except:
-      print "Socket error trying to find open port"
-
-    return seed
-
-def _pop_last_line(file):
-  '''
-  Utility function to get the last line from a file (shared between ADB and
-  SUT device managers). Function also removes it from the file. Intended to
-  strip off the return code from a shell command.
-  '''
-  bytes_from_end = 1
-  file.seek(0, 2)
-  length = file.tell() + 1
-  while bytes_from_end < length:
-    file.seek((-1)*bytes_from_end, 2)
-    data = file.read()
-
-    if bytes_from_end == length-1 and len(data) == 0: # no data, return None
-      return None
-
-    if data[0] == '\n' or bytes_from_end == length-1:
-      # found the last line, which should have the return value
-      if data[0] == '\n':
-        data = data[1:]
-
-      # truncate off the return code line
-      file.truncate(length - bytes_from_end)
-      file.seek(0,2)
-      file.write('\0')
-
-      return data
-
-    bytes_from_end += 1
-
-  return None
deleted file mode 100644
--- a/build/mobile/devicemanagerADB.py
+++ /dev/null
@@ -1,900 +0,0 @@
-# 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/.
-
-import subprocess
-from devicemanager import DeviceManager, DMError, _pop_last_line
-import re
-import os
-import tempfile
-import time
-
-class DeviceManagerADB(DeviceManager):
-
-  def __init__(self, host=None, port=20701, retrylimit=5, packageName='fennec',
-               adbPath='adb', deviceSerial=None, deviceRoot=None):
-    self.host = host
-    self.port = port
-    self.retrylimit = retrylimit
-    self.retries = 0
-    self._sock = None
-    self.useRunAs = False
-    self.haveRoot = False
-    self.useDDCopy = False
-    self.useZip = False
-    self.packageName = None
-    self.tempDir = None
-    self.deviceRoot = deviceRoot
-
-    # the path to adb, or 'adb' to assume that it's on the PATH
-    self.adbPath = adbPath
-
-    # The serial number of the device to use with adb, used in cases
-    # where multiple devices are being managed by the same adb instance.
-    self.deviceSerial = deviceSerial
-
-    if packageName == 'fennec':
-      if os.getenv('USER'):
-        self.packageName = 'org.mozilla.fennec_' + os.getenv('USER')
-      else:
-        self.packageName = 'org.mozilla.fennec_'
-    elif packageName:
-      self.packageName = packageName
-
-    # verify that we can run the adb command. can't continue otherwise
-    self.verifyADB()
-
-    # try to connect to the device over tcp/ip if we have a hostname
-    if self.host:
-      self.connectRemoteADB()
-
-    # verify that we can connect to the device. can't continue
-    self.verifyDevice()
-
-    # set up device root
-    self.setupDeviceRoot()
-
-    # Can we use run-as? (currently not required)
-    try:
-      self.verifyRunAs()
-    except DMError:
-      pass
-
-    # Can we run things as root? (currently not required)
-    useRunAsTmp = self.useRunAs
-    self.useRunAs = False
-    try:
-      self.verifyRoot()
-    except DMError:
-      try:
-        self.checkCmd(["root"])
-        # The root command does not fail even if ADB cannot get
-        # root rights (e.g. due to production builds), so we have
-        # to check again ourselves that we have root now.
-        self.verifyRoot()
-      except DMError:
-        if useRunAsTmp:
-          print "restarting as root failed, but run-as available"
-        else:
-          print "restarting as root failed"
-    self.useRunAs = useRunAsTmp
-
-    # can we use zip to speed up some file operations? (currently not
-    # required)
-    try:
-      self.verifyZip()
-    except DMError:
-      pass
-
-  def __del__(self):
-    if self.host:
-      self.disconnectRemoteADB()
-
-  # external function: executes shell command on device.
-  # timeout is specified in seconds, and if no timeout is given, 
-  # we will run until the script returns
-  # returns:
-  # success: <return code>
-  # failure: None
-  def shell(self, cmd, outputfile, env=None, cwd=None, timeout=None, root=False):
-    # FIXME: this function buffers all output of the command into memory,
-    # always. :(
-
-    # Getting the return code is more complex than you'd think because adb
-    # doesn't actually return the return code from a process, so we have to
-    # capture the output to get it
-    if root:
-      cmdline = "su -c \"%s\"" % self._escapedCommandLine(cmd)
-    else:
-      cmdline = self._escapedCommandLine(cmd)
-    cmdline += "; echo $?"
-
-    # prepend cwd and env to command if necessary
-    if cwd:
-      cmdline = "cd %s; %s" % (cwd, cmdline)
-    if env:
-      envstr = '; '.join(map(lambda x: 'export %s=%s' % (x[0], x[1]), env.iteritems()))
-      cmdline = envstr + "; " + cmdline
-
-    # all output should be in stdout
-    args=[self.adbPath]
-    if self.deviceSerial:
-        args.extend(['-s', self.deviceSerial])
-    args.extend(["shell", cmdline])
-    proc = subprocess.Popen(args,
-                            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-    if timeout:
-        timeout = int(timeout)
-        start_time = time.time()
-        ret_code = proc.poll()
-        while ((time.time() - start_time) <= timeout) and ret_code == None:
-            time.sleep(1)
-            ret_code = proc.poll()
-        if ret_code == None:
-            proc.kill()
-            raise DMError("Timeout exceeded for shell call")
-    (stdout, stderr) = proc.communicate()
-    outputfile.write(stdout.rstrip('\n'))
-
-    lastline = _pop_last_line(outputfile)
-    if lastline:
-      m = re.search('([0-9]+)', lastline)
-      if m:
-        return_code = m.group(1)
-        outputfile.seek(-2, 2)
-        outputfile.truncate() # truncate off the return code
-        return int(return_code)
-
-    return None
-
-  def connectRemoteADB(self):
-    self.checkCmd(["connect", self.host + ":" + str(self.port)])
-
-  def disconnectRemoteADB(self):
-    self.checkCmd(["disconnect", self.host + ":" + str(self.port)])
-
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def pushFile(self, localname, destname):
-    try:
-      if (os.name == "nt"):
-        destname = destname.replace('\\', '/')
-      if (self.useRunAs):
-        remoteTmpFile = self.getTempDir() + "/" + os.path.basename(localname)
-        self.checkCmd(["push", os.path.realpath(localname), remoteTmpFile])
-        if self.useDDCopy:
-          self.checkCmdAs(["shell", "dd", "if=" + remoteTmpFile, "of=" + destname])
-        else:
-          self.checkCmdAs(["shell", "cp", remoteTmpFile, destname])
-        self.checkCmd(["shell", "rm", remoteTmpFile])
-      else:
-        self.checkCmd(["push", os.path.realpath(localname), destname])
-      if (self.isDir(destname)):
-        destname = destname + "/" + os.path.basename(localname)
-      return True
-    except:
-      return False
-
-  # external function
-  # returns:
-  #  success: directory name
-  #  failure: None
-  def mkDir(self, name):
-    try:
-      result = self.runCmdAs(["shell", "mkdir", name]).stdout.read()
-      if 'read-only file system' in result.lower():
-        return None
-      if 'file exists' in result.lower():
-        return name
-      return name
-    except:
-      return None
-
-  # push localDir from host to remoteDir on the device
-  # external function
-  # returns:
-  #  success: remoteDir
-  #  failure: None
-  def pushDir(self, localDir, remoteDir):
-    # adb "push" accepts a directory as an argument, but if the directory
-    # contains symbolic links, the links are pushed, rather than the linked
-    # files; we either zip/unzip or push file-by-file to get around this 
-    # limitation
-    try:
-      if (not self.dirExists(remoteDir)):
-        self.mkDirs(remoteDir+"/x")
-      if (self.useZip):
-        try:
-          localZip = tempfile.mktemp()+".zip"
-          remoteZip = remoteDir + "/adbdmtmp.zip"
-          subprocess.check_output(["zip", "-r", localZip, '.'], cwd=localDir)
-          self.pushFile(localZip, remoteZip)
-          os.remove(localZip)
-          data = self.runCmdAs(["shell", "unzip", "-o", remoteZip, "-d", remoteDir]).stdout.read()
-          self.checkCmdAs(["shell", "rm", remoteZip])
-          if (re.search("unzip: exiting", data) or re.search("Operation not permitted", data)):
-            raise Exception("unzip failed, or permissions error")
-        except:
-          print "zip/unzip failure: falling back to normal push"
-          self.useZip = False
-          self.pushDir(localDir, remoteDir)
-      else:
-        for root, dirs, files in os.walk(localDir, followlinks=True):
-          relRoot = os.path.relpath(root, localDir)
-          for file in files:
-            localFile = os.path.join(root, file)
-            remoteFile = remoteDir + "/"
-            if (relRoot!="."):
-              remoteFile = remoteFile + relRoot + "/"
-            remoteFile = remoteFile + file
-            self.pushFile(localFile, remoteFile)
-          for dir in dirs:
-            targetDir = remoteDir + "/"
-            if (relRoot!="."):
-              targetDir = targetDir + relRoot + "/"
-            targetDir = targetDir + dir
-            if (not self.dirExists(targetDir)):
-              self.mkDir(targetDir)
-      return remoteDir
-    except:
-      print "pushing " + localDir + " to " + remoteDir + " failed"
-      return None
-
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def dirExists(self, dirname):
-    return self.isDir(dirname)
-
-  # Because we always have / style paths we make this a lot easier with some
-  # assumptions
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def fileExists(self, filepath):
-    p = self.runCmd(["shell", "ls", "-a", filepath])
-    data = p.stdout.readlines()
-    if (len(data) == 1):
-      if (data[0].rstrip() == filepath):
-        return True
-    return False
-
-  def removeFile(self, filename):
-    return self.runCmd(["shell", "rm", filename]).stdout.read()
-
-  # does a recursive delete of directory on the device: rm -Rf remoteDir
-  # external function
-  # returns:
-  #  success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
-  #  failure: None
-  def removeSingleDir(self, remoteDir):
-    return self.runCmd(["shell", "rmdir", remoteDir]).stdout.read()
-
-  # does a recursive delete of directory on the device: rm -Rf remoteDir
-  # external function
-  # returns:
-  #  success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
-  #  failure: None
-  def removeDir(self, remoteDir):
-      out = ""
-      if (self.isDir(remoteDir)):
-          files = self.listFiles(remoteDir.strip())
-          for f in files:
-              if (self.isDir(remoteDir.strip() + "/" + f.strip())):
-                  out += self.removeDir(remoteDir.strip() + "/" + f.strip())
-              else:
-                  out += self.removeFile(remoteDir.strip() + "/" + f.strip())
-          out += self.removeSingleDir(remoteDir.strip())
-      else:
-          out += self.removeFile(remoteDir.strip())
-      return out
-
-  def isDir(self, remotePath):
-      p = self.runCmd(["shell", "ls", "-a", remotePath + '/'])
-
-      data = p.stdout.readlines()
-      if len(data) == 1:
-          res = data[0]
-          if "Not a directory" in res or "No such file or directory" in res:
-              return False
-
-      return True
-
-  def listFiles(self, rootdir):
-      p = self.runCmd(["shell", "ls", "-a", rootdir])
-      data = p.stdout.readlines()
-      data[:] = [item.rstrip('\r\n') for item in data]
-      if (len(data) == 1):
-          if (data[0] == rootdir):
-              return []
-          if (data[0].find("No such file or directory") != -1):
-              return []
-          if (data[0].find("Not a directory") != -1):
-              return []
-          if (data[0].find("Permission denied") != -1):
-              return []
-          if (data[0].find("opendir failed") != -1):
-              return []
-      return data
-
-  # external function
-  # returns:
-  #  success: array of process tuples
-  #  failure: []
-  def getProcessList(self):
-    p = self.runCmd(["shell", "ps"])
-      # first line is the headers
-    p.stdout.readline()
-    proc = p.stdout.readline()
-    ret = []
-    while (proc):
-      els = proc.split()
-      ret.append(list([els[1], els[len(els) - 1], els[0]]))
-      proc =  p.stdout.readline()
-    return ret
-
-  # external function
-  # DEPRECATED: Use shell() or launchApplication() for new code
-  # returns:
-  #  success: pid
-  #  failure: None
-  def fireProcess(self, appname, failIfRunning=False):
-    #strip out env vars
-    parts = appname.split('"');
-    if (len(parts) > 2):
-      parts = parts[2:]
-    return self.launchProcess(parts, failIfRunning)
-
-  # external function
-  # DEPRECATED: Use shell() or launchApplication() for new code
-  # returns:
-  #  success: output filename
-  #  failure: None
-  def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = '', failIfRunning=False):
-    if cmd[0] == "am":
-      self.checkCmd(["shell"] + cmd)
-      return outputFile
-
-    acmd = ["shell", "am", "start", "-W"]
-    cmd = ' '.join(cmd).strip()
-    i = cmd.find(" ")
-    # SUT identifies the URL by looking for :\\ -- another strategy to consider
-    re_url = re.compile('^[http|file|chrome|about].*')
-    last = cmd.rfind(" ")
-    uri = ""
-    args = ""
-    if re_url.match(cmd[last:].strip()):
-      args = cmd[i:last].strip()
-      uri = cmd[last:].strip()
-    else:
-      args = cmd[i:].strip()
-    acmd.append("-n")
-    acmd.append(cmd[0:i] + "/.App")
-    if args != "":
-      acmd.append("--es")
-      acmd.append("args")
-      acmd.append(args)
-    if env != '' and env != None:
-      envCnt = 0
-      # env is expected to be a dict of environment variables
-      for envkey, envval in env.iteritems():
-        acmd.append("--es")
-        acmd.append("env" + str(envCnt))
-        acmd.append(envkey + "=" + envval);
-        envCnt += 1
-    if uri != "":
-      acmd.append("-d")
-      acmd.append(''.join(['\'',uri, '\'']));
-    print acmd
-    self.checkCmd(acmd)
-    return outputFile
-
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def killProcess(self, appname, forceKill=False):
-    procs = self.getProcessList()
-    didKillProcess = False
-    for (pid, name, user) in procs:
-      if name == appname:
-         args = ["shell", "kill"]
-         if forceKill:
-           args.append("-9")
-         args.append(pid)
-         p = self.runCmdAs(args)
-         p.communicate()
-         if p.returncode == 0:
-             didKillProcess = True
-
-    return didKillProcess
-
-  # external function
-  # returns:
-  #  success: filecontents
-  #  failure: None
-  def catFile(self, remoteFile):
-    #p = self.runCmd(["shell", "cat", remoteFile])
-    #return p.stdout.read()
-    return self.getFile(remoteFile)
-
-  # external function
-  # returns:
-  #  success: output of pullfile, string
-  #  failure: None
-  def pullFile(self, remoteFile):
-    #return self.catFile(remoteFile)
-    return self.getFile(remoteFile)
-
-  # copy file from device (remoteFile) to host (localFile)
-  # external function
-  # returns:
-  #  success: output of pullfile, string
-  #  failure: None
-  def getFile(self, remoteFile, localFile = 'tmpfile_dm_adb'):
-    # TODO: add debug flags and allow for printing stdout
-    # self.runCmd(["pull", remoteFile, localFile])
-    try:
-
-      # First attempt to pull file regularly
-      outerr = self.runCmd(["pull",  remoteFile, localFile]).communicate()
-
-      # Now check stderr for errors
-      if outerr[1]:
-        errl = outerr[1].splitlines()
-        if (len(errl) == 1):
-          if (((errl[0].find("Permission denied") != -1)
-            or (errl[0].find("does not exist") != -1))
-            and self.useRunAs):
-            # If we lack permissions to read but have run-as, then we should try
-            # to copy the file to a world-readable location first before attempting
-            # to pull it again.
-            remoteTmpFile = self.getTempDir() + "/" + os.path.basename(remoteFile)
-            self.checkCmdAs(["shell", "dd", "if=" + remoteFile, "of=" + remoteTmpFile])
-            self.checkCmdAs(["shell", "chmod", "777", remoteTmpFile])
-            self.runCmd(["pull",  remoteTmpFile, localFile]).stdout.read()
-            # Clean up temporary file
-            self.checkCmdAs(["shell", "rm", remoteTmpFile])
-
-      f = open(localFile)
-      ret = f.read()
-      f.close()
-      return ret
-    except:
-      return None
-
-  # copy directory structure from device (remoteDir) to host (localDir)
-  # external function
-  # checkDir exists so that we don't create local directories if the
-  # remote directory doesn't exist but also so that we don't call isDir
-  # twice when recursing.
-  # returns:
-  #  success: list of files, string
-  #  failure: None
-  def getDirectory(self, remoteDir, localDir, checkDir=True):
-    ret = []
-    p = self.runCmd(["pull", remoteDir, localDir])
-    p.stdout.readline()
-    line = p.stdout.readline()
-    while (line):
-      els = line.split()
-      f = els[len(els) - 1]
-      i = f.find(localDir)
-      if (i != -1):
-        if (localDir[len(localDir) - 1] != '/'):
-          i = i + 1
-        f = f[i + len(localDir):]
-      i = f.find("/")
-      if (i > 0):
-        f = f[0:i]
-      ret.append(f)
-      line =  p.stdout.readline()
-    #the last line is a summary
-    if (len(ret) > 0):
-      ret.pop()
-    return ret
-
-
-
-  # true/false check if the two files have the same md5 sum
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def validateFile(self, remoteFile, localFile):
-    return self.getRemoteHash(remoteFile) == self.getLocalHash(localFile)
-
-  # return the md5 sum of a remote file
-  # internal function
-  # returns:
-  #  success: MD5 hash for given filename
-  #  failure: None
-  def getRemoteHash(self, filename):
-    data = self.runCmd(["shell", "ls", "-l", filename]).stdout.read()
-    return data.split()[3]
-
-  def getLocalHash(self, filename):
-    data = subprocess.Popen(["ls", "-l", filename], stdout=subprocess.PIPE).stdout.read()
-    return data.split()[4]
-
-  # Internal method to setup the device root and cache its value
-  def setupDeviceRoot(self):
-    # if self.deviceRoot is already set, create it if necessary, and use it
-    if self.deviceRoot:
-      if not self.dirExists(self.deviceRoot):
-        if not self.mkDir(self.deviceRoot):
-          raise DMError("Unable to create device root %s" % self.deviceRoot)
-      return
-
-    # /mnt/sdcard/tests is preferred to /data/local/tests, but this can be
-    # over-ridden by creating /data/local/tests
-    testRoot = "/data/local/tests"
-    if (self.dirExists(testRoot)):
-      self.deviceRoot = testRoot
-      return
-
-    for (basePath, subPath) in [('/mnt/sdcard', 'tests'),
-                                ('/data/local', 'tests')]:
-      if self.dirExists(basePath):
-        testRoot = os.path.join(basePath, subPath)
-        if self.mkDir(testRoot):
-          self.deviceRoot = testRoot
-          return
-
-    raise DMError("Unable to set up device root as /mnt/sdcard/tests "
-                  "or /data/local/tests")
-
-  # Gets the device root for the testing area on the device
-  # For all devices we will use / type slashes and depend on the device-agent
-  # to sort those out.  The agent will return us the device location where we
-  # should store things, we will then create our /tests structure relative to
-  # that returned path.
-  # Structure on the device is as follows:
-  # /tests
-  #       /<fennec>|<firefox>  --> approot
-  #       /profile
-  #       /xpcshell
-  #       /reftest
-  #       /mochitest
-  #
-  # external function
-  # returns:
-  #  success: path for device root
-  #  failure: None
-  def getDeviceRoot(self):
-    return self.deviceRoot
-
-  # Gets the temporary directory we are using on this device
-  # base on our device root, ensuring also that it exists.
-  #
-  # internal function
-  # returns:
-  #  success: path for temporary directory
-  #  failure: None
-  def getTempDir(self):
-    # Cache result to speed up operations depending
-    # on the temporary directory.
-    if self.tempDir == None:
-      self.tempDir = self.getDeviceRoot() + "/tmp"
-      if (not self.dirExists(self.tempDir)):
-        return self.mkDir(self.tempDir)
-
-    return self.tempDir
-
-  # Either we will have /tests/fennec or /tests/firefox but we will never have
-  # both.  Return the one that exists
-  # TODO: ensure we can support org.mozilla.firefox
-  # external function
-  # returns:
-  #  success: path for app root
-  #  failure: None
-  def getAppRoot(self, packageName):
-    devroot = self.getDeviceRoot()
-    if (devroot == None):
-      return None
-
-    if (packageName and self.dirExists('/data/data/' + packageName)):
-      self.packageName = packageName
-      return '/data/data/' + packageName
-    elif (self.packageName and self.dirExists('/data/data/' + self.packageName)):
-      return '/data/data/' + self.packageName
-
-    # Failure (either not installed or not a recognized platform)
-    print "devicemanagerADB: getAppRoot failed"
-    return None
-
-  # Gets the directory location on the device for a specific test type
-  # Type is one of: xpcshell|reftest|mochitest
-  # external function
-  # returns:
-  #  success: path for test root
-  #  failure: None
-  def getTestRoot(self, type):
-    devroot = self.getDeviceRoot()
-    if (devroot == None):
-      return None
-
-    if (re.search('xpcshell', type, re.I)):
-      self.testRoot = devroot + '/xpcshell'
-    elif (re.search('?(i)reftest', type)):
-      self.testRoot = devroot + '/reftest'
-    elif (re.search('?(i)mochitest', type)):
-      self.testRoot = devroot + '/mochitest'
-    return self.testRoot
-
-
-  # external function
-  # returns:
-  #  success: status from test agent
-  #  failure: None
-  def reboot(self, wait = False):
-    ret = self.runCmd(["reboot"]).stdout.read()
-    if (not wait):
-      return "Success"
-    countdown = 40
-    while (countdown > 0):
-      countdown
-      try:
-        self.checkCmd(["wait-for-device", "shell", "ls", "/sbin"])
-        return ret
-      except:
-        try:
-          self.checkCmd(["root"])
-        except:
-          time.sleep(1)
-          print "couldn't get root"
-    return "Success"
-
-  # external function
-  # returns:
-  #  success: text status from command or callback server
-  #  failure: None
-  def updateApp(self, appBundlePath, processName=None, destPath=None, ipAddr=None, port=30000):
-    return self.runCmd(["install", "-r", appBundlePath]).stdout.read()
-
-  # external function
-  # returns:
-  #  success: time in ms
-  #  failure: None
-  def getCurrentTime(self):
-    timestr = self.runCmd(["shell", "date", "+%s"]).stdout.read().strip()
-    if (not timestr or not timestr.isdigit()):
-        return None
-    return str(int(timestr)*1000)
-
-  # Returns information about the device:
-  # Directive indicates the information you want to get, your choices are:
-  # os - name of the os
-  # id - unique id of the device
-  # uptime - uptime of the device
-  # systime - system time of the device
-  # screen - screen resolution
-  # memory - memory stats
-  # process - list of running processes (same as ps)
-  # disk - total, free, available bytes on disk
-  # power - power status (charge, battery temp)
-  # all - all of them - or call it with no parameters to get all the information
-  ### Note that uptimemillis is NOT supported, as there is no way to get this
-  ### data from the shell.
-  # returns:
-  #   success: dict of info strings by directive name
-  #   failure: {}
-  def getInfo(self, directive="all"):
-    ret = {}
-    if (directive == "id" or directive == "all"):
-      ret["id"] = self.runCmd(["get-serialno"]).stdout.read()
-    if (directive == "os" or directive == "all"):
-      ret["os"] = self.runCmd(["shell", "getprop", "ro.build.display.id"]).stdout.read()
-    if (directive == "uptime" or directive == "all"):
-      utime = self.runCmd(["shell", "uptime"]).stdout.read()
-      if (not utime):
-        raise DMError("error getting uptime")
-      utime = utime[9:]
-      hours = utime[0:utime.find(":")]
-      utime = utime[utime[1:].find(":") + 2:]
-      minutes = utime[0:utime.find(":")]
-      utime = utime[utime[1:].find(":") +  2:]
-      seconds = utime[0:utime.find(",")]
-      ret["uptime"] = ["0 days " + hours + " hours " + minutes + " minutes " + seconds + " seconds"]
-    if (directive == "process" or directive == "all"):
-      ret["process"] = self.runCmd(["shell", "ps"]).stdout.read()
-    if (directive == "systime" or directive == "all"):
-      ret["systime"] = self.runCmd(["shell", "date"]).stdout.read()
-    print ret
-    return ret
-
-  def runCmd(self, args):
-    # If we are not root but have run-as, and we're trying to execute
-    # a shell command then using run-as is the best we can do
-    finalArgs = [self.adbPath]
-    if self.deviceSerial:
-      finalArgs.extend(['-s', self.deviceSerial])
-    if (not self.haveRoot and self.useRunAs and args[0] == "shell" and args[1] != "run-as"):
-      args.insert(1, "run-as")
-      args.insert(2, self.packageName)
-    finalArgs.extend(args)
-    return subprocess.Popen(finalArgs, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-
-  def runCmdAs(self, args):
-    if self.useRunAs:
-      args.insert(1, "run-as")
-      args.insert(2, self.packageName)
-    return self.runCmd(args)
-
-  # timeout is specified in seconds, and if no timeout is given, 
-  # we will run until the script returns
-  def checkCmd(self, args, timeout=None):
-    # If we are not root but have run-as, and we're trying to execute
-    # a shell command then using run-as is the best we can do
-    finalArgs = [self.adbPath]
-    if self.deviceSerial:
-      finalArgs.extend(['-s', self.deviceSerial])
-    if (not self.haveRoot and self.useRunAs and args[0] == "shell" and args[1] != "run-as"):
-      args.insert(1, "run-as")
-      args.insert(2, self.packageName)
-    finalArgs.extend(args)
-    if timeout:
-        timeout = int(timeout)
-        proc = subprocess.Popen(finalArgs)
-        start_time = time.time()
-        ret_code = proc.poll()
-        while ((time.time() - start_time) <= timeout) and ret_code == None:
-            time.sleep(1)
-            ret_code = proc.poll()
-        if ret_code == None:
-            proc.kill()
-            raise DMError("Timeout exceeded for checkCmd call")
-        return ret_code
-    return subprocess.check_call(finalArgs)
-
-  def checkCmdAs(self, args, timeout=None):
-    if (self.useRunAs):
-      args.insert(1, "run-as")
-      args.insert(2, self.packageName)
-    return self.checkCmd(args, timeout)
-
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def chmodDir(self, remoteDir):
-    if (self.isDir(remoteDir)):
-      files = self.listFiles(remoteDir.strip())
-      for f in files:
-        remoteEntry = remoteDir.strip() + "/" + f.strip()
-        if (self.isDir(remoteEntry)):
-          self.chmodDir(remoteEntry)
-        else:
-          self.checkCmdAs(["shell", "chmod", "777", remoteEntry])
-          print "chmod " + remoteEntry
-      self.checkCmdAs(["shell", "chmod", "777", remoteDir])
-      print "chmod " + remoteDir
-    else:
-      self.checkCmdAs(["shell", "chmod", "777", remoteDir.strip()])
-      print "chmod " + remoteDir.strip()
-    return True
-
-  def verifyADB(self):
-    # Check to see if adb itself can be executed.
-    if self.adbPath != 'adb':
-      if not os.access(self.adbPath, os.X_OK):
-        raise DMError("invalid adb path, or adb not executable: %s", self.adbPath)
-
-    try:
-      self.checkCmd(["version"])
-    except os.error, err:
-      raise DMError("unable to execute ADB (%s): ensure Android SDK is installed and adb is in your $PATH" % err)
-    except subprocess.CalledProcessError:
-      raise DMError("unable to execute ADB: ensure Android SDK is installed and adb is in your $PATH")
-
-  def verifyDevice(self):
-    # If there is a device serial number, see if adb is connected to it
-    if self.deviceSerial:
-      deviceStatus = None
-      proc = subprocess.Popen([self.adbPath, "devices"],
-                              stdout=subprocess.PIPE,
-                              stderr=subprocess.STDOUT)
-      for line in proc.stdout:
-        m = re.match('(.+)?\s+(.+)$', line)
-        if m:
-          if self.deviceSerial == m.group(1):
-            deviceStatus = m.group(2)
-      if deviceStatus == None:
-        raise DMError("device not found: %s" % self.deviceSerial)
-      elif deviceStatus != "device":
-        raise DMError("bad status for device %s: %s" % (self.deviceSerial,
-                                                        deviceStatus))
-
-    # Check to see if we can connect to device and run a simple command
-    try:
-      self.checkCmd(["shell", "echo"])
-    except subprocess.CalledProcessError:
-      raise DMError("unable to connect to device: is it plugged in?")
-
-  def verifyRoot(self):
-    # a test to see if we have root privs
-    p = self.runCmd(["shell", "id"])
-    response = p.stdout.readline()
-    response = response.rstrip()
-    response = response.split(' ')
-    if (response[0].find('uid=0') < 0 or response[1].find('gid=0') < 0):
-      print "NOT running as root ", response[0].find('uid=0')
-      raise DMError("not running as root")
-
-    self.haveRoot = True
-
-  def isCpAvailable(self):
-    # Some Android systems may not have a cp command installed,
-    # or it may not be executable by the user.
-    data = self.runCmd(["shell", "cp"]).stdout.read()
-    if (re.search('Usage', data)):
-      return True
-    else:
-      data = self.runCmd(["shell", "dd", "-"]).stdout.read()
-      if (re.search('unknown operand', data)):
-        print "'cp' not found, but 'dd' was found as a replacement"
-        self.useDDCopy = True
-        return True
-      print "unable to execute 'cp' on device; consider installing busybox from Android Market"
-      return False
-
-  def verifyRunAs(self):
-    # If a valid package name is available, and certain other
-    # conditions are met, devicemanagerADB can execute file operations
-    # via the "run-as" command, so that pushed files and directories 
-    # are created by the uid associated with the package, more closely
-    # echoing conditions encountered by Fennec at run time.
-    # Check to see if run-as can be used here, by verifying a 
-    # file copy via run-as.
-    self.useRunAs = False
-    devroot = self.getDeviceRoot()
-    if (self.packageName and self.isCpAvailable() and devroot):
-      tmpDir = self.getTempDir()
-
-      # The problem here is that run-as doesn't cause a non-zero exit code
-      # when failing because of a non-existent or non-debuggable package :(
-      runAsOut = self.runCmd(["shell", "run-as", self.packageName, "mkdir", devroot + "/sanity"]).communicate()[0]
-      if runAsOut.startswith("run-as:") and ("not debuggable" in runAsOut or
-                                             "is unknown" in runAsOut):
-        raise DMError("run-as failed sanity check")
-
-      tmpfile = tempfile.NamedTemporaryFile()
-      self.checkCmd(["push", tmpfile.name, tmpDir + "/tmpfile"])
-      if self.useDDCopy:
-        self.checkCmd(["shell", "run-as", self.packageName, "dd", "if=" + tmpDir + "/tmpfile", "of=" + devroot + "/sanity/tmpfile"])
-      else:
-        self.checkCmd(["shell", "run-as", self.packageName, "cp", tmpDir + "/tmpfile", devroot + "/sanity"])
-      if (self.fileExists(devroot + "/sanity/tmpfile")):
-        print "will execute commands via run-as " + self.packageName
-        self.useRunAs = True
-      self.checkCmd(["shell", "rm", devroot + "/tmp/tmpfile"])
-      self.checkCmd(["shell", "run-as", self.packageName, "rm", "-r", devroot + "/sanity"])
-
-  def isUnzipAvailable(self):
-    data = self.runCmdAs(["shell", "unzip"]).stdout.read()
-    if (re.search('Usage', data)):
-      return True
-    else:
-      return False
-
-  def isLocalZipAvailable(self):
-    try:
-      subprocess.check_call(["zip", "-?"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-    except:
-      return False
-    return True
-
-  def verifyZip(self):
-    # If "zip" can be run locally, and "unzip" can be run remotely, then pushDir
-    # can use these to push just one file per directory -- a significant
-    # optimization for large directories.
-    self.useZip = False
-    if (self.isUnzipAvailable() and self.isLocalZipAvailable()):
-      print "will use zip to push directories"
-      self.useZip = True
-    else:
-      raise DMError("zip not available")
deleted file mode 100644
--- a/build/mobile/devicemanagerSUT.py
+++ /dev/null
@@ -1,1240 +0,0 @@
-# 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/.
-
-import select
-import socket
-import SocketServer
-import time
-import os
-import re
-import posixpath
-import subprocess
-from threading import Thread
-import StringIO
-from devicemanager import DeviceManager, FileError, NetworkTools, _pop_last_line
-import errno
-from distutils.version import StrictVersion
-
-class AgentError(Exception):
-  "SUTAgent-specific exception."
-
-  def __init__(self, msg= '', fatal = False):
-    self.msg = msg
-    self.fatal = fatal
-
-  def __str__(self):
-    return self.msg
-
-class DeviceManagerSUT(DeviceManager):
-  host = ''
-  port = 0
-  debug = 2
-  retries = 0
-  tempRoot = os.getcwd()
-  base_prompt = '$>'
-  base_prompt_re = '\$\>'
-  prompt_sep = '\x00'
-  prompt_regex = '.*(' + base_prompt_re + prompt_sep + ')'
-  agentErrorRE = re.compile('^##AGENT-WARNING##\ ?(.*)')
-
-  # TODO: member variable to indicate error conditions.
-  # This should be set to a standard error from the errno module.
-  # So, for example, when an error occurs because of a missing file/directory,
-  # before returning, the function would do something like 'self.error = errno.ENOENT'.
-  # The error would be set where appropriate--so sendCMD() could set socket errors,
-  # pushFile() and other file-related commands could set filesystem errors, etc.
-
-  def __init__(self, host, port = 20701, retrylimit = 5, deviceRoot = None):
-    self.host = host
-    self.port = port
-    self.retrylimit = retrylimit
-    self.retries = 0
-    self._sock = None
-    self.deviceRoot = deviceRoot
-    if self.getDeviceRoot() == None:
-      raise BaseException("Failed to connect to SUT Agent and retrieve the device root.")
-    try:
-      verstring = self.runCmds([{ 'cmd': 'ver' }])
-      self.agentVersion = re.sub('SUTAgentAndroid Version ', '', verstring)
-    except AgentError, err:
-      raise BaseException("Failed to get SUTAgent version")
-
-  def _cmdNeedsResponse(self, cmd):
-    """ Not all commands need a response from the agent:
-        * rebt obviously doesn't get a response
-        * uninstall performs a reboot to ensure starting in a clean state and
-          so also doesn't look for a response
-    """
-    noResponseCmds = [re.compile('^rebt'),
-                      re.compile('^uninst .*$'),
-                      re.compile('^pull .*$')]
-
-    for c in noResponseCmds:
-      if (c.match(cmd)):
-        return False
-
-    # If the command is not in our list, then it gets a response
-    return True
-
-  def _stripPrompt(self, data):
-    '''
-    internal function
-    take a data blob and strip instances of the prompt '$>\x00'
-    '''
-    promptre = re.compile(self.prompt_regex + '.*')
-    retVal = []
-    lines = data.split('\n')
-    for line in lines:
-      foundPrompt = False
-      try:
-        while (promptre.match(line)):
-          foundPrompt = True
-          pieces = line.split(self.prompt_sep)
-          index = pieces.index('$>')
-          pieces.pop(index)
-          line = self.prompt_sep.join(pieces)
-      except(ValueError):
-        pass
-
-      # we don't want to append lines that are blank after stripping the
-      # prompt (those are basically "prompts")
-      if not foundPrompt or line:
-        retVal.append(line)
-
-    return '\n'.join(retVal)
-
-  def _shouldCmdCloseSocket(self, cmd):
-    """ Some commands need to close the socket after they are sent:
-    * rebt
-    * uninst
-    * quit
-    """
-    socketClosingCmds = [re.compile('^quit.*'),
-                         re.compile('^rebt.*'),
-                         re.compile('^uninst .*$')]
-
-    for c in socketClosingCmds:
-      if (c.match(cmd)):
-        return True
-
-    return False
-
-  def sendCmds(self, cmdlist, outputfile, timeout = None):
-    '''
-    a wrapper for _doCmds that loops up to self.retrylimit iterations.
-    this allows us to move the retry logic outside of the _doCmds() to make it
-    easier for debugging in the future.
-    note that since cmdlist is a list of commands, they will all be retried if
-    one fails.  this is necessary in particular for pushFile(), where we don't want
-    to accidentally send extra data if a failure occurs during data transmission.
-    '''
-    if timeout:
-      raise NotImplementedError("'timeout' parameter is not yet supported")
-    while self.retries < self.retrylimit:
-      try:
-        self._doCmds(cmdlist, outputfile, timeout)
-        return
-      except AgentError, err:
-        # re-raise error if it's fatal (i.e. the device got the command but
-        # couldn't execute it). retry otherwise
-        if err.fatal:
-          raise err
-        if self.debug >= 2:
-          print err
-        self.retries += 1
-
-    raise AgentError("unable to connect to %s after %s attempts" % (self.host, self.retrylimit))
-
-  def runCmds(self, cmdlist, timeout = None):
-    '''
-    similar to sendCmds, but just returns any output as a string instead of
-    writing to a file. this is normally what you want to call to send a set
-    of commands to the agent
-    '''
-    outputfile = StringIO.StringIO()
-    self.sendCmds(cmdlist, outputfile, timeout)
-    outputfile.seek(0)
-    return outputfile.read()
-
-  def _doCmds(self, cmdlist, outputfile, timeout):
-    promptre = re.compile(self.prompt_regex + '$')
-    shouldCloseSocket = False
-    recvGuard = 1000
-
-    if not self._sock:
-      try:
-        if self.debug >= 1:
-          print "reconnecting socket"
-        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-      except socket.error, msg:
-        self._sock = None
-        raise AgentError("unable to create socket: "+str(msg))
-
-      try:
-        self._sock.connect((self.host, int(self.port)))
-        self._sock.recv(1024)
-      except socket.error, msg:
-        self._sock.close()
-        self._sock = None
-        raise AgentError("unable to connect socket: "+str(msg))
-
-    for cmd in cmdlist:
-      cmdline = '%s\r\n' % cmd['cmd']
-
-      try:
-        sent = self._sock.send(cmdline)
-        if sent != len(cmdline):
-          raise AgentError("ERROR: our cmd was %s bytes and we "
-                           "only sent %s" % (len(cmdline), sent))
-        if cmd.get('data'):
-          sent = self._sock.send(cmd['data'])
-          if sent != len(cmd['data']):
-              raise AgentError("ERROR: we had %s bytes of data to send, but "
-                               "only sent %s" % (len(cmd['data']), sent))
-
-        if (self.debug >= 4): print "sent cmd: " + str(cmd['cmd'])
-      except socket.error, msg:
-        self._sock.close()
-        self._sock = None
-        if self.debug >= 1:
-          print "Error sending data to socket. cmd="+str(cmd['cmd'])+"; err="+str(msg)
-        return False
-
-      # Check if the command should close the socket
-      shouldCloseSocket = self._shouldCmdCloseSocket(cmd['cmd'])
-
-      # Handle responses from commands
-      if (self._cmdNeedsResponse(cmd['cmd'])):
-        found = False
-        loopguard = 0
-        data = ""
-
-        while (found == False and (loopguard < recvGuard)):
-          temp = ''
-          if (self.debug >= 4): print "recv'ing..."
-
-          # Get our response
-          try:
-             # Wait up to a second for socket to become ready for reading...
-            if select.select([self._sock], [], [], 1)[0]:
-                temp = self._sock.recv(1024)
-            if (self.debug >= 4): print "response: " + str(temp)
-          except socket.error, err:
-            self._sock.close()
-            self._sock = None
-            # This error shows up with we have our tegra rebooted.
-            if err[0] == errno.ECONNRESET:
-              raise AgentError("Automation error: Error receiving data from socket (possible reboot). cmd=%s; err=%s" % (cmd, err))
-            raise AgentError("Error receiving data from socket. cmd=%s; err=%s" % (cmd, err))
-
-          data += temp
-
-          # If something goes wrong in the agent it will send back a string that
-          # starts with '##AGENT-WARNING##'
-          errorMatch = self.agentErrorRE.match(data)
-          if errorMatch:
-            raise AgentError("Agent Error processing command '%s'; err='%s'" %
-                             (cmd['cmd'], errorMatch.group(1)), fatal=True)
-
-          for line in data.splitlines():
-            if promptre.match(line):
-              found = True
-              data = self._stripPrompt(data)
-              break
-
-          # periodically flush data to output file to make sure it doesn't get
-          # too big/unwieldly
-          if len(data) > 1024:
-              outputfile.write(data[0:1024])
-              data = data[1024:]
-
-          # If we violently lose the connection to the device, this loop tends to spin,
-          # this guard prevents that
-          if (temp == ''):
-            loopguard += 1
-
-        # Write any remaining data to outputfile
-        outputfile.write(data)
-
-    if shouldCloseSocket:
-      try:
-        self._sock.close()
-        self._sock = None
-      except:
-        self._sock = None
-        raise AgentError("Error closing socket")
-
-  # external function: executes shell command on device
-  # returns:
-  # success: <return code>
-  # failure: None
-  def shell(self, cmd, outputfile, env=None, cwd=None, timeout=None, root=False):
-    cmdline = self._escapedCommandLine(cmd)
-    if env:
-      cmdline = '%s %s' % (self.formatEnvString(env), cmdline)
-
-    haveExecSu = (StrictVersion(self.agentVersion) >= StrictVersion('1.13'))
-
-    # Depending on agent version we send one of the following commands here:
-    # * exec (run as normal user)
-    # * execsu (run as privileged user)
-    # * execcwd (run as normal user from specified directory)
-    # * execcwdsu (run as privileged user from specified directory)
-
-    cmd = "exec"
-    if cwd:
-      cmd += "cwd"
-    if root and haveExecSu:
-      cmd += "su"
-
-    try:
-      if cwd:
-        self.sendCmds([{ 'cmd': '%s %s %s' % (cmd, cwd, cmdline) }], outputfile, timeout)
-      else:
-        if (not root) or haveExecSu:
-          self.sendCmds([{ 'cmd': '%s %s' % (cmd, cmdline) }], outputfile, timeout)
-        else:
-          # need to manually inject su -c for backwards compatibility (this may
-          # not work on ICS or above!!)
-          # (FIXME: this backwards compatibility code is really ugly and should
-          # be deprecated at some point in the future)
-          self.sendCmds([ { 'cmd': '%s su -c "%s"' % (cmd, cmdline) }], outputfile,
-                          timeout)
-    except AgentError:
-      return None
-
-    # dig through the output to get the return code
-    lastline = _pop_last_line(outputfile)
-    if lastline:
-      m = re.search('return code \[([0-9]+)\]', lastline)
-      if m:
-        return int(m.group(1))
-
-    # woops, we couldn't find an end of line/return value
-    return None
-
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def pushFile(self, localname, destname):
-    if (os.name == "nt"):
-      destname = destname.replace('\\', '/')
-
-    if (self.debug >= 3): print "in push file with: " + localname + ", and: " + destname
-    if (self.dirExists(destname)):
-      if (not destname.endswith('/')):
-        destname = destname + '/'
-      destname = destname + os.path.basename(localname)
-    if (self.validateFile(destname, localname) == True):
-      if (self.debug >= 3): print "files are validated"
-      return True
-
-    if self.mkDirs(destname) == None:
-      print "unable to make dirs: " + destname
-      return False
-
-    if (self.debug >= 3): print "sending: push " + destname
-
-    filesize = os.path.getsize(localname)
-    f = open(localname, 'rb')
-    data = f.read()
-    f.close()
-
-    try:
-      retVal = self.runCmds([{ 'cmd': 'push ' + destname + ' ' + str(filesize),
-                               'data': data }])
-    except AgentError, e:
-      print "error pushing file: %s" % e.msg
-      return False
-
-    if (self.debug >= 3): print "push returned: " + str(retVal)
-
-    validated = False
-    if (retVal):
-      retline = retVal.strip()
-      if (retline == None):
-        # Then we failed to get back a hash from agent, try manual validation
-        validated = self.validateFile(destname, localname)
-      else:
-        # Then we obtained a hash from push
-        localHash = self.getLocalHash(localname)
-        if (str(localHash) == str(retline)):
-          validated = True
-    else:
-      # We got nothing back from sendCMD, try manual validation
-      validated = self.validateFile(destname, localname)
-
-    if (validated):
-      if (self.debug >= 3): print "Push File Validated!"
-      return True
-    else:
-      if (self.debug >= 2): print "Push File Failed to Validate!"
-      return False
-
-  # external function
-  # returns:
-  #  success: directory name
-  #  failure: None
-  def mkDir(self, name):
-    if (self.dirExists(name)):
-      return name
-    else:
-      try:
-        retVal = self.runCmds([{ 'cmd': 'mkdr ' + name }])
-      except AgentError:
-        retVal = None
-      return retVal
-
-  # push localDir from host to remoteDir on the device
-  # external function
-  # returns:
-  #  success: remoteDir
-  #  failure: None
-  def pushDir(self, localDir, remoteDir):
-    if (self.debug >= 2): print "pushing directory: %s to %s" % (localDir, remoteDir)
-    for root, dirs, files in os.walk(localDir, followlinks=True):
-      parts = root.split(localDir)
-      for file in files:
-        remoteRoot = remoteDir + '/' + parts[1]
-        if (remoteRoot.endswith('/')):
-          remoteName = remoteRoot + file
-        else:
-          remoteName = remoteRoot + '/' + file
-        if (parts[1] == ""): remoteRoot = remoteDir
-        if (self.pushFile(os.path.join(root, file), remoteName) == False):
-          # retry once
-          self.removeFile(remoteName)
-          if (self.pushFile(os.path.join(root, file), remoteName) == False):
-            return None
-    return remoteDir
-
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def dirExists(self, dirname):
-    match = ".*" + dirname.replace('^', '\^') + "$"
-    dirre = re.compile(match)
-    try:
-      data = self.runCmds([ { 'cmd': 'cd ' + dirname }, { 'cmd': 'cwd' }])
-    except AgentError:
-      return False
-
-    found = False
-    for d in data.splitlines():
-      if (dirre.match(d)):
-        found = True
-
-    return found
-
-  # Because we always have / style paths we make this a lot easier with some
-  # assumptions
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def fileExists(self, filepath):
-    s = filepath.split('/')
-    containingpath = '/'.join(s[:-1])
-    listfiles = self.listFiles(containingpath)
-    for f in listfiles:
-      if (f == s[-1]):
-        return True
-    return False
-
-  # list files on the device, requires cd to directory first
-  # external function
-  # returns:
-  #  success: array of filenames, ['file1', 'file2', ...]
-  #  failure: []
-  def listFiles(self, rootdir):
-    rootdir = rootdir.rstrip('/')
-    if (self.dirExists(rootdir) == False):
-      return []
-    try:
-      data = self.runCmds([{ 'cmd': 'cd ' + rootdir }, { 'cmd': 'ls' }])
-    except AgentError:
-      return []
-
-    files = filter(lambda x: x, data.splitlines())
-    if len(files) == 1 and files[0] == '<empty>':
-      # special case on the agent: empty directories return just the string "<empty>"
-      return []
-    return files
-
-  # external function
-  # returns:
-  #  success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
-  #  failure: None
-  def removeFile(self, filename):
-    if (self.debug>= 2): print "removing file: " + filename
-    try:
-      retVal = self.runCmds([{ 'cmd': 'rm ' + filename }])
-    except AgentError:
-      return None
-
-    return retVal
-
-  # does a recursive delete of directory on the device: rm -Rf remoteDir
-  # external function
-  # returns:
-  #  success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
-  #  failure: None
-  def removeDir(self, remoteDir):
-    try:
-      retVal = self.runCmds([{ 'cmd': 'rmdr ' + remoteDir }])
-    except AgentError:
-      return None
-
-    return retVal
-
-  # external function
-  # returns:
-  #  success: array of process tuples
-  #  failure: []
-  def getProcessList(self):
-    try:
-      data = self.runCmds([{ 'cmd': 'ps' }])
-    except AgentError:
-      return []
-
-    files = []
-    for line in data.splitlines():
-      if line:
-        pidproc = line.strip().split()
-        if (len(pidproc) == 2):
-          files += [[pidproc[0], pidproc[1]]]
-        elif (len(pidproc) == 3):
-          #android returns <userID> <procID> <procName>
-          files += [[pidproc[1], pidproc[2], pidproc[0]]]
-    return files
-
-  # external function
-  # DEPRECATED: Use shell() or launchApplication() for new code
-  # returns:
-  #  success: pid
-  #  failure: None
-  def fireProcess(self, appname, failIfRunning=False):
-    if (not appname):
-      if (self.debug >= 1): print "WARNING: fireProcess called with no command to run"
-      return None
-
-    if (self.debug >= 2): print "FIRE PROC: '" + appname + "'"
-
-    if (self.processExist(appname) != None):
-      print "WARNING: process %s appears to be running already\n" % appname
-      if (failIfRunning):
-        return None
-
-    try:
-      self.runCmds([{ 'cmd': 'exec ' + appname }])
-    except AgentError:
-      return None
-
-    # The 'exec' command may wait for the process to start and end, so checking
-    # for the process here may result in process = None.
-    process = self.processExist(appname)
-    if (self.debug >= 4): print "got pid: %s for process: %s" % (process, appname)
-
-    return process
-
-  # external function
-  # DEPRECATED: Use shell() or launchApplication() for new code
-  # returns:
-  #  success: output filename
-  #  failure: None
-  def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = '', failIfRunning=False):
-    if not cmd:
-      if (self.debug >= 1): print "WARNING: launchProcess called without command to run"
-      return None
-
-    cmdline = subprocess.list2cmdline(cmd)
-    if (outputFile == "process.txt" or outputFile == None):
-      outputFile = self.getDeviceRoot();
-      if outputFile is None:
-        return None
-      outputFile += "/process.txt"
-      cmdline += " > " + outputFile
-
-    # Prepend our env to the command
-    cmdline = '%s %s' % (self.formatEnvString(env), cmdline)
-
-    if self.fireProcess(cmdline, failIfRunning) is None:
-      return None
-    return outputFile
-
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def killProcess(self, appname, forceKill=False):
-    if forceKill:
-      print "WARNING: killProcess(): forceKill parameter unsupported on SUT"
-    try:
-      self.runCmds([{ 'cmd': 'kill ' + appname }])
-    except AgentError:
-      return False
-
-    return True
-
-  # external function
-  # returns:
-  #  success: tmpdir, string
-  #  failure: None
-  def getTempDir(self):
-    try:
-      data = self.runCmds([{ 'cmd': 'tmpd' }])
-    except AgentError:
-      return None
-
-    return data.strip()
-
-  # external function
-  # returns:
-  #  success: filecontents
-  #  failure: None
-  def catFile(self, remoteFile):
-    try:
-      data = self.runCmds([{ 'cmd': 'cat ' + remoteFile }])
-    except AgentError:
-      return None
-
-    return data
-
-  # external function
-  # returns:
-  #  success: output of pullfile, string
-  #  failure: None
-  def pullFile(self, remoteFile):
-    """Returns contents of remoteFile using the "pull" command.
-    The "pull" command is different from other commands in that DeviceManager
-    has to read a certain number of bytes instead of just reading to the
-    next prompt.  This is more robust than the "cat" command, which will be
-    confused if the prompt string exists within the file being catted.
-    However it means we can't use the response-handling logic in sendCMD().
-    """
-
-    def err(error_msg):
-        err_str = 'error returned from pull: %s' % error_msg
-        print err_str
-        self._sock = None
-        raise FileError(err_str)
-
-    # FIXME: We could possibly move these socket-reading functions up to
-    # the class level if we wanted to refactor sendCMD().  For now they are
-    # only used to pull files.
-
-    def uread(to_recv, error_msg):
-      """ unbuffered read """
-      try:
-        data = self._sock.recv(to_recv)
-        if not data:
-          err(error_msg)
-          return None
-        return data
-      except:
-        err(error_msg)
-        return None
-
-    def read_until_char(c, buffer, error_msg):
-      """ read until 'c' is found; buffer rest """
-      while not '\n' in buffer:
-        data = uread(1024, error_msg)
-        if data == None:
-          err(error_msg)
-          return ('', '', '')
-        buffer += data
-      return buffer.partition(c)
-
-    def read_exact(total_to_recv, buffer, error_msg):
-      """ read exact number of 'total_to_recv' bytes """
-      while len(buffer) < total_to_recv:
-        to_recv = min(total_to_recv - len(buffer), 1024)
-        data = uread(to_recv, error_msg)
-        if data == None:
-          return None
-        buffer += data
-      return buffer
-
-    prompt = self.base_prompt + self.prompt_sep
-    buffer = ''
-
-    # expected return value:
-    # <filename>,<filesize>\n<filedata>
-    # or, if error,
-    # <filename>,-1\n<error message>
-    try:
-      # just send the command first, we read the response inline below
-      self.runCmds([{ 'cmd': 'pull ' + remoteFile }])
-    except AgentError:
-      return None
-
-    # read metadata; buffer the rest
-    metadata, sep, buffer = read_until_char('\n', buffer, 'could not find metadata')
-    if not metadata:
-      return None
-    if self.debug >= 3:
-      print 'metadata: %s' % metadata
-
-    filename, sep, filesizestr = metadata.partition(',')
-    if sep == '':
-      err('could not find file size in returned metadata')
-      return None
-    try:
-        filesize = int(filesizestr)
-    except ValueError:
-      err('invalid file size in returned metadata')
-      return None
-
-    if filesize == -1:
-      # read error message
-      error_str, sep, buffer = read_until_char('\n', buffer, 'could not find error message')
-      if not error_str:
-        return None
-      # prompt should follow
-      read_exact(len(prompt), buffer, 'could not find prompt')
-      print "DeviceManager: error pulling file '%s': %s" % (remoteFile, error_str)
-      return None
-
-    # read file data
-    total_to_recv = filesize + len(prompt)
-    buffer = read_exact(total_to_recv, buffer, 'could not get all file data')
-    if buffer == None:
-      return None
-    if buffer[-len(prompt):] != prompt:
-      err('no prompt found after file data--DeviceManager may be out of sync with agent')
-      return buffer
-    return buffer[:-len(prompt)]
-
-  # copy file from device (remoteFile) to host (localFile)
-  # external function
-  # returns:
-  #  success: output of pullfile, string
-  #  failure: None
-  def getFile(self, remoteFile, localFile = ''):
-    if localFile == '':
-      localFile = os.path.join(self.tempRoot, "temp.txt")
-
-    try:
-      retVal = self.pullFile(remoteFile)
-    except:
-      return None
-
-    if (retVal is None):
-      return None
-
-    fhandle = open(localFile, 'wb')
-    fhandle.write(retVal)
-    fhandle.close()
-    if not self.validateFile(remoteFile, localFile):
-      print 'failed to validate file when downloading %s!' % remoteFile
-      return None
-    return retVal
-
-  # copy directory structure from device (remoteDir) to host (localDir)
-  # external function
-  # checkDir exists so that we don't create local directories if the
-  # remote directory doesn't exist but also so that we don't call isDir
-  # twice when recursing.
-  # returns:
-  #  success: list of files, string
-  #  failure: None
-  def getDirectory(self, remoteDir, localDir, checkDir=True):
-    if (self.debug >= 2): print "getting files in '" + remoteDir + "'"
-    if checkDir:
-      try:
-        is_dir = self.isDir(remoteDir)
-      except FileError:
-        return None
-      if not is_dir:
-        return None
-
-    filelist = self.listFiles(remoteDir)
-    if (self.debug >= 3): print filelist
-    if not os.path.exists(localDir):
-      os.makedirs(localDir)
-
-    for f in filelist:
-      if f == '.' or f == '..':
-        continue
-      remotePath = remoteDir + '/' + f
-      localPath = os.path.join(localDir, f)
-      try:
-        is_dir = self.isDir(remotePath)
-      except FileError:
-        print 'isdir failed on file "%s"; continuing anyway...' % remotePath
-        continue
-      if is_dir:
-        if (self.getDirectory(remotePath, localPath, False) == None):
-          print 'failed to get directory "%s"' % remotePath
-          return None
-      else:
-        # It's sometimes acceptable to have getFile() return None, such as
-        # when the agent encounters broken symlinks.
-        # FIXME: This should be improved so we know when a file transfer really
-        # failed.
-        if self.getFile(remotePath, localPath) == None:
-          print 'failed to get file "%s"; continuing anyway...' % remotePath
-    return filelist
-
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  #  Throws a FileError exception when null (invalid dir/filename)
-  def isDir(self, remotePath):
-    try:
-      data = self.runCmds([{ 'cmd': 'isdir ' + remotePath }])
-    except AgentError:
-      # normally there should be no error here; a nonexistent file/directory will
-      # return the string "<filename>: No such file or directory".
-      # However, I've seen AGENT-WARNING returned before.
-      return False
-
-    retVal = data.strip()
-    if not retVal:
-      raise FileError('isdir returned null')
-    return retVal == 'TRUE'
-
-  # true/false check if the two files have the same md5 sum
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def validateFile(self, remoteFile, localFile):
-    remoteHash = self.getRemoteHash(remoteFile)
-    localHash = self.getLocalHash(localFile)
-
-    if (remoteHash == None):
-      return False
-
-    if (remoteHash == localHash):
-      return True
-
-    return False
-
-  # return the md5 sum of a remote file
-  # internal function
-  # returns:
-  #  success: MD5 hash for given filename
-  #  failure: None
-  def getRemoteHash(self, filename):
-    try:
-      data = self.runCmds([{ 'cmd': 'hash ' + filename }])
-    except AgentError:
-      return None
-
-    retVal = None
-    if data:
-      retVal = data.strip()
-    if self.debug >= 3:
-      print "remote hash returned: '%s'" % retVal
-    return retVal
-
-  # Gets the device root for the testing area on the device
-  # For all devices we will use / type slashes and depend on the device-agent
-  # to sort those out.  The agent will return us the device location where we
-  # should store things, we will then create our /tests structure relative to
-  # that returned path.
-  # Structure on the device is as follows:
-  # /tests
-  #       /<fennec>|<firefox>  --> approot
-  #       /profile
-  #       /xpcshell
-  #       /reftest
-  #       /mochitest
-  #
-  # external function
-  # returns:
-  #  success: path for device root
-  #  failure: None
-  def getDeviceRoot(self):
-    if self.deviceRoot:
-      deviceRoot = self.deviceRoot
-    else:
-      try:
-        data = self.runCmds([{ 'cmd': 'testroot' }])
-      except:
-        return None
-
-      deviceRoot = data.strip() + '/tests'
-
-    if (not self.dirExists(deviceRoot)):
-      if (self.mkDir(deviceRoot) == None):
-        return None
-
-    self.deviceRoot = deviceRoot
-    return self.deviceRoot
-
-  def getAppRoot(self, packageName):
-    try:
-      data = self.runCmds([{ 'cmd': 'getapproot '+packageName }])
-    except:
-      return None
-
-    return data.strip()
-
-  # external function
-  # returns:
-  #  success: output of unzip command
-  #  failure: None
-  def unpackFile(self, file_path, dest_dir=None):
-    devroot = self.getDeviceRoot()
-    if (devroot == None):
-      return None
-
-    # if no dest_dir is passed in just set it to file_path's folder
-    if not dest_dir:
-      dest_dir = posixpath.dirname(file_path)
-
-    if dest_dir[-1] != '/':
-      dest_dir += '/'
-
-    try:
-      data = self.runCmds([{ 'cmd': 'unzp %s %s' % (file_path, dest_dir)}])
-    except AgentError:
-      return None
-
-    return data
-
-  # external function
-  # returns:
-  #  success: status from test agent
-  #  failure: None
-  def reboot(self, ipAddr=None, port=30000):
-    cmd = 'rebt'
-
-    if (self.debug > 3): print "INFO: sending rebt command"
-
-    if (ipAddr is not None):
-    #create update.info file:
-      try:
-        destname = '/data/data/com.mozilla.SUTAgentAndroid/files/update.info'
-        data = "%s,%s\rrebooting\r" % (ipAddr, port)
-        self.runCmds([{ 'cmd': 'push %s %s' % (destname, len(data)),
-                        'data': data }])
-      except AgentError:
-        return None
-
-      ip, port = self.getCallbackIpAndPort(ipAddr, port)
-      cmd += " %s %s" % (ip, port)
-      # Set up our callback server
-      callbacksvr = callbackServer(ip, port, self.debug)
-
-    try:
-      status = self.runCmds([{ 'cmd': cmd }])
-    except AgentError:
-      return None
-
-    if (ipAddr is not None):
-      status = callbacksvr.disconnect()
-
-    if (self.debug > 3): print "INFO: rebt- got status back: " + str(status)
-    return status
-
-  # Returns information about the device:
-  # Directive indicates the information you want to get, your choices are:
-  # os - name of the os
-  # id - unique id of the device
-  # uptime - uptime of the device
-  # uptimemillis - uptime of the device in milliseconds (SUTAgent 1.11+)
-  # systime - system time of the device
-  # screen - screen resolution
-  # rotation - rotation of the device (in degrees)
-  # memory - memory stats
-  # process - list of running processes (same as ps)
-  # disk - total, free, available bytes on disk
-  # power - power status (charge, battery temp)
-  # all - all of them - or call it with no parameters to get all the information
-  # returns:
-  #   success: dict of info strings by directive name
-  #   failure: {}
-  def getInfo(self, directive=None):
-    data = None
-    result = {}
-    collapseSpaces = re.compile('  +')
-
-    directives = ['os','id','uptime','uptimemillis','systime','screen',
-                  'rotation','memory','process','disk','power']
-    if (directive in directives):
-      directives = [directive]
-
-    for d in directives:
-      try:
-        data = self.runCmds([{ 'cmd': 'info ' + d }])
-      except AgentError:
-        return result
-
-      if (data is None):
-        continue
-      data = collapseSpaces.sub(' ', data)
-      result[d] = data.split('\n')
-
-    # Get rid of any 0 length members of the arrays
-    for k, v in result.iteritems():
-      result[k] = filter(lambda x: x != '', result[k])
-
-    # Format the process output
-    if 'process' in result:
-      proclist = []
-      for l in result['process']:
-        if l:
-          proclist.append(l.split('\t'))
-      result['process'] = proclist
-
-    if (self.debug >= 3): print "results: " + str(result)
-    return result
-
-  """
-  Installs the application onto the device
-  Application bundle - path to the application bundle on the device
-  Destination - destination directory of where application should be
-                installed to (optional)
-  Returns None for success, or output if known failure
-  """
-  # external function
-  # returns:
-  #  success: output from agent for inst command
-  #  failure: None
-  def installApp(self, appBundlePath, destPath=None):
-    cmd = 'inst ' + appBundlePath
-    if destPath:
-      cmd += ' ' + destPath
-    try:
-      data = self.runCmds([{ 'cmd': cmd }])
-    except AgentError:
-      return None
-
-    f = re.compile('Failure')
-    for line in data.split():
-      if (f.match(line)):
-        return data
-    return None
-
-  """
-  Uninstalls the named application from device and causes a reboot.
-  Takes an optional argument of installation path - the path to where the application
-  was installed.
-  Returns True, but it doesn't mean anything other than the command was sent,
-  the reboot happens and we don't know if this succeeds or not.
-  """
-  # external function
-  # returns:
-  #  success: True
-  #  failure: None
-  def uninstallAppAndReboot(self, appName, installPath=None):
-    cmd = 'uninst ' + appName
-    if installPath:
-      cmd += ' ' + installPath
-    try:
-      data = self.runCmds([{ 'cmd': cmd }])
-    except AgentError:
-      return None
-
-    if (self.debug > 3): print "uninstallAppAndReboot: " + str(data)
-    return True
-
-  """
-  Updates the application on the device.
-  Application bundle - path to the application bundle on the device
-  Process name of application - used to end the process if the applicaiton is
-                                currently running
-  Destination - Destination directory to where the application should be
-                installed (optional)
-  ipAddr - IP address to await a callback ping to let us know that the device has updated
-           properly - defaults to current IP.
-  port - port to await a callback ping to let us know that the device has updated properly
-         defaults to 30000, and counts up from there if it finds a conflict
-  Returns True if succeeds, False if not
-  """
-  # external function
-  # returns:
-  #  success: text status from command or callback server
-  #  failure: None
-  def updateApp(self, appBundlePath, processName=None, destPath=None, ipAddr=None, port=30000):
-    status = None
-    cmd = 'updt '
-    if (processName == None):
-      # Then we pass '' for processName
-      cmd += "'' " + appBundlePath
-    else:
-      cmd += processName + ' ' + appBundlePath
-
-    if (destPath):
-      cmd += " " + destPath
-
-    if (ipAddr is not None):
-      ip, port = self.getCallbackIpAndPort(ipAddr, port)
-      cmd += " %s %s" % (ip, port)
-      # Set up our callback server
-      callbacksvr = callbackServer(ip, port, self.debug)
-
-    if (self.debug >= 3): print "INFO: updateApp using command: " + str(cmd)
-
-    try:
-      status = self.runCmds([{ 'cmd': cmd }])
-    except AgentError:
-      return None
-
-    if ipAddr is not None:
-      status = callbacksvr.disconnect()
-
-    if (self.debug >= 3): print "INFO: updateApp: got status back: " + str(status)
-
-    return status
-
-  """
-    return the current time on the device
-  """
-  # external function
-  # returns:
-  #  success: time in ms
-  #  failure: None
-  def getCurrentTime(self):
-    try:
-      data = self.runCmds([{ 'cmd': 'clok' }])
-    except AgentError:
-      return None
-
-    return data.strip()
-
-  """
-    Connect the ipaddress and port for a callback ping.  Defaults to current IP address
-    And ports starting at 30000.
-    NOTE: the detection for current IP address only works on Linux!
-  """
-  def getCallbackIpAndPort(self, aIp, aPort):
-    ip = aIp
-    nettools = NetworkTools()
-    if (ip == None):
-      ip = nettools.getLanIp()
-    if (aPort != None):
-      port = nettools.findOpenPort(ip, aPort)
-    else:
-      port = nettools.findOpenPort(ip, 30000)
-    return ip, port
-
-  """
-    Returns a properly formatted env string for the agent.
-    Input - env, which is either None, '', or a dict
-    Output - a quoted string of the form: '"envvar1=val1,envvar2=val2..."'
-    If env is None or '' return '' (empty quoted string)
-  """
-  def formatEnvString(self, env):
-    if (env == None or env == ''):
-      return ''
-
-    retVal = '"%s"' % ','.join(map(lambda x: '%s=%s' % (x[0], x[1]), env.iteritems()))
-    if (retVal == '""'):
-      return ''
-
-    return retVal
-
-  """
-    adjust the screen resolution on the device, REBOOT REQUIRED
-    NOTE: this only works on a tegra ATM
-    success: True
-    failure: False
-
-    supported resolutions: 640x480, 800x600, 1024x768, 1152x864, 1200x1024, 1440x900, 1680x1050, 1920x1080
-  """
-  def adjustResolution(self, width=1680, height=1050, type='hdmi'):
-    if self.getInfo('os')['os'][0].split()[0] != 'harmony-eng':
-      if (self.debug >= 2): print "WARNING: unable to adjust screen resolution on non Tegra device"
-      return False
-
-    results = self.getInfo('screen')
-    parts = results['screen'][0].split(':')
-    if (self.debug >= 3): print "INFO: we have a current resolution of %s, %s" % (parts[1].split()[0], parts[2].split()[0])
-
-    #verify screen type is valid, and set it to the proper value (https://bugzilla.mozilla.org/show_bug.cgi?id=632895#c4)
-    screentype = -1
-    if (type == 'hdmi'):
-      screentype = 5
-    elif (type == 'vga' or type == 'crt'):
-      screentype = 3
-    else:
-      return False
-
-    #verify we have numbers
-    if not (isinstance(width, int) and isinstance(height, int)):
-      return False
-
-    if (width < 100 or width > 9999):
-      return False
-
-    if (height < 100 or height > 9999):
-      return False
-
-    if (self.debug >= 3): print "INFO: adjusting screen resolution to %s, %s and rebooting" % (width, height)
-    try:
-      self.runCmds([{ 'cmd': "exec setprop persist.tegra.dpy%s.mode.width %s" % (screentype, width) }])
-      self.runCmds([{ 'cmd': "exec setprop persist.tegra.dpy%s.mode.height %s" % (screentype, height) }])
-    except AgentError:
-      return False
-
-    return True
-
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def chmodDir(self, remoteDir):
-    try:
-      self.runCmds([{ 'cmd': "chmod "+remoteDir }])
-    except AgentError:
-      return False
-    return True
-
-gCallbackData = ''
-
-class myServer(SocketServer.TCPServer):
-  allow_reuse_address = True
-
-class callbackServer():
-  def __init__(self, ip, port, debuglevel):
-    global gCallbackData
-    if (debuglevel >= 1): print "DEBUG: gCallbackData is: %s on port: %s" % (gCallbackData, port)
-    gCallbackData = ''
-    self.ip = ip
-    self.port = port
-    self.connected = False
-    self.debug = debuglevel
-    if (self.debug >= 3): print "Creating server with " + str(ip) + ":" + str(port)
-    self.server = myServer((ip, port), self.myhandler)
-    self.server_thread = Thread(target=self.server.serve_forever)
-    self.server_thread.setDaemon(True)
-    self.server_thread.start()
-
-  def disconnect(self, step = 60, timeout = 600):
-    t = 0
-    if (self.debug >= 3): print "Calling disconnect on callback server"
-    while t < timeout:
-      if (gCallbackData):
-        # Got the data back
-        if (self.debug >= 3): print "Got data back from agent: " + str(gCallbackData)
-        break
-      else:
-        if (self.debug >= 0): print '.',
-      time.sleep(step)
-      t += step
-
-    try:
-      if (self.debug >= 3): print "Shutting down server now"
-      self.server.shutdown()
-    except:
-      if (self.debug >= 1): print "Unable to shutdown callback server - check for a connection on port: " + str(self.port)
-
-    #sleep 1 additional step to ensure not only we are online, but all our services are online
-    time.sleep(step)
-    return gCallbackData
-
-  class myhandler(SocketServer.BaseRequestHandler):
-    def handle(self):
-      global gCallbackData
-      gCallbackData = self.request.recv(1024)
-      #print "Callback Handler got data: " + str(gCallbackData)
-      self.request.send("OK")
-
deleted file mode 100644
--- a/build/mobile/droid.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# 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/.
-
-from devicemanagerADB import DeviceManagerADB
-from devicemanagerSUT import DeviceManagerSUT
-import StringIO
-
-class DroidMixin(object):
-  """Mixin to extend DeviceManager with Android-specific functionality"""
-
-  def launchApplication(self, appName, activityName, intent, url=None,
-                        extras=None):
-    """
-    Launches an Android application
-    returns:
-    success: True
-    failure: False
-    """
-    # only one instance of an application may be running at once
-    if self.processExist(appName):
-      return False
-
-    acmd = [ "am", "start", "-W", "-n", "%s/%s" % (appName, activityName)]
-
-    if intent:
-      acmd.extend(["-a", intent])
-
-    if extras:
-      for (key, val) in extras.iteritems():
-        if type(val) is int:
-          extraTypeParam = "--ei"
-        elif type(val) is bool:
-          extraTypeParam = "--ez"
-        else:
-          extraTypeParam = "--es"
-        acmd.extend([extraTypeParam, str(key), str(val)])
-
-    if url:
-      acmd.extend(["-d", url])
-
-    # shell output not that interesting and debugging logs should already
-    # show what's going on here... so just create an empty memory buffer
-    # and ignore
-    shellOutput = StringIO.StringIO()
-    if self.shell(acmd, shellOutput) == 0:
-      return True
-
-    return False
-
-  def launchFennec(self, appName, intent="android.intent.action.VIEW",
-                   mozEnv=None, extraArgs=None, url=None):
-    """
-    Convenience method to launch Fennec on Android with various debugging
-    arguments
-    WARNING: FIXME: This would go better in mozrunner. Please do not
-    use this method if you are not comfortable with it going away sometime
-    in the near future
-    returns:
-    success: True
-    failure: False
-    """
-    extras = {}
-
-    if mozEnv:
-      # mozEnv is expected to be a dictionary of environment variables: Fennec
-      # itself will set them when launched
-      for (envCnt, (envkey, envval)) in enumerate(mozEnv.iteritems()):
-        extras["env" + str(envCnt)] = envkey + "=" + envval
-
-    # Additional command line arguments that fennec will read and use (e.g.
-    # with a custom profile)
-    if extraArgs:
-      extras['args'] = " ".join(extraArgs)
-
-    return self.launchApplication(appName, ".App", intent, url=url,
-                                  extras=extras)
-
-class DroidADB(DeviceManagerADB, DroidMixin):
-  pass
-
-class DroidSUT(DeviceManagerSUT, DroidMixin):
-  pass
deleted file mode 100644
--- a/build/mobile/emulator.py
+++ /dev/null
@@ -1,296 +0,0 @@
-# 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/.
-
-from abc import abstractmethod
-import datetime
-from mozprocess import ProcessHandlerMixin
-import multiprocessing
-import os
-import re
-import shutil
-import socket
-import subprocess
-from telnetlib import Telnet
-import tempfile
-import time
-
-from emulator_battery import EmulatorBattery
-
-
-class LogcatProc(ProcessHandlerMixin):
-    """Process handler for logcat which saves all output to a logfile.
-    """
-
-    def __init__(self, logfile, cmd, **kwargs):
-        self.logfile = logfile
-        kwargs.setdefault('processOutputLine', []).append(self.log_output)
-        ProcessHandlerMixin.__init__(self, cmd, **kwargs)
-
-    def log_output(self, line):
-        f = open(self.logfile, 'a')
-        f.write(line + "\n")
-        f.flush()
-
-
-class Emulator(object):
-
-    deviceRe = re.compile(r"^emulator-(\d+)(\s*)(.*)$")
-
-    def __init__(self, noWindow=False, logcat_dir=None, arch="x86",
-                 emulatorBinary=None, res='480x800', userdata=None,
-                 memory='512', partition_size='512'):
-        self.port = None
-        self._emulator_launched = False
-        self.proc = None
-        self.local_port = None
-        self.telnet = None
-        self._tmp_userdata = None
-        self._adb_started = False
-        self.logcat_dir = logcat_dir
-        self.logcat_proc = None
-        self.arch = arch
-        self.binary = emulatorBinary
-        self.memory = str(memory)
-        self.partition_size = str(partition_size)
-        self.res = res
-        self.battery = EmulatorBattery(self)
-        self.noWindow = noWindow
-        self.dataImg = userdata
-        self.copy_userdata = self.dataImg is None
-
-    def __del__(self):
-        if self.telnet:
-            self.telnet.write('exit\n')
-            self.telnet.read_all()
-
-    @property
-    def args(self):
-        qemuArgs = [self.binary,
-                    '-kernel', self.kernelImg,
-                    '-sysdir', self.sysDir,
-                    '-data', self.dataImg]
-        if self.noWindow:
-            qemuArgs.append('-no-window')
-        qemuArgs.extend(['-memory', self.memory,
-                         '-partition-size', self.partition_size,
-                         '-verbose',
-                         '-skin', self.res,
-                         '-gpu', 'on',
-                         '-qemu'] + self.tail_args)
-        return qemuArgs
-
-    @property
-    def is_running(self):
-        if self._emulator_launched:
-            return self.proc is not None and self.proc.poll() is None
-        else:
-            return self.port is not None
-
-    def _default_adb(self):
-        adb = subprocess.Popen(['which', 'adb'],
-                               stdout=subprocess.PIPE,
-                               stderr=subprocess.STDOUT)
-        retcode = adb.wait()
-        if retcode == 0:
-            self.adb = adb.stdout.read().strip() # remove trailing newline
-        return retcode
-
-    def _check_for_adb(self):
-        if not os.path.exists(self.adb):
-            if self._default_adb() != 0:
-                raise Exception('adb not found!')
-
-    def _run_adb(self, args):
-        args.insert(0, self.adb)
-        adb = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-        retcode = adb.wait()
-        if retcode:
-            raise Exception('adb terminated with exit code %d: %s'
-            % (retcode, adb.stdout.read()))
-        return adb.stdout.read()
-
-    def _get_telnet_response(self, command=None):
-        output = []
-        assert(self.telnet)
-        if command is not None:
-            self.telnet.write('%s\n' % command)
-        while True:
-            line = self.telnet.read_until('\n')
-            output.append(line.rstrip())
-            if line.startswith('OK'):
-                return output
-            elif line.startswith('KO:'):
-                raise Exception('bad telnet response: %s' % line)
-
-    def _run_telnet(self, command):
-        if not self.telnet:
-            self.telnet = Telnet('localhost', self.port)
-            self._get_telnet_response()
-        return self._get_telnet_response(command)
-
-    def close(self):
-        if self.is_running and self._emulator_launched:
-            self.proc.terminate()
-            self.proc.wait()
-        if self._adb_started:
-            self._run_adb(['kill-server'])
-            self._adb_started = False
-        if self.proc:
-            retcode = self.proc.poll()
-            self.proc = None
-            if self._tmp_userdata:
-                os.remove(self._tmp_userdata)
-                self._tmp_userdata = None
-            return retcode
-        if self.logcat_proc:
-            self.logcat_proc.kill()
-        return 0
-
-    def _get_adb_devices(self):
-        offline = set()
-        online = set()
-        output = self._run_adb(['devices'])
-        for line in output.split('\n'):
-            m = self.deviceRe.match(line)
-            if m:
-                if m.group(3) == 'offline':
-                    offline.add(m.group(1))
-                else:
-                    online.add(m.group(1))
-        return (online, offline)
-
-    def restart(self):
-        if not self._emulator_launched:
-            return
-        self.close()
-        self.start()
-
-    def start_adb(self):
-        result = self._run_adb(['start-server'])
-        # We keep track of whether we've started adb or not, so we know
-        # if we need to kill it.
-        if 'daemon started successfully' in result:
-            self._adb_started = True
-        else:
-            self._adb_started = False
-
-    def connect(self):
-        self._check_for_adb()
-        self.start_adb()
-
-        online, offline = self._get_adb_devices()
-        now = datetime.datetime.now()
-        while online == set([]):
-            time.sleep(1)
-            if datetime.datetime.now() - now > datetime.timedelta(seconds=60):
-                raise Exception('timed out waiting for emulator to be available')
-            online, offline = self._get_adb_devices()
-        self.port = int(list(online)[0])
-
-    @abstractmethod
-    def _locate_files(self):
-        pass
-
-    def start(self):
-        self._locate_files()
-        self.start_adb()
-
-        qemu_args = self.args[:]
-        if self.copy_userdata:
-            # Make a copy of the userdata.img for this instance of the emulator
-            # to use.
-            self._tmp_userdata = tempfile.mktemp(prefix='emulator')
-            shutil.copyfile(self.dataImg, self._tmp_userdata)
-            qemu_args[qemu_args.index('-data') + 1] = self._tmp_userdata
-
-        original_online, original_offline = self._get_adb_devices()
-
-        self.proc = subprocess.Popen(qemu_args,
-                                     stdout=subprocess.PIPE,
-                                     stderr=subprocess.PIPE)
-
-        online, offline = self._get_adb_devices()
-        now = datetime.datetime.now()
-        while online - original_online == set([]):
-            time.sleep(1)
-            if datetime.datetime.now() - now > datetime.timedelta(seconds=60):
-                raise Exception('timed out waiting for emulator to start')
-            online, offline = self._get_adb_devices()
-        self.port = int(list(online - original_online)[0])
-        self._emulator_launched = True
-
-        if self.logcat_dir:
-            self.save_logcat()
-
-        # setup DNS fix for networking
-        self._run_adb(['-s', 'emulator-%d' % self.port,
-                       'shell', 'setprop', 'net.dns1', '10.0.2.3'])
-
-    def _save_logcat_proc(self, filename, cmd):
-        self.logcat_proc = LogcatProc(filename, cmd)
-        self.logcat_proc.run()
-        self.logcat_proc.waitForFinish()
-        self.logcat_proc = None
-
-    def rotate_log(self, srclog, index=1):
-        """ Rotate a logfile, by recursively rotating logs further in the sequence,
-            deleting the last file if necessary.
-        """
-        destlog = os.path.join(self.logcat_dir, 'emulator-%d.%d.log' % (self.port, index))
-        if os.path.exists(destlog):
-            if index == 3:
-                os.remove(destlog)
-            else:
-                self.rotate_log(destlog, index + 1)
-        shutil.move(srclog, destlog)
-
-    def save_logcat(self):
-        """ Save the output of logcat to a file.
-        """
-        filename = os.path.join(self.logcat_dir, "emulator-%d.log" % self.port)
-        if os.path.exists(filename):
-            self.rotate_log(filename)
-        cmd = [self.adb, '-s', 'emulator-%d' % self.port, 'logcat']
-
-        # We do this in a separate process because we call mozprocess's
-        # waitForFinish method to process logcat's output, and this method
-        # blocks.
-        proc = multiprocessing.Process(target=self._save_logcat_proc, args=(filename, cmd))
-        proc.daemon = True
-        proc.start()
-
-    def setup_port_forwarding(self, remote_port):
-        """ Set up TCP port forwarding to the specified port on the device,
-            using any availble local port, and return the local port.
-        """
-
-        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        s.bind(("", 0))
-        local_port = s.getsockname()[1]
-        s.close()
-
-        self._run_adb(['-s', 'emulator-%d' % self.port, 'forward',
-                       'tcp:%d' % local_port,
-                       'tcp:%d' % remote_port])
-
-        self.local_port = local_port
-
-        return local_port
-
-    def wait_for_port(self, timeout=300):
-        assert(self.local_port)
-        starttime = datetime.datetime.now()
-        while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout):
-            try:
-                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-                sock.connect(('localhost', self.local_port))
-                data = sock.recv(16)
-                sock.close()
-                if '"from"' in data:
-                    return True
-            except:
-                import traceback
-                print traceback.format_exc()
-            time.sleep(1)
-        return False
deleted file mode 100644
--- a/build/mobile/emulator_battery.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# 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/.
-
-class EmulatorBattery(object):
-
-    def __init__(self, emulator):
-        self.emulator = emulator
-
-    def get_state(self):
-        status = {}
-        state = {}
-
-        response = self.emulator._run_telnet('power display')
-        for line in response:
-            if ':' in line:
-                field, value = line.split(':')
-                value = value.strip()
-                if value == 'true':
-                    value = True
-                elif value == 'false':
-                    value = False
-                elif field == 'capacity':
-                    value = float(value)
-                status[field] = value
-
-        state['level'] = status.get('capacity', 0.0) / 100
-        if status.get('AC') == 'online':
-            state['charging'] = True
-        else:
-            state['charging'] = False
-
-        return state
-
-    def get_charging(self):
-        return self.get_state()['charging']
-
-    def get_level(self):
-        return self.get_state()['level']
-
-    def set_level(self, level):
-        self.emulator._run_telnet('power capacity %d' % (level * 100))
-
-    def set_charging(self, charging):
-        if charging:
-            cmd = 'power ac on'
-        else:
-            cmd = 'power ac off'
-        self.emulator._run_telnet(cmd)
-
-    charging = property(get_charging, set_charging)
-    level = property(get_level, set_level)
--- a/build/mobile/sutagent/android/AndroidManifest.xml
+++ b/build/mobile/sutagent/android/AndroidManifest.xml
@@ -12,18 +12,17 @@
                   android:label="@string/app_name">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
         <receiver android:name=".SUTStartupIntentReceiver">
             <intent-filter>
-                 <action android:value="android.intent.action.BOOT_COMPLETED" android:name="android.intent.action.BOOT_COMPLETED"/>
-                 <category android:value="android.intent.category.HOME" android:name="android.intent.category.HOME"/>
+                 <action android:name="android.intent.action.MEDIA_MOUNTED"></action>
             </intent-filter>
         </receiver>
         <service android:name=".service.ASMozStub">
 		    <intent-filter>
 			    <action android:name="com.mozilla.SUTAgentAndroid.service.LISTENER_SERVICE" />
 		    </intent-filter>
 	    </service>
 	</application>
@@ -53,9 +52,9 @@
 <uses-permission android:name="android.permission.STATUS_BAR"></uses-permission>
 <uses-permission android:name="android.permission.VIBRATE"></uses-permission>
 <uses-permission android:name="android.permission.SET_TIME"></uses-permission>
 <uses-permission android:name="android.permission.SET_TIME_ZONE"></uses-permission>
 
 <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"></uses-permission>
 <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"></uses-permission>
 
-</manifest> 
\ No newline at end of file
+</manifest> 
--- a/build/mobile/sutagent/android/watcher/WatcherService.java
+++ b/build/mobile/sutagent/android/watcher/WatcherService.java
@@ -186,19 +186,25 @@ public class WatcherService extends Serv
             {
             sComp = e.toString();
             }
         return (sRet);
         }
 
     private void handleCommand(Intent intent)
         {
-        String sCmd = intent.getStringExtra("command");
+        // Note: intent can be null "if the service is being restarted after its process
+        // has gone away". In this case, we will consider that to be equivalent to a start
+        // http://developer.android.com/reference/android/app/Service.html#onStartCommand%28android.content.Intent,%20int,%20int%29
 
-//        Debug.waitForDebugger();
+        String sCmd = "start";
+        if (intent != null)
+            {
+            sCmd = intent.getStringExtra("command");
+            }
 
         if (sCmd != null)
             {
             if (sCmd.equalsIgnoreCase("updt"))
                 {
                 String sPkgName = intent.getStringExtra("pkgName");
                 String sPkgFile = intent.getStringExtra("pkgFile");
                 String sOutFile = intent.getStringExtra("outFile");
--- a/build/pymake/pymake/process.py
+++ b/build/pymake/pymake/process.py
@@ -259,18 +259,18 @@ class PythonJob(Job):
             print >>sys.stderr, e
             return e.exitcode
         except:
             e = sys.exc_info()[1]
             if isinstance(e, SystemExit) and (e.code == 0 or e.code is None):
                 pass # sys.exit(0) is not a failure
             else:
                 print >>sys.stderr, e
-                print >>sys.stderr, traceback.print_exc()
-                return (e.code if isinstance(e.code, int) else 1)
+                traceback.print_exc()
+                return -127
         finally:
             os.environ.clear()
             os.environ.update(oldenv)
         return 0
 
 def job_runner(job):
     """
     Run a job. Called in a Process pool.
new file mode 100644
--- /dev/null
+++ b/build/pymake/tests/native-command-raise.mk
@@ -0,0 +1,9 @@
+#T gmake skip
+#T returncode: 2
+#T grep-for: "Exception: info-exception"
+
+CMD = %pycmd asplode_raise
+PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir
+
+all:
+	@$(CMD) info-exception
--- a/build/pymake/tests/pycmd.py
+++ b/build/pymake/tests/pycmd.py
@@ -25,8 +25,11 @@ def convertasplode(arg):
 
 def asplode(args):
   arg0 = convertasplode(args[0])
   sys.exit(arg0)
 
 def asplode_return(args):
   arg0 = convertasplode(args[0])
   return arg0
+
+def asplode_raise(args):
+  raise Exception(args[0])
--- a/caps/include/nsScriptSecurityManager.h
+++ b/caps/include/nsScriptSecurityManager.h
@@ -387,17 +387,17 @@ private:
 
     // GetScriptSecurityManager is the only call that can make one
     nsScriptSecurityManager();
     virtual ~nsScriptSecurityManager();
 
     static JSBool
     CheckObjectAccess(JSContext *cx, JSHandleObject obj,
                       JSHandleId id, JSAccessMode mode,
-                      jsval *vp);
+                      JSMutableHandleValue vp);
     
     // Decides, based on CSP, whether or not eval() and stuff can be executed.
     static JSBool
     ContentSecurityPolicyPermitsJSAction(JSContext *cx);
 
     // Returns null if a principal cannot be found; generally callers
     // should error out at that point.
     static nsIPrincipal* doGetObjectPrincipal(JSObject *obj);
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -527,35 +527,35 @@ nsScriptSecurityManager::ContentSecurity
 
     return evalOK;
 }
 
 
 JSBool
 nsScriptSecurityManager::CheckObjectAccess(JSContext *cx, JSHandleObject obj,
                                            JSHandleId id, JSAccessMode mode,
-                                           jsval *vp)
+                                           JSMutableHandleValue vp)
 {
     // Get the security manager
     nsScriptSecurityManager *ssm =
         nsScriptSecurityManager::GetScriptSecurityManager();
 
     NS_ASSERTION(ssm, "Failed to get security manager service");
     if (!ssm)
         return JS_FALSE;
 
     // Get the object being accessed.  We protect these cases:
     // 1. The Function.prototype.caller property's value, which might lead
     //    an attacker up a call-stack to a function or another object from
     //    a different trust domain.
     // 2. A user-defined getter or