Merge inbound to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Thu, 19 Jun 2014 18:07:43 -0700
changeset 189634 bdac18bd6c74
parent 189602 aaaa56174361 (current diff)
parent 189633 5ffc3f1b9db9 (diff)
child 189647 dc02747d73df
child 189723 0326959ec1c8
child 189791 36efd6ffbcd0
push id26992
push userkwierso@gmail.com
push dateFri, 20 Jun 2014 01:07:53 +0000
treeherdermozilla-central@bdac18bd6c74 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone33.0a1
first release with
nightly linux32
bdac18bd6c74 / 33.0a1 / 20140620030201 / files
nightly linux64
bdac18bd6c74 / 33.0a1 / 20140620030201 / files
nightly mac
bdac18bd6c74 / 33.0a1 / 20140620030201 / files
nightly win32
bdac18bd6c74 / 33.0a1 / 20140620030201 / files
nightly win64
bdac18bd6c74 / 33.0a1 / 20140620030201 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to m-c a=merge
browser/components/translation/Translation.jsm
js/src/tests/ecma_6/LexicalEnvironment/browser.js
js/src/tests/ecma_6/LexicalEnvironment/shell.js
js/src/tests/ecma_6/LexicalEnvironment/with-global-ignores-global-let-variables.js
testing/marionette/client/marionette/b2gbuild.py
testing/marionette/client/marionette/b2ginstance.py
testing/marionette/client/marionette/emulator.py
testing/marionette/client/marionette/emulator_battery.py
testing/marionette/client/marionette/emulator_geo.py
testing/marionette/client/marionette/emulator_screen.py
testing/mozbase/mozrunner/mozrunner/base.py
testing/mozbase/mozrunner/mozrunner/local.py
testing/mozbase/mozrunner/mozrunner/remote.py
--- a/browser/components/translation/Translation.jsm
+++ b/browser/components/translation/Translation.jsm
@@ -43,19 +43,26 @@ this.Translation = {
                                       .getSelectedLocale("global")
                                       .split("-")[0];
     }
     return this._defaultTargetLanguage;
   },
 
   documentStateReceived: function(aBrowser, aData) {
     if (aData.state == this.STATE_OFFER) {
-      if (this.supportedSourceLanguages.indexOf(aData.detectedLanguage) == -1 ||
-          aData.detectedLanguage == this.defaultTargetLanguage)
+      if (aData.detectedLanguage == this.defaultTargetLanguage) {
+        // Detected language is the same as the user's locale.
         return;
+      }
+
+      if (this.supportedSourceLanguages.indexOf(aData.detectedLanguage) == -1) {
+        // Detected language is not part of the supported languages.
+        TranslationHealthReport.recordMissedTranslationOpportunity(aData.detectedLanguage);
+        return;
+      }
 
       TranslationHealthReport.recordTranslationOpportunity(aData.detectedLanguage);
     }
 
     if (!Services.prefs.getBoolPref(TRANSLATION_PREF_SHOWUI))
       return;
 
     if (!aBrowser.translationUI)
@@ -253,16 +260,27 @@ let TranslationHealthReport = {
    * Record a translation opportunity in the health report.
    * @param language
    *        The language of the page.
    */
   recordTranslationOpportunity: function (language) {
     this._withProvider(provider => provider.recordTranslationOpportunity(language));
    },
 
+  /**
+   * Record a missed translation opportunity in the health report.
+   * A missed opportunity is when the language detected is not part
+   * of the supported languages.
+   * @param language
+   *        The language of the page.
+   */
+  recordMissedTranslationOpportunity: function (language) {
+    this._withProvider(provider => provider.recordMissedTranslationOpportunity(language));
+  },
+
    /**
    * Record a translation in the health report.
    * @param langFrom
    *        The language of the page.
    * @param langTo
    *        The language translated to
    * @param numCharacters
    *        The number of characters that were translated
@@ -332,19 +350,21 @@ function TranslationMeasurement1() {
 TranslationMeasurement1.prototype = Object.freeze({
   __proto__: Metrics.Measurement.prototype,
 
   name: "translation",
   version: 1,
 
   fields: {
     translationOpportunityCount: DAILY_COUNTER_FIELD,
+    missedTranslationOpportunityCount: DAILY_COUNTER_FIELD,
     pageTranslatedCount: DAILY_COUNTER_FIELD,
     charactersTranslatedCount: DAILY_COUNTER_FIELD,
     translationOpportunityCountsByLanguage: DAILY_LAST_TEXT_FIELD,
+    missedTranslationOpportunityCountsByLanguage: DAILY_LAST_TEXT_FIELD,
     pageTranslatedCountsByLanguage: DAILY_LAST_TEXT_FIELD,
     detectedLanguageChangedBefore: DAILY_COUNTER_FIELD,
     detectedLanguageChangedAfter: DAILY_COUNTER_FIELD,
     detectLanguageEnabled: DAILY_LAST_NUMERIC_FIELD,
     showTranslationUI: DAILY_LAST_NUMERIC_FIELD,
   },
 
   shouldIncludeField: function (field) {
@@ -379,16 +399,17 @@ TranslationMeasurement1.prototype = Obje
     };
 
     return function (data) {
       let result = serializer(data);
 
       // Special case the serialization of these fields so that
       // they are sent as objects, not stringified objects.
       _parseInPlace(result, "translationOpportunityCountsByLanguage");
+      _parseInPlace(result, "missedTranslationOpportunityCountsByLanguage");
       _parseInPlace(result, "pageTranslatedCountsByLanguage");
 
       return result;
     }
   }
 });
 
 this.TranslationProvider = function () {
@@ -418,16 +439,35 @@ TranslationProvider.prototype = Object.f
       langCounts = JSON.stringify(langCounts);
 
       yield m.setDailyLastText("translationOpportunityCountsByLanguage",
                                langCounts, date);
 
     }.bind(this));
   },
 
+  recordMissedTranslationOpportunity: function (language, date=new Date()) {
+    let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
+                                TranslationMeasurement1.prototype.version);
+
+    return this._enqueueTelemetryStorageTask(function* recordTask() {
+      yield m.incrementDailyCounter("missedTranslationOpportunityCount", date);
+
+      let langCounts = yield m._getDailyLastTextFieldAsJSON(
+        "missedTranslationOpportunityCountsByLanguage", date);
+
+      langCounts[language] = (langCounts[language] || 0) + 1;
+      langCounts = JSON.stringify(langCounts);
+
+      yield m.setDailyLastText("missedTranslationOpportunityCountsByLanguage",
+                               langCounts, date);
+
+    }.bind(this));
+  },
+
   recordTranslation: function (langFrom, langTo, numCharacters, date=new Date()) {
     let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
                                 TranslationMeasurement1.prototype.version);
 
     return this._enqueueTelemetryStorageTask(function* recordTask() {
       yield m.incrementDailyCounter("pageTranslatedCount", date);
       yield m.incrementDailyCounter("charactersTranslatedCount", date,
                                     numCharacters);
--- a/browser/components/translation/test/unit/test_healthreport.js
+++ b/browser/components/translation/test/unit/test_healthreport.js
@@ -64,31 +64,57 @@ add_task(function* test_translation_oppo
   let day = values.days.getDay(now);
   Assert.ok(day.has("translationOpportunityCount"));
   Assert.equal(day.get("translationOpportunityCount"), 1);
 
   Assert.ok(day.has("translationOpportunityCountsByLanguage"));
   let countsByLanguage = JSON.parse(day.get("translationOpportunityCountsByLanguage"));
   Assert.equal(countsByLanguage["fr"], 1);
 
+  // Record a missed opportunity.
+  yield provider.recordMissedTranslationOpportunity("it", now);
+
+  values = yield m.getValues();
+  day = values.days.getDay(now);
+  Assert.equal(values.days.size, 1);
+  Assert.ok(values.days.hasDay(now));
+  Assert.ok(day.has("missedTranslationOpportunityCount"));
+  Assert.equal(day.get("missedTranslationOpportunityCount"), 1);
+
+  Assert.ok(day.has("missedTranslationOpportunityCountsByLanguage"));
+  let missedCountsByLanguage = JSON.parse(day.get("missedTranslationOpportunityCountsByLanguage"));
+  Assert.equal(missedCountsByLanguage["it"], 1);
+
   // Record more opportunities.
   yield provider.recordTranslationOpportunity("fr", now);
   yield provider.recordTranslationOpportunity("fr", now);
   yield provider.recordTranslationOpportunity("es", now);
 
+  yield provider.recordMissedTranslationOpportunity("it", now);
+  yield provider.recordMissedTranslationOpportunity("cs", now);
+  yield provider.recordMissedTranslationOpportunity("fi", now);
+
   values = yield m.getValues();
-  let day = values.days.getDay(now);
+  day = values.days.getDay(now);
   Assert.ok(day.has("translationOpportunityCount"));
   Assert.equal(day.get("translationOpportunityCount"), 4);
+  Assert.ok(day.has("missedTranslationOpportunityCount"));
+  Assert.equal(day.get("missedTranslationOpportunityCount"), 4);
 
   Assert.ok(day.has("translationOpportunityCountsByLanguage"));
   countsByLanguage = JSON.parse(day.get("translationOpportunityCountsByLanguage"));
   Assert.equal(countsByLanguage["fr"], 3);
   Assert.equal(countsByLanguage["es"], 1);
 
+  Assert.ok(day.has("missedTranslationOpportunityCountsByLanguage"));
+  missedCountsByLanguage = JSON.parse(day.get("missedTranslationOpportunityCountsByLanguage"));
+  Assert.equal(missedCountsByLanguage["it"], 2);
+  Assert.equal(missedCountsByLanguage["cs"], 1);
+  Assert.equal(missedCountsByLanguage["fi"], 1);
+
   yield provider.shutdown();
   yield storage.close();
 });
 
 // Test recording a translation.
 add_task(function* test_record_translation() {
   let storage = yield Metrics.Storage("translation");
   let provider = new TranslationProvider();
--- a/browser/installer/windows/nsis/maintenanceservice_installer.nsi
+++ b/browser/installer/windows/nsis/maintenanceservice_installer.nsi
@@ -185,21 +185,21 @@ Section "MaintenanceService"
   ; If a service already exists, the command line parameter will stop the
   ; service and only install itself if it is newer than the already installed
   ; service.  If successful it will remove the old maintenanceservice.exe
   ; and replace it with maintenanceservice_tmp.exe.
   ClearErrors
   ${GetParameters} $0
   ${GetOptions} "$0" "/Upgrade" $0
   ${If} ${Errors}
-    nsExec::Exec '"$INSTDIR\$TempMaintServiceName" install'
+    ExecWait '"$INSTDIR\$TempMaintServiceName" install'
   ${Else}
     ; The upgrade cmdline is the same as install except
     ; It will fail if the service isn't already installed.
-    nsExec::Exec '"$INSTDIR\$TempMaintServiceName" upgrade'
+    ExecWait '"$INSTDIR\$TempMaintServiceName" upgrade'
   ${EndIf}
 
   WriteUninstaller "$INSTDIR\Uninstall.exe"
   WriteRegStr HKLM "${MaintUninstallKey}" "DisplayName" "${MaintFullName}"
   WriteRegStr HKLM "${MaintUninstallKey}" "UninstallString" \
                    '"$INSTDIR\uninstall.exe"'
   WriteRegStr HKLM "${MaintUninstallKey}" "DisplayIcon" \
                    "$INSTDIR\Uninstall.exe,0"
@@ -250,17 +250,17 @@ Function un.RenameDelete
   ${Else} 
     Delete /REBOOTOK "$9.moz-delete"
   ${EndIf}
   ClearErrors
 FunctionEnd
 
 Section "Uninstall"
   ; Delete the service so that no updates will be attempted
-  nsExec::Exec '"$INSTDIR\maintenanceservice.exe" uninstall'
+  ExecWait '"$INSTDIR\maintenanceservice.exe" uninstall'
 
   Push "$INSTDIR\updater.ini"
   Call un.RenameDelete
   Push "$INSTDIR\maintenanceservice.exe"
   Call un.RenameDelete
   Push "$INSTDIR\maintenanceservice_tmp.exe"
   Call un.RenameDelete
   Push "$INSTDIR\maintenanceservice.old"
--- a/build/autoconf/compiler-opts.m4
+++ b/build/autoconf/compiler-opts.m4
@@ -192,44 +192,31 @@ AC_DEFUN([MOZ_COMPILER_OPTS],
 
   MOZ_DEBUGGING_OPTS
   MOZ_RTTI
 if test "$CLANG_CXX"; then
     ## We disable return-type-c-linkage because jsval is defined as a C++ type but is
     ## returned by C functions. This is possible because we use knowledge about the ABI
     ## to typedef it to a C type with the same layout when the headers are included
     ## from C.
-    ##
-    ## mismatched-tags is disabled (bug 780474) mostly because it's useless.
-    ## Worse, it's not supported by gcc, so it will cause tryserver bustage
-    ## without any easy way for non-Clang users to check for it.
-    _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wno-unknown-warning-option -Wno-return-type-c-linkage -Wno-mismatched-tags"
+    _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wno-unknown-warning-option -Wno-return-type-c-linkage"
 fi
 
 AC_MSG_CHECKING([whether the C++ compiler ($CXX $CXXFLAGS $LDFLAGS) actually is a C++ compiler])
 AC_LANG_SAVE
 AC_LANG_CPLUSPLUS
 _SAVE_LIBS=$LIBS
 LIBS=
 AC_TRY_LINK([#include <new>], [int *foo = new int;],,
             AC_MSG_RESULT([no])
             AC_MSG_ERROR([$CXX $CXXFLAGS $LDFLAGS failed to compile and link a simple C++ source.]))
 LIBS=$_SAVE_LIBS
 AC_LANG_RESTORE
 AC_MSG_RESULT([yes])
 
-if test -z "$GNU_CC"; then
-    case "$target" in
-    *-mingw*)
-        ## Warning 4099 (equivalent of mismatched-tags) is disabled (bug 780474)
-        ## for the same reasons as above.
-        _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -wd4099"
-    esac
-fi
-
 if test -n "$DEVELOPER_OPTIONS"; then
     MOZ_FORCE_GOLD=1
 fi
 
 MOZ_ARG_ENABLE_BOOL(gold,
 [  --enable-gold           Enable GNU Gold Linker when it is not already the default],
     MOZ_FORCE_GOLD=1,
     MOZ_FORCE_GOLD=
--- a/build/mobile/b2gautomation.py
+++ b/build/mobile/b2gautomation.py
@@ -189,17 +189,17 @@ class B2GRemoteAutomation(Automation):
     def restartB2G(self):
         # TODO hangs in subprocess.Popen without this delay
         time.sleep(5)
         self._devicemanager._checkCmd(['shell', 'stop', 'b2g'])
         # Wait for a bit to make sure B2G has completely shut down.
         time.sleep(10)
         self._devicemanager._checkCmd(['shell', 'start', 'b2g'])
         if self._is_emulator:
-            self.marionette.emulator.wait_for_port()
+            self.marionette.emulator.wait_for_port(self.marionette.port)
 
     def rebootDevice(self):
         # find device's current status and serial number
         serial, status = self.getDeviceStatus()
 
         # reboot!
         self._devicemanager._runCmd(['shell', '/system/bin/reboot'])
 
@@ -257,17 +257,17 @@ class B2GRemoteAutomation(Automation):
         # Set up port forwarding again for Marionette, since any that
         # existed previously got wiped out by the reboot.
         if not self._is_emulator:
             self._devicemanager._checkCmd(['forward',
                                            'tcp:%s' % self.marionette.port,
                                            'tcp:%s' % self.marionette.port])
 
         if self._is_emulator:
-            self.marionette.emulator.wait_for_port()
+            self.marionette.emulator.wait_for_port(self.marionette.port)
         else:
             time.sleep(5)
 
         # start a marionette session
         session = self.marionette.start_session()
         if 'b2g' not in session:
             raise Exception("bad session value %s returned by start_session" % session)
 
--- a/build/valgrind/mach_commands.py
+++ b/build/valgrind/mach_commands.py
@@ -126,17 +126,17 @@ class MachCommands(MachCommandBase):
                 valgrind_args.append('--suppressions=' + supps_file2)
 
             exitcode = None
             try:
                 runner = FirefoxRunner(profile=profile,
                                        binary=self.get_binary_path(),
                                        cmdargs=firefox_args,
                                        env=env,
-                                       kp_kwargs=kp_kwargs)
+                                       process_args=kp_kwargs)
                 runner.start(debug_args=valgrind_args)
                 exitcode = runner.wait()
 
             finally:
                 errs = outputHandler.error_count
                 supps = outputHandler.suppression_count
                 if errs != supps:
                     status = 1  # turns the TBPL job orange
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -2035,21 +2035,18 @@ public:
   };
   /**
    * Parses the value of the autocomplete attribute into aResult, ensuring it's
    * composed of valid tokens, otherwise the value "" is used.
    * Note that this method is used for form fields, not on a <form> itself.
    *
    * @return whether aAttr was valid and can be cached.
    */
-  static AutocompleteAttrState
-  SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
-                                 nsAString& aResult,
-                                 AutocompleteAttrState aCachedState =
-                                   eAutocompleteAttrState_Unknown);
+  static AutocompleteAttrState SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
+                                                          nsAString& aResult);
 
   /**
    * This will parse aSource, to extract the value of the pseudo attribute
    * with the name specified in aName. See
    * http://www.w3.org/TR/xml-stylesheet/#NT-StyleSheetPI for the specification
    * which is used to parse aSource.
    *
    * @param aSource the string to parse
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -850,53 +850,35 @@ nsContentUtils::IsAutocompleteEnabled(ns
     form->GetAutocomplete(autocomplete);
   }
 
   return !autocomplete.EqualsLiteral("off");
 }
 
 nsContentUtils::AutocompleteAttrState
 nsContentUtils::SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
-                                               nsAString& aResult,
-                                               AutocompleteAttrState aCachedState)
-{
-  if (!aAttr ||
-      aCachedState == nsContentUtils::eAutocompleteAttrState_Invalid) {
-    return aCachedState;
-  }
-
-  if (aCachedState == nsContentUtils::eAutocompleteAttrState_Valid) {
-    uint32_t atomCount = aAttr->GetAtomCount();
-    for (uint32_t i = 0; i < atomCount; i++) {
-      if (i != 0) {
-        aResult.Append(' ');
-      }
-      aResult.Append(nsDependentAtomString(aAttr->AtomAt(i)));
-    }
-    nsContentUtils::ASCIIToLower(aResult);
-    return aCachedState;
-  }
-
+                                           nsAString& aResult)
+{
   AutocompleteAttrState state = InternalSerializeAutocompleteAttribute(aAttr, aResult);
   if (state == eAutocompleteAttrState_Valid) {
     ASCIIToLower(aResult);
   } else {
     aResult.Truncate();
   }
   return state;
 }
 
 /**
  * Helper to validate the @autocomplete tokens.
  *
  * @return {AutocompleteAttrState} The state of the attribute (invalid/valid).
  */
 nsContentUtils::AutocompleteAttrState
 nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrVal,
-                                                       nsAString& aResult)
+                                                   nsAString& aResult)
 {
   // No sandbox attribute so we are done
   if (!aAttrVal) {
     return eAutocompleteAttrState_Invalid;
   }
 
   uint32_t numTokens = aAttrVal->GetAtomCount();
   if (!numTokens) {
--- a/content/html/content/src/HTMLInputElement.cpp
+++ b/content/html/content/src/HTMLInputElement.cpp
@@ -1515,20 +1515,33 @@ NS_IMPL_STRING_ATTR(HTMLInputElement, Pl
 NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLInputElement, Type, type,
                                 kInputDefaultType->tag)
 
 NS_IMETHODIMP
 HTMLInputElement::GetAutocomplete(nsAString& aValue)
 {
   aValue.Truncate(0);
   const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
-
-  mAutocompleteAttrState =
-    nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aValue,
-                                                   mAutocompleteAttrState);
+  if (!attributeVal ||
+      mAutocompleteAttrState == nsContentUtils::eAutocompleteAttrState_Invalid) {
+    return NS_OK;
+  }
+  if (mAutocompleteAttrState == nsContentUtils::eAutocompleteAttrState_Valid) {
+    uint32_t atomCount = attributeVal->GetAtomCount();
+    for (uint32_t i = 0; i < atomCount; i++) {
+      if (i != 0) {
+        aValue.Append(' ');
+      }
+      aValue.Append(nsDependentAtomString(attributeVal->AtomAt(i)));
+    }
+    nsContentUtils::ASCIIToLower(aValue);
+    return NS_OK;
+  }
+
+  mAutocompleteAttrState = nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aValue);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HTMLInputElement::SetAutocomplete(const nsAString& aValue)
 {
   return SetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, nullptr, aValue, true);
 }
--- a/content/html/content/src/HTMLSelectElement.cpp
+++ b/content/html/content/src/HTMLSelectElement.cpp
@@ -99,17 +99,16 @@ SafeOptionListMutation::~SafeOptionListM
 
 // construction, destruction
 
 
 HTMLSelectElement::HTMLSelectElement(already_AddRefed<nsINodeInfo>& aNodeInfo,
                                      FromParser aFromParser)
   : nsGenericHTMLFormElementWithState(aNodeInfo),
     mOptions(new HTMLOptionsCollection(MOZ_THIS_IN_INITIALIZER_LIST())),
-    mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown),
     mIsDoneAddingChildren(!aFromParser),
     mDisabledChanged(false),
     mMutating(false),
     mInhibitStateRestoration(!!(aFromParser & FROM_PARSER_FRAGMENT)),
     mSelectionHasChanged(false),
     mDefaultSelectionSet(false),
     mCanShowInvalidUI(true),
     mCanShowValidUI(true),
@@ -173,26 +172,16 @@ HTMLSelectElement::SetCustomValidity(con
 {
   nsIConstraintValidation::SetCustomValidity(aError);
 
   UpdateState(true);
 
   return NS_OK;
 }
 
-void
-HTMLSelectElement::GetAutocomplete(DOMString& aValue)
-{
-  const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
-
-  mAutocompleteAttrState =
-    nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aValue,
-                                                   mAutocompleteAttrState);
-}
-
 NS_IMETHODIMP
 HTMLSelectElement::GetForm(nsIDOMHTMLFormElement** aForm)
 {
   return nsGenericHTMLFormElementWithState::GetForm(aForm);
 }
 
 nsresult
 HTMLSelectElement::InsertChildAt(nsIContent* aKid,
@@ -1339,19 +1328,16 @@ nsresult
 HTMLSelectElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None) {
     if (aName == nsGkAtoms::disabled) {
       UpdateBarredFromConstraintValidation();
     } else if (aName == nsGkAtoms::required) {
       UpdateValueMissingValidityState();
-    } else if (aName == nsGkAtoms::autocomplete) {
-      // Clear the cached @autocomplete attribute state
-      mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
     }
 
     UpdateState(aNotify);
   }
 
   return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName,
                                                          aValue, aNotify);
 }
@@ -1429,23 +1415,18 @@ HTMLSelectElement::DoneAddingChildren(bo
 }
 
 bool
 HTMLSelectElement::ParseAttribute(int32_t aNamespaceID,
                                   nsIAtom* aAttribute,
                                   const nsAString& aValue,
                                   nsAttrValue& aResult)
 {
-  if (kNameSpaceID_None == aNamespaceID) {
-    if (aAttribute == nsGkAtoms::size) {
-      return aResult.ParsePositiveIntValue(aValue);
-    } else if (aAttribute == nsGkAtoms::autocomplete) {
-      aResult.ParseAtomArray(aValue);
-      return true;
-    }
+  if (aAttribute == nsGkAtoms::size && kNameSpaceID_None == aNamespaceID) {
+    return aResult.ParsePositiveIntValue(aValue);
   }
   return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                               aResult);
 }
 
 void
 HTMLSelectElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
                                          nsRuleData* aData)
--- a/content/html/content/src/HTMLSelectElement.h
+++ b/content/html/content/src/HTMLSelectElement.h
@@ -12,17 +12,16 @@
 
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/HTMLOptionsCollection.h"
 #include "mozilla/ErrorResult.h"
 #include "nsCheapSets.h"
 #include "nsCOMPtr.h"
 #include "nsError.h"
 #include "mozilla/dom/HTMLFormElement.h"
-#include "nsContentUtils.h"
 
 class nsContentList;
 class nsIDOMHTMLOptionElement;
 class nsIHTMLCollection;
 class nsISelectControlFrame;
 class nsPresState;
 
 namespace mozilla {
@@ -155,21 +154,16 @@ public:
   bool Autofocus() const
   {
     return GetBoolAttr(nsGkAtoms::autofocus);
   }
   void SetAutofocus(bool aVal, ErrorResult& aRv)
   {
     SetHTMLBoolAttr(nsGkAtoms::autofocus, aVal, aRv);
   }
-  void GetAutocomplete(DOMString& aValue);
-  void SetAutocomplete(const nsAString& aValue, ErrorResult& aRv)
-  {
-    SetHTMLAttr(nsGkAtoms::autocomplete, aValue, aRv);
-  }
   bool Disabled() const
   {
     return GetBoolAttr(nsGkAtoms::disabled);
   }
   void SetDisabled(bool aVal, ErrorResult& aRv)
   {
     SetHTMLBoolAttr(nsGkAtoms::disabled, aVal, aRv);
   }
@@ -606,17 +600,16 @@ protected:
       return true;
     }
 
     return mSelectionHasChanged;
   }
 
   /** The options[] array */
   nsRefPtr<HTMLOptionsCollection> mOptions;
-  nsContentUtils::AutocompleteAttrState mAutocompleteAttrState;
   /** false if the parser is in the middle of adding children. */
   bool            mIsDoneAddingChildren;
   /** true if our disabled state has changed from the default **/
   bool            mDisabledChanged;
   /** true if child nodes are being added or removed.
    *  Used by SafeOptionListMutation.
    */
   bool            mMutating;
--- a/content/html/content/test/forms/test_input_autocomplete.html
+++ b/content/html/content/test/forms/test_input_autocomplete.html
@@ -8,18 +8,17 @@ Test @autocomplete on <input>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
 </head>
 
 <body>
 <p id="display"></p>
 <div id="content" style="display: none">
   <form>
-    <input id="input-field" />
-    <select id="select-field" />
+    <input id="field" />
   </form>
 </div>
 <pre id="test">
 <script>
 "use strict";
 
 var values = [
   // @autocomplete content attribute, expected IDL attribute value
@@ -65,41 +64,38 @@ var values = [
   // Four tokens (invalid)
   ["billing billing mobile tel", ""],
 
   // Five tokens (invalid)
   ["billing billing billing mobile tel", ""],
 ];
 
 var types = [undefined, "hidden", "text", "search"]; // Valid types for all non-multiline hints.
+var field = document.getElementById("field");
 
-function checkAutocompleteValues(field, type) {
+function checkAutocompleteValues(type) {
   for (var test of values) {
     if (typeof(test[0]) === "undefined")
       field.removeAttribute("autocomplete");
     else
       field.setAttribute("autocomplete", test[0]);
     ise(field.autocomplete, test[1], "Checking @autocomplete for @type=" + type + " of: " + test[0]);
     ise(field.autocomplete, test[1], "Checking cached @autocomplete for @type=" + type + " of: " + test[0]);
   }
 }
 
 function start() {
-  var inputField = document.getElementById("input-field");
   for (var type of types) {
     // Switch the input type
     if (typeof(type) === "undefined")
-      inputField.removeAttribute("type");
+      field.removeAttribute("type");
     else
-      inputField.type = type;
-    checkAutocompleteValues(inputField, type || "");
+      field.type = type;
+    checkAutocompleteValues(type || "");
   }
-
-  var selectField = document.getElementById("select-field");
-  checkAutocompleteValues(selectField, "select");
   SimpleTest.finish();
 }
 
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPrefEnv({"set": [["dom.forms.autocomplete.experimental", true]]}, start);
 
 </script>
 </pre>
--- a/dom/webidl/HTMLSelectElement.webidl
+++ b/dom/webidl/HTMLSelectElement.webidl
@@ -5,18 +5,16 @@
  *
  * The origin of this IDL file is
  * http://www.whatwg.org/html/#the-select-element
  */
 
 interface HTMLSelectElement : HTMLElement {
   [SetterThrows, Pure]
            attribute boolean autofocus;
-  [Pref="dom.forms.autocomplete.experimental", SetterThrows, Pure]
-           attribute DOMString autocomplete;
   [SetterThrows, Pure]
            attribute boolean disabled;
   [Pure]
   readonly attribute HTMLFormElement? form;
   [SetterThrows, Pure]
            attribute boolean multiple;
   [SetterThrows, Pure]
            attribute DOMString name;
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -121,18 +121,16 @@ TiledContentClient::UseTiledLayerBuffer(
   // TiledLayerBufferComposite.
   buffer->ReadLock();
 
   mForwarder->UseTiledLayerBuffer(this, buffer->GetSurfaceDescriptorTiles());
   buffer->ClearPaintedRegion();
 }
 
 SharedFrameMetricsHelper::SharedFrameMetricsHelper()
-  : mLastProgressiveUpdateWasLowPrecision(false)
-  , mProgressiveUpdateWasInDanger(false)
 {
   MOZ_COUNT_CTOR(SharedFrameMetricsHelper);
 }
 
 SharedFrameMetricsHelper::~SharedFrameMetricsHelper()
 {
   MOZ_COUNT_DTOR(SharedFrameMetricsHelper);
 }
@@ -170,27 +168,16 @@ SharedFrameMetricsHelper::UpdateFromComp
                                                 compositorMetrics)) {
     FindFallbackContentFrameMetrics(aLayer, aCompositionBounds, aZoom);
     return false;
   }
 
   aCompositionBounds = ParentLayerRect(compositorMetrics.mCompositionBounds);
   aZoom = compositorMetrics.GetZoomToParent();
 
-  // Reset the checkerboard risk flag when switching to low precision
-  // rendering.
-  if (aLowPrecision && !mLastProgressiveUpdateWasLowPrecision) {
-    // Skip low precision rendering until we're at risk of checkerboarding.
-    if (!mProgressiveUpdateWasInDanger) {
-      return true;
-    }
-    mProgressiveUpdateWasInDanger = false;
-  }
-  mLastProgressiveUpdateWasLowPrecision = aLowPrecision;
-
   // Always abort updates if the resolution has changed. There's no use
   // in drawing at the incorrect resolution.
   if (!FuzzyEquals(compositorMetrics.GetZoom().scale, contentMetrics.GetZoom().scale)) {
     return true;
   }
 
   // Never abort drawing if we can't be sure we've sent a more recent
   // display-port. If we abort updating when we shouldn't, we can end up
@@ -200,23 +187,30 @@ SharedFrameMetricsHelper::UpdateFromComp
       fabsf(contentMetrics.GetScrollOffset().y - compositorMetrics.GetScrollOffset().y) <= 2 &&
       fabsf(contentMetrics.mDisplayPort.x - compositorMetrics.mDisplayPort.x) <= 2 &&
       fabsf(contentMetrics.mDisplayPort.y - compositorMetrics.mDisplayPort.y) <= 2 &&
       fabsf(contentMetrics.mDisplayPort.width - compositorMetrics.mDisplayPort.width) <= 2 &&
       fabsf(contentMetrics.mDisplayPort.height - compositorMetrics.mDisplayPort.height)) {
     return false;
   }
 
-  // When not a low precision pass and the page is in danger of checker boarding
-  // abort update.
-  if (!aLowPrecision && !mProgressiveUpdateWasInDanger) {
-    if (AboutToCheckerboard(contentMetrics, compositorMetrics)) {
-      mProgressiveUpdateWasInDanger = true;
-      return true;
-    }
+  bool scrollUpdatePending = contentMetrics.GetScrollOffsetUpdated() &&
+      contentMetrics.GetScrollGeneration() != compositorMetrics.GetScrollGeneration();
+  // If scrollUpdatePending is true, then that means the content-side
+  // metrics has a new scroll offset that is going to be forced into the
+  // compositor but it hasn't gotten there yet.
+  // Even though right now comparing the metrics might indicate we're
+  // about to checkerboard (and that's true), the checkerboarding will
+  // disappear as soon as the new scroll offset update is processed
+  // on the compositor side. To avoid leaving things in a low-precision
+  // paint, we need to detect and handle this case (bug 1026756).
+  if (!aLowPrecision && !scrollUpdatePending && AboutToCheckerboard(contentMetrics, compositorMetrics)) {
+    TILING_PRLOG_OBJ(("TILING: Checkerboard abort content %s\n", tmpstr.get()), contentMetrics);
+    TILING_PRLOG_OBJ(("TILING: Checkerboard abort compositor %s\n", tmpstr.get()), compositorMetrics);
+    return true;
   }
 
   // Abort drawing stale low-precision content if there's a more recent
   // display-port in the pipeline.
   if (aLowPrecision && !aHasPendingNewThebesContent) {
     return true;
   }
 
--- a/gfx/layers/client/TiledContentClient.h
+++ b/gfx/layers/client/TiledContentClient.h
@@ -331,19 +331,16 @@ public:
    * Determines if the compositor's upcoming composition bounds has fallen
    * outside of the contents display port. If it has then the compositor
    * will start to checker board. Checker boarding is when the compositor
    * tries to composite a tile and it is not available. Historically
    * a tile with a checker board pattern was used. Now a blank tile is used.
    */
   bool AboutToCheckerboard(const FrameMetrics& aContentMetrics,
                            const FrameMetrics& aCompositorMetrics);
-private:
-  bool mLastProgressiveUpdateWasLowPrecision;
-  bool mProgressiveUpdateWasInDanger;
 };
 
 /**
  * Provide an instance of TiledLayerBuffer backed by drawable TextureClients.
  * This buffer provides an implementation of ValidateTile using a
  * thebes callback and can support painting using a single paint buffer.
  * Whether a single paint buffer is used is controlled by
  * gfxPrefs::PerTileDrawing().
--- a/gfx/layers/composite/ThebesLayerComposite.cpp
+++ b/gfx/layers/composite/ThebesLayerComposite.cpp
@@ -128,17 +128,17 @@ ThebesLayerComposite::RenderLayer(const 
     }
   }
 #endif
 
   EffectChain effectChain(this);
   LayerManagerComposite::AutoAddMaskEffect autoMaskEffect(mMaskLayer, effectChain);
   AddBlendModeEffect(effectChain);
 
-  nsIntRegion visibleRegion = GetEffectiveVisibleRegion();
+  const nsIntRegion& visibleRegion = GetEffectiveVisibleRegion();
 
   TiledLayerProperties tiledLayerProps;
   if (mRequiresTiledProperties) {
     tiledLayerProps.mVisibleRegion = visibleRegion;
     tiledLayerProps.mEffectiveResolution = GetEffectiveResolution();
     tiledLayerProps.mValidRegion = mValidRegion;
   }
 
--- a/js/src/irregexp/NativeRegExpMacroAssembler.cpp
+++ b/js/src/irregexp/NativeRegExpMacroAssembler.cpp
@@ -750,17 +750,18 @@ NativeRegExpMacroAssembler::CheckNotBack
         // Parameters are
         //   Address byte_offset1 - Address captured substring's start.
         //   Address byte_offset2 - Address of current character position.
         //   size_t byte_length - length of capture in bytes(!)
         masm.setupUnalignedABICall(3, temp0);
         masm.passABIArg(current_character);
         masm.passABIArg(current_position);
         masm.passABIArg(temp1);
-        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, CaseInsensitiveCompareStrings));
+        int (*fun)(const jschar*, const jschar*, size_t) = CaseInsensitiveCompareStrings;
+        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, fun));
         masm.storeCallResult(temp0);
 
         masm.PopRegsInMask(volatileRegs);
 
         // Check if function returned non-zero for success or zero for failure.
         masm.branchTest32(Assembler::Zero, temp0, temp0, BranchOrBacktrack(on_no_match));
 
         // On success, increment position by length of capture.
@@ -807,19 +808,22 @@ NativeRegExpMacroAssembler::CheckCharact
     masm.branch32(Assembler::Above, temp0, Imm32(to - from), BranchOrBacktrack(on_not_in_range));
 }
 
 void
 NativeRegExpMacroAssembler::CheckBitInTable(uint8_t *table, Label *on_bit_set)
 {
     IonSpew(SPEW_PREFIX "CheckBitInTable");
 
-    JS_ASSERT(mode_ != ASCII); // Ascii case not handled here.
+    masm.movePtr(ImmPtr(table), temp0);
 
-    masm.movePtr(ImmPtr(table), temp0);
+    // kTableMask is currently 127, so we need to mask even if the input is
+    // Latin1. V8 has the same issue.
+    static_assert(JSString::MAX_LATIN1_CHAR > kTableMask,
+                  "No need to mask if MAX_LATIN1_CHAR <= kTableMask");
     masm.move32(Imm32(kTableSize - 1), temp1);
     masm.and32(current_character, temp1);
 
     masm.load8ZeroExtend(BaseIndex(temp0, temp1, TimesOne), temp0);
     masm.branchTest32(Assembler::NonZero, temp0, temp0, BranchOrBacktrack(on_bit_set));
 }
 
 void
@@ -870,17 +874,25 @@ NativeRegExpMacroAssembler::LoadCurrentC
 }
 
 void
 NativeRegExpMacroAssembler::LoadCurrentCharacterUnchecked(int cp_offset, int characters)
 {
     IonSpew(SPEW_PREFIX "LoadCurrentCharacterUnchecked(%d, %d)", cp_offset, characters);
 
     if (mode_ == ASCII) {
-        MOZ_ASSUME_UNREACHABLE("Ascii loading not implemented");
+        BaseIndex address(input_end_pointer, current_position, TimesOne, cp_offset);
+        if (characters == 4) {
+            masm.load32(address, current_character);
+        } else if (characters == 2) {
+            masm.load16ZeroExtend(address, current_character);
+        } else {
+            JS_ASSERT(characters = 1);
+            masm.load8ZeroExtend(address, current_character);
+        }
     } else {
         JS_ASSERT(mode_ == JSCHAR);
         JS_ASSERT(characters <= 2);
         BaseIndex address(input_end_pointer, current_position, TimesOne, cp_offset * sizeof(jschar));
         if (characters == 2)
             masm.load32(address, current_character);
         else
             masm.load16ZeroExtend(address, current_character);
--- a/js/src/irregexp/NativeRegExpMacroAssembler.h
+++ b/js/src/irregexp/NativeRegExpMacroAssembler.h
@@ -35,29 +35,30 @@
 
 #include "irregexp/RegExpMacroAssembler.h"
 
 namespace js {
 namespace irregexp {
 
 struct InputOutputData
 {
-    const jschar *inputStart;
-    const jschar *inputEnd;
+    const void *inputStart;
+    const void *inputEnd;
 
     // Index into inputStart (in chars) at which to begin matching.
     size_t startIndex;
 
     MatchPairs *matches;
 
     // RegExpMacroAssembler::Result for non-global regexps, number of captures
     // for global regexps.
     int32_t result;
 
-    InputOutputData(const jschar *inputStart, const jschar *inputEnd,
+    template <typename CharT>
+    InputOutputData(const CharT *inputStart, const CharT *inputEnd,
                     size_t startIndex, MatchPairs *matches)
       : inputStart(inputStart),
         inputEnd(inputEnd),
         startIndex(startIndex),
         matches(matches),
         result(0)
     {}
 };
--- a/js/src/irregexp/RegExpEngine.cpp
+++ b/js/src/irregexp/RegExpEngine.cpp
@@ -1661,34 +1661,47 @@ irregexp::CompilePattern(JSContext *cx, 
         assembler->set_global_mode((data->tree->min_match() > 0)
                                    ? RegExpMacroAssembler::GLOBAL_NO_ZERO_LENGTH_CHECK
                                    : RegExpMacroAssembler::GLOBAL);
     }
 
     return compiler.Assemble(cx, assembler, node, data->capture_count);
 }
 
+template <typename CharT>
 RegExpRunStatus
-irregexp::ExecuteCode(JSContext *cx, jit::JitCode *codeBlock,
-                      const jschar *chars, size_t start, size_t length, MatchPairs *matches)
+irregexp::ExecuteCode(JSContext *cx, jit::JitCode *codeBlock, const CharT *chars, size_t start,
+                      size_t length, MatchPairs *matches)
 {
 #ifdef JS_ION
     typedef void (*RegExpCodeSignature)(InputOutputData *);
 
     InputOutputData data(chars, chars + length, start, matches);
 
     RegExpCodeSignature function = reinterpret_cast<RegExpCodeSignature>(codeBlock->raw());
-    CALL_GENERATED_REGEXP(function, &data);
+
+    {
+        JS::AutoSuppressGCAnalysis nogc;
+        CALL_GENERATED_REGEXP(function, &data);
+    }
 
     return (RegExpRunStatus) data.result;
 #else
     MOZ_CRASH();
 #endif
 }
 
+template RegExpRunStatus
+irregexp::ExecuteCode(JSContext *cx, jit::JitCode *codeBlock, const Latin1Char *chars, size_t start,
+                      size_t length, MatchPairs *matches);
+
+template RegExpRunStatus
+irregexp::ExecuteCode(JSContext *cx, jit::JitCode *codeBlock, const jschar *chars, size_t start,
+                      size_t length, MatchPairs *matches);
+
 // -------------------------------------------------------------------
 // Tree to graph conversion
 
 RegExpNode *
 RegExpAtom::ToNode(RegExpCompiler* compiler, RegExpNode* on_success)
 {
     TextElementVector *elms =
         compiler->alloc()->newInfallible<TextElementVector>(*compiler->alloc());
--- a/js/src/irregexp/RegExpEngine.h
+++ b/js/src/irregexp/RegExpEngine.h
@@ -103,23 +103,25 @@ struct RegExpCode
 
 RegExpCode
 CompilePattern(JSContext *cx, RegExpShared *shared, RegExpCompileData *data,
                HandleLinearString sample,  bool is_global, bool ignore_case = false,
                bool is_ascii = false);
 
 // Note: this may return RegExpRunStatus_Error if an interrupt was requested
 // while the code was executing.
+template <typename CharT>
 RegExpRunStatus
-ExecuteCode(JSContext *cx, jit::JitCode *codeBlock,
-            const jschar *chars, size_t start, size_t length, MatchPairs *matches);
+ExecuteCode(JSContext *cx, jit::JitCode *codeBlock, const CharT *chars, size_t start,
+            size_t length, MatchPairs *matches);
 
+template <typename CharT>
 RegExpRunStatus
-InterpretCode(JSContext *cx, const uint8_t *byteCode,
-              const jschar *chars, size_t start, size_t length, MatchPairs *matches);
+InterpretCode(JSContext *cx, const uint8_t *byteCode, const CharT *chars, size_t start,
+              size_t length, MatchPairs *matches);
 
 #define FOR_EACH_NODE_TYPE(VISIT)                                    \
   VISIT(End)                                                         \
   VISIT(Action)                                                      \
   VISIT(Choice)                                                      \
   VISIT(BackReference)                                               \
   VISIT(Assertion)                                                   \
   VISIT(Text)
--- a/js/src/irregexp/RegExpInterpreter.cpp
+++ b/js/src/irregexp/RegExpInterpreter.cpp
@@ -99,23 +99,24 @@ static int32_t
 Load16Aligned(const uint8_t* pc)
 {
     JS_ASSERT((reinterpret_cast<uintptr_t>(pc) & 1) == 0);
     return *reinterpret_cast<const uint16_t *>(pc);
 }
 
 #define BYTECODE(name)  case BC_##name:
 
+template <typename CharT>
 RegExpRunStatus
-irregexp::InterpretCode(JSContext *cx, const uint8_t *byteCode,
-                        const jschar *chars, size_t current, size_t length, MatchPairs *matches)
+irregexp::InterpretCode(JSContext *cx, const uint8_t *byteCode, const CharT *chars, size_t current,
+                        size_t length, MatchPairs *matches)
 {
     const uint8_t* pc = byteCode;
 
-    jschar current_char = current ? chars[current - 1] : '\n';
+    uint32_t current_char = current ? chars[current - 1] : '\n';
 
     RegExpStackCursor stack(cx);
 
     int32_t numRegisters = Load32Aligned(pc);
     pc += 4;
 
     Vector<int32_t, 0, SystemAllocPolicy> registers;
     if (!registers.growByUninitialized(numRegisters))
@@ -221,18 +222,18 @@ irregexp::InterpretCode(JSContext *cx, c
             pc += BC_LOAD_CURRENT_CHAR_UNCHECKED_LENGTH;
             break;
           }
           BYTECODE(LOAD_2_CURRENT_CHARS) {
             size_t pos = current + (insn >> BYTECODE_SHIFT);
             if (pos + 2 > length) {
                 pc = byteCode + Load32Aligned(pc + 4);
             } else {
-                jschar next = chars[pos + 1];
-                current_char = (chars[pos] | (next << (kBitsPerByte * sizeof(jschar))));
+                CharT next = chars[pos + 1];
+                current_char = (chars[pos] | (next << (kBitsPerByte * sizeof(CharT))));
                 pc += BC_LOAD_2_CURRENT_CHARS_LENGTH;
             }
             break;
           }
           BYTECODE(LOAD_2_CURRENT_CHARS_UNCHECKED) {
             int pos = current + (insn >> BYTECODE_SHIFT);
             jschar next = chars[pos + 1];
             current_char = (chars[pos] | (next << (kBitsPerByte * sizeof(jschar))));
@@ -416,17 +417,17 @@ irregexp::InterpretCode(JSContext *cx, c
             if (from < 0 || len <= 0) {
                 pc += BC_CHECK_NOT_BACK_REF_NO_CASE_LENGTH;
                 break;
             }
             if (current + len > length) {
                 pc = byteCode + Load32Aligned(pc + 4);
                 break;
             }
-            if (CaseInsensitiveCompareStrings(chars + from, chars + current, len * 2)) {
+            if (CaseInsensitiveCompareStrings(chars + from, chars + current, len * sizeof(CharT))) {
                 current += len;
                 pc += BC_CHECK_NOT_BACK_REF_NO_CASE_LENGTH;
             } else {
                 pc = byteCode + Load32Aligned(pc + 4);
             }
             break;
           }
           BYTECODE(CHECK_AT_START)
@@ -451,8 +452,16 @@ irregexp::InterpretCode(JSContext *cx, c
             break;
           }
           default:
             MOZ_ASSUME_UNREACHABLE("Bad bytecode");
             break;
         }
     }
 }
+
+template RegExpRunStatus
+irregexp::InterpretCode(JSContext *cx, const uint8_t *byteCode, const Latin1Char *chars, size_t current,
+                        size_t length, MatchPairs *matches);
+
+template RegExpRunStatus
+irregexp::InterpretCode(JSContext *cx, const uint8_t *byteCode, const jschar *chars, size_t current,
+                        size_t length, MatchPairs *matches);
--- a/js/src/irregexp/RegExpMacroAssembler.cpp
+++ b/js/src/irregexp/RegExpMacroAssembler.cpp
@@ -30,37 +30,46 @@
 
 #include "irregexp/RegExpMacroAssembler.h"
 
 #include "irregexp/RegExpBytecode.h"
 
 using namespace js;
 using namespace js::irregexp;
 
+template <typename CharT>
 int
-irregexp::CaseInsensitiveCompareStrings(const jschar *substring1, const jschar *substring2,
+irregexp::CaseInsensitiveCompareStrings(const CharT *substring1, const CharT *substring2,
 					size_t byteLength)
 {
-    JS_ASSERT(byteLength % 2 == 0);
-    size_t length = byteLength >> 1;
+    JS_ASSERT(byteLength % sizeof(CharT) == 0);
+    size_t length = byteLength / sizeof(CharT);
 
     for (size_t i = 0; i < length; i++) {
         jschar c1 = substring1[i];
         jschar c2 = substring2[i];
         if (c1 != c2) {
             c1 = unicode::ToLowerCase(c1);
             c2 = unicode::ToLowerCase(c2);
             if (c1 != c2)
                 return 0;
         }
     }
 
     return 1;
 }
 
+template int
+irregexp::CaseInsensitiveCompareStrings(const Latin1Char *substring1, const Latin1Char *substring2,
+					size_t byteLength);
+
+template int
+irregexp::CaseInsensitiveCompareStrings(const jschar *substring1, const jschar *substring2,
+					size_t byteLength);
+
 InterpretedRegExpMacroAssembler::InterpretedRegExpMacroAssembler(LifoAlloc *alloc, RegExpShared *shared,
                                                                  size_t numSavedRegisters)
   : RegExpMacroAssembler(*alloc, shared, numSavedRegisters),
     pc_(0),
     advance_current_start_(0),
     advance_current_offset_(0),
     advance_current_end_(kInvalidPC),
     buffer_(nullptr),
--- a/js/src/irregexp/RegExpMacroAssembler.h
+++ b/js/src/irregexp/RegExpMacroAssembler.h
@@ -212,18 +212,19 @@ class MOZ_STACK_CLASS RegExpMacroAssembl
         if (num_registers_ <= reg)
             num_registers_ = reg + 1;
     }
 
   public:
     RegExpShared *shared;
 };
 
+template <typename CharT>
 int
-CaseInsensitiveCompareStrings(const jschar *substring1, const jschar *substring2, size_t byteLength);
+CaseInsensitiveCompareStrings(const CharT *substring1, const CharT *substring2, size_t byteLength);
 
 class MOZ_STACK_CLASS InterpretedRegExpMacroAssembler : public RegExpMacroAssembler
 {
   public:
     InterpretedRegExpMacroAssembler(LifoAlloc *alloc, RegExpShared *shared, size_t numSavedRegisters);
     ~InterpretedRegExpMacroAssembler();
 
     // Inherited virtual methods.
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-28.js
@@ -0,0 +1,109 @@
+// Test that on->off->on and off->on->off toggles don't crash.
+
+function addRemove(dbg, g) {
+  dbg.addDebuggee(g);
+  var f = dbg.getNewestFrame();
+  while (f)
+    f = f.older;
+  dbg.removeDebuggee(g);
+}
+
+function removeAdd(dbg, g) {
+  dbg.removeDebuggee(g);
+  dbg.addDebuggee(g);
+  var f = dbg.getNewestFrame();
+  while (f)
+    f = f.older;
+}
+
+function newGlobalDebuggerPair(toggleSeq) {
+  var g = newGlobal();
+  var dbg = new Debugger;
+
+  if (toggleSeq == removeAdd)
+    dbg.addDebuggee(g);
+
+  g.eval("" + function f() { return g(); });
+  g.eval("" + function g() { return h(); });
+  g.eval("line0 = Error().lineNumber;");
+  g.eval("" + function h() {
+    for (var i = 0; i < 100; i++)
+      interruptIf(i == 95);
+    debugger;
+    return i;
+  });
+
+  setInterruptCallback(function () { return true; });
+
+  return [g, dbg];
+}
+
+function testInterrupt(toggleSeq) {
+  var [g, dbg] = newGlobalDebuggerPair(toggleSeq);
+
+  setInterruptCallback(function () {
+    toggleSeq(dbg, g);
+    return true;
+  });
+
+  assertEq(g.f(), 100);
+}
+
+function testPrologue(toggleSeq) {
+  var [g, dbg] = newGlobalDebuggerPair(toggleSeq);
+
+  dbg.onEnterFrame = function (f) {
+    if (f.callee && f.callee.name == "h")
+      toggleSeq(dbg, g);
+  };
+
+  assertEq(g.f(), 100);
+}
+
+function testEpilogue(toggleSeq) {
+  var [g, dbg] = newGlobalDebuggerPair(toggleSeq);
+
+  dbg.onEnterFrame = function (f) {
+    if (f.callee && f.callee.name == "h") {
+      f.onPop = function () {
+        toggleSeq(dbg, g);
+      };
+    }
+  };
+
+  assertEq(g.f(), 100);
+}
+
+function testTrap(toggleSeq) {
+  var [g, dbg] = newGlobalDebuggerPair(toggleSeq);
+
+  dbg.onEnterFrame = function (f) {
+    if (f.callee && f.callee.name == "h") {
+      var offs = f.script.getLineOffsets(g.line0 + 2);
+      assertEq(offs.length > 0, true);
+      f.script.setBreakpoint(offs[0], { hit: function () {
+        toggleSeq(dbg, g);
+      }});
+    }
+  };
+
+  assertEq(g.f(), 100);
+}
+
+function testDebugger(toggleSeq) {
+ var [g, dbg] = newGlobalDebuggerPair(toggleSeq);
+
+  dbg.onDebuggerStatement = function () {
+    toggleSeq(dbg, g);
+  };
+
+  assertEq(g.f(), 100);
+}
+
+testInterrupt(addRemove);
+testInterrupt(removeAdd);
+
+testPrologue(removeAdd);
+testEpilogue(removeAdd);
+testTrap(removeAdd);
+testDebugger(removeAdd);
--- a/js/src/jit-test/tests/latin1/regexp.js
+++ b/js/src/jit-test/tests/latin1/regexp.js
@@ -8,8 +8,26 @@ assertEq(re.sticky, false);
 
 // TwoByte
 re = new RegExp("foo[bB]a\\r\u1200", "im");
 assertEq(isLatin1(re.source), false);
 assertEq(re.source, "foo[bB]a\\r\u1200");
 assertEq(re.multiline, true);
 assertEq(re.ignoreCase, true);
 assertEq(re.sticky, false);
+
+re = /b[aA]r/;
+
+// Latin1
+assertEq(toLatin1("foobAr1234").search(re), 3);
+assertEq(toLatin1("bar1234").search(re), 0);
+assertEq(toLatin1("foobbr1234").search(re), -1);
+
+// TwoByte
+assertEq("foobAr1234\u1200".search(re), 3);
+assertEq("bar1234\u1200".search(re), 0);
+assertEq("foobbr1234\u1200".search(re), -1);
+
+re = /abcdefghijklm[0-5]/;
+assertEq(toLatin1("1abcdefghijklm4").search(re), 1);
+assertEq("\u12001abcdefghijklm0".search(re), 2);
+assertEq(toLatin1("1abcdefghijklm8").search(re), -1);
+assertEq("\u12001abcdefghijklm8".search(re), -1);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/latin1/replace.js
@@ -0,0 +1,126 @@
+function testDollarReplacement() {
+    // Latin1 input, pat and replacement
+    var s = toLatin1("Foobarbaz123");
+    var pat = toLatin1("bar");
+    assertEq(s.replace(pat, toLatin1("AA")), "FooAAbaz123");
+    assertEq(s.replace(pat, toLatin1("A$$A")), "FooA$Abaz123");
+    assertEq(s.replace(pat, toLatin1("A$`A")), "FooAFooAbaz123");
+    assertEq(s.replace(pat, toLatin1("A$&A")), "FooAbarAbaz123");
+    assertEq(s.replace(pat, toLatin1("A$'A")), "FooAbaz123Abaz123");
+
+    // Latin1 input and pat, TwoByte replacement
+    assertEq(s.replace(pat, "A\u1200"), "FooA\u1200baz123");
+    assertEq(s.replace(pat, "A$$\u1200"), "FooA$\u1200baz123");
+    assertEq(s.replace(pat, "A$`\u1200"), "FooAFoo\u1200baz123");
+    assertEq(s.replace(pat, "A$&\u1200"), "FooAbar\u1200baz123");
+    assertEq(s.replace(pat, "A$'\u1200"), "FooAbaz123\u1200baz123");
+
+    // TwoByte input, Latin1 pat and replacement
+    s = "Foobarbaz123\u1200";
+    assertEq(s.replace(pat, toLatin1("A")), "FooAbaz123\u1200");
+    assertEq(s.replace(pat, toLatin1("A$$")), "FooA$baz123\u1200");
+    assertEq(s.replace(pat, toLatin1("A$`")), "FooAFoobaz123\u1200");
+    assertEq(s.replace(pat, toLatin1("A$&")), "FooAbarbaz123\u1200");
+    assertEq(s.replace(pat, toLatin1("A$'")), "FooAbaz123\u1200baz123\u1200");
+
+    // TwoByte input and pat, Latin1 replacement
+    s = "Foobar\u1200baz123";
+    pat += "\u1200";
+    assertEq(s.replace(pat, toLatin1("AB")), "FooABbaz123");
+    assertEq(s.replace(pat, toLatin1("A$$B")), "FooA$Bbaz123");
+    assertEq(s.replace(pat, toLatin1("A$`B")), "FooAFooBbaz123");
+    assertEq(s.replace(pat, toLatin1("A$&B")), "FooAbar\u1200Bbaz123");
+    assertEq(s.replace(pat, toLatin1("A$'B")), "FooAbaz123Bbaz123");
+
+    // TwoByte input, pat and replacement
+    assertEq(s.replace(pat, "A\u1300"), "FooA\u1300baz123");
+    assertEq(s.replace(pat, "A$$\u1300"), "FooA$\u1300baz123");
+    assertEq(s.replace(pat, "A$`\u1300"), "FooAFoo\u1300baz123");
+    assertEq(s.replace(pat, "A$&\u1300"), "FooAbar\u1200\u1300baz123");
+    assertEq(s.replace(pat, "A$'\u1300"), "FooAbaz123\u1300baz123");
+}
+testDollarReplacement();
+
+function testRegExp() {
+    var s = toLatin1("Foobar123bar234");
+    assertEq(s.replace(/bar\d\d/, "456"), "Foo4563bar234");
+
+    // Latin1 input and replacement
+    var re1 = /bar\d\d/;
+    var re2 = /bar\d\d/g;
+    assertEq(s.replace(re1, toLatin1("789")), "Foo7893bar234");
+    assertEq(s.replace(re2, toLatin1("789\u00ff")), "Foo789\u00ff3789\u00ff4");
+
+    // Latin1 input, TwoByte replacement
+    assertEq(s.replace(re1, "789\u1200"), "Foo789\u12003bar234");
+    assertEq(s.replace(re2, "789\u1200"), "Foo789\u12003789\u12004");
+
+    // TwoByte input, Latin1 replacement
+    s += "\u1200";
+    assertEq(s.replace(re1, toLatin1("7890")), "Foo78903bar234\u1200");
+    assertEq(s.replace(re2, toLatin1("7890\u00ff")), "Foo7890\u00ff37890\u00ff4\u1200");
+
+    // TwoByte input and replacement
+    assertEq(s.replace(re1, "789\u1200"), "Foo789\u12003bar234\u1200");
+    assertEq(s.replace(re2, "789\u1200"), "Foo789\u12003789\u12004\u1200");
+}
+testRegExp();
+
+function testRegExpDollar() {
+    var s = toLatin1("Foobar123bar2345");
+
+    // Latin1 input and replacement
+    var re1 = /bar\d\d/;
+    var re2 = /bar(\d\d)/g;
+    assertEq(s.replace(re1, toLatin1("--$&--")), "Foo--bar12--3bar2345");
+    assertEq(s.replace(re2, toLatin1("--$'\u00ff--")), "Foo--3bar2345\xFF--3--45\xFF--45");
+    assertEq(s.replace(re2, toLatin1("--$`--")), "Foo--Foo--3--Foobar123--45");
+
+    // Latin1 input, TwoByte replacement
+    assertEq(s.replace(re1, "\u1200$$"), "Foo\u1200$3bar2345");
+    assertEq(s.replace(re2, "\u1200$1"), "Foo\u1200123\u12002345");
+    assertEq(s.replace(re2, "\u1200$'"), "Foo\u12003bar23453\u12004545");
+
+    // TwoByte input, Latin1 replacement
+    s += "\u1200";
+    assertEq(s.replace(re1, toLatin1("**$&**")), "Foo**bar12**3bar2345\u1200");
+    assertEq(s.replace(re2, toLatin1("**$1**")), "Foo**12**3**23**45\u1200");
+    assertEq(s.replace(re2, toLatin1("**$`**")), "Foo**Foo**3**Foobar123**45\u1200");
+    assertEq(s.replace(re2, toLatin1("**$'$$**")), "Foo**3bar2345\u1200$**3**45\u1200$**45\u1200");
+
+    // TwoByte input and replacement
+    assertEq(s.replace(re1, "**$&**\ueeee"), "Foo**bar12**\ueeee3bar2345\u1200");
+    assertEq(s.replace(re2, "**$1**\ueeee"), "Foo**12**\ueeee3**23**\ueeee45\u1200");
+    assertEq(s.replace(re2, "\ueeee**$`**"), "Foo\ueeee**Foo**3\ueeee**Foobar123**45\u1200");
+    assertEq(s.replace(re2, "\ueeee**$'$$**"), "Foo\ueeee**3bar2345\u1200$**3\ueeee**45\u1200$**45\u1200");
+}
+testRegExpDollar();
+
+function testFlattenPattern() {
+    var s = "abcdef[g]abc";
+
+    // Latin1 pattern
+    assertEq(s.replace(toLatin1("def[g]"), "--$&--", "gi"), "abc--def[g]--abc");
+
+    // TwoByte pattern
+    s = "abcdef[g]\u1200abc";
+    assertEq(s.replace("def[g]\u1200", "++$&++", "gi"), "abc++def[g]\u1200++abc");
+}
+testFlattenPattern();
+
+function testReplaceEmpty() {
+    // Latin1
+    var s = toLatin1("--abcdefghijkl--abcdefghijkl--abcdefghijkl--abcdefghijkl");
+    assertEq(s.replace(/abcd[eE]/g, ""), "--fghijkl--fghijkl--fghijkl--fghijkl");
+
+    s = "--abcdEf--";
+    assertEq(s.replace(/abcd[eE]/g, ""), "--f--");
+
+    // TwoByte
+    s = "--abcdefghijkl--abcdefghijkl--abcdefghijkl--abcdefghijkl\u1200";
+    assertEq(s.replace(/abcd[eE]/g, ""), "--fghijkl--fghijkl--fghijkl--fghijkl\u1200");
+
+    s = "--abcdEf--\u1200";
+    assertEq(s.replace(/abcd[eE]/g, ""), "--f--\u1200");
+}
+testReplaceEmpty();
--- a/js/src/jit/BaselineDebugModeOSR.cpp
+++ b/js/src/jit/BaselineDebugModeOSR.cpp
@@ -45,33 +45,31 @@ struct DebugModeOSREntry
         newStub(nullptr),
         recompInfo(nullptr),
         pcOffset(icEntry.pcOffset()),
         frameKind(icEntry.kind())
     {
 #ifdef DEBUG
         MOZ_ASSERT(pcOffset == icEntry.pcOffset());
         MOZ_ASSERT(frameKind == icEntry.kind());
+#endif
+    }
 
-        // Assert that if we have a NonOp ICEntry, that there are no unsynced
-        // slots, since such a recompile could have only been triggered from
-        // either an interrupt check or a debug trap handler.
-        //
-        // If triggered from an interrupt check, the stack should be fully
-        // synced.
-        //
-        // If triggered from a debug trap handler, we must be recompiling for
-        // toggling debug mode on->off, in which case the old baseline script
-        // should have fully synced stack at every bytecode.
-        if (frameKind == ICEntry::Kind_NonOp) {
-            PCMappingSlotInfo slotInfo;
-            jsbytecode *pc = script->offsetToPC(pcOffset);
-            oldBaselineScript->nativeCodeForPC(script, pc, &slotInfo);
-            MOZ_ASSERT(slotInfo.numUnsynced() == 0);
-        }
+    DebugModeOSREntry(JSScript *script, BaselineDebugModeOSRInfo *info)
+      : script(script),
+        oldBaselineScript(script->baselineScript()),
+        oldStub(nullptr),
+        newStub(nullptr),
+        recompInfo(nullptr),
+        pcOffset(script->pcToOffset(info->pc)),
+        frameKind(info->frameKind)
+    {
+#ifdef DEBUG
+        MOZ_ASSERT(pcOffset == script->pcToOffset(info->pc));
+        MOZ_ASSERT(frameKind == info->frameKind);
 #endif
     }
 
     DebugModeOSREntry(DebugModeOSREntry &&other)
       : script(other.script),
         oldBaselineScript(other.oldBaselineScript),
         oldStub(other.oldStub),
         newStub(other.newStub),
@@ -94,17 +92,17 @@ struct DebugModeOSREntry
                 frameKind == ICEntry::Kind_DebugEpilogue);
     }
 
     bool recompiled() const {
         return oldBaselineScript != script->baselineScript();
     }
 
     BaselineDebugModeOSRInfo *takeRecompInfo() {
-        MOZ_ASSERT(recompInfo);
+        MOZ_ASSERT(needsRecompileInfo() && recompInfo);
         BaselineDebugModeOSRInfo *tmp = recompInfo;
         recompInfo = nullptr;
         return tmp;
     }
 
     bool allocateRecompileInfo(JSContext *cx) {
         MOZ_ASSERT(needsRecompileInfo());
 
@@ -170,21 +168,34 @@ CollectOnStackScripts(JSContext *cx, con
                       DebugModeOSREntryVector &entries)
 {
     ICStub *prevFrameStubPtr = nullptr;
     bool needsRecompileHandler = false;
     for (JitFrameIterator iter(activation); !iter.done(); ++iter) {
         switch (iter.type()) {
           case JitFrame_BaselineJS: {
             JSScript *script = iter.script();
-            uint8_t *retAddr = iter.returnAddressToFp();
-            ICEntry &entry = script->baselineScript()->icEntryFromReturnAddress(retAddr);
 
-            if (!entries.append(DebugModeOSREntry(script, entry)))
-                return false;
+            if (BaselineDebugModeOSRInfo *info = iter.baselineFrame()->getDebugModeOSRInfo()) {
+                // If patching a previously patched yet unpopped frame, we can
+                // use the BaselineDebugModeOSRInfo on the frame directly to
+                // patch. Indeed, we cannot use iter.returnAddressToFp(), as
+                // it points into the debug mode OSR handler and cannot be
+                // used to look up a corresponding ICEntry.
+                //
+                // See cases F and G in PatchBaselineFrameForDebugMode.
+                if (!entries.append(DebugModeOSREntry(script, info)))
+                    return false;
+            } else {
+                // Otherwise, use the return address to look up the ICEntry.
+                uint8_t *retAddr = iter.returnAddressToFp();
+                ICEntry &entry = script->baselineScript()->icEntryFromReturnAddress(retAddr);
+                if (!entries.append(DebugModeOSREntry(script, entry)))
+                    return false;
+            }
 
             if (entries.back().needsRecompileInfo()) {
                 if (!entries.back().allocateRecompileInfo(cx))
                     return false;
 
                 needsRecompileHandler |= true;
             }
 
@@ -279,24 +290,31 @@ PatchBaselineFramesForDebugMode(JSContex
     //  B. From a VM call (interrupt handler, debugger statement handler).
     //
     // On to Off:
     //  - All the ways above.
     //  C. From the debug trap handler.
     //  D. From the debug prologue.
     //  E. From the debug epilogue.
     //
+    // Off to On to Off:
+    //  F. Undo case B above on previously patched yet unpopped frames.
+    //
+    // On to Off to On:
+    //  G. Undo cases B, C, D, or E above on previously patched yet unpopped
+    //     frames.
+    //
     // In general, we patch the return address from the VM call to return to a
     // "continuation fixer" to fix up machine state (registers and stack
     // state). Specifics on what need to be done are documented below.
     //
 
     IonCommonFrameLayout *prev = nullptr;
     size_t entryIndex = *start;
-    DebugOnly<bool> expectedDebugMode = cx->compartment()->debugMode();
+    bool expectedDebugMode = cx->compartment()->debugMode();
 
     for (JitFrameIterator iter(activation); !iter.done(); ++iter) {
         DebugModeOSREntry &entry = entries[entryIndex];
 
         switch (iter.type()) {
           case JitFrame_BaselineJS: {
             // If the script wasn't recompiled, there's nothing to patch.
             if (!entry.recompiled()) {
@@ -327,22 +345,43 @@ PatchBaselineFramesForDebugMode(JSContex
                 // directly to the IC resume address.
                 uint8_t *retAddr = bl->returnAddressForIC(bl->icEntryFromPCOffset(pcOffset));
                 SpewPatchBaselineFrame(prev->returnAddress(), retAddr, script, kind, pc);
                 prev->setReturnAddress(retAddr);
                 entryIndex++;
                 break;
             }
 
-            bool popFrameReg;
+            // Cases F and G above.
+            //
+            // We undo a previous recompile by handling cases B, C, D, and E
+            // like normal, except that we retrieved the pc information via
+            // the previous OSR debug info stashed on the frame.
+            if (BaselineDebugModeOSRInfo *info = iter.baselineFrame()->getDebugModeOSRInfo()) {
+                MOZ_ASSERT(info->pc == pc);
+                MOZ_ASSERT(info->frameKind == kind);
+
+                // Case G, might need to undo B, C, D, or E.
+                MOZ_ASSERT_IF(expectedDebugMode, (kind == ICEntry::Kind_CallVM ||
+                                                  kind == ICEntry::Kind_DebugTrap ||
+                                                  kind == ICEntry::Kind_DebugPrologue ||
+                                                  kind == ICEntry::Kind_DebugEpilogue));
+                // Case F, should only need to undo case B.
+                MOZ_ASSERT_IF(!expectedDebugMode, kind == ICEntry::Kind_CallVM);
+
+                // We will have allocated a new recompile info, so delete the
+                // existing one.
+                iter.baselineFrame()->deleteDebugModeOSRInfo();
+            }
 
             // The RecompileInfo must already be allocated so that this
             // function may be infallible.
             BaselineDebugModeOSRInfo *recompInfo = entry.takeRecompInfo();
 
+            bool popFrameReg;
             switch (kind) {
               case ICEntry::Kind_CallVM:
                 // Case B above.
                 //
                 // Patching returns from an interrupt handler or the debugger
                 // statement handler is similar in that we can resume at the
                 // next op.
                 pc += GetBytecodeLength(pc);
--- a/js/src/jit/IonFrames.cpp
+++ b/js/src/jit/IonFrames.cpp
@@ -202,37 +202,42 @@ JitFrameIterator::script() const
     JS_ASSERT(isScripted());
     if (isBaselineJS())
         return baselineFrame()->script();
     JSScript *script = ScriptFromCalleeToken(calleeToken());
     JS_ASSERT(script);
     return script;
 }
 
+uint8_t *
+JitFrameIterator::resumeAddressToFp() const
+{
+    // If we are settled on a patched BaselineFrame due to debug mode OSR, get
+    // the real return address via the stashed DebugModeOSRInfo.
+    if (isBaselineJS() && baselineFrame()->getDebugModeOSRInfo())
+        return baselineFrame()->debugModeOSRInfo()->resumeAddr;
+    return returnAddressToFp();
+}
+
 void
 JitFrameIterator::baselineScriptAndPc(JSScript **scriptRes, jsbytecode **pcRes) const
 {
     JS_ASSERT(isBaselineJS());
     JSScript *script = this->script();
     if (scriptRes)
         *scriptRes = script;
-    uint8_t *retAddr = returnAddressToFp();
+    uint8_t *retAddr = resumeAddressToFp();
 
     // If we have unwound the scope due to exception handling to a different
     // pc, the frame should behave as if it were settled on that pc.
     if (jsbytecode *overridePc = baselineFrame()->getUnwoundScopeOverridePc()) {
         *pcRes = overridePc;
         return;
     }
 
-    // If we are in the middle of a recompile handler, get the real return
-    // address as stashed in the RecompileInfo.
-    if (BaselineDebugModeOSRInfo *info = baselineFrame()->getDebugModeOSRInfo())
-        retAddr = info->resumeAddr;
-
     if (pcRes) {
         // If the return address is into the prologue entry address or just
         // after the debug prologue, then assume start of script.
         if (retAddr == script->baselineScript()->prologueEntryAddr() ||
             retAddr == script->baselineScript()->postDebugPrologueAddr())
         {
             *pcRes = script->code();
             return;
--- a/js/src/jit/JitFrameIterator.h
+++ b/js/src/jit/JitFrameIterator.h
@@ -186,16 +186,20 @@ class JitFrameIterator
     Value *actualArgs() const;
 
     // Returns the return address of the frame above this one (that is, the
     // return address that returns back to the current frame).
     uint8_t *returnAddressToFp() const {
         return returnAddressToFp_;
     }
 
+    // Returns the resume address. As above, except taking
+    // BaselineDebugModeOSRInfo into account, if present.
+    uint8_t *resumeAddressToFp() const;
+
     // Previous frame information extracted from the current frame.
     inline size_t prevFrameLocalSize() const;
     inline FrameType prevType() const;
     uint8_t *prevFp() const;
 
     // Returns the stack space used by the current frame, in bytes. This does
     // not include the size of its fixed header.
     size_t frameSize() const {
--- a/js/src/jit/arm/Simulator-arm.cpp
+++ b/js/src/jit/arm/Simulator-arm.cpp
@@ -1486,18 +1486,18 @@ Simulator::setCallResult(int64_t res)
 {
     set_register(r0, static_cast<int32_t>(res));
     set_register(r1, static_cast<int32_t>(res >> 32));
 }
 
 int
 Simulator::readW(int32_t addr, SimInstruction *instr)
 {
-    // The regexp engines emit unaligned loads, so we don't check for them here
-    // like the other methods below.
+    // The regexp engine emits unaligned loads, so we don't check for them here
+    // like most of the other methods do.
     intptr_t *ptr = reinterpret_cast<intptr_t*>(addr);
     return *ptr;
 }
 
 void
 Simulator::writeW(int32_t addr, int value, SimInstruction *instr)
 {
     if ((addr & 3) == 0) {
@@ -1507,23 +1507,20 @@ Simulator::writeW(int32_t addr, int valu
         printf("Unaligned write at 0x%08x, pc=%p\n", addr, instr);
         MOZ_CRASH();
     }
 }
 
 uint16_t
 Simulator::readHU(int32_t addr, SimInstruction *instr)
 {
-    if ((addr & 1) == 0) {
-        uint16_t *ptr = reinterpret_cast<uint16_t*>(addr);
-        return *ptr;
-    }
-    printf("Unaligned unsigned halfword read at 0x%08x, pc=%p\n", addr, instr);
-    MOZ_CRASH();
-    return 0;
+    // The regexp engine emits unaligned loads, so we don't check for them here
+    // like most of the other methods do.
+    uint16_t *ptr = reinterpret_cast<uint16_t*>(addr);
+    return *ptr;
 }
 
 int16_t
 Simulator::readH(int32_t addr, SimInstruction *instr)
 {
     if ((addr & 1) == 0) {
         int16_t *ptr = reinterpret_cast<int16_t*>(addr);
         return *ptr;
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1759,29 +1759,32 @@ extern JS_PUBLIC_API(bool)
 JS_GetClassObject(JSContext *cx, JSProtoKey key, JS::MutableHandle<JSObject*> objp);
 
 extern JS_PUBLIC_API(bool)
 JS_GetClassPrototype(JSContext *cx, JSProtoKey key, JS::MutableHandle<JSObject*> objp);
 
 namespace JS {
 
 /*
- * Determine if the given object is an instance or prototype for a standard
+ * Determine if the given object is an instance/prototype/constructor for a standard
  * class. If so, return the associated JSProtoKey. If not, return JSProto_Null.
  */
 
 extern JS_PUBLIC_API(JSProtoKey)
 IdentifyStandardInstance(JSObject *obj);
 
 extern JS_PUBLIC_API(JSProtoKey)
 IdentifyStandardPrototype(JSObject *obj);
 
 extern JS_PUBLIC_API(JSProtoKey)
 IdentifyStandardInstanceOrPrototype(JSObject *obj);
 
+extern JS_PUBLIC_API(JSProtoKey)
+IdentifyStandardConstructor(JSObject *obj);
+
 } /* namespace JS */
 
 extern JS_PUBLIC_API(JSProtoKey)
 JS_IdToProtoKey(JSContext *cx, JS::HandleId id);
 
 /*
  * Returns the original value of |Function.prototype| from the global object in
  * which |forObj| was created.
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3434,16 +3434,36 @@ JS::IdentifyStandardPrototype(JSObject *
 }
 
 JSProtoKey
 JS::IdentifyStandardInstanceOrPrototype(JSObject *obj)
 {
     return JSCLASS_CACHED_PROTO_KEY(obj->getClass());
 }
 
+JSProtoKey
+JS::IdentifyStandardConstructor(JSObject *obj)
+{
+    // Note that NATIVE_CTOR does not imply that we are a standard constructor,
+    // but the converse is true (at least until we start having self-hosted
+    // constructors for standard classes). This lets us avoid a costly loop for
+    // many functions (which, depending on the call site, may be the common case).
+    if (!obj->is<JSFunction>() || !(obj->as<JSFunction>().flags() & JSFunction::NATIVE_CTOR))
+        return JSProto_Null;
+
+    GlobalObject &global = obj->global();
+    for (size_t k = 0; k < JSProto_LIMIT; ++k) {
+        JSProtoKey key = static_cast<JSProtoKey>(k);
+        if (global.getConstructor(key) == ObjectValue(*obj))
+            return key;
+    }
+
+    return JSProto_Null;
+}
+
 bool
 js::FindClassObject(ExclusiveContext *cx, MutableHandleObject protop, const Class *clasp)
 {
     JSProtoKey protoKey = GetClassProtoKey(clasp);
     if (protoKey != JSProto_Null) {
         JS_ASSERT(JSProto_Null < protoKey);
         JS_ASSERT(protoKey < JSProto_LIMIT);
         return GetBuiltinConstructor(cx, protoKey, protop);
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -2015,35 +2015,50 @@ class MOZ_STACK_CLASS StringRegExpGuard
     RootedObject obj_;
 
     /*
      * Upper bound on the number of characters we are willing to potentially
      * waste on searching for RegExp meta-characters.
      */
     static const size_t MAX_FLAT_PAT_LEN = 256;
 
+    template <typename CharT>
+    static bool
+    flattenPattern(StringBuffer &sb, const CharT *chars, size_t len)
+    {
+        static const char ESCAPE_CHAR = '\\';
+        for (const CharT *it = chars; it < chars + len; ++it) {
+            if (IsRegExpMetaChar(*it)) {
+                if (!sb.append(ESCAPE_CHAR) || !sb.append(*it))
+                    return false;
+            } else {
+                if (!sb.append(*it))
+                    return false;
+            }
+        }
+        return true;
+    }
+
     static JSAtom *
-    flattenPattern(JSContext *cx, JSAtom *patstr)
+    flattenPattern(JSContext *cx, JSAtom *pat)
     {
         StringBuffer sb(cx);
-        if (!sb.reserve(patstr->length()))
+        if (!sb.reserve(pat->length()))
             return nullptr;
 
-        static const jschar ESCAPE_CHAR = '\\';
-        const jschar *chars = patstr->chars();
-        size_t len = patstr->length();
-        for (const jschar *it = chars; it != chars + len; ++it) {
-            if (IsRegExpMetaChar(*it)) {
-                if (!sb.append(ESCAPE_CHAR) || !sb.append(*it))
-                    return nullptr;
-            } else {
-                if (!sb.append(*it))
-                    return nullptr;
-            }
+        if (pat->hasLatin1Chars()) {
+            AutoCheckCannotGC nogc;
+            if (!flattenPattern(sb, pat->latin1Chars(nogc), pat->length()))
+                return nullptr;
+        } else {
+            AutoCheckCannotGC nogc;
+            if (!flattenPattern(sb, pat->twoByteChars(nogc), pat->length()))
+                return nullptr;
         }
+
         return sb.finishAtom();
     }
 
   public:
     explicit StringRegExpGuard(JSContext *cx)
       : re_(cx), fm(cx), obj_(cx)
     { }
 
@@ -2450,36 +2465,45 @@ class RopeBuilder {
 
     inline JSString *result() {
         return res;
     }
 };
 
 namespace {
 
+template <typename CharT>
+static uint32_t
+FindDollarIndex(const CharT *chars, size_t length)
+{
+    if (const CharT *p = js_strchr_limit(chars, '$', chars + length)) {
+        uint32_t dollarIndex = p - chars;
+        MOZ_ASSERT(dollarIndex < length);
+        return dollarIndex;
+    }
+    return UINT32_MAX;
+}
+
 struct ReplaceData
 {
     explicit ReplaceData(JSContext *cx)
       : str(cx), g(cx), lambda(cx), elembase(cx), repstr(cx),
         fig(cx, NullValue()), sb(cx)
     {}
 
     inline void setReplacementString(JSLinearString *string) {
         JS_ASSERT(string);
         lambda = nullptr;
         elembase = nullptr;
         repstr = string;
 
-        const jschar *chars = repstr->chars();
-        if (const jschar *p = js_strchr_limit(chars, '$', chars + repstr->length())) {
-            dollarIndex = p - chars;
-            MOZ_ASSERT(dollarIndex < repstr->length());
-        } else {
-            dollarIndex = UINT32_MAX;
-        }
+        AutoCheckCannotGC nogc;
+        dollarIndex = string->hasLatin1Chars()
+                      ? FindDollarIndex(string->latin1Chars(nogc), string->length())
+                      : FindDollarIndex(string->twoByteChars(nogc), string->length());
     }
 
     inline void setReplacementFunction(JSObject *func) {
         JS_ASSERT(func);
         lambda = func;
         elembase = nullptr;
         repstr = nullptr;
         dollarIndex = UINT32_MAX;
@@ -2545,35 +2569,36 @@ DoMatchForReplaceGlobal(JSContext *cx, R
             return false;
         if (!res->matched())
             ++i;
     }
 
     return true;
 }
 
+template <typename CharT>
 static bool
-InterpretDollar(RegExpStatics *res, const jschar *dp, const jschar *ep,
+InterpretDollar(RegExpStatics *res, const CharT *bp, const CharT *dp, const CharT *ep,
                 ReplaceData &rdata, JSSubString *out, size_t *skip)
 {
     JS_ASSERT(*dp == '$');
 
     /* If there is only a dollar, bail now */
     if (dp + 1 >= ep)
         return false;
 
     /* Interpret all Perl match-induced dollar variables. */
     jschar dc = dp[1];
     if (JS7_ISDEC(dc)) {
         /* ECMA-262 Edition 3: 1-9 or 01-99 */
         unsigned num = JS7_UNDEC(dc);
         if (num > res->getMatches().parenCount())
             return false;
 
-        const jschar *cp = dp + 2;
+        const CharT *cp = dp + 2;
         if (cp < ep && (dc = *cp, JS7_ISDEC(dc))) {
             unsigned tmp = 10 * num + JS7_UNDEC(dc);
             if (tmp <= res->getMatches().parenCount()) {
                 cp++;
                 num = tmp;
             }
         }
         if (num == 0)
@@ -2589,17 +2614,17 @@ InterpretDollar(RegExpStatics *res, cons
          */
         res->getParen(num, out);
         return true;
     }
 
     *skip = 2;
     switch (dc) {
       case '$':
-        out->init(rdata.repstr, dp - rdata.repstr->chars(), 1);
+        out->init(rdata.repstr, dp - bp, 1);
         return true;
       case '&':
         res->getLastMatch(out);
         return true;
       case '+':
         res->getLastParen(out);
         return true;
       case '`':
@@ -2607,16 +2632,55 @@ InterpretDollar(RegExpStatics *res, cons
         return true;
       case '\'':
         res->getRightContext(out);
         return true;
     }
     return false;
 }
 
+template <typename CharT>
+static bool
+FindReplaceLengthString(JSContext *cx, RegExpStatics *res, ReplaceData &rdata, size_t *sizep)
+{
+    JSLinearString *repstr = rdata.repstr;
+    CheckedInt<uint32_t> replen = repstr->length();
+
+    if (rdata.dollarIndex != UINT32_MAX) {
+        AutoCheckCannotGC nogc;
+        MOZ_ASSERT(rdata.dollarIndex < repstr->length());
+        const CharT *bp = repstr->chars<CharT>(nogc);
+        const CharT *dp = bp + rdata.dollarIndex;
+        const CharT *ep = bp + repstr->length();
+        do {
+            JSSubString sub;
+            size_t skip;
+            if (InterpretDollar(res, bp, dp, ep, rdata, &sub, &skip)) {
+                if (sub.length > skip)
+                    replen += sub.length - skip;
+                else
+                    replen -= skip - sub.length;
+                dp += skip;
+            } else {
+                dp++;
+            }
+
+            dp = js_strchr_limit(dp, '$', ep);
+        } while (dp);
+    }
+
+    if (!replen.isValid()) {
+        js_ReportAllocationOverflow(cx);
+        return false;
+    }
+
+    *sizep = replen.value();
+    return true;
+}
+
 static bool
 FindReplaceLength(JSContext *cx, RegExpStatics *res, ReplaceData &rdata, size_t *sizep)
 {
     if (rdata.elembase) {
         /*
          * The base object is used when replace was passed a lambda which looks like
          * 'function(a) { return b[a]; }' for the base object b.  b will not change
          * in the course of the replace unless we end up making a scripted call due
@@ -2696,73 +2760,48 @@ FindReplaceLength(JSContext *cx, RegExpS
             return false;
         rdata.repstr = repstr->ensureLinear(cx);
         if (!rdata.repstr)
             return false;
         *sizep = rdata.repstr->length();
         return true;
     }
 
-    JSLinearString *repstr = rdata.repstr;
-    CheckedInt<uint32_t> replen = repstr->length();
-    if (rdata.dollarIndex != UINT32_MAX) {
-        MOZ_ASSERT(rdata.dollarIndex < repstr->length());
-        const jschar *dp = repstr->chars() + rdata.dollarIndex;
-        const jschar *ep = repstr->chars() + repstr->length();
-        do {
-            JSSubString sub;
-            size_t skip;
-            if (InterpretDollar(res, dp, ep, rdata, &sub, &skip)) {
-                if (sub.length > skip)
-                    replen += sub.length - skip;
-                else
-                    replen -= skip - sub.length;
-                dp += skip;
-            } else {
-                dp++;
-            }
-
-            dp = js_strchr_limit(dp, '$', ep);
-        } while (dp);
-    }
-
-    if (!replen.isValid()) {
-        js_ReportAllocationOverflow(cx);
-        return false;
-    }
-
-    *sizep = replen.value();
-    return true;
+    return rdata.repstr->hasLatin1Chars()
+           ? FindReplaceLengthString<Latin1Char>(cx, res, rdata, sizep)
+           : FindReplaceLengthString<jschar>(cx, res, rdata, sizep);
 }
 
 /*
  * Precondition: |rdata.sb| already has necessary growth space reserved (as
  * derived from FindReplaceLength), and has been inflated to TwoByte if
  * necessary.
  */
+template <typename CharT>
 static void
 DoReplace(RegExpStatics *res, ReplaceData &rdata)
 {
+    AutoCheckCannotGC nogc;
     JSLinearString *repstr = rdata.repstr;
-    const jschar *bp = repstr->chars();
-    const jschar *cp = bp;
+    const CharT *bp = repstr->chars<CharT>(nogc);
+    const CharT *cp = bp;
 
     if (rdata.dollarIndex != UINT32_MAX) {
         MOZ_ASSERT(rdata.dollarIndex < repstr->length());
-        const jschar *dp = bp + rdata.dollarIndex;
-        const jschar *ep = bp + repstr->length();
+        const CharT *dp = bp + rdata.dollarIndex;
+        const CharT *ep = bp + repstr->length();
         do {
             /* Move one of the constant portions of the replacement value. */
             size_t len = dp - cp;
             rdata.sb.infallibleAppend(cp, len);
             cp = dp;
 
             JSSubString sub;
             size_t skip;
-            if (InterpretDollar(res, dp, ep, rdata, &sub, &skip)) {
+            if (InterpretDollar(res, bp, dp, ep, rdata, &sub, &skip)) {
                 rdata.sb.infallibleAppendSubstring(sub.base, sub.offset, sub.length);
                 cp += skip;
                 dp += skip;
             } else {
                 dp++;
             }
 
             dp = js_strchr_limit(dp, '$', ep);
@@ -2805,20 +2844,22 @@ ReplaceRegExp(JSContext *cx, RegExpStati
         if (!rdata.sb.ensureTwoByteChars())
             return false;
     }
 
     if (!rdata.sb.reserve(newlen.value()))
         return false;
 
     /* Append skipped-over portion of the search value. */
-    const jschar *left = str.chars() + leftoff;
-    rdata.sb.infallibleAppend(left, leftlen);
-
-    DoReplace(res, rdata);
+    rdata.sb.infallibleAppendSubstring(&str, leftoff, leftlen);
+
+    if (rdata.repstr->hasLatin1Chars())
+        DoReplace<Latin1Char>(res, rdata);
+    else
+        DoReplace<jschar>(res, rdata);
     return true;
 }
 
 static bool
 BuildFlatReplacement(JSContext *cx, HandleString textstr, HandleString repstr,
                      const FlatMatch &fm, MutableHandleValue rval)
 {
     RopeBuilder builder(cx);
@@ -2890,16 +2931,67 @@ BuildFlatReplacement(JSContext *cx, Hand
             return false;
         }
     }
 
     rval.setString(builder.result());
     return true;
 }
 
+template <typename CharT>
+static bool
+AppendDollarReplacement(StringBuffer &newReplaceChars, size_t firstDollarIndex,
+                        const FlatMatch &fm, JSLinearString *text,
+                        const CharT *repChars, size_t repLength)
+{
+    JS_ASSERT(firstDollarIndex < repLength);
+
+    size_t matchStart = fm.match();
+    size_t matchLimit = matchStart + fm.patternLength();
+
+    /* Move the pre-dollar chunk in bulk. */
+    newReplaceChars.infallibleAppend(repChars, firstDollarIndex);
+
+    /* Move the rest char-by-char, interpreting dollars as we encounter them. */
+    const CharT *repLimit = repChars + repLength;
+    for (const CharT *it = repChars + firstDollarIndex; it < repLimit; ++it) {
+        if (*it != '$' || it == repLimit - 1) {
+            if (!newReplaceChars.append(*it))
+                return false;
+            continue;
+        }
+
+        switch (*(it + 1)) {
+          case '$': /* Eat one of the dollars. */
+            if (!newReplaceChars.append(*it))
+                return false;
+            break;
+          case '&':
+            if (!newReplaceChars.appendSubstring(text, matchStart, matchLimit - matchStart))
+                return false;
+            break;
+          case '`':
+            if (!newReplaceChars.appendSubstring(text, 0, matchStart))
+                return false;
+            break;
+          case '\'':
+            if (!newReplaceChars.appendSubstring(text, matchLimit, text->length() - matchLimit))
+                return false;
+            break;
+          default: /* The dollar we saw was not special (no matter what its mother told it). */
+            if (!newReplaceChars.append(*it))
+                return false;
+            continue;
+        }
+        ++it; /* We always eat an extra char in the above switch. */
+    }
+
+    return true;
+}
+
 /*
  * Perform a linear-scan dollar substitution on the replacement text,
  * constructing a result string that looks like:
  *
  *      newstring = string[:matchStart] + dollarSub(replaceValue) + string[matchLimit:]
  */
 static inline bool
 BuildDollarReplacement(JSContext *cx, JSString *textstrArg, JSLinearString *repstr,
@@ -2921,55 +3013,28 @@ BuildDollarReplacement(JSContext *cx, JS
      */
     StringBuffer newReplaceChars(cx);
     if (repstr->hasTwoByteChars() && !newReplaceChars.ensureTwoByteChars())
         return false;
 
     if (!newReplaceChars.reserve(textstr->length() - fm.patternLength() + repstr->length()))
         return false;
 
-    JS_ASSERT(firstDollarIndex < repstr->length());
-
-    /* Move the pre-dollar chunk in bulk. */
-    newReplaceChars.infallibleAppend(repstr->chars(), firstDollarIndex);
-
-    /* Move the rest char-by-char, interpreting dollars as we encounter them. */
-    const jschar *textchars = textstr->chars();
-    const jschar *repstrLimit = repstr->chars() + repstr->length();
-    for (const jschar *it = repstr->chars() + firstDollarIndex; it < repstrLimit; ++it) {
-        if (*it != '$' || it == repstrLimit - 1) {
-            if (!newReplaceChars.append(*it))
-                return false;
-            continue;
-        }
-
-        switch (*(it + 1)) {
-          case '$': /* Eat one of the dollars. */
-            if (!newReplaceChars.append(*it))
-                return false;
-            break;
-          case '&':
-            if (!newReplaceChars.append(textchars + matchStart, textchars + matchLimit))
-                return false;
-            break;
-          case '`':
-            if (!newReplaceChars.append(textchars, textchars + matchStart))
-                return false;
-            break;
-          case '\'':
-            if (!newReplaceChars.append(textchars + matchLimit, textchars + textstr->length()))
-                return false;
-            break;
-          default: /* The dollar we saw was not special (no matter what its mother told it). */
-            if (!newReplaceChars.append(*it))
-                return false;
-            continue;
-        }
-        ++it; /* We always eat an extra char in the above switch. */
+    bool res;
+    if (repstr->hasLatin1Chars()) {
+        AutoCheckCannotGC nogc;
+        res = AppendDollarReplacement(newReplaceChars, firstDollarIndex, fm, textstr,
+                                      repstr->latin1Chars(nogc), repstr->length());
+    } else {
+        AutoCheckCannotGC nogc;
+        res = AppendDollarReplacement(newReplaceChars, firstDollarIndex, fm, textstr,
+                                      repstr->twoByteChars(nogc), repstr->length());
     }
+    if (!res)
+        return false;
 
     RootedString leftSide(cx, js_NewDependentString(cx, textstr, 0, matchStart));
     if (!leftSide)
         return false;
 
     RootedString newReplace(cx, newReplaceChars.finishString());
     if (!newReplace)
         return false;
@@ -2993,74 +3058,85 @@ struct StringRange
     size_t start;
     size_t length;
 
     StringRange(size_t s, size_t l)
       : start(s), length(l)
     { }
 };
 
+template <typename CharT>
+static void
+CopySubstringsToFatInline(JSFatInlineString *dest, const CharT *src, const StringRange *ranges,
+                          size_t rangesLen, size_t outputLen)
+{
+    CharT *buf = dest->init<CharT>(outputLen);
+    size_t pos = 0;
+    for (size_t i = 0; i < rangesLen; i++) {
+        PodCopy(buf + pos, src + ranges[i].start, ranges[i].length);
+        pos += ranges[i].length;
+    }
+
+    MOZ_ASSERT(pos == outputLen);
+    buf[outputLen] = 0;
+}
+
 static inline JSFatInlineString *
-FlattenSubstrings(JSContext *cx, const jschar *chars,
-                  const StringRange *ranges, size_t rangesLen, size_t outputLen)
+FlattenSubstrings(JSContext *cx, Handle<JSFlatString*> flatStr, const StringRange *ranges,
+                  size_t rangesLen, size_t outputLen)
 {
-    JS_ASSERT(JSFatInlineString::twoByteLengthFits(outputLen));
-
     JSFatInlineString *str = js_NewGCFatInlineString<CanGC>(cx);
     if (!str)
         return nullptr;
 
-    jschar *buf = str->initTwoByte(outputLen);
-    size_t pos = 0;
-    for (size_t i = 0; i < rangesLen; i++) {
-        PodCopy(buf + pos, chars + ranges[i].start, ranges[i].length);
-        pos += ranges[i].length;
-    }
-    JS_ASSERT(pos == outputLen);
-
-    buf[outputLen] = 0;
+    AutoCheckCannotGC nogc;
+    if (flatStr->hasLatin1Chars())
+        CopySubstringsToFatInline(str, flatStr->latin1Chars(nogc), ranges, rangesLen, outputLen);
+    else
+        CopySubstringsToFatInline(str, flatStr->twoByteChars(nogc), ranges, rangesLen, outputLen);
     return str;
 }
 
 static JSString *
 AppendSubstrings(JSContext *cx, Handle<JSFlatString*> flatStr,
                  const StringRange *ranges, size_t rangesLen)
 {
     JS_ASSERT(rangesLen);
 
     /* For single substrings, construct a dependent string. */
     if (rangesLen == 1)
         return js_NewDependentString(cx, flatStr, ranges[0].start, ranges[0].length);
 
-    const jschar *chars = flatStr->getChars(cx);
-    if (!chars)
-        return nullptr;
+    bool isLatin1 = flatStr->hasLatin1Chars();
+    uint32_t fatInlineMaxLength = isLatin1
+                                  ? JSFatInlineString::MAX_LENGTH_LATIN1
+                                  : JSFatInlineString::MAX_LENGTH_TWO_BYTE;
 
     /* Collect substrings into a rope */
     size_t i = 0;
     RopeBuilder rope(cx);
     RootedString part(cx, nullptr);
     while (i < rangesLen) {
 
         /* Find maximum range that fits in JSFatInlineString */
         size_t substrLen = 0;
         size_t end = i;
         for (; end < rangesLen; end++) {
-            if (substrLen + ranges[end].length > JSFatInlineString::MAX_LENGTH_TWO_BYTE)
+            if (substrLen + ranges[end].length > fatInlineMaxLength)
                 break;
             substrLen += ranges[end].length;
         }
 
         if (i == end) {
             /* Not even one range fits JSFatInlineString, use DependentString */
             const StringRange &sr = ranges[i++];
             part = js_NewDependentString(cx, flatStr, sr.start, sr.length);
         } else {
             /* Copy the ranges (linearly) into a JSFatInlineString */
-            part = FlattenSubstrings(cx, chars, ranges + i, end - i, substrLen);
+            part = FlattenSubstrings(cx, flatStr, ranges + i, end - i, substrLen);
             i = end;
         }
 
         if (!part)
             return nullptr;
 
         /* Appending to the rope permanently roots the substring. */
         if (!rope.append(part))
@@ -4618,27 +4694,34 @@ js_strdup(js::ThreadSafeContext *cx, con
     jschar *ret = cx->pod_malloc<jschar>(n + 1);
     if (!ret)
         return nullptr;
     js_strncpy(ret, s, n);
     ret[n] = '\0';
     return ret;
 }
 
-jschar *
-js_strchr_limit(const jschar *s, jschar c, const jschar *limit)
+template <typename CharT>
+const CharT *
+js_strchr_limit(const CharT *s, jschar c, const CharT *limit)
 {
     while (s < limit) {
         if (*s == c)
-            return (jschar *)s;
+            return s;
         s++;
     }
     return nullptr;
 }
 
+template const Latin1Char *
+js_strchr_limit(const Latin1Char *s, jschar c, const Latin1Char *limit);
+
+template const jschar *
+js_strchr_limit(const jschar *s, jschar c, const jschar *limit);
+
 jschar *
 js::InflateString(ThreadSafeContext *cx, const char *bytes, size_t *lengthp)
 {
     size_t nchars;
     jschar *chars;
     size_t nbytes = *lengthp;
 
     nchars = nbytes;
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -226,18 +226,19 @@ StringHasRegExpMetaChars(const jschar *c
 } /* namespace js */
 
 extern size_t
 js_strlen(const jschar *s);
 
 extern int32_t
 js_strcmp(const jschar *lhs, const jschar *rhs);
 
-extern jschar *
-js_strchr_limit(const jschar *s, jschar c, const jschar *limit);
+template <typename CharT>
+extern const CharT *
+js_strchr_limit(const CharT *s, jschar c, const CharT *limit);
 
 static MOZ_ALWAYS_INLINE void
 js_strncpy(jschar *dst, const jschar *src, size_t nelem)
 {
     return mozilla::PodCopy(dst, src, nelem);
 }
 
 extern jschar *
--- a/js/src/tests/browser.js
+++ b/js/src/tests/browser.js
@@ -338,20 +338,16 @@ function jsTestDriverBrowserInit()
     else if (properties.test.match(/^js1_7/))
     {
       properties.version = '1.7';
     }
     else if (properties.test.match(/^js1_8/))
     {
       properties.version = '1.8';
     }
-    else if (properties.test.match(/^ecma_6\/LexicalEnvironment/))
-    {
-      properties.version = '1.8';
-    }
   }
 
   // default to language=type;text/javascript. required for
   // reftest style manifests.
   if (!properties.language)
   {
     properties.language = 'type';
     properties.mimetype = 'text/javascript';
deleted file mode 100644
deleted file mode 100644
--- a/js/src/tests/ecma_6/LexicalEnvironment/shell.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// NOTE: This only turns on 1.8.5 in shell builds.  The browser requires the
-//       futzing in js/src/tests/browser.js (which only turns on 1.8, the most
-//       the browser supports).
-if (typeof version != 'undefined')
-  version(185);
deleted file mode 100644
--- a/js/src/tests/ecma_6/LexicalEnvironment/with-global-ignores-global-let-variables.js
+++ /dev/null
@@ -1,18 +0,0 @@
-// |reftest| fails-if(Function("try{Function('let\x20x=5;');return(1,eval)('let\x20x=3;\\'x\\'\x20in\x20this');}catch(e){return(true);}")()) -- needs bug 589199 fix (top-level let not same as var)
-// Any copyright is dedicated to the Public Domain.
-// http://creativecommons.org/licenses/publicdomain/
-
-let v = "global-v";
-
-function f(v, global)
-{
-  with (global)
-    return v;
-}
-
-assertEq(f("argument-v", this), "argument-v");
-
-if (typeof reportCompare === "function")
-  reportCompare(true, true);
-
-print("Tests complete");
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -23,16 +23,18 @@
 #include "vm/Shape-inl.h"
 
 using namespace js;
 
 using mozilla::DebugOnly;
 using mozilla::Maybe;
 using js::frontend::TokenStream;
 
+using JS::AutoCheckCannotGC;
+
 JS_STATIC_ASSERT(IgnoreCaseFlag == JSREG_FOLD);
 JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB);
 JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE);
 JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY);
 
 /* RegExpObjectBuilder */
 
 RegExpObjectBuilder::RegExpObjectBuilder(ExclusiveContext *cx, RegExpObject *reobj)
@@ -587,34 +589,51 @@ RegExpShared::execute(JSContext *cx, Han
 
         matches.checkAgainst(origLength);
         *lastIndex = matches[0].limit;
         return RegExpRunStatus_Success;
     }
 
     if (uint8_t *byteCode = maybeByteCode(input->hasLatin1Chars())) {
         AutoTraceLog logInterpreter(logger, TraceLogger::IrregexpExecute);
-        const jschar *chars = input->chars() + charsOffset;
-        RegExpRunStatus result =
-            irregexp::InterpretCode(cx, byteCode, chars, start, length, &matches);
+
+        AutoStableStringChars inputChars(cx, input);
+        if (!inputChars.init())
+            return RegExpRunStatus_Error;
+
+        RegExpRunStatus result;
+        if (inputChars.isLatin1()) {
+            const Latin1Char *chars = inputChars.latin1Range().start().get() + charsOffset;
+            result = irregexp::InterpretCode(cx, byteCode, chars, start, length, &matches);
+        } else {
+            const jschar *chars = inputChars.twoByteRange().start().get() + charsOffset;
+            result = irregexp::InterpretCode(cx, byteCode, chars, start, length, &matches);
+        }
+
         if (result == RegExpRunStatus_Success) {
             matches.displace(displacement);
             matches.checkAgainst(origLength);
             *lastIndex = matches[0].limit;
         }
         return result;
     }
 
 #ifdef JS_ION
     while (true) {
         RegExpRunStatus result;
         {
             AutoTraceLog logJIT(logger, TraceLogger::IrregexpExecute);
-            const jschar *chars = input->chars() + charsOffset;
-            result = irregexp::ExecuteCode(cx, jitCodeTwoByte, chars, start, length, &matches);
+            AutoCheckCannotGC nogc;
+            if (input->hasLatin1Chars()) {
+                const Latin1Char *chars = input->latin1Chars(nogc) + charsOffset;
+                result = irregexp::ExecuteCode(cx, jitCodeLatin1, chars, start, length, &matches);
+            } else {
+                const jschar *chars = input->twoByteChars(nogc) + charsOffset;
+                result = irregexp::ExecuteCode(cx, jitCodeTwoByte, chars, start, length, &matches);
+            }
         }
 
         if (result == RegExpRunStatus_Error) {
             // The RegExp engine might exit with an exception if an interrupt
             // was requested. Check this case and retry until a clean result is
             // obtained.
             bool interrupted;
             {
@@ -880,20 +899,20 @@ js::ParseRegExpFlags(JSContext *cx, JSSt
     if (!linear)
         return false;
 
     size_t len = linear->length();
 
     bool ok;
     jschar lastParsed;
     if (linear->hasLatin1Chars()) {
-        JS::AutoCheckCannotGC nogc;
+        AutoCheckCannotGC nogc;
         ok = ::ParseRegExpFlags(linear->latin1Chars(nogc), len, flagsOut, &lastParsed);
     } else {
-        JS::AutoCheckCannotGC nogc;
+        AutoCheckCannotGC nogc;
         ok = ::ParseRegExpFlags(linear->twoByteChars(nogc), len, flagsOut, &lastParsed);
     }
 
     if (!ok) {
         char charBuf[2];
         charBuf[0] = char(lastParsed);
         charBuf[1] = '\0';
         JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage, nullptr,
--- a/js/src/vm/StringBuffer.h
+++ b/js/src/vm/StringBuffer.h
@@ -250,17 +250,17 @@ StringBuffer::append(JSLinearString *str
            ? twoByteChars().append(str->latin1Chars(nogc), str->length())
            : twoByteChars().append(str->twoByteChars(nogc), str->length());
 }
 
 inline void
 StringBuffer::infallibleAppendSubstring(JSLinearString *base, size_t off, size_t len)
 {
     MOZ_ASSERT(off + len <= base->length());
-    MOZ_ASSERT(base->hasLatin1Chars() == isLatin1());
+    MOZ_ASSERT_IF(base->hasTwoByteChars(), isTwoByte());
 
     JS::AutoCheckCannotGC nogc;
     if (base->hasLatin1Chars())
         infallibleAppend(base->latin1Chars(nogc) + off, len);
     else
         infallibleAppend(base->twoByteChars(nogc) + off, len);
 }
 
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -75,18 +75,20 @@ const char* const XPCJSRuntime::mStrings
     "Function",             // IDX_FUNCTION
     "prototype",            // IDX_PROTOTYPE
     "createInstance",       // IDX_CREATE_INSTANCE
     "item",                 // IDX_ITEM
     "__proto__",            // IDX_PROTO
     "__iterator__",         // IDX_ITERATOR
     "__exposedProps__",     // IDX_EXPOSEDPROPS
     "eval",                 // IDX_EVAL
-    "controllers",           // IDX_CONTROLLERS
+    "controllers",          // IDX_CONTROLLERS
     "realFrameElement",     // IDX_REALFRAMEELEMENT
+    "length",               // IDX_LENGTH
+    "name",                 // IDX_NAME
 };
 
 /***************************************************************************/
 
 static mozilla::Atomic<bool> sDiscardSystemSource(false);
 
 bool
 xpc::ShouldDiscardSystemSource() { return sDiscardSystemSource; }
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -475,16 +475,18 @@ public:
         IDX_CREATE_INSTANCE         ,
         IDX_ITEM                    ,
         IDX_PROTO                   ,
         IDX_ITERATOR                ,
         IDX_EXPOSEDPROPS            ,
         IDX_EVAL                    ,
         IDX_CONTROLLERS             ,
         IDX_REALFRAMEELEMENT        ,
+        IDX_LENGTH                  ,
+        IDX_NAME                    ,
         IDX_TOTAL_COUNT // just a count of the above
     };
 
     JS::HandleId GetStringID(unsigned index) const
     {
         MOZ_ASSERT(index < IDX_TOTAL_COUNT, "index out of range");
         // fromMarkedLocation() is safe because the string is interned.
         return JS::HandleId::fromMarkedLocation(&mStrIDs[index]);
--- a/js/xpconnect/tests/chrome/test_bug448587.xul
+++ b/js/xpconnect/tests/chrome/test_bug448587.xul
@@ -24,13 +24,14 @@ https://bugzilla.mozilla.org/show_bug.cg
   ok(!SpecialPowers.Services.prefs.prefHasUserValue('testing.some_arbitrary_pref'),
      "Pref shouldn't carry over from previous test!");
 
 
   /** Test for Bug 448587 **/
   const Cu = Components.utils;
   var sandbox = new Cu.Sandbox("about:blank");
   var fwrapper = Cu.evalInSandbox("function f() {} f", sandbox);
-  is(fwrapper.prototype, Cu.evalInSandbox("f.prototype", sandbox),
-     "we don't censor .prototype through .wrappedJSObject");
+  is(Cu.unwaiveXrays(Cu.waiveXrays(fwrapper).prototype), Cu.evalInSandbox("f.prototype", sandbox),
+     ".prototype visible through .wrappedJSObject");
+  is(fwrapper.prototype, undefined, ".prototype invisible through Xrays");
   ]]>
   </script>
 </window>
--- a/js/xpconnect/tests/chrome/test_bug812415.xul
+++ b/js/xpconnect/tests/chrome/test_bug812415.xul
@@ -51,17 +51,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     checkThrows('expFun.bar = 0', regular, "Regular shouldn't write properties");
 
     // Check functions.
     is(Cu.evalInSandbox('regFun()', expanded), 42, "Expanded can call regular function");
     checkThrows('expFun()', regular, "Regular cannot call expanded function");
     is(Cu.evalInSandbox('regFun.name', expanded), 'reg', "Expanded can see regular function's name");
     checkThrows('expFun.name', regular, "Regular can't see expanded function's name");
     Cu.evalInSandbox('regFun.expando = 30', expanded);
-    is(expanded.regFun.expando, 30, "Expanded can set expandos");
+    is(Cu.evalInSandbox('regFun.expando', expanded), 30, "Expanded can set expandos");
     checkThrows('expFun.expando = 29', regular, "Regular can't set expandos");
 
     // Check __proto__ stuff.
     is(Cu.evalInSandbox('regFun.__proto__', expanded), regular.Function.prototype, "expanded can get __proto__");
     checkThrows('expFun.__proto__', regular, "regular can't use __proto__");
     checkThrows('expFun.__proto__ = {}', regular, "regular can't mutate __proto__");
   }
 
--- a/js/xpconnect/tests/chrome/test_xrayToJS.xul
+++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul
@@ -48,29 +48,28 @@ https://bugzilla.mozilla.org/show_bug.cg
 
     // Test constructors that can be instantiated with zero arguments.
     for (var c of simpleConstructors) {
       ok(iwin[c], "Constructors appear: " + c);
       is(iwin[c], Cu.unwaiveXrays(iwin.wrappedJSObject[c]),
          "we end up with the appropriate constructor: " + c);
       is(Cu.unwaiveXrays(Cu.waiveXrays(new iwin[c]).constructor), iwin[c],
          "constructor property is set up right: " + c);
-      is(Object.getPrototypeOf(new iwin[c]),
-         Cu.unwaiveXrays(Cu.waiveXrays(iwin[c]).prototype),
+      is(Object.getPrototypeOf(new iwin[c]), iwin[c].prototype,
          "prototype is correct: " + c);
       is(global(new iwin[c]), iwin, "Got the right global: " + c);
     }
 
     // Test Object in more detail.
     var num = new iwin.Object(4);
     is(num.valueOf(), 4, "primitive object construction works");
     is(global(num), iwin, "correct global for num");
     var obj = new iwin.Object();
     obj.foo = 2;
-    var withProto = iwin.Object.create(obj);
+    var withProto = Cu.unwaiveXrays(Cu.waiveXrays(iwin).Object.create(obj));
     is(global(withProto), iwin, "correct global for withProto");
     is(Cu.waiveXrays(withProto).foo, 2, "Inherits properly");
 
     // Test Function.
     var primitiveFun = new iwin.Function('return 2');
     is(global(primitiveFun), iwin, "function construction works");
     is(primitiveFun(), 2, "basic function works");
     var doSetFoo = new iwin.Function('arg', 'arg.foo = 2;');
@@ -80,16 +79,35 @@ https://bugzilla.mozilla.org/show_bug.cg
       ok(false, "should have thrown while setting property on object");
     } catch (e) {
       ok(!!/denied/.test(e), "Threw correctly: " + e);
     }
     var factoryFun = new iwin.Function('return {foo: 32}');
     is(global(factoryFun), iwin, "proper global for factoryFun");
     is(factoryFun().foo, 32, "factoryFun invokable");
     is(global(factoryFun()), iwin, "minted objects live in the content scope");
+    testXray('Function', factoryFun, new iwin.Function(), ['caller', 'arguments', 'length', 'name']);
+    var echoThis = new iwin.Function('return this;');
+    echoThis.wrappedJSObject.bind = 42;
+    var boundEchoThis = echoThis.bind(document);
+    is(boundEchoThis(), document, "bind() works correctly over Xrays");
+    is(global(boundEchoThis), window, "bound functions live in the caller's scope");
+    ok(/return this/.test(echoThis.toSource()), 'toSource works: ' + echoThis.toSource());
+    ok(/return this/.test(echoThis.toString()), 'toString works: ' + echoThis.toString());
+    is(iwin.Function.prototype, Object.getPrototypeOf(echoThis), "Function.prototype works for standard classes");
+    is(echoThis.prototype, undefined, "Function.prototype not visible for non standard constructors");
+    iwin.eval('var foopyFunction = function namedFoopyFunction(a, b, c) {}');
+    var foopyFunction = Cu.unwaiveXrays(Cu.waiveXrays(iwin).foopyFunction);
+    ok(Cu.isXrayWrapper(foopyFunction), "Should be Xrays");
+    is(foopyFunction.name, "namedFoopyFunction", ".name works over Xrays");
+    is(foopyFunction.length, 3, ".length works over Xrays");
+    ok(Object.getOwnPropertyNames(foopyFunction).indexOf('length') >= 0, "Should list length");
+    ok(Object.getOwnPropertyNames(foopyFunction).indexOf('name') >= 0, "Should list name");
+    ok(Object.getOwnPropertyNames(foopyFunction).indexOf('prototype') == -1, "Should not list prototype");
+    ok(Object.getOwnPropertyNames(iwin.Array).indexOf('prototype') >= 0, "Should list prototype for standard constructor");
 
     // Test interface objects that don't actually construct things.
     is(iwin.Math.tan(4.5), Math.tan(4.5), "Math.tan works");
     is(iwin.Math.E, Math.E, "Math.E works");
     var json = JSON.stringify({a: 2, b: 'hi', c: {d: 'there'}});
     is(global(iwin.JSON.parse(json)), iwin, "JSON rehydrated into the right context");
     is(iwin.JSON.stringify(iwin.JSON.parse(json)), json, "JSON composition identity holds");
 
@@ -159,16 +177,19 @@ https://bugzilla.mozilla.org/show_bug.cg
       "reducePar", "scanPar", "scatterPar", "filterPar", "find", "findIndex", "copyWithin",
       "fill", "@@iterator", "entries", "keys", "constructor"];
   for (var c of typedArrayClasses) {
     gPrototypeProperties[c] = ["constructor", "BYTES_PER_ELEMENT", "length", "buffer",
                                "byteLength", "byteOffset", "@@iterator", "subarray", "set"];
     if (!isReleaseBuild)
       gPrototypeProperties[c].push("move");
   }
+  gPrototypeProperties['Function'] =
+    ["constructor", "toSource", "toString", "apply", "call", "bind",
+     "isGenerator", "length", "name", "arguments", "caller"];
 
   function filterOut(array, props) {
     return array.filter(p => props.indexOf(p) == -1);
   }
 
   function testXray(classname, xray, xray2, propsToSkip) {
     propsToSkip = propsToSkip || [];
     let xrayProto = Object.getPrototypeOf(xray);
@@ -237,17 +258,18 @@ https://bugzilla.mozilla.org/show_bug.cg
     var d = new iwin.Date();
     isnot(d.toLocaleString, Cu.unwaiveXrays(d.wrappedJSObject.toLocaleString), "Different function identities");
     is(Cu.getGlobalForObject(d.toLocaleString), window, "Xray global is correct");
     is(Cu.getGlobalForObject(d.wrappedJSObject.toLocaleString), iwin, "Underlying global is correct");
     is(d.toLocaleString('de-DE'), d.wrappedJSObject.toLocaleString('de-DE'), "Results match");
   }
 
   function testObject() {
-    testXray('Object', iwin.Object.create(new iwin.Object()), new iwin.Object(), []);
+    testXray('Object', Cu.unwaiveXrays(Cu.waiveXrays(iwin).Object.create(new iwin.Object())),
+             new iwin.Object(), []);
 
     // Construct an object full of tricky things.
     var trickyObject =
       iwin.eval('new Object({ \
                    primitiveProp: 42, objectProp: { foo: 2 }, \
                    xoProp: top.location, hasOwnProperty: 10, \
                    get getterProp() { return 2; }, \
                    set setterProp(x) { }, \
--- a/js/xpconnect/wrappers/WrapperFactory.cpp
+++ b/js/xpconnect/wrappers/WrapperFactory.cpp
@@ -123,19 +123,19 @@ WrapperFactory::DoubleWrap(JSContext *cx
 // In general, we're trying to deprecate COWs incrementally as we introduce
 // Xrays to the corresponding object types. But switching off COWs for Object
 // and Array instances would be too tumultuous at present, so we punt on that
 // for later.
 static bool
 ForceCOWBehavior(JSObject *obj)
 {
     JSProtoKey key = IdentifyStandardInstanceOrPrototype(obj);
-    if (key == JSProto_Object || key == JSProto_Array) {
+    if (key == JSProto_Object || key == JSProto_Array || key == JSProto_Function) {
         MOZ_ASSERT(GetXrayType(obj) == XrayForJSObject,
-                   "We should use XrayWrappers for standard ES Object and Array "
+                   "We should use XrayWrappers for standard ES Object, Array, and Function "
                    "instances modulo this hack");
         return true;
     }
     return false;
 }
 
 JSObject *
 WrapperFactory::PrepareForWrapping(JSContext *cx, HandleObject scope,
@@ -355,23 +355,16 @@ SelectWrapper(bool securityWrapper, bool
         return &WaiveXrayWrapper::singleton;
     }
 
     // If we don't want or can't use Xrays, select a wrapper that's either
     // entirely transparent or entirely opaque.
     if (!wantXrays || xrayType == NotXray) {
         if (!securityWrapper)
             return &CrossCompartmentWrapper::singleton;
-        // In general, we don't want opaque function wrappers to be callable.
-        // But in the case of XBL, we rely on content being able to invoke
-        // functions exposed from the XBL scope. We could remove this exception,
-        // if needed, by using ExportFunction to generate the content-side
-        // representations of XBL methods.
-        else if (originIsXBLScope)
-            return &FilteringWrapper<CrossCompartmentSecurityWrapper, OpaqueWithCall>::singleton;
         return &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
     }
 
     // Ok, we're using Xray. If this isn't a security wrapper, use the permissive
     // version and skip the filter.
     if (!securityWrapper) {
         if (xrayType == XrayForWrappedNative)
             return &PermissiveXrayXPCWN::singleton;
@@ -385,17 +378,25 @@ SelectWrapper(bool securityWrapper, bool
     if (xrayType == XrayForWrappedNative)
         return &FilteringWrapper<SecurityXrayXPCWN,
                                  CrossOriginAccessiblePropertiesOnly>::singleton;
     else if (xrayType == XrayForDOMObject)
         return &FilteringWrapper<SecurityXrayDOM,
                                  CrossOriginAccessiblePropertiesOnly>::singleton;
     // There's never any reason to expose pure JS objects to non-subsuming actors.
     // Just use an opaque wrapper in this case.
+    //
+    // In general, we don't want opaque function wrappers to be callable.
+    // But in the case of XBL, we rely on content being able to invoke
+    // functions exposed from the XBL scope. We could remove this exception,
+    // if needed, by using ExportFunction to generate the content-side
+    // representations of XBL methods.
     MOZ_ASSERT(xrayType == XrayForJSObject);
+    if (originIsXBLScope)
+        return &FilteringWrapper<CrossCompartmentSecurityWrapper, OpaqueWithCall>::singleton;
     return &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
 }
 
 JSObject *
 WrapperFactory::Rewrap(JSContext *cx, HandleObject existing, HandleObject obj,
                        HandleObject parent, unsigned flags)
 {
     MOZ_ASSERT(!IsWrapper(obj) ||
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -66,16 +66,17 @@ static bool
 IsJSXraySupported(JSProtoKey key)
 {
     if (IsTypedArrayKey(key))
         return true;
     switch (key) {
       case JSProto_Date:
       case JSProto_Object:
       case JSProto_Array:
+      case JSProto_Function:
         return true;
       default:
         return false;
     }
 }
 
 XrayType
 GetXrayType(JSObject *obj)
@@ -166,16 +167,18 @@ public:
     ResolvingIdDummy(JSContext *cx, HandleObject wrapper, HandleId id)
     {
     }
 };
 
 class XrayTraits
 {
 public:
+    XrayTraits() {}
+
     static JSObject* getTargetObject(JSObject *wrapper) {
         return js::UncheckedUnwrap(wrapper, /* stopAtOuter = */ false);
     }
 
     virtual bool resolveNativeProperty(JSContext *cx, HandleObject wrapper,
                                        HandleObject holder, HandleId id,
                                        MutableHandle<JSPropertyDescriptor> desc) = 0;
     // NB: resolveOwnProperty may decide whether or not to cache what it finds
@@ -219,16 +222,19 @@ private:
                                       nsIPrincipal *consumerOrigin,
                                       HandleObject exclusiveGlobal);
     JSObject* getExpandoObjectInternal(JSContext *cx, HandleObject target,
                                        nsIPrincipal *origin,
                                        JSObject *exclusiveGlobal);
     JSObject* attachExpandoObject(JSContext *cx, HandleObject target,
                                   nsIPrincipal *origin,
                                   HandleObject exclusiveGlobal);
+
+    XrayTraits(XrayTraits &) MOZ_DELETE;
+    const XrayTraits& operator=(XrayTraits &) MOZ_DELETE;
 };
 
 class XPCWrappedNativeXrayTraits : public XrayTraits
 {
 public:
     enum {
         HasPrototype = 0
     };
@@ -344,26 +350,34 @@ public:
                         Handle<JSPropertyDescriptor> existingDesc, bool *defined);
 
     virtual bool enumerateNames(JSContext *cx, HandleObject wrapper, unsigned flags,
                                 AutoIdVector &props);
 
     static bool call(JSContext *cx, HandleObject wrapper,
                      const JS::CallArgs &args, js::Wrapper& baseInstance)
     {
-        // We'll handle this when we start supporting Functions.
+        JSXrayTraits &self = JSXrayTraits::singleton;
+        RootedObject holder(cx, self.ensureHolder(cx, wrapper));
+        if (self.getProtoKey(holder) == JSProto_Function)
+            return baseInstance.call(cx, wrapper, args);
+
         RootedValue v(cx, ObjectValue(*wrapper));
         js_ReportIsNotFunction(cx, v);
         return false;
     }
 
     static bool construct(JSContext *cx, HandleObject wrapper,
                           const JS::CallArgs &args, js::Wrapper& baseInstance)
     {
-        // We'll handle this when we start supporting Functions.
+        JSXrayTraits &self = JSXrayTraits::singleton;
+        RootedObject holder(cx, self.ensureHolder(cx, wrapper));
+        if (self.getProtoKey(holder) == JSProto_Function)
+            return baseInstance.construct(cx, wrapper, args);
+
         RootedValue v(cx, ObjectValue(*wrapper));
         js_ReportIsNotFunction(cx, v);
         return false;
     }
 
     static bool isResolving(JSContext *cx, JSObject *holder, jsid id)
     {
         return false;
@@ -398,29 +412,35 @@ public:
         // the target is the canonical representation of state. If it gets
         // collected, then expandos and such should be collected too. So there's
         // nothing to do here.
     }
 
     enum {
         SLOT_PROTOKEY = 0,
         SLOT_ISPROTOTYPE,
+        SLOT_CONSTRUCTOR_FOR,
         SLOT_COUNT
     };
     virtual JSObject* createHolder(JSContext *cx, JSObject *wrapper) MOZ_OVERRIDE;
 
     static JSProtoKey getProtoKey(JSObject *holder) {
         int32_t key = js::GetReservedSlot(holder, SLOT_PROTOKEY).toInt32();
         return static_cast<JSProtoKey>(key);
     }
 
     static bool isPrototype(JSObject *holder) {
         return js::GetReservedSlot(holder, SLOT_ISPROTOTYPE).toBoolean();
     }
 
+    static JSProtoKey constructorFor(JSObject *holder) {
+        int32_t key = js::GetReservedSlot(holder, SLOT_CONSTRUCTOR_FOR).toInt32();
+        return static_cast<JSProtoKey>(key);
+    }
+
     static bool getOwnPropertyFromTargetIfSafe(JSContext *cx,
                                                HandleObject target,
                                                HandleObject wrapper,
                                                HandleId id,
                                                MutableHandle<JSPropertyDescriptor> desc);
 
     static const JSClass HolderClass;
     static JSXrayTraits singleton;
@@ -547,16 +567,42 @@ JSXrayTraits::resolveOwnProperty(JSConte
             return JS_WrapPropertyDescriptor(cx, desc);
         } else if (IsTypedArrayKey(key)) {
             if (IsArrayIndex(GetArrayIndexFromId(cx, id))) {
                 JS_ReportError(cx, "Accessing TypedArray data over Xrays is slow, and forbidden "
                                    "in order to encourage performant code. To copy TypedArrays "
                                    "across origin boundaries, consider using Components.utils.cloneInto().");
                 return false;
             }
+        } else if (key == JSProto_Function) {
+            if (id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_LENGTH)) {
+                FillPropertyDescriptor(desc, wrapper, JSPROP_PERMANENT | JSPROP_READONLY,
+                                       NumberValue(JS_GetFunctionArity(JS_GetObjectFunction(target))));
+                return true;
+            } else if (id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_NAME)) {
+                FillPropertyDescriptor(desc, wrapper, JSPROP_PERMANENT | JSPROP_READONLY,
+                                       StringValue(JS_GetFunctionId(JS_GetObjectFunction(target))));
+            } else if (id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_PROTOTYPE)) {
+                // Handle the 'prototype' property to make xrayedGlobal.StandardClass.prototype work.
+                JSProtoKey standardConstructor = constructorFor(holder);
+                if (standardConstructor != JSProto_Null) {
+                    RootedObject standardProto(cx);
+                    {
+                        JSAutoCompartment ac(cx, target);
+                        if (!JS_GetClassPrototype(cx, standardConstructor, &standardProto))
+                            return false;
+                        MOZ_ASSERT(standardProto);
+                    }
+                    if (!JS_WrapObject(cx, &standardProto))
+                        return false;
+                    FillPropertyDescriptor(desc, wrapper, JSPROP_PERMANENT | JSPROP_READONLY,
+                                           ObjectValue(*standardProto));
+                    return true;
+                }
+            }
         }
 
         // The rest of this function applies only to prototypes.
         return true;
     }
 
     // The non-HasPrototypes semantics implemented by traditional Xrays are kind
     // of broken with respect to |own|-ness and the holder. The common code
@@ -800,16 +846,26 @@ JSXrayTraits::enumerateNames(JSContext *
         } else if (IsTypedArrayKey(key)) {
             uint32_t length = JS_GetTypedArrayLength(target);
             // TypedArrays enumerate every indexed property in range, but
             // |length| is a getter that lives on the proto, like it should be.
             if (!props.reserve(length))
                 return false;
             for (int32_t i = 0; i <= int32_t(length - 1); ++i)
                 props.infallibleAppend(INT_TO_JSID(i));
+        } else if (key == JSProto_Function) {
+            if (!props.append(GetRTIdByIndex(cx, XPCJSRuntime::IDX_LENGTH)))
+                return false;
+            if (!props.append(GetRTIdByIndex(cx, XPCJSRuntime::IDX_NAME)))
+                return false;
+            // Handle the .prototype property on standard constructors.
+            if (constructorFor(holder) != JSProto_Null) {
+                if (!props.append(GetRTIdByIndex(cx, XPCJSRuntime::IDX_PROTOTYPE)))
+                    return false;
+            }
         }
 
         // The rest of this function applies only to prototypes.
         return true;
     }
 
     // Grab the JSClass. We require all Xrayable classes to have a ClassSpec.
     const js::Class *clasp = js::GetObjectClass(target);
@@ -864,16 +920,23 @@ JSXrayTraits::createHolder(JSContext *cx
 
     // Store it on the holder.
     RootedValue v(cx);
     v.setNumber(static_cast<uint32_t>(key));
     js::SetReservedSlot(holder, SLOT_PROTOKEY, v);
     v.setBoolean(isPrototype);
     js::SetReservedSlot(holder, SLOT_ISPROTOTYPE, v);
 
+    // If this is a function, also compute whether it serves as a constructor
+    // for a standard class.
+    if (key == JSProto_Function) {
+        v.setNumber(static_cast<uint32_t>(IdentifyStandardConstructor(target)));
+        js::SetReservedSlot(holder, SLOT_CONSTRUCTOR_FOR, v);
+    }
+
     return holder;
 }
 
 XPCWrappedNativeXrayTraits XPCWrappedNativeXrayTraits::singleton;
 DOMXrayTraits DOMXrayTraits::singleton;
 JSXrayTraits JSXrayTraits::singleton;
 
 XrayTraits*
--- a/layout/tools/reftest/b2g_desktop.py
+++ b/layout/tools/reftest/b2g_desktop.py
@@ -17,26 +17,29 @@ from marionette import Marionette
 from mozprocess import ProcessHandler
 from mozrunner import FirefoxRunner
 import mozinfo
 import mozlog
 
 log = mozlog.getLogger('REFTEST')
 
 class B2GDesktopReftest(RefTest):
-    def __init__(self, marionette):
+    marionette = None
+
+    def __init__(self, marionette_args):
         RefTest.__init__(self)
         self.last_test = os.path.basename(__file__)
-        self.marionette = marionette
+        self.marionette_args = marionette_args
         self.profile = None
         self.runner = None
         self.test_script = os.path.join(here, 'b2g_start_script.js')
         self.timeout = None
 
     def run_marionette_script(self):
+        self.marionette = Marionette(**self.marionette_args)
         assert(self.marionette.wait_for_port())
         self.marionette.start_session()
         self.marionette.set_context(self.marionette.CONTEXT_CHROME)
 
         if os.path.isfile(self.test_script):
             f = open(self.test_script, 'r')
             self.test_script = f.read()
             f.close()
@@ -66,18 +69,18 @@ class B2GDesktopReftest(RefTest):
         cmd, args = self.build_command_line(options.app,
                             ignore_window_size=options.ignoreWindowSize,
                             browser_arg=options.browser_arg)
         self.runner = FirefoxRunner(profile=self.profile,
                                     binary=cmd,
                                     cmdargs=args,
                                     env=env,
                                     process_class=ProcessHandler,
-                                    symbols_path=options.symbolsPath,
-                                    kp_kwargs=kp_kwargs)
+                                    process_args=kp_kwargs,
+                                    symbols_path=options.symbolsPath)
 
         status = 0
         try:
             self.runner.start(outputTimeout=self.timeout)
             log.info("%s | Application pid: %d",
                      os.path.basename(__file__),
                      self.runner.process_handler.pid)
 
@@ -146,24 +149,23 @@ class B2GDesktopReftest(RefTest):
         msg = "%s | application timed out after %s seconds with no output"
         log.testFail(msg % (self.last_test, self.timeout))
 
         # kill process to get a stack
         self.runner.stop(sig=signal.SIGABRT)
 
 
 def run_desktop_reftests(parser, options, args):
-    kwargs = {}
+    marionette_args = {}
     if options.marionette:
         host, port = options.marionette.split(':')
-        kwargs['host'] = host
-        kwargs['port'] = int(port)
-    marionette = Marionette.getMarionetteOrExit(**kwargs)
+        marionette_args['host'] = host
+        marionette_args['port'] = int(port)
 
-    reftest = B2GDesktopReftest(marionette)
+    reftest = B2GDesktopReftest(marionette_args)
 
     options = ReftestOptions.verifyCommonOptions(parser, options, reftest)
     if options == None:
         sys.exit(1)
 
     # add a -bin suffix if b2g-bin exists, but just b2g was specified
     if options.app[-4:] != '-bin':
         if os.path.isfile("%s-bin" % options.app):
--- a/layout/tools/reftest/mach_commands.py
+++ b/layout/tools/reftest/mach_commands.py
@@ -196,17 +196,17 @@ class ReftestRunner(MozbuildObject):
 
         try:
             which.which('adb')
         except which.WhichError:
             # TODO Find adb automatically if it isn't on the path
             raise Exception(ADB_NOT_FOUND % ('%s-remote' % suite, b2g_home))
 
         options.b2gPath = b2g_home
-        options.logcat_dir = self.reftest_dir
+        options.logdir = self.reftest_dir
         options.httpdPath = os.path.join(self.topsrcdir, 'netwerk', 'test', 'httpserver')
         options.xrePath = xre_path
         options.ignoreWindowSize = True
 
         # Don't enable oop for crashtest until they run oop in automation
         if suite == 'reftest':
             options.oop = True
 
@@ -330,19 +330,19 @@ def ReftestCommand(func):
 
 def B2GCommand(func):
     """Decorator that adds shared command arguments to b2g mochitest commands."""
 
     busybox = CommandArgument('--busybox', default=None,
         help='Path to busybox binary to install on device')
     func = busybox(func)
 
-    logcatdir = CommandArgument('--logcat-dir', default=None,
-        help='directory to store logcat dump files')
-    func = logcatdir(func)
+    logdir = CommandArgument('--logdir', default=None,
+        help='directory to store log files')
+    func = logdir(func)
 
     sdcard = CommandArgument('--sdcard', default="10MB",
         help='Define size of sdcard: 1MB, 50MB...etc')
     func = sdcard(func)
 
     emulator_res = CommandArgument('--emulator-res', default='800x1000',
         help='Emulator resolution of the format \'<width>x<height>\'')
     func = emulator_res(func)
--- a/layout/tools/reftest/runreftestb2g.py
+++ b/layout/tools/reftest/runreftestb2g.py
@@ -53,19 +53,19 @@ class B2GOptions(ReftestOptions):
         defaults["emulator_res"] = None
 
         self.add_option("--no-window", action="store_true",
                     dest = "noWindow",
                     help = "Pass --no-window to the emulator")
         defaults["noWindow"] = False
 
         self.add_option("--adbpath", action="store",
-                    type = "string", dest = "adbPath",
+                    type = "string", dest = "adb_path",
                     help = "path to adb")
-        defaults["adbPath"] = "adb"
+        defaults["adb_path"] = "adb"
 
         self.add_option("--deviceIP", action="store",
                     type = "string", dest = "deviceIP",
                     help = "ip address of remote device to test")
         defaults["deviceIP"] = None
 
         self.add_option("--devicePort", action="store",
                     type = "string", dest = "devicePort",
@@ -96,20 +96,20 @@ class B2GOptions(ReftestOptions):
                     type = "string", dest = "pidFile",
                     help = "name of the pidfile to generate")
         defaults["pidFile"] = ""
         self.add_option("--gecko-path", action="store",
                         type="string", dest="geckoPath",
                         help="the path to a gecko distribution that should "
                         "be installed on the emulator prior to test")
         defaults["geckoPath"] = None
-        self.add_option("--logcat-dir", action="store",
-                        type="string", dest="logcat_dir",
-                        help="directory to store logcat dump files")
-        defaults["logcat_dir"] = None
+        self.add_option("--logdir", action="store",
+                        type="string", dest="logdir",
+                        help="directory to store log files")
+        defaults["logdir"] = None
         self.add_option('--busybox', action='store',
                         type='string', dest='busybox',
                         help="Path to busybox binary to install on device")
         defaults['busybox'] = None
         self.add_option("--httpd-path", action = "store",
                     type = "string", dest = "httpdPath",
                     help = "path to the httpd.js file")
         defaults["httpdPath"] = None
@@ -161,18 +161,18 @@ class B2GOptions(ReftestOptions):
             options.httpPort = auto.DEFAULT_HTTP_PORT
 
         if not options.sslPort:
             options.sslPort = auto.DEFAULT_SSL_PORT
 
         if options.geckoPath and not options.emulator:
             self.error("You must specify --emulator if you specify --gecko-path")
 
-        if options.logcat_dir and not options.emulator:
-            self.error("You must specify --emulator if you specify --logcat-dir")
+        if options.logdir and not options.emulator:
+            self.error("You must specify --emulator if you specify --logdir")
 
         #if not options.emulator and not options.deviceIP:
         #    print "ERROR: you must provide a device IP"
         #    return None
 
         if options.remoteLogFile == None:
             options.remoteLogFile = "reftest.log"
 
@@ -492,43 +492,45 @@ def run_remote_reftests(parser, options,
     kwargs = {}
     if options.emulator:
         kwargs['emulator'] = options.emulator
         auto.setEmulator(True)
         if options.noWindow:
             kwargs['noWindow'] = True
         if options.geckoPath:
             kwargs['gecko_path'] = options.geckoPath
-        if options.logcat_dir:
-            kwargs['logcat_dir'] = options.logcat_dir
+        if options.logdir:
+            kwargs['logdir'] = options.logdir
         if options.busybox:
             kwargs['busybox'] = options.busybox
         if options.symbolsPath:
             kwargs['symbols_path'] = options.symbolsPath
     if options.emulator_res:
         kwargs['emulator_res'] = options.emulator_res
     if options.b2gPath:
         kwargs['homedir'] = options.b2gPath
     if options.marionette:
         host,port = options.marionette.split(':')
         kwargs['host'] = host
         kwargs['port'] = int(port)
-    marionette = Marionette.getMarionetteOrExit(**kwargs)
+    if options.adb_path:
+        kwargs['adb_path'] = options.adb_path
+    marionette = Marionette(**kwargs)
     auto.marionette = marionette
 
     if options.emulator:
         dm = marionette.emulator.dm
     else:
         # create the DeviceManager
-        kwargs = {'adbPath': options.adbPath,
+        kwargs = {'adbPath': options.adb_path,
                   'deviceRoot': options.remoteTestRoot}
         if options.deviceIP:
             kwargs.update({'host': options.deviceIP,
                            'port': options.devicePort})
-        dm = DeviagerADB(**kwargs)
+        dm = DeviceManagerADB(**kwargs)
     auto.setDeviceManager(dm)
 
     options = parser.verifyRemoteOptions(options, auto)
 
     if (options == None):
         print "ERROR: Invalid options specified, use --help for a list of valid options"
         sys.exit(1)
 
--- a/media/libstagefright/moz.build
+++ b/media/libstagefright/moz.build
@@ -105,16 +105,17 @@ if CONFIG['_MSC_VER']:
     ]
     CXXFLAGS += [
         '-wd4018', # '<' : signed/unsigned mismatch
         '-wd4275', # non dll-interface class used as base for dll-interface class
         '-wd4305', # '=' : truncation from 'double' to 'float'
         '-wd4309', # '=' : truncation of constant value
         '-wd4355', # 'this' : used in base member initializer list
         '-wd4804', # '>' : unsafe use of type 'bool' in operation
+        '-wd4099', # mismatched class/struct tags
     ]
 elif CONFIG['GNU_CXX']:
     CFLAGS += [
         '-Wno-comment',
         '-Wno-declaration-after-statement',
         '-Wno-sign-compare'
     ]
     CXXFLAGS += [
--- a/security/manager/ssl/src/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/src/SSLServerCertVerification.cpp
@@ -132,18 +132,16 @@
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gPIPNSSLog;
 #endif
 
 namespace mozilla { namespace psm {
 
 namespace {
 
-NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID);
-
 NSSCleanupAutoPtrClass(CERTCertificate, CERT_DestroyCertificate)
 NSSCleanupAutoPtrClass_WithParam(PLArenaPool, PORT_FreeArena, FalseParam, false)
 
 // do not use a nsCOMPtr to avoid static initializer/destructor
 nsIThreadPool* gCertVerificationThreadPool = nullptr;
 
 // We avoid using a mutex for the success case to avoid lock-related
 // performance issues. However, we do use a lock in the error case to simplify
--- a/security/manager/ssl/src/TransportSecurityInfo.cpp
+++ b/security/manager/ssl/src/TransportSecurityInfo.cpp
@@ -15,16 +15,17 @@
 #include "nsDateTimeFormatCID.h"
 #include "nsICertOverrideService.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 #include "nsNSSCertHelper.h"
 #include "nsIProgrammingLanguage.h"
 #include "nsIArray.h"
 #include "nsComponentManagerUtils.h"
+#include "nsReadableUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "PSMRunnable.h"
 
 #include "secerr.h"
 
 //#define DEBUG_SSL_VERBOSE //Enable this define to get minimal 
                             //reports when doing SSL read/write
                             
@@ -619,22 +620,31 @@ GetSubjectAltNames(CERTCertificate *nssC
 
   SECITEM_FreeItem(&altNameExtension, false);
 
   CERTGeneralName *current = sanNameList;
   do {
     nsAutoString name;
     switch (current->type) {
       case certDNSName:
-        name.AssignASCII((char*)current->name.other.data, current->name.other.len);
-        if (!allNames.IsEmpty()) {
-          allNames.AppendLiteral(", ");
+        {
+          nsDependentCSubstring nameFromCert(reinterpret_cast<char*>
+                                              (current->name.other.data),
+                                              current->name.other.len);
+          // dNSName fields are defined as type IA5String and thus should
+          // be limited to ASCII characters.
+          if (IsASCII(nameFromCert)) {
+            name.Assign(NS_ConvertASCIItoUTF16(nameFromCert));
+            if (!allNames.IsEmpty()) {
+              allNames.AppendLiteral(", ");
+            }
+            ++nameCount;
+            allNames.Append(name);
+          }
         }
-        ++nameCount;
-        allNames.Append(name);
         break;
 
       case certIPAddress:
         {
           char buf[INET6_ADDRSTRLEN];
           PRNetAddr addr;
           if (current->name.other.len == 4) {
             addr.inet.family = PR_AF_INET;
@@ -704,18 +714,25 @@ AppendErrorTextMismatch(const nsString &
   bool useSAN = false;
 
   if (nssCert)
     useSAN = GetSubjectAltNames(nssCert.get(), component, allNames, nameCount);
 
   if (!useSAN) {
     char *certName = CERT_GetCommonName(&nssCert->subject);
     if (certName) {
-      ++nameCount;
-      allNames.Assign(NS_ConvertUTF8toUTF16(certName));
+      nsDependentCSubstring commonName(certName, strlen(certName));
+      if (IsUTF8(commonName)) {
+        // Bug 1024781
+        // We should actually check that the common name is a valid dns name or
+        // ip address and not any string value before adding it to the display
+        // list.
+        ++nameCount;
+        allNames.Assign(NS_ConvertUTF8toUTF16(commonName));
+      }
       PORT_Free(certName);
     }
   }
 
   if (nameCount > 1) {
     nsString message;
     rv = component->GetPIPNSSBundleString("certErrorMismatchMultiple", 
                                           message);
--- a/security/manager/ssl/src/nsKeygenHandler.cpp
+++ b/security/manager/ssl/src/nsKeygenHandler.cpp
@@ -62,19 +62,16 @@ DERTemplate CERTPublicKeyAndChallengeTem
 const SEC_ASN1Template SECKEY_PQGParamsTemplate[] = {
     { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(PQGParams) },
     { SEC_ASN1_INTEGER, offsetof(PQGParams,prime) },
     { SEC_ASN1_INTEGER, offsetof(PQGParams,subPrime) },
     { SEC_ASN1_INTEGER, offsetof(PQGParams,base) },
     { 0, }
 };
 
-
-static NS_DEFINE_IID(kIDOMHTMLSelectElementIID, NS_IDOMHTMLSELECTELEMENT_IID);
-
 static PQGParams *
 decode_pqg_params(char *aStr)
 {
     unsigned char *buf = nullptr;
     unsigned int len;
     PLArenaPool *arena = nullptr;
     PQGParams *params = nullptr;
     SECStatus status;
--- a/services/healthreport/docs/dataformat.rst
+++ b/services/healthreport/docs/dataformat.rst
@@ -1537,16 +1537,20 @@ FHR if telemetry is enabled.
 
 Version 1
 ^^^^^^^^^
 
 Daily counts are reported in the following properties:
 
 translationOpportunityCount
     Integer count of the number of opportunities there were to translate a page.
+missedTranslationOpportunityCount
+    Integer count of the number of missed opportunities there were to translate a page.
+    A missed opportunity is when the page language is not supported by the translation
+    provider.
 pageTranslatedCount
     Integer count of the number of pages translated.
 charactersTranslatedCount
     Integer count of the number of characters translated.
 detectedLanguageChangedBefore
     Integer count of the number of times the user manually adjusted the detected
     language before translating.
 detectedLanguageChangedAfter
@@ -1554,16 +1558,19 @@ detectedLanguageChangedAfter
     language after having first translated the page.
 
 Additional daily counts broken down by language are reported in the following
 properties:
 
 translationOpportunityCountsByLanguage
     A mapping from language to count of opportunities to translate that
     language.
+missedTranslationOpportunityCountsByLanguage
+    A mapping from language to count of missed opportunities to translate that
+    language.
 pageTranslatedCountsByLanguage
     A mapping from language to the counts of pages translated from that
     language. Each language entry will be an object containing a "total" member
     along with individual counts for each language translated to.
 
 Other properties:
 
 detectLanguageEnabled
--- a/testing/config/mozharness/b2g_emulator_config.py
+++ b/testing/config/mozharness/b2g_emulator_config.py
@@ -1,50 +1,50 @@
 # 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/.
 
 config = {
     "jsreftest_options": [
         "--adbpath=%(adbpath)s", "--b2gpath=%(b2gpath)s", "--emulator=%(emulator)s",
-        "--emulator-res=800x1000", "--logcat-dir=%(logcat_dir)s",
+        "--emulator-res=800x1000", "--logdir=%(logcat_dir)s",
         "--remote-webserver=%(remote_webserver)s", "--ignore-window-size",
         "--xre-path=%(xre_path)s", "--symbols-path=%(symbols_path)s", "--busybox=%(busybox)s",
         "--total-chunks=%(total_chunks)s", "--this-chunk=%(this_chunk)s",
         "--extra-profile-file=jsreftest/tests/user.js",
         "%(test_manifest)s",
     ],
 
     "mochitest_options": [
         "--adbpath=%(adbpath)s", "--b2gpath=%(b2gpath)s", "--console-level=INFO",
-        "--emulator=%(emulator)s", "--logcat-dir=%(logcat_dir)s",
+        "--emulator=%(emulator)s", "--logdir=%(logcat_dir)s",
         "--remote-webserver=%(remote_webserver)s", "%(test_manifest)s",
         "--xre-path=%(xre_path)s", "--symbols-path=%(symbols_path)s", "--busybox=%(busybox)s",
         "--total-chunks=%(total_chunks)s", "--this-chunk=%(this_chunk)s",
         "--quiet", "--certificate-path=%(certificate_path)s",
         "--test-path=%(test_path)s",
     ],
 
     "reftest_options": [
         "--adbpath=%(adbpath)s", "--b2gpath=%(b2gpath)s", "--emulator=%(emulator)s",
-        "--emulator-res=800x1000", "--logcat-dir=%(logcat_dir)s",
+        "--emulator-res=800x1000", "--logdir=%(logcat_dir)s",
         "--remote-webserver=%(remote_webserver)s", "--ignore-window-size",
         "--xre-path=%(xre_path)s", "--symbols-path=%(symbols_path)s", "--busybox=%(busybox)s",
         "--total-chunks=%(total_chunks)s", "--this-chunk=%(this_chunk)s", "--enable-oop",
         "%(test_manifest)s",
     ],
 
     "crashtest_options": [
         "--adbpath=%(adbpath)s", "--b2gpath=%(b2gpath)s", "--emulator=%(emulator)s",
-        "--emulator-res=800x1000", "--logcat-dir=%(logcat_dir)s",
+        "--emulator-res=800x1000", "--logdir=%(logcat_dir)s",
         "--remote-webserver=%(remote_webserver)s", "--ignore-window-size",
         "--xre-path=%(xre_path)s", "--symbols-path=%(symbols_path)s", "--busybox=%(busybox)s",
         "--total-chunks=%(total_chunks)s", "--this-chunk=%(this_chunk)s",
         "%(test_manifest)s",
     ],
 
     "xpcshell_options": [
         "--adbpath=%(adbpath)s", "--b2gpath=%(b2gpath)s", "--emulator=%(emulator)s",
-        "--logcat-dir=%(logcat_dir)s", "--manifest=%(test_manifest)s", "--use-device-libs",
+        "--logdir=%(logcat_dir)s", "--manifest=%(test_manifest)s", "--use-device-libs",
         "--testing-modules-dir=%(modules_dir)s", "--symbols-path=%(symbols_path)s",
         "--busybox=%(busybox)s", "--total-chunks=%(total_chunks)s", "--this-chunk=%(this_chunk)s",
     ],
 }
--- a/testing/marionette/client/marionette/__init__.py
+++ b/testing/marionette/client/marionette/__init__.py
@@ -1,26 +1,54 @@
 # 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 gestures import smooth_scroll, pinch
 from by import By
 from marionette import Marionette, HTMLElement, Actions, MultiActions
 from marionette_test import MarionetteTestCase, MarionetteJSTestCase, CommonTestCase, expectedFailure, skip, SkipTest
-from emulator import Emulator
 from errors import (
-        ErrorCodes, MarionetteException, InstallGeckoError, TimeoutException, InvalidResponseException,
-        JavascriptException, NoSuchElementException, XPathLookupException, NoSuchWindowException,
-        StaleElementException, ScriptTimeoutException, ElementNotVisibleException,
-        NoSuchFrameException, InvalidElementStateException, NoAlertPresentException,
-        InvalidCookieDomainException, UnableToSetCookieException, InvalidSelectorException,
-        MoveTargetOutOfBoundsException, FrameSendNotInitializedError, FrameSendFailureError
-        )
+    ElementNotVisibleException,
+    ErrorCodes,
+    FrameSendFailureError,
+    FrameSendNotInitializedError,
+    InvalidCookieDomainException,
+    InvalidElementStateException,
+    InvalidResponseException,
+    InvalidSelectorException,
+    JavascriptException,
+    MarionetteException,
+    MoveTargetOutOfBoundsException,
+    NoAlertPresentException,
+    NoSuchElementException,
+    NoSuchFrameException,
+    NoSuchWindowException,
+    ScriptTimeoutException,
+    StaleElementException,
+    TimeoutException,
+    UnableToSetCookieException,
+    XPathLookupException,
+)
 from runner import (
-        B2GTestCaseMixin, B2GTestResultMixin, BaseMarionetteOptions, BaseMarionetteTestRunner, EnduranceOptionsMixin,
-        EnduranceTestCaseMixin, HTMLReportingOptionsMixin, HTMLReportingTestResultMixin, HTMLReportingTestRunnerMixin,
-        Marionette, MarionetteTest, MarionetteTestResult, MarionetteTextTestRunner, MemoryEnduranceTestCaseMixin,
-        MozHttpd, OptionParser, TestManifest, TestResult, TestResultCollection
-        )
+        B2GTestCaseMixin,
+        B2GTestResultMixin,
+        BaseMarionetteOptions,
+        BaseMarionetteTestRunner,
+        EnduranceOptionsMixin,
+        EnduranceTestCaseMixin,
+        HTMLReportingOptionsMixin,
+        HTMLReportingTestResultMixin,
+        HTMLReportingTestRunnerMixin,
+        Marionette,
+        MarionetteTest,
+        MarionetteTestResult,
+        MarionetteTextTestRunner,
+        MemoryEnduranceTestCaseMixin,
+        MozHttpd,
+        OptionParser,
+        TestManifest,
+        TestResult,
+        TestResultCollection
+)
 from wait import Wait
 from date_time_value import DateTimeValue
 import decorators
deleted file mode 100644
--- a/testing/marionette/client/marionette/b2gbuild.py
+++ /dev/null
@@ -1,98 +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
-import subprocess
-import sys
-
-
-class B2GBuild(object):
-
-    @classmethod
-    def find_b2g_dir(cls):
-        for env_var in ('B2G_DIR', 'B2G_HOME'):
-            if env_var in os.environ:
-                env_dir = os.environ[env_var]
-                env_dir = cls.check_b2g_dir(env_dir)
-                if env_dir:
-                    return env_dir
-
-        cwd = os.getcwd()
-        cwd = cls.check_b2g_dir(cwd)
-        if cwd:
-            return cwd
-
-        return None
-
-    @classmethod
-    def check_adb(cls, homedir):
-        if 'ADB' in os.environ:
-            env_adb = os.environ['ADB']
-            if os.path.exists(env_adb):
-                return env_adb
-
-        return cls.check_host_binary(homedir, 'adb')
-
-    @classmethod
-    def check_b2g_dir(cls, dir):
-        if os.path.isfile(os.path.join(dir, 'load-config.sh')):
-            return dir
-
-        oldstyle_dir = os.path.join(dir, 'glue', 'gonk-ics')
-        if os.access(oldstyle_dir, os.F_OK):
-            return oldstyle_dir
-
-        return None
-
-    @classmethod
-    def check_fastboot(cls, homedir):
-        return cls.check_host_binary(homedir, 'fastboot')
-
-    @classmethod
-    def check_host_binary(cls, homedir, binary):
-        host_dir = "linux-x86"
-        if platform.system() == "Darwin":
-            host_dir = "darwin-x86"
-        binary_path = subprocess.Popen(['which', binary],
-                                       stdout=subprocess.PIPE,
-                                       stderr=subprocess.STDOUT)
-        if binary_path.wait() == 0:
-            return binary_path.stdout.read().strip()  # remove trailing newline
-        binary_paths = [os.path.join(homedir, 'glue', 'gonk', 'out', 'host',
-                                     host_dir, 'bin', binary),
-                        os.path.join(homedir, 'out', 'host', host_dir,
-                                     'bin', binary),
-                        os.path.join(homedir, 'bin', binary)]
-        for option in binary_paths:
-            if os.path.exists(option):
-                return option
-        raise Exception('%s not found!' % binary)
-
-    def __init__(self, homedir=None, emulator=False):
-        if not homedir:
-            homedir = self.find_b2g_dir()
-        else:
-            homedir = self.check_b2g_dir(homedir)
-
-        if not homedir:
-            raise EnvironmentError('Must define B2G_HOME or pass the homedir parameter')
-
-        self.homedir = homedir
-        self.adb_path = self.check_adb(self.homedir)
-        self.update_tools = os.path.join(self.homedir, 'tools', 'update-tools')
-        self.fastboot_path = None if emulator else self.check_fastboot(self.homedir)
-
-    def import_update_tools(self):
-        """Import the update_tools package from B2G"""
-        sys.path.append(self.update_tools)
-        import update_tools
-        sys.path.pop()
-        return update_tools
-
-    def check_file(self, filePath):
-        if not os.access(filePath, os.F_OK):
-            raise Exception(('File not found: %s; did you pass the B2G home '
-                             'directory as the homedir parameter, or set '
-                             'B2G_HOME correctly?') % filePath)
deleted file mode 100644
--- a/testing/marionette/client/marionette/b2ginstance.py
+++ /dev/null
@@ -1,70 +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 ConfigParser import ConfigParser
-import posixpath
-import shutil
-import tempfile
-import traceback
-
-from b2gbuild import B2GBuild
-from mozdevice import DeviceManagerADB
-import mozcrash
-
-
-class B2GInstance(B2GBuild):
-
-    def __init__(self, devicemanager=None, symbols_path=None, **kwargs):
-        B2GBuild.__init__(self, **kwargs)
-
-        self._dm = devicemanager
-        self._remote_profiles = None
-        self.symbols_path = symbols_path
-
-    @property
-    def dm(self):
-        if not self._dm:
-            self._dm = DeviceManagerADB(adbPath=self.adb_path)
-        return self._dm
-
-    @property
-    def remote_profiles(self):
-        if not self._remote_profiles:
-            self.check_remote_profiles()
-        return self._remote_profiles
-
-    def check_remote_profiles(self, remote_profiles_ini='/data/b2g/mozilla/profiles.ini'):
-        if not self.dm.fileExists(remote_profiles_ini):
-            raise Exception("Remote file '%s' not found" % remote_profiles_ini)
-
-        local_profiles_ini = tempfile.NamedTemporaryFile()
-        self.dm.getFile(remote_profiles_ini, local_profiles_ini.name)
-        cfg = ConfigParser()
-        cfg.read(local_profiles_ini.name)
-
-        remote_profiles = []
-        for section in cfg.sections():
-            if cfg.has_option(section, 'Path'):
-                if cfg.has_option(section, 'IsRelative') and cfg.getint(section, 'IsRelative'):
-                    remote_profiles.append(posixpath.join(posixpath.dirname(remote_profiles_ini), cfg.get(section, 'Path')))
-                else:
-                    remote_profiles.append(cfg.get(section, 'Path'))
-        self._remote_profiles = remote_profiles
-        return remote_profiles
-
-    def check_for_crashes(self):
-        remote_dump_dirs = [posixpath.join(p, 'minidumps') for p in self.remote_profiles]
-        crashed = False
-        for remote_dump_dir in remote_dump_dirs:
-            local_dump_dir = tempfile.mkdtemp()
-            self.dm.getDirectory(remote_dump_dir, local_dump_dir)
-            try:
-                if mozcrash.check_for_crashes(local_dump_dir, self.symbols_path):
-                    crashed = True
-            except:
-                traceback.print_exc()
-            finally:
-                shutil.rmtree(local_dump_dir)
-                self.dm.removeDir(remote_dump_dir)
-        return crashed
deleted file mode 100644
--- a/testing/marionette/client/marionette/emulator.py
+++ /dev/null
@@ -1,539 +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 b2ginstance import B2GInstance
-import datetime
-from mozdevice import devicemanagerADB, DMError
-from mozprocess import ProcessHandlerMixin
-import os
-import re
-import platform
-import shutil
-import socket
-import subprocess
-from telnetlib import Telnet
-import tempfile
-import time
-import traceback
-
-from emulator_battery import EmulatorBattery
-from emulator_geo import EmulatorGeo
-from emulator_screen import EmulatorScreen
-from decorators import uses_marionette
-
-from errors import (
-    InstallGeckoError,
-    InvalidResponseException,
-    MarionetteException,
-    ScriptTimeoutException,
-    TimeoutException
-)
-
-
-class LogOutputProc(ProcessHandlerMixin):
-    """
-    Process handler for processes which save all output to a logfile.
-    If no logfile is specified, output will still be consumed to prevent
-    the output pipe's from overflowing.
-    """
-
-    def __init__(self, cmd, logfile=None,  **kwargs):
-        self.logfile = logfile
-        kwargs.setdefault('processOutputLine', []).append(self.log_output)
-        ProcessHandlerMixin.__init__(self, cmd, **kwargs)
-
-    def log_output(self, line):
-        if not self.logfile:
-            return
-
-        f = open(self.logfile, 'a')
-        f.write(line + "\n")
-        f.flush()
-
-
-class Emulator(object):
-
-    deviceRe = re.compile(r"^emulator-(\d+)(\s*)(.*)$")
-    _default_res = '320x480'
-    prefs = {'app.update.enabled': False,
-             'app.update.staging.enabled': False,
-             'app.update.service.enabled': False}
-    env = {'MOZ_CRASHREPORTER': '1',
-           'MOZ_CRASHREPORTER_NO_REPORT': '1',
-           'MOZ_CRASHREPORTER_SHUTDOWN': '1'}
-
-    def __init__(self, homedir=None, noWindow=False, logcat_dir=None,
-                 arch="x86", emulatorBinary=None, res=None, sdcard=None,
-                 symbols_path=None, userdata=None):
-        self.port = None
-        self.dm = None
-        self._emulator_launched = False
-        self.proc = None
-        self.marionette_port = None
-        self.telnet = None
-        self._tmp_sdcard = 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.res = res or self._default_res
-        self.battery = EmulatorBattery(self)
-        self.geo = EmulatorGeo(self)
-        self.screen = EmulatorScreen(self)
-        self.homedir = homedir
-        self.sdcard = sdcard
-        self.symbols_path = symbols_path
-        self.noWindow = noWindow
-        if self.homedir is not None:
-            self.homedir = os.path.expanduser(homedir)
-        self.dataImg = userdata
-        self.copy_userdata = self.dataImg is None
-
-    def _check_for_b2g(self):
-        self.b2g = B2GInstance(homedir=self.homedir, emulator=True,
-                               symbols_path=self.symbols_path)
-        self.adb = self.b2g.adb_path
-        self.homedir = self.b2g.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"]
-
-        if(self.sdcard):
-            self.mksdcard = os.path.join(self.homedir, host_bin_dir, "mksdcard")
-            self.create_sdcard(self.sdcard)
-
-        if not self.binary:
-            self.binary = os.path.join(self.homedir, binary)
-
-        self.b2g.check_file(self.binary)
-
-        self.kernelImg = os.path.join(self.homedir, kernel)
-        self.b2g.check_file(self.kernelImg)
-
-        self.sysDir = os.path.join(self.homedir, sysdir)
-        self.b2g.check_file(self.sysDir)
-
-        if not self.dataImg:
-            self.dataImg = os.path.join(self.sysDir, 'userdata.img')
-        self.b2g.check_file(self.dataImg)
-
-    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._tmp_sdcard:
-            qemuArgs.extend(['-sdcard', self._tmp_sdcard])
-        if self.noWindow:
-            qemuArgs.append('-no-window')
-        qemuArgs.extend(['-memory', '512',
-                         '-partition-size', '512',
-                         '-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 check_for_crash(self):
-        """
-        Checks if the emulator has crashed or not.  Always returns False if
-        we've connected to an already-running emulator, since we can't track
-        the emulator's pid in that case.  Otherwise, returns True iff
-        self.proc is not None (meaning the emulator hasn't been explicitly
-        closed), and self.proc.poll() is also not None (meaning the emulator
-        process has terminated).
-        """
-        return self._emulator_launched and self.proc is not None \
-                                       and self.proc.poll() is not None
-
-    def check_for_minidumps(self):
-        return self.b2g.check_for_crashes()
-
-    def create_sdcard(self, sdcard):
-        self._tmp_sdcard = tempfile.mktemp(prefix='sdcard')
-        sdargs = [self.mksdcard, "-l", "mySdCard", sdcard, self._tmp_sdcard]
-        sd = subprocess.Popen(sdargs, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-        retcode = sd.wait()
-        if retcode:
-            raise Exception('unable to create sdcard : exit code %d: %s'
-                            % (retcode, sd.stdout.read()))
-        return None
-
-    def _run_adb(self, args):
-        args.insert(0, self.adb)
-        if self.port:
-            args.insert(1, '-s')
-            args.insert(2, 'emulator-%d' % self.port)
-        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 _run_shell(self, args):
-        args.insert(0, 'shell')
-        return self._run_adb(args).split('\r\n')
-
-    def close(self):
-        if self.is_running and self._emulator_launched:
-            self.proc.kill()
-        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
-            if self._tmp_sdcard:
-                os.remove(self._tmp_sdcard)
-                self._tmp_sdcard = None
-            return retcode
-        if self.logcat_proc and self.logcat_proc.proc.poll() is None:
-            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 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
-
-    @uses_marionette
-    def wait_for_system_message(self, marionette):
-        marionette.set_script_timeout(45000)
-        # Telephony API's won't be available immediately upon emulator
-        # boot; we have to wait for the syste-message-listener-ready
-        # message before we'll be able to use them successfully.  See
-        # bug 792647.
-        print 'waiting for system-message-listener-ready...'
-        try:
-            marionette.execute_async_script("""
-waitFor(
-    function() { marionetteScriptFinished(true); },
-    function() { return isSystemMessageListenerReady(); }
-);
-            """)
-        except ScriptTimeoutException:
-            print 'timed out'
-            # We silently ignore the timeout if it occurs, since
-            # isSystemMessageListenerReady() isn't available on
-            # older emulators.  45s *should* be enough of a delay
-            # to allow telephony API's to work.
-            pass
-        except (InvalidResponseException, IOError):
-            self.check_for_minidumps()
-            raise
-        print '...done'
-
-    def connect(self):
-        self.adb = B2GInstance.check_adb(self.homedir, emulator=True)
-        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])
-
-        self.dm = devicemanagerADB.DeviceManagerADB(adbPath=self.adb,
-                                                    deviceSerial='emulator-%d' % self.port)
-
-    def start(self):
-        self._check_for_b2g()
-        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='marionette')
-            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()
-
-        filename = None
-        if self.logcat_dir:
-            filename = os.path.join(self.logcat_dir, 'qemu.log')
-            if os.path.isfile(filename):
-                self.rotate_log(filename)
-
-        self.proc = LogOutputProc(qemu_args, filename)
-        self.proc.run()
-
-        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 TimeoutException('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
-
-        self.dm = devicemanagerADB.DeviceManagerADB(adbPath=self.adb,
-                                                    deviceSerial='emulator-%d' % self.port)
-
-        # bug 802877
-        time.sleep(10)
-        self.geo.set_default_location()
-        self.screen.initialize()
-
-        if self.logcat_dir:
-            self.save_logcat()
-
-        # setup DNS fix for networking
-        self._run_adb(['shell', 'setprop', 'net.dns1', '10.0.2.3'])
-
-    @uses_marionette
-    def wait_for_homescreen(self, marionette):
-        print 'waiting for homescreen...'
-
-        marionette.set_context(marionette.CONTEXT_CONTENT)
-        marionette.execute_async_script("""
-log('waiting for mozbrowserloadend');
-window.addEventListener('mozbrowserloadend', function loaded(aEvent) {
-  log('received mozbrowserloadend for ' + aEvent.target.src);
-  if (aEvent.target.src.indexOf('ftu') != -1 || aEvent.target.src.indexOf('homescreen') != -1) {
-    window.removeEventListener('mozbrowserloadend', loaded);
-    marionetteScriptFinished();
-  }
-});""", script_timeout=120000)
-        print '...done'
-
-    def setup(self, marionette, gecko_path=None, busybox=None):
-        self.set_environment(marionette)
-        if busybox:
-            self.install_busybox(busybox)
-
-        if gecko_path:
-            self.install_gecko(gecko_path, marionette)
-
-        self.wait_for_system_message(marionette)
-        self.set_prefs(marionette)
-
-    @uses_marionette
-    def set_environment(self, marionette):
-        for k, v in self.env.iteritems():
-            marionette.execute_script("""
-            let env = Cc["@mozilla.org/process/environment;1"].
-                      getService(Ci.nsIEnvironment);
-            env.set("%s", "%s");
-            """ % (k, v))
-
-    @uses_marionette
-    def set_prefs(self, marionette):
-        for pref in self.prefs:
-            marionette.execute_script("""
-            Components.utils.import("resource://gre/modules/Services.jsm");
-            let argtype = typeof(arguments[1]);
-            switch(argtype) {
-                case 'boolean':
-                    Services.prefs.setBoolPref(arguments[0], arguments[1]);
-                    break;
-                case 'number':
-                    Services.prefs.setIntPref(arguments[0], arguments[1]);
-                    break;
-                default:
-                    Services.prefs.setCharPref(arguments[0], arguments[1]);
-            }
-            """, [pref, self.prefs[pref]])
-
-    def restart_b2g(self):
-        print 'restarting B2G'
-        self.dm.shellCheckOutput(['stop', 'b2g'])
-        time.sleep(10)
-        self.dm.shellCheckOutput(['start', 'b2g'])
-
-        if not self.wait_for_port():
-            raise TimeoutException("Timeout waiting for marionette on port '%s'" % self.marionette_port)
-
-    def install_gecko(self, gecko_path, marionette):
-        """
-        Install gecko into the emulator using adb push.  Restart b2g after the
-        installation.
-        """
-        # See bug 800102.  We use this particular method of installing
-        # gecko in order to avoid an adb bug in which adb will sometimes
-        # hang indefinitely while copying large files to the system
-        # partition.
-        print 'installing gecko binaries...'
-
-        # see bug 809437 for the path that lead to this madness
-        try:
-            # need to remount so we can write to /system/b2g
-            self._run_adb(['remount'])
-            self.dm.removeDir('/data/local/b2g')
-            self.dm.mkDir('/data/local/b2g')
-            self.dm.pushDir(gecko_path, '/data/local/b2g', retryLimit=10)
-
-            self.dm.shellCheckOutput(['stop', 'b2g'])
-
-            for root, dirs, files in os.walk(gecko_path):
-                for filename in files:
-                    rel_path = os.path.relpath(os.path.join(root, filename), gecko_path)
-                    data_local_file = os.path.join('/data/local/b2g', rel_path)
-                    system_b2g_file = os.path.join('/system/b2g', rel_path)
-
-                    print 'copying', data_local_file, 'to', system_b2g_file
-                    self.dm.shellCheckOutput(['dd',
-                                              'if=%s' % data_local_file,
-                                              'of=%s' % system_b2g_file])
-            self.restart_b2g()
-
-        except (DMError, MarionetteException):
-            # Bug 812395 - raise a single exception type for these so we can
-            # explicitly catch them elsewhere.
-
-            # print exception, but hide from mozharness error detection
-            exc = traceback.format_exc()
-            exc = exc.replace('Traceback', '_traceback')
-            print exc
-
-            raise InstallGeckoError("unable to restart B2G after installing gecko")
-
-    def install_busybox(self, busybox):
-        self._run_adb(['remount'])
-
-        remote_file = "/system/bin/busybox"
-        print 'pushing %s' % remote_file
-        self.dm.pushFile(busybox, remote_file, retryLimit=10)
-        self._run_adb(['shell', 'cd /system/bin; chmod 555 busybox; for x in `./busybox --list`; do ln -s ./busybox $x; done'])
-        self.dm._verifyZip()
-
-    def rotate_log(self, srclog, index=1):
-        """ Rotate a logfile, by recursively rotating logs further in the sequence,
-            deleting the last file if necessary.
-        """
-        basename = os.path.basename(srclog)
-        basename = basename[:-len('.log')]
-        if index > 1:
-            basename = basename[:-len('.1')]
-        basename = '%s.%d.log' % (basename, index)
-
-        destlog = os.path.join(self.logcat_dir, basename)
-        if os.path.isfile(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.isfile(filename):
-            self.rotate_log(filename)
-        cmd = [self.adb, '-s', 'emulator-%d' % self.port, 'logcat', '-v', 'threadtime']
-
-        self.logcat_proc = LogOutputProc(cmd, filename)
-        self.logcat_proc.run()
-
-    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(['forward',
-                       'tcp:%d' % local_port,
-                       'tcp:%d' % remote_port])
-
-        self.marionette_port = local_port
-
-        return local_port
-
-    def wait_for_port(self, timeout=300):
-        assert(self.marionette_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.marionette_port))
-                data = sock.recv(16)
-                sock.close()
-                if ':' in data:
-                    return True
-            except:
-                import traceback
-                print traceback.format_exc()
-            time.sleep(1)
-        return False
deleted file mode 100644
--- a/testing/marionette/client/marionette/emulator_battery.py
+++ /dev/null
@@ -1,53 +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)
-
deleted file mode 100644
--- a/testing/marionette/client/marionette/emulator_geo.py
+++ /dev/null
@@ -1,17 +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 EmulatorGeo(object):
-
-    def __init__(self, emulator):
-        self.emulator = emulator
-
-    def set_default_location(self):
-        self.lon = -122.08769
-        self.lat = 37.41857
-        self.set_location(self.lon, self.lat)
-
-    def set_location(self, lon, lat):
-        self.emulator._run_telnet('geo fix %0.5f %0.5f' % (self.lon, self.lat))
-
deleted file mode 100644
--- a/testing/marionette/client/marionette/emulator_screen.py
+++ /dev/null
@@ -1,78 +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 EmulatorScreen(object):
-    """Class for screen related emulator commands."""
-
-    SO_PORTRAIT_PRIMARY = 'portrait-primary'
-    SO_PORTRAIT_SECONDARY = 'portrait-secondary'
-    SO_LANDSCAPE_PRIMARY = 'landscape-primary'
-    SO_LANDSCAPE_SECONDARY = 'landscape-secondary'
-
-    def __init__(self, emulator):
-        self.emulator = emulator
-
-    def initialize(self):
-        self.orientation = self.SO_PORTRAIT_PRIMARY
-
-    def _get_raw_orientation(self):
-        """Get the raw value of the current device orientation."""
-        response = self.emulator._run_telnet('sensor get orientation')
-
-        return response[0].split('=')[1].strip()
-
-    def _set_raw_orientation(self, data):
-        """Set the raw value of the specified device orientation."""
-        self.emulator._run_telnet('sensor set orientation %s' % data)
-
-    def get_orientation(self):
-        """Get the current device orientation.
-
-        Returns;
-            orientation -- Orientation of the device. One of:
-                            SO_PORTRAIT_PRIMARY - system buttons at the bottom
-                            SO_PORTRIAT_SECONDARY - system buttons at the top
-                            SO_LANDSCAPE_PRIMARY - system buttons at the right
-                            SO_LANDSCAPE_SECONDARY - system buttons at the left
-
-        """
-        data = self._get_raw_orientation()
-
-        if data == '0:-90:0':
-            orientation = self.SO_PORTRAIT_PRIMARY
-        elif data == '0:90:0':
-            orientation = self.SO_PORTRAIT_SECONDARY
-        elif data == '0:0:90':
-            orientation = self.SO_LANDSCAPE_PRIMARY
-        elif data == '0:0:-90':
-            orientation = self.SO_LANDSCAPE_SECONDARY
-        else:
-            raise ValueError('Unknown orientation sensor value: %s.' % data)
-
-        return orientation
-
-    def set_orientation(self, orientation):
-        """Set the specified device orientation.
-
-        Args
-            orientation -- Orientation of the device. One of:
-                            SO_PORTRAIT_PRIMARY - system buttons at the bottom
-                            SO_PORTRIAT_SECONDARY - system buttons at the top
-                            SO_LANDSCAPE_PRIMARY - system buttons at the right
-                            SO_LANDSCAPE_SECONDARY - system buttons at the left
-        """
-        if orientation == self.SO_PORTRAIT_PRIMARY:
-            data = '0:-90:0'
-        elif orientation == self.SO_PORTRAIT_SECONDARY:
-            data = '0:90:0'
-        elif orientation == self.SO_LANDSCAPE_PRIMARY:
-            data = '0:0:90'
-        elif orientation == self.SO_LANDSCAPE_SECONDARY:
-            data = '0:0:-90'
-        else:
-            raise ValueError('Invalid orientation: %s' % orientation)
-
-        self._set_raw_orientation(data)
-
-    orientation = property(get_orientation, set_orientation)
--- a/testing/marionette/client/marionette/geckoinstance.py
+++ b/testing/marionette/client/marionette/geckoinstance.py
@@ -20,31 +20,30 @@ class GeckoInstance(object):
                       "browser.sessionstore.resume_from_crash": False,
                       "browser.warnOnQuit": False}
 
     def __init__(self, host, port, bin, profile, app_args=None, symbols_path=None,
                   gecko_log=None):
         self.marionette_host = host
         self.marionette_port = port
         self.bin = bin
-        self.profile = profile
+        self.profile_path = profile
         self.app_args = app_args or []
         self.runner = None
         self.symbols_path = symbols_path
         self.gecko_log = gecko_log
 
     def start(self):
-        profile_path = self.profile
         profile_args = {"preferences": self.required_prefs}
-        if not profile_path:
-            runner_class = Runner
+        if not self.profile_path:
             profile_args["restore"] = False
+            profile = Profile(**profile_args)
         else:
-            runner_class = CloneRunner
-            profile_args["path_from"] = profile_path
+            profile_args["path_from"] = self.profile_path
+            profile = Profile.clone(**profile_args)
 
         if self.gecko_log is None:
             self.gecko_log = 'gecko.log'
         elif os.path.isdir(self.gecko_log):
             fname = "gecko-%d.log" % time.time()
             self.gecko_log = os.path.join(self.gecko_log, fname)
 
         self.gecko_log = os.path.realpath(self.gecko_log)
@@ -52,44 +51,39 @@ class GeckoInstance(object):
             os.remove(self.gecko_log)
 
         env = os.environ.copy()
 
         # environment variables needed for crashreporting
         # https://developer.mozilla.org/docs/Environment_variables_affecting_crash_reporting
         env.update({ 'MOZ_CRASHREPORTER': '1',
                      'MOZ_CRASHREPORTER_NO_REPORT': '1', })
-        self.runner = runner_class.create(
+        self.runner = Runner(
             binary=self.bin,
-            profile_args=profile_args,
+            profile=profile,
             cmdargs=['-no-remote', '-marionette'] + self.app_args,
             env=env,
             symbols_path=self.symbols_path,
-            kp_kwargs={
+            process_args={
                 'processOutputLine': [NullOutput()],
                 'logfile': self.gecko_log})
         self.runner.start()
 
     def check_for_crashes(self):
         return self.runner.check_for_crashes()
 
     def close(self):
-        self.runner.stop()
-        self.runner.cleanup()
+        if self.runner:
+            self.runner.stop()
+            self.runner.cleanup()
 
 
 class B2GDesktopInstance(GeckoInstance):
+    required_prefs = {"focusmanager.testmode": True}
 
-    required_prefs = {"focusmanager.testmode": True}
+
+class NullOutput(object):
+    def __call__(self, line):
+        pass
+
 
 apps = {'b2g': B2GDesktopInstance,
         'b2gdesktop': B2GDesktopInstance}
-
-
-class CloneRunner(Runner):
-
-    profile_class = Profile.clone
-
-
-class NullOutput(object):
-
-    def __call__(self, line):
-        pass
--- a/testing/marionette/client/marionette/marionette.py
+++ b/testing/marionette/client/marionette/marionette.py
@@ -1,37 +1,30 @@
 # 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 ConfigParser
 import datetime
 import os
 import socket
-import sys
+import StringIO
 import time
 import traceback
 import base64
 
 from application_cache import ApplicationCache
 from decorators import do_crash_check
-from emulator import Emulator
-from emulator_screen import EmulatorScreen
-from errors import (
-        ErrorCodes, MarionetteException, InstallGeckoError, TimeoutException, InvalidResponseException,
-        JavascriptException, NoSuchElementException, XPathLookupException, NoSuchWindowException,
-        StaleElementException, ScriptTimeoutException, ElementNotVisibleException,
-        NoSuchFrameException, InvalidElementStateException, NoAlertPresentException,
-        InvalidCookieDomainException, UnableToSetCookieException, InvalidSelectorException,
-        MoveTargetOutOfBoundsException, FrameSendNotInitializedError, FrameSendFailureError
-        )
 from keys import Keys
 from marionette_transport import MarionetteTransport
 
+from mozrunner import B2GEmulatorRunner
+
 import geckoinstance
+import errors
 
 class HTMLElement(object):
     """
     Represents a DOM Element.
     """
 
     CLASS = "class name"
     SELECTOR = "css selector"
@@ -446,51 +439,43 @@ class Marionette(object):
     Represents a Marionette connection to a browser or device.
     """
 
     CONTEXT_CHROME = 'chrome' # non-browser content: windows, dialogs, etc.
     CONTEXT_CONTENT = 'content' # browser content: iframes, divs, etc.
     TIMEOUT_SEARCH = 'implicit'
     TIMEOUT_SCRIPT = 'script'
     TIMEOUT_PAGE = 'page load'
-    SCREEN_ORIENTATIONS = {"portrait": EmulatorScreen.SO_PORTRAIT_PRIMARY,
-                           "landscape": EmulatorScreen.SO_LANDSCAPE_PRIMARY,
-                           "portrait-primary": EmulatorScreen.SO_PORTRAIT_PRIMARY,
-                           "landscape-primary": EmulatorScreen.SO_LANDSCAPE_PRIMARY,
-                           "portrait-secondary": EmulatorScreen.SO_PORTRAIT_SECONDARY,
-                           "landscape-secondary": EmulatorScreen.SO_LANDSCAPE_SECONDARY}
 
     def __init__(self, host='localhost', port=2828, app=None, app_args=None, bin=None,
-                 profile=None, emulator=None, sdcard=None, emulatorBinary=None,
-                 emulatorImg=None, emulator_res=None, gecko_path=None,
-                 connectToRunningEmulator=False, homedir=None, baseurl=None,
-                 noWindow=False, logcat_dir=None, busybox=None, symbols_path=None,
-                 timeout=None, device_serial=None, gecko_log=None):
+                 profile=None, emulator=None, sdcard=None, emulator_img=None,
+                 emulator_binary=None, emulator_res=None, connect_to_running_emulator=False,
+                 gecko_log=None, homedir=None, baseurl=None, no_window=False, logdir=None,
+                 busybox=None, symbols_path=None, timeout=None, device_serial=None,
+                 adb_path=None):
         self.host = host
         self.port = self.local_port = port
         self.bin = bin
         self.instance = None
-        self.profile = profile
         self.session = None
         self.window = None
+        self.runner = None
         self.emulator = None
         self.extra_emulators = []
-        self.homedir = homedir
         self.baseurl = baseurl
-        self.noWindow = noWindow
-        self.logcat_dir = logcat_dir
+        self.no_window = no_window
         self._test_name = None
         self.timeout = timeout
         self.device_serial = device_serial
 
         if bin:
             port = int(self.port)
             if not Marionette.is_port_available(port, host=self.host):
                 ex_msg = "%s:%d is unavailable." % (self.host, port)
-                raise MarionetteException(message=ex_msg)
+                raise errors.MarionetteException(message=ex_msg)
             if app:
                 # select instance class for the given app
                 try:
                     instance_class = geckoinstance.apps[app]
                 except KeyError:
                     msg = 'Application "%s" unknown (should be one of %s)'
                     raise NotImplementedError(msg % (app, geckoinstance.apps.keys()))
             else:
@@ -499,62 +484,66 @@ class Marionette(object):
                     config.read(os.path.join(os.path.dirname(bin), 'application.ini'))
                     app = config.get('App', 'Name')
                     instance_class = geckoinstance.apps[app.lower()]
                 except (ConfigParser.NoOptionError,
                         ConfigParser.NoSectionError,
                         KeyError):
                     instance_class = geckoinstance.GeckoInstance
             self.instance = instance_class(host=self.host, port=self.port,
-                                           bin=self.bin, profile=self.profile,
+                                           bin=self.bin, profile=profile,
                                            app_args=app_args, symbols_path=symbols_path,
                                            gecko_log=gecko_log)
             self.instance.start()
             assert(self.wait_for_port()), "Timed out waiting for port!"
 
         if emulator:
-            self.emulator = Emulator(homedir=homedir,
-                                     noWindow=self.noWindow,
-                                     logcat_dir=self.logcat_dir,
-                                     arch=emulator,
-                                     sdcard=sdcard,
-                                     symbols_path=symbols_path,
-                                     emulatorBinary=emulatorBinary,
-                                     userdata=emulatorImg,
-                                     res=emulator_res)
+            self.runner = B2GEmulatorRunner(b2g_home=homedir,
+                                            no_window=self.no_window,
+                                            logdir=logdir,
+                                            arch=emulator,
+                                            sdcard=sdcard,
+                                            symbols_path=symbols_path,
+                                            binary=emulator_binary,
+                                            userdata=emulator_img,
+                                            resolution=emulator_res,
+                                            profile=profile,
+                                            adb_path=adb_path)
+            self.emulator = self.runner.device
             self.emulator.start()
             self.port = self.emulator.setup_port_forwarding(self.port)
-            assert(self.emulator.wait_for_port()), "Timed out waiting for port!"
+            assert(self.emulator.wait_for_port(self.port)), "Timed out waiting for port!"
 
-        if connectToRunningEmulator:
-            self.emulator = Emulator(homedir=homedir,
-                                     logcat_dir=self.logcat_dir)
+        if connect_to_running_emulator:
+            self.runner = B2GEmulatorRunner(b2g_home=homedir,
+                                            logdir=logdir)
+            self.emulator = self.runner.device
             self.emulator.connect()
             self.port = self.emulator.setup_port_forwarding(self.port)
-            assert(self.emulator.wait_for_port()), "Timed out waiting for port!"
+            assert(self.emulator.wait_for_port(self.port)), "Timed out waiting for port!"
 
         self.client = MarionetteTransport(self.host, self.port)
 
         if emulator:
-            self.emulator.setup(self,
-                                gecko_path=gecko_path,
-                                busybox=busybox)
+            if busybox:
+                self.emulator.install_busybox(busybox=busybox)
+            self.emulator.wait_for_system_message(self)
 
     def cleanup(self):
         if self.session:
             try:
                 self.delete_session()
-            except (MarionetteException, socket.error, IOError):
+            except (errors.MarionetteException, socket.error, IOError):
                 # These exceptions get thrown if the Marionette server
                 # hit an exception/died or the connection died. We can
                 # do no further server-side cleanup in this case.
                 pass
             self.session = None
-        if self.emulator:
-            self.emulator.close()
+        if self.runner:
+            self.runner.cleanup()
         if self.instance:
             self.instance.close()
         for qemu in self.extra_emulators:
             qemu.emulator.close()
 
     def __del__(self):
         self.cleanup()
 
@@ -565,36 +554,16 @@ class Marionette(object):
         try:
             s.bind((host, port))
             return True
         except socket.error:
             return False
         finally:
             s.close()
 
-    @classmethod
-    def getMarionetteOrExit(cls, *args, **kwargs):
-        try:
-            m = cls(*args, **kwargs)
-            return m
-        except InstallGeckoError:
-            # Bug 812395 - the process of installing gecko into the emulator
-            # and then restarting B2G tickles some bug in the emulator/b2g
-            # that intermittently causes B2G to fail to restart.  To work
-            # around this in TBPL runs, we will fail gracefully from this
-            # error so that the mozharness script can try the run again.
-
-            # This string will get caught by mozharness and will cause it
-            # to retry the tests.
-            print "Error installing gecko!"
-
-            # Exit without a normal exception to prevent mozharness from
-            # flagging the error.
-            sys.exit()
-
     def wait_for_port(self, timeout=60):
         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((self.host, self.port))
                 data = sock.recv(16)
                 sock.close()
@@ -604,32 +573,32 @@ class Marionette(object):
             except socket.error:
                 pass
             time.sleep(1)
         return False
 
     @do_crash_check
     def _send_message(self, command, response_key="ok", **kwargs):
         if not self.session and command != "newSession":
-            raise MarionetteException("Please start a session")
+            raise errors.MarionetteException("Please start a session")
 
         message = {"name": command}
         if self.session:
             message["sessionId"] = self.session
         if kwargs:
             message["parameters"] = kwargs
 
         try:
             response = self.client.send(message)
         except socket.timeout:
             self.session = None
             self.window = None
             self.client.close()
-            raise TimeoutException(
-                "Connection timed out", status=ErrorCodes.TIMEOUT)
+            raise errors.TimeoutException(
+                "Connection timed out", status=errors.ErrorCodes.TIMEOUT)
 
         # Process any emulator commands that are sent from a script
         # while it's executing.
         while True:
             if response.get("emulator_cmd"):
                 response = self._handle_emulator_cmd(response)
                 continue;
 
@@ -641,97 +610,97 @@ class Marionette(object):
 
         if response_key in response:
             return response[response_key]
         self._handle_error(response)
 
     def _handle_emulator_cmd(self, response):
         cmd = response.get("emulator_cmd")
         if not cmd or not self.emulator:
-            raise MarionetteException(
+            raise errors.MarionetteException(
                 "No emulator in this test to run command against")
         cmd = cmd.encode("ascii")
         result = self.emulator._run_telnet(cmd)
         return self.client.send({"name": "emulatorCmdResult",
                                  "id": response.get("id"),
                                  "result": result})
 
     def _handle_emulator_shell(self, response):
         args = response.get("emulator_shell")
         if not isinstance(args, list) or not self.emulator:
-            raise MarionetteException(
+            raise errors.MarionetteException(
                 "No emulator in this test to run shell command against")
-        result = self.emulator._run_shell(args)
+        buf = StringIO.StringIO()
+        self.emulator.dm.shell(args, buf)
+        result = str(buf.getvalue()[0:-1]).rstrip().splitlines()
+        buf.close()
         return self.client.send({"name": "emulatorCmdResult",
                                  "id": response.get("id"),
                                  "result": result})
 
     def _handle_error(self, response):
         if 'error' in response and isinstance(response['error'], dict):
             status = response['error'].get('status', 500)
             message = response['error'].get('message')
             stacktrace = response['error'].get('stacktrace')
             # status numbers come from
             # http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
-            if status == ErrorCodes.NO_SUCH_ELEMENT:
-                raise NoSuchElementException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.NO_SUCH_FRAME:
-                raise NoSuchFrameException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.STALE_ELEMENT_REFERENCE:
-                raise StaleElementException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.ELEMENT_NOT_VISIBLE:
-                raise ElementNotVisibleException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.INVALID_ELEMENT_STATE:
-                raise InvalidElementStateException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.UNKNOWN_ERROR:
-                raise MarionetteException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.ELEMENT_IS_NOT_SELECTABLE:
-                raise ElementNotSelectableException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.JAVASCRIPT_ERROR:
-                raise JavascriptException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.XPATH_LOOKUP_ERROR:
-                raise XPathLookupException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.TIMEOUT:
-                raise TimeoutException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.NO_SUCH_WINDOW:
-                raise NoSuchWindowException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.INVALID_COOKIE_DOMAIN:
-                raise InvalidCookieDomainException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.UNABLE_TO_SET_COOKIE:
-                raise UnableToSetCookieException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.NO_ALERT_OPEN:
-                raise NoAlertPresentException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.SCRIPT_TIMEOUT:
-                raise ScriptTimeoutException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.INVALID_SELECTOR \
-                 or status == ErrorCodes.INVALID_XPATH_SELECTOR \
-                 or status == ErrorCodes.INVALID_XPATH_SELECTOR_RETURN_TYPER:
-                raise InvalidSelectorException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.MOVE_TARGET_OUT_OF_BOUNDS:
-                raise MoveTargetOutOfBoundsException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.FRAME_SEND_NOT_INITIALIZED_ERROR:
-                raise FrameSendNotInitializedError(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.FRAME_SEND_FAILURE_ERROR:
-                raise FrameSendFailureError(message=message, status=status, stacktrace=stacktrace)
+            if status == errors.ErrorCodes.NO_SUCH_ELEMENT:
+                raise errors.NoSuchElementException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.NO_SUCH_FRAME:
+                raise errors.NoSuchFrameException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.STALE_ELEMENT_REFERENCE:
+                raise errors.StaleElementException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.ELEMENT_NOT_VISIBLE:
+                raise errors.ElementNotVisibleException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.INVALID_ELEMENT_STATE:
+                raise errors.InvalidElementStateException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.UNKNOWN_ERROR:
+                raise errors.MarionetteException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.ELEMENT_IS_NOT_SELECTABLE:
+                raise errors.ElementNotSelectableException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.JAVASCRIPT_ERROR:
+                raise errors.JavascriptException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.XPATH_LOOKUP_ERROR:
+                raise errors.XPathLookupException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.TIMEOUT:
+                raise errors.TimeoutException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.NO_SUCH_WINDOW:
+                raise errors.NoSuchWindowException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.INVALID_COOKIE_DOMAIN:
+                raise errors.InvalidCookieDomainException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.UNABLE_TO_SET_COOKIE:
+                raise errors.UnableToSetCookieException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.NO_ALERT_OPEN:
+                raise errors.NoAlertPresentException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.SCRIPT_TIMEOUT:
+                raise errors.ScriptTimeoutException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.INVALID_SELECTOR \
+                 or status == errors.ErrorCodes.INVALID_XPATH_SELECTOR \
+                 or status == errors.ErrorCodes.INVALID_XPATH_SELECTOR_RETURN_TYPER:
+                raise errors.InvalidSelectorException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.MOVE_TARGET_OUT_OF_BOUNDS:
+                raise errors.MoveTargetOutOfBoundsException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.FRAME_SEND_NOT_INITIALIZED_ERROR:
+                raise errors.FrameSendNotInitializedError(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.FRAME_SEND_FAILURE_ERROR:
+                raise errors.FrameSendFailureError(message=message, status=status, stacktrace=stacktrace)
             else:
-                raise MarionetteException(message=message, status=status, stacktrace=stacktrace)
-        raise MarionetteException(message=response, status=500)
+                raise errors.MarionetteException(message=message, status=status, stacktrace=stacktrace)
+        raise errors.MarionetteException(message=response, status=500)
 
     def check_for_crash(self):
         returncode = None
         name = None
         crashed = False
-        if self.emulator:
-            if self.emulator.check_for_crash():
+        if self.runner:
+            if self.runner.check_for_crashes():
                 returncode = self.emulator.proc.returncode
                 name = 'emulator'
                 crashed = True
-
-            if self.emulator.check_for_minidumps():
-                crashed = True
         elif self.instance:
             if self.instance.check_for_crashes():
                 crashed = True
         if returncode is not None:
             print ('PROCESS-CRASH | %s | abnormal termination with exit code %d' %
                 (name, returncode))
         return crashed
 
@@ -1454,9 +1423,9 @@ class Marionette(object):
         respectively, and "portrait-secondary" as well as
         "landscape-secondary".
 
         :param orientation: The orientation to lock the screen in.
 
         """
         self._send_message("setScreenOrientation", "ok", orientation=orientation)
         if self.emulator:
-            self.emulator.screen.orientation = self.SCREEN_ORIENTATIONS[orientation.lower()]
+            self.emulator.screen.orientation = orientation.lower()
--- a/testing/marionette/client/marionette/runner/base.py
+++ b/testing/marionette/client/marionette/runner/base.py
@@ -362,39 +362,39 @@ class BaseMarionetteOptions(OptionParser
                         dest='emulator',
                         choices=['x86', 'arm'],
                         help='if no --address is given, then the harness will launch a B2G emulator on which to run '
                              'emulator tests. if --address is given, then the harness assumes you are running an '
                              'emulator already, and will run the emulator tests using that emulator. you need to '
                              'specify which architecture to emulate for both cases')
         self.add_option('--emulator-binary',
                         action='store',
-                        dest='emulatorBinary',
+                        dest='emulator_binary',
                         help='launch a specific emulator binary rather than launching from the B2G built emulator')
         self.add_option('--emulator-img',
                         action='store',
-                        dest='emulatorImg',
+                        dest='emulator_img',
                         help='use a specific image file instead of a fresh one')
         self.add_option('--emulator-res',
                         action='store',
                         dest='emulator_res',
                         type='str',
                         help='set a custom resolution for the emulator'
                              'Example: "480x800"')
         self.add_option('--sdcard',
                         action='store',
                         dest='sdcard',
                         help='size of sdcard to create for the emulator')
         self.add_option('--no-window',
                         action='store_true',
-                        dest='noWindow',
+                        dest='no_window',
                         default=False,
                         help='when Marionette launches an emulator, start it with the -no-window argument')
         self.add_option('--logcat-dir',
-                        dest='logcat_dir',
+                        dest='logdir',
                         action='store',
                         help='directory to store logcat dump files')
         self.add_option('--address',
                         dest='address',
                         action='store',
                         help='host:port of running Gecko instance to connect to')
         self.add_option('--device',
                         dest='device_serial',
@@ -436,20 +436,16 @@ class BaseMarionetteOptions(OptionParser
                         action='store',
                         type=int,
                         default=0,
                         help='number of times to repeat the test(s)')
         self.add_option('-x', '--xml-output',
                         action='store',
                         dest='xml_output',
                         help='xml output')
-        self.add_option('--gecko-path',
-                        dest='gecko_path',
-                        action='store',
-                        help='path to b2g gecko binaries that should be installed on the device or emulator')
         self.add_option('--testvars',
                         dest='testvars',
                         action='store',
                         help='path to a json file with any test data required')
         self.add_option('--tree',
                         dest='tree',
                         action='store',
                         default='b2g',
@@ -520,18 +516,18 @@ class BaseMarionetteOptions(OptionParser
             print 'can\'t specify both --emulator and --binary'
             sys.exit(1)
 
         if not options.es_servers:
             options.es_servers = ['elasticsearch-zlb.dev.vlan81.phx.mozilla.com:9200',
                                   'elasticsearch-zlb.webapp.scl3.mozilla.com:9200']
 
         # default to storing logcat output for emulator runs
-        if options.emulator and not options.logcat_dir:
-            options.logcat_dir = 'logcat'
+        if options.emulator and not options.logdir:
+            options.logdir = 'logcat'
 
         # check for valid resolution string, strip whitespaces
         try:
             if options.emulator_res:
                 dims = options.emulator_res.split('x')
                 assert len(dims) == 2
                 width = str(int(dims[0]))
                 height = str(int(dims[1]))
@@ -557,48 +553,47 @@ class BaseMarionetteOptions(OptionParser
 
         return (options, tests)
 
 
 class BaseMarionetteTestRunner(object):
 
     textrunnerclass = MarionetteTextTestRunner
 
-    def __init__(self, address=None, emulator=None, emulatorBinary=None,
-                 emulatorImg=None, emulator_res='480x800', homedir=None,
+    def __init__(self, address=None, emulator=None, emulator_binary=None,
+                 emulator_img=None, emulator_res='480x800', homedir=None,
                  app=None, app_args=None, bin=None, profile=None, autolog=False,
-                 revision=None, logger=None, testgroup="marionette", noWindow=False,
-                 logcat_dir=None, xml_output=None, repeat=0, gecko_path=None,
+                 revision=None, logger=None, testgroup="marionette", no_window=False,
+                 logdir=None, xml_output=None, repeat=0,
                  testvars=None, tree=None, type=None, device_serial=None,
                  symbols_path=None, timeout=None, es_servers=None, shuffle=False,
                  shuffle_seed=random.randint(0, sys.maxint), sdcard=None,
                  this_chunk=1, total_chunks=1, sources=None, server_root=None,
                  gecko_log=None,
                  **kwargs):
         self.address = address
         self.emulator = emulator
-        self.emulatorBinary = emulatorBinary
-        self.emulatorImg = emulatorImg
+        self.emulator_binary = emulator_binary
+        self.emulator_img = emulator_img
         self.emulator_res = emulator_res
         self.homedir = homedir
         self.app = app
         self.app_args = app_args or []
         self.bin = bin
         self.profile = profile
         self.autolog = autolog
         self.testgroup = testgroup
         self.revision = revision
         self.logger = logger
-        self.noWindow = noWindow
+        self.no_window = no_window
         self.httpd = None
         self.marionette = None
-        self.logcat_dir = logcat_dir
+        self.logdir = logdir
         self.xml_output = xml_output
         self.repeat = repeat
-        self.gecko_path = gecko_path
         self.testvars = {}
         self.test_kwargs = kwargs
         self.tree = tree
         self.type = type
         self.device_serial = device_serial
         self.symbols_path = symbols_path
         self.timeout = timeout
         self._device = None
@@ -635,19 +630,19 @@ class BaseMarionetteTestRunner(object):
 
         self.reset_test_stats()
 
         if self.logger is None:
             self.logger = logging.getLogger('Marionette')
             self.logger.setLevel(logging.INFO)
             self.logger.addHandler(logging.StreamHandler())
 
-        if self.logcat_dir:
-            if not os.access(self.logcat_dir, os.F_OK):
-                os.mkdir(self.logcat_dir)
+        if self.logdir:
+            if not os.access(self.logdir, os.F_OK):
+                os.mkdir(self.logdir)
 
         # for XML output
         self.testvars['xml_output'] = self.xml_output
         self.results = []
 
     @property
     def capabilities(self):
         if self._capabilities:
@@ -712,18 +707,17 @@ class BaseMarionetteTestRunner(object):
                 'bin': self.bin,
                 'profile': self.profile,
                 'gecko_log': self.gecko_log,
             })
 
         if self.emulator:
             kwargs.update({
                 'homedir': self.homedir,
-                'logcat_dir': self.logcat_dir,
-                'gecko_path': self.gecko_path,
+                'logdir': self.logdir,
             })
 
         if self.address:
             host, port = self.address.split(':')
             kwargs.update({
                 'host': host,
                 'port': int(port),
             })
@@ -736,33 +730,33 @@ class BaseMarionetteTestRunner(object):
                     connection = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
                     connection.connect((host,int(port)))
                     connection.close()
                 except Exception, e:
                     raise Exception("Connection attempt to %s:%s failed with error: %s" %(host,port,e))
         elif self.emulator:
             kwargs.update({
                 'emulator': self.emulator,
-                'emulatorBinary': self.emulatorBinary,
-                'emulatorImg': self.emulatorImg,
+                'emulator_binary': self.emulator_binary,
+                'emulator_img': self.emulator_img,
                 'emulator_res': self.emulator_res,
-                'noWindow': self.noWindow,
+                'no_window': self.no_window,
                 'sdcard': self.sdcard,
             })
         return kwargs
 
     def start_marionette(self):
         self.marionette = Marionette(**self._build_kwargs())
 
     def post_to_autolog(self, elapsedtime):
         self.logger.info('posting results to autolog')
 
         logfile = None
         if self.emulator:
-            filename = os.path.join(os.path.abspath(self.logcat_dir),
+            filename = os.path.join(os.path.abspath(self.logdir),
                                     "emulator-%d.log" % self.marionette.emulator.port)
             if os.access(filename, os.F_OK):
                 logfile = filename
 
         for es_server in self.es_servers:
 
             # This is all autolog stuff.
             # See: https://wiki.mozilla.org/Auto-tools/Projects/Autolog
--- a/testing/marionette/client/marionette/runner/mixins/b2g.py
+++ b/testing/marionette/client/marionette/runner/mixins/b2g.py
@@ -4,24 +4,20 @@
 
 import mozdevice
 import os
 import re
 
 
 def get_dm(marionette=None,**kwargs):
     dm_type = os.environ.get('DM_TRANS', 'adb')
-    if marionette and marionette.emulator:
-        adb_path = marionette.emulator.b2g.adb_path
-        return mozdevice.DeviceManagerADB(adbPath=adb_path,
-                                          deviceSerial='emulator-%d' % marionette.emulator.port,
-                                          **kwargs)
+    if marionette and hasattr(marionette.runner, 'device'):
+        return marionette.runner.app_ctx.dm
     elif marionette and marionette.device_serial and dm_type == 'adb':
-        return mozdevice.DeviceManagerADB(deviceSerial=marionette.device_serial,
-                                          **kwargs)
+        return mozdevice.DeviceManagerADB(deviceSerial=marionette.device_serial, **kwargs)
     else:
         if dm_type == 'adb':
             return mozdevice.DeviceManagerADB(**kwargs)
         elif dm_type == 'sut':
             host = os.environ.get('TEST_DEVICE')
             if not host:
                 raise Exception('Must specify host with SUT!')
             return mozdevice.DeviceManagerSUT(host=host)
--- a/testing/marionette/client/marionette/tests/unit/test_clearing.py
+++ b/testing/marionette/client/marionette/tests/unit/test_clearing.py
@@ -1,14 +1,14 @@
 # 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 marionette_test import MarionetteTestCase
-from marionette import InvalidElementStateException
+from errors import InvalidElementStateException
 
 class TestClear(MarionetteTestCase):
     def testWriteableTextInputShouldClear(self):
         test_html = self.marionette.absolute_url("test_clearing.html")
         self.marionette.navigate(test_html)
         element = self.marionette.find_element("id", "writableTextInput")
         element.clear()
         self.assertEqual("", element.get_attribute("value"))
--- a/testing/marionette/client/marionette/tests/unit/test_element_touch.py
+++ b/testing/marionette/client/marionette/tests/unit/test_element_touch.py
@@ -1,14 +1,14 @@
 # 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 marionette_test import MarionetteTestCase
-from marionette import MarionetteException
+from errors import MarionetteException
 
 class testElementTouch(MarionetteTestCase):
     def test_touch(self):
       testAction = self.marionette.absolute_url("testAction.html")
       self.marionette.navigate(testAction)
       button = self.marionette.find_element("id", "button1")
       button.tap()
       expected = "button1-touchstart-touchend-mousemove-mousedown-mouseup-click"
--- a/testing/marionette/client/marionette/tests/unit/test_emulator.py
+++ b/testing/marionette/client/marionette/tests/unit/test_emulator.py
@@ -1,30 +1,30 @@
 # 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 marionette_test import MarionetteTestCase
-from marionette import JavascriptException, MarionetteException
+from errors import JavascriptException, MarionetteException
 
 
 class TestEmulatorContent(MarionetteTestCase):
 
     def test_emulator_cmd(self):
         self.marionette.set_script_timeout(10000)
         expected = ["<build>",
                     "OK"]
         result = self.marionette.execute_async_script("""
         runEmulatorCmd("avd name", marionetteScriptFinished)
         """);
         self.assertEqual(result, expected)
 
     def test_emulator_shell(self):
         self.marionette.set_script_timeout(10000)
-        expected = ["Hello World!", ""]
+        expected = ["Hello World!"]
         result = self.marionette.execute_async_script("""
         runEmulatorShell(["echo", "Hello World!"], marionetteScriptFinished)
         """);
         self.assertEqual(result, expected)
 
     def test_emulator_order(self):
         self.marionette.set_script_timeout(10000)
         self.assertRaises(MarionetteException,
--- a/testing/marionette/client/marionette/tests/unit/test_execute_async_script.py
+++ b/testing/marionette/client/marionette/tests/unit/test_execute_async_script.py
@@ -1,14 +1,14 @@
 # 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 marionette_test import MarionetteTestCase
-from marionette import JavascriptException, MarionetteException, ScriptTimeoutException
+from errors import JavascriptException, MarionetteException, ScriptTimeoutException
 import time
 
 
 class TestExecuteAsyncContent(MarionetteTestCase):
     def setUp(self):
         super(TestExecuteAsyncContent, self).setUp()
         self.marionette.set_script_timeout(1000)
 
--- a/testing/marionette/client/marionette/tests/unit/test_execute_isolate.py
+++ b/testing/marionette/client/marionette/tests/unit/test_execute_isolate.py
@@ -1,14 +1,14 @@
 # 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 marionette_test import MarionetteTestCase, skip_if_b2g
-from marionette import JavascriptException, MarionetteException, ScriptTimeoutException
+from errors import JavascriptException, MarionetteException, ScriptTimeoutException
 
 class TestExecuteIsolationContent(MarionetteTestCase):
     def setUp(self):
         super(TestExecuteIsolationContent, self).setUp()
         self.content = True
 
     def test_execute_async_isolate(self):
         # Results from one execute call that has timed out should not
--- a/testing/marionette/client/marionette/tests/unit/test_execute_script.py
+++ b/testing/marionette/client/marionette/tests/unit/test_execute_script.py
@@ -1,16 +1,16 @@
 # 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 urllib
 
 from by import By
-from marionette import JavascriptException, MarionetteException
+from errors import JavascriptException, MarionetteException
 from marionette_test import MarionetteTestCase
 
 def inline(doc):
     return "data:text/html;charset=utf-8,%s" % urllib.quote(doc)
 
 elements = inline("<p>foo</p> <p>bar</p>")
 
 class TestExecuteContent(MarionetteTestCase):
--- a/testing/marionette/client/marionette/tests/unit/test_findelement.py
+++ b/testing/marionette/client/marionette/tests/unit/test_findelement.py
@@ -1,16 +1,16 @@
 # 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 marionette_test import MarionetteTestCase
 from marionette import HTMLElement
 from by import By
-from marionette import NoSuchElementException
+from errors import NoSuchElementException
 
 
 class TestElements(MarionetteTestCase):
     def test_id(self):
         test_html = self.marionette.absolute_url("test.html")
         self.marionette.navigate(test_html)
         el = self.marionette.execute_script("return window.document.getElementById('mozLink');")
         found_el = self.marionette.find_element(By.ID, "mozLink")
--- a/testing/marionette/client/marionette/tests/unit/test_findelement_chrome.py
+++ b/testing/marionette/client/marionette/tests/unit/test_findelement_chrome.py
@@ -1,16 +1,16 @@
 # 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 marionette_test import MarionetteTestCase
 from marionette import HTMLElement
 from by import By
-from marionette import NoSuchElementException
+from errors import NoSuchElementException
 
 
 class TestElementsChrome(MarionetteTestCase):
     def setUp(self):
         MarionetteTestCase.setUp(self)
         self.marionette.set_context("chrome")
         self.win = self.marionette.current_window_handle
         self.marionette.execute_script("window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen');")
--- a/testing/marionette/client/marionette/tests/unit/test_implicit_waits.py
+++ b/testing/marionette/client/marionette/tests/unit/test_implicit_waits.py
@@ -1,14 +1,14 @@
 # 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 marionette_test import MarionetteTestCase
-from marionette import NoSuchElementException
+from errors import NoSuchElementException
 
 class TestImplicitWaits(MarionetteTestCase):
     def testShouldImplicitlyWaitForASingleElement(self):
         test_html = self.marionette.absolute_url("test_dynamic.html")
         self.marionette.navigate(test_html)
         add = self.marionette.find_element("id", "adder")
         self.marionette.set_search_timeout("3000")
         add.click()
--- a/testing/marionette/client/marionette/tests/unit/test_navigation.py
+++ b/testing/marionette/client/marionette/tests/unit/test_navigation.py
@@ -1,15 +1,14 @@
 # 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 marionette_test import MarionetteTestCase
-from marionette import MarionetteException
-from marionette import TimeoutException
+from errors import MarionetteException, TimeoutException
 
 class TestNavigate(MarionetteTestCase):
     def test_navigate(self):
         self.assertTrue(self.marionette.execute_script("window.location.href = 'about:blank'; return true;"))
         self.assertEqual("about:blank", self.marionette.execute_script("return window.location.href;"))
         test_html = self.marionette.absolute_url("test.html")
         self.marionette.navigate(test_html)
         self.assertNotEqual("about:blank", self.marionette.execute_script("return window.location.href;"))
--- a/testing/marionette/client/marionette/tests/unit/test_screen_orientation.py
+++ b/testing/marionette/client/marionette/tests/unit/test_screen_orientation.py
@@ -1,17 +1,17 @@
 # -*- fill-column: 100; comment-column: 100; -*-
 
 # 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 emulator_screen import EmulatorScreen
-from marionette import MarionetteException
+from errors import MarionetteException
 from marionette_test import MarionetteTestCase
+from mozrunner.devices.emulator_screen import EmulatorScreen
 
 default_orientation = "portrait-primary"
 unknown_orientation = "Unknown screen orientation: %s"
 
 class TestScreenOrientation(MarionetteTestCase):
     def tearDown(self):
         self.marionette.set_orientation(default_orientation)
         self.assertEqual(self.marionette.orientation, default_orientation, "invalid state")
--- a/testing/marionette/client/marionette/tests/unit/test_simpletest_sanity.py
+++ b/testing/marionette/client/marionette/tests/unit/test_simpletest_sanity.py
@@ -1,14 +1,14 @@
 # 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 marionette_test import MarionetteTestCase
-from marionette import JavascriptException, MarionetteException, ScriptTimeoutException
+from errors import JavascriptException, MarionetteException, ScriptTimeoutException
 
 class SimpletestSanityTest(MarionetteTestCase):
 
     callFinish = "return finish();"
 
     def test_is(self):
         def runtests():
             sentFail1 = "is(true, false, 'isTest1', TEST_UNEXPECTED_FAIL, TEST_PASS);" + self.callFinish
--- a/testing/marionette/client/marionette/tests/unit/test_single_finger.py
+++ b/testing/marionette/client/marionette/tests/unit/test_single_finger.py
@@ -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/.
 
 from marionette_test import MarionetteTestCase
 from marionette import Actions
-from marionette import MarionetteException
+from errors import MarionetteException
 #add this directory to the path
 import os
 import sys
 sys.path.append(os.path.dirname(__file__))
 from single_finger_functions import (
         chain, chain_flick, context_menu, double_tap,
         long_press_action, long_press_on_xy_action,
         move_element, move_element_offset, press_release, single_tap, wait,
--- a/testing/marionette/client/marionette/tests/unit/test_single_finger_desktop.py
+++ b/testing/marionette/client/marionette/tests/unit/test_single_finger_desktop.py
@@ -1,11 +1,11 @@
 from marionette_test import MarionetteTestCase
 from marionette import Actions
-from marionette import MarionetteException
+from errors import MarionetteException
 #add this directory to the path
 import os
 import sys
 sys.path.append(os.path.dirname(__file__))
 from single_finger_functions import (
         chain, chain_flick, context_menu, double_tap,
         long_press_action, long_press_on_xy_action,
         move_element, move_element_offset, press_release, single_tap, wait,
--- a/testing/marionette/client/marionette/tests/unit/test_specialpowers.py
+++ b/testing/marionette/client/marionette/tests/unit/test_specialpowers.py
@@ -1,14 +1,14 @@
 # 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 marionette_test import MarionetteTestCase
-from marionette import JavascriptException, MarionetteException
+from errors import JavascriptException, MarionetteException
 
 class TestSpecialPowersContent(MarionetteTestCase):
 
     testpref = "testing.marionette.contentcharpref"
     testvalue = "blabla"
 
     def test_prefs(self):
         result = self.marionette.execute_script("""
--- a/testing/marionette/client/marionette/tests/unit/test_switch_frame.py
+++ b/testing/marionette/client/marionette/tests/unit/test_switch_frame.py
@@ -1,14 +1,14 @@
 # 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 marionette_test import MarionetteTestCase
-from marionette import JavascriptException
+from errors import JavascriptException
 
 
 class TestSwitchFrame(MarionetteTestCase):
     def test_switch_simple(self):
         start_url = "test_iframe.html"
         verify_title = "Marionette IFrame Test"
         verify_url = "test.html"
         test_html = self.marionette.absolute_url(start_url)
--- a/testing/marionette/client/marionette/tests/unit/test_switch_frame_chrome.py
+++ b/testing/marionette/client/marionette/tests/unit/test_switch_frame_chrome.py
@@ -1,14 +1,14 @@
 # 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 marionette_test import MarionetteTestCase
-from marionette import JavascriptException
+from errors import JavascriptException
 
 class TestSwitchFrameChrome(MarionetteTestCase):
     def setUp(self):
         MarionetteTestCase.setUp(self)
         self.marionette.set_context("chrome")
         self.win = self.marionette.current_window_handle
         self.marionette.execute_script("window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen');")
         self.marionette.switch_to_window('foo')
--- a/testing/marionette/client/marionette/tests/unit/test_timeouts.py
+++ b/testing/marionette/client/marionette/tests/unit/test_timeouts.py
@@ -1,16 +1,16 @@
 # 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
 from marionette_test import MarionetteTestCase
 from marionette import HTMLElement
-from marionette import NoSuchElementException, JavascriptException, MarionetteException, ScriptTimeoutException
+from errors import NoSuchElementException, JavascriptException, MarionetteException, ScriptTimeoutException
 
 class TestTimeouts(MarionetteTestCase):
     def test_pagetimeout_notdefinetimeout_pass(self):
         test_html = self.marionette.absolute_url("test.html")
         self.marionette.navigate(test_html)
 
     def test_pagetimeout_fail(self):
         self.marionette.timeouts("page load", 0)
--- a/testing/marionette/client/marionette/tests/unit/test_typing.py
+++ b/testing/marionette/client/marionette/tests/unit/test_typing.py
@@ -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/.
 
 from marionette_test import MarionetteTestCase
 from keys import Keys
-from marionette import ElementNotVisibleException
+from errors import ElementNotVisibleException
 
 
 class TestTyping(MarionetteTestCase):
 
     def testShouldFireKeyPressEvents(self):
         test_html = self.marionette.absolute_url("javascriptPage.html")
         self.marionette.navigate(test_html)
         keyReporter = self.marionette.find_element("id", "keyReporter")
--- a/testing/marionette/client/requirements.txt
+++ b/testing/marionette/client/requirements.txt
@@ -1,12 +1,12 @@
 marionette-transport == 0.2
 manifestparser
 mozhttpd >= 0.5
 mozinfo >= 0.7
 mozprocess >= 0.9
-mozrunner >= 5.15
-mozdevice >= 0.22
+mozrunner >= 6.0
+mozdevice >= 0.37
 moznetwork >= 0.21
 mozcrash >= 0.5
 mozprofile >= 0.7
 moztest >= 0.1
 mozversion >= 0.2
--- a/testing/mochitest/mach_commands.py
+++ b/testing/mochitest/mach_commands.py
@@ -173,17 +173,17 @@ class MochitestRunner(MozbuildObject):
         try:
             which.which('adb')
         except which.WhichError:
             # TODO Find adb automatically if it isn't on the path
             print(ADB_NOT_FOUND % ('mochitest-remote', b2g_home))
             return 1
 
         options.b2gPath = b2g_home
-        options.logcat_dir = self.mochitest_dir
+        options.logdir = self.mochitest_dir
         options.httpdPath = self.mochitest_dir
         options.xrePath = xre_path
         return mochitest.run_remote_mochitests(parser, options)
 
     def run_desktop_test(self, context, suite=None, test_paths=None, debugger=None,
         debugger_args=None, slowscript=False, screenshot_on_fail = False, shuffle=False, keep_open=False,
         rerun_failures=False, no_autorun=False, repeat=0, run_until_failure=False,
         slow=False, chunk_by_dir=0, total_chunks=None, this_chunk=None,
@@ -550,19 +550,19 @@ def MochitestCommand(func):
 
 def B2GCommand(func):
     """Decorator that adds shared command arguments to b2g mochitest commands."""
 
     busybox = CommandArgument('--busybox', default=None,
         help='Path to busybox binary to install on device')
     func = busybox(func)
 
-    logcatdir = CommandArgument('--logcat-dir', default=None,
-        help='directory to store logcat dump files')
-    func = logcatdir(func)
+    logdir = CommandArgument('--logdir', default=None,
+        help='directory to store log files')
+    func = logdir(func)
 
     profile = CommandArgument('--profile', default=None,
         help='for desktop testing, the path to the \
               gaia profile to use')
     func = profile(func)
 
     geckopath = CommandArgument('--gecko-path', default=None,
         help='the path to a gecko distribution that should \
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -700,21 +700,21 @@ class B2GOptions(MochitestOptions):
         [["--profile"],
         { "action": "store",
           "type": "string",
           "dest": "profile",
           "help": "for desktop testing, the path to the \
                    gaia profile to use",
           "default": None,
         }],
-        [["--logcat-dir"],
+        [["--logdir"],
         { "action": "store",
           "type": "string",
-          "dest": "logcat_dir",
-          "help": "directory to store logcat dump files",
+          "dest": "logdir",
+          "help": "directory to store log files",
           "default": None,
         }],
         [['--busybox'],
         { "action": 'store',
           "type": 'string',
           "dest": 'busybox',
           "help": "Path to busybox binary to install on device",
           "default": None,
@@ -733,17 +733,16 @@ class B2GOptions(MochitestOptions):
         MochitestOptions.__init__(self)
 
         for option in self.b2g_options:
             self.add_option(*option[0], **option[1])
 
         defaults = {}
         defaults["httpPort"] = DEFAULT_PORTS['http']
         defaults["sslPort"] = DEFAULT_PORTS['https']
-        defaults["remoteTestRoot"] = "/data/local/tests"
         defaults["logFile"] = "mochitest.log"
         defaults["autorun"] = True
         defaults["closeWhenDone"] = True
         defaults["testPath"] = ""
         defaults["extensionsToExclude"] = ["specialpowers"]
         self.set_defaults(**defaults)
 
     def verifyRemoteOptions(self, options):
@@ -752,18 +751,18 @@ class B2GOptions(MochitestOptions):
                 options.remoteWebServer = moznetwork.get_ip()
             else:
                 self.error("You must specify a --remote-webserver=<ip address>")
         options.webServer = options.remoteWebServer
 
         if options.geckoPath and not options.emulator:
             self.error("You must specify --emulator if you specify --gecko-path")
 
-        if options.logcat_dir and not options.emulator:
-            self.error("You must specify --emulator if you specify --logcat-dir")
+        if options.logdir and not options.emulator:
+            self.error("You must specify --emulator if you specify --logdir")
 
         if not os.path.isdir(options.xrePath):
             self.error("--xre-path '%s' is not a directory" % options.xrePath)
         xpcshell = os.path.join(options.xrePath, 'xpcshell')
         if not os.access(xpcshell, os.F_OK):
             self.error('xpcshell not found at %s' % xpcshell)
         if self.elf_arm(xpcshell):
             self.error('--xre-path points to an ARM version of xpcshell; it '
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -1253,33 +1253,28 @@ class Mochitest(MochitestUtilsMixin):
                    'cwd': SCRIPT_DIR,
                    'onTimeout': [timeoutHandler]}
       kp_kwargs['processOutputLine'] = [outputHandler]
 
       # create mozrunner instance and start the system under test process
       self.lastTestSeen = self.test_name
       startTime = datetime.now()
 
-      # b2g desktop requires FirefoxRunner even though appname is b2g
+      # b2g desktop requires Runner even though appname is b2g
       if mozinfo.info.get('appname') == 'b2g' and mozinfo.info.get('toolkit') != 'gonk':
-          runner_cls = mozrunner.FirefoxRunner
+          runner_cls = mozrunner.Runner
       else:
           runner_cls = mozrunner.runners.get(mozinfo.info.get('appname', 'firefox'),
                                              mozrunner.Runner)
       runner = runner_cls(profile=self.profile,
                           binary=cmd,
                           cmdargs=args,
                           env=env,
                           process_class=mozprocess.ProcessHandlerMixin,
-                          kp_kwargs=kp_kwargs,
-                          )
-
-      # XXX work around bug 898379 until mozrunner is updated for m-c; see
-      # https://bugzilla.mozilla.org/show_bug.cgi?id=746243#c49
-      runner.kp_kwargs = kp_kwargs
+                          process_args=kp_kwargs)
 
       # start the runner
       runner.start(debug_args=debug_args,
                    interactive=interactive,
                    outputTimeout=timeout)
       proc = runner.process_handler
       log.info("INFO | runtests.py | Application pid: %d", proc.pid)
 
--- a/testing/mochitest/runtestsb2g.py
+++ b/testing/mochitest/runtestsb2g.py
@@ -4,46 +4,44 @@
 
 import json
 import os
 import posixpath
 import shutil
 import sys
 import tempfile
 import threading
-import time
 import traceback
 
 here = os.path.abspath(os.path.dirname(__file__))
 sys.path.insert(0, here)
 
 from runtests import Mochitest
 from runtests import MochitestUtilsMixin
-from runtests import MochitestOptions
 from runtests import MochitestServer
 from mochitest_options import B2GOptions, MochitestOptions
 
 from marionette import Marionette
 
 from mozdevice import DeviceManagerADB
 from mozprofile import Profile, Preferences
-from mozrunner import B2GRunner
 import mozlog
 import mozinfo
-import moznetwork
 
 log = mozlog.getLogger('Mochitest')
 
 class B2GMochitest(MochitestUtilsMixin):
-    def __init__(self, marionette,
+    marionette = None
+
+    def __init__(self, marionette_args,
                        out_of_process=True,
                        profile_data_dir=None,
                        locations=os.path.join(here, 'server-locations.txt')):
         super(B2GMochitest, self).__init__()
-        self.marionette = marionette
+        self.marionette_args = marionette_args
         self.out_of_process = out_of_process
         self.locations_file = locations
         self.preferences = []
         self.webapps = None
         self.test_script = os.path.join(here, 'b2g_start_script.js')
         self.test_script_args = [self.out_of_process]
         self.product = 'b2g'
 
@@ -116,97 +114,117 @@ class B2GMochitest(MochitestUtilsMixin):
         return manifest
 
     def run_tests(self, options):
         """ Prepare, configure, run tests and cleanup """
 
         self.leak_report_file = os.path.join(options.profilePath, "runtests_leaks.log")
         manifest = self.build_profile(options)
 
-        self.startServers(options, None)
-        self.buildURLOptions(options, {'MOZ_HIDE_RESULTS_TABLE': '1'})
-        self.test_script_args.append(not options.emulator)
-        self.test_script_args.append(options.wifi)
-
         if options.debugger or not options.autorun:
             timeout = None
         else:
             if not options.timeout:
                 if mozinfo.info['debug']:
                     options.timeout = 420
                 else:
                     options.timeout = 300
             timeout = options.timeout + 30.0
 
         log.info("runtestsb2g.py | Running tests: start.")
         status = 0
         try:
-            runner_args = { 'profile': self.profile,
-                            'devicemanager': self._dm,
-                            'marionette': self.marionette,
-                            'remote_test_root': self.remote_test_root,
-                            'symbols_path': options.symbolsPath,
-                            'test_script': self.test_script,
-                            'test_script_args': self.test_script_args }
-            self.runner = B2GRunner(**runner_args)
+            self.marionette_args['profile'] = self.profile
+            self.marionette = Marionette(**self.marionette_args)
+            self.runner = self.marionette.runner
+            self.app_ctx = self.runner.app_ctx
+
+            self.remote_log = posixpath.join(self.app_ctx.remote_test_root,
+                                             'log', 'mochitest.log')
+            if not self.app_ctx.dm.dirExists(posixpath.dirname(self.remote_log)):
+                self.app_ctx.dm.mkDirs(self.remote_log)
+
+            self.startServers(options, None)
+            self.buildURLOptions(options, {'MOZ_HIDE_RESULTS_TABLE': '1'})
+            self.test_script_args.append(not options.emulator)
+            self.test_script_args.append(options.wifi)
+
+
             self.runner.start(outputTimeout=timeout)
+
+            self.marionette.wait_for_port()
+            self.marionette.start_session()
+            self.marionette.set_context(self.marionette.CONTEXT_CHROME)
+
+            # Disable offline status management (bug 777145), otherwise the network
+            # will be 'offline' when the mochitests start.  Presumably, the network
+            # won't be offline on a real device, so we only do this for emulators.
+            self.marionette.execute_script("""
+                Components.utils.import("resource://gre/modules/Services.jsm");
+                Services.io.manageOfflineStatus = false;
+                Services.io.offline = false;
+                """)
+
+            if os.path.isfile(self.test_script):
+                with open(self.test_script, 'r') as script:
+                    self.marionette.execute_script(script.read(),
+                                                   script_args=self.test_script_args)
+            else:
+                self.marionette.execute_script(self.test_script,
+                                               script_args=self.test_script_args)
             status = self.runner.wait()
             if status is None:
                 # the runner has timed out
                 status = 124
         except KeyboardInterrupt:
             log.info("runtests.py | Received keyboard interrupt.\n");
             status = -1
         except:
             traceback.print_exc()
             log.error("Automation Error: Received unexpected exception while running application\n")
-            self.runner.check_for_crashes()
+            if hasattr(self, 'runner'):
+                self.runner.check_for_crashes()
             status = 1
 
         self.stopServers()
 
         log.info("runtestsb2g.py | Running tests: end.")
 
         if manifest is not None:
             self.cleanup(manifest, options)
         return status
 
 
 class B2GDeviceMochitest(B2GMochitest, Mochitest):
+    remote_log = None
 
-    _dm = None
-
-    def __init__(self, marionette, devicemanager, profile_data_dir,
+    def __init__(self, marionette_args, profile_data_dir,
                  local_binary_dir, remote_test_root=None, remote_log_file=None):
-        B2GMochitest.__init__(self, marionette, out_of_process=True, profile_data_dir=profile_data_dir)
-        Mochitest.__init__(self)
-        self._dm = devicemanager
-        self.remote_test_root = remote_test_root or self._dm.getDeviceRoot()
-        self.remote_profile = posixpath.join(self.remote_test_root, 'profile')
-        self.remote_log = remote_log_file or posixpath.join(self.remote_test_root, 'log', 'mochitest.log')
+        B2GMochitest.__init__(self, marionette_args, out_of_process=True, profile_data_dir=profile_data_dir)
         self.local_log = None
         self.local_binary_dir = local_binary_dir
 
-        if not self._dm.dirExists(posixpath.dirname(self.remote_log)):
-            self._dm.mkDirs(self.remote_log)
-
     def cleanup(self, manifest, options):
         if self.local_log:
-            self._dm.getFile(self.remote_log, self.local_log)
-            self._dm.removeFile(self.remote_log)
+            self.app_ctx.dm.getFile(self.remote_log, self.local_log)
+            self.app_ctx.dm.removeFile(self.remote_log)
 
         if options.pidFile != "":
             try:
                 os.remove(options.pidFile)
                 os.remove(options.pidFile + ".xpcshell.pid")
             except:
                 print "Warning: cleaning up pidfile '%s' was unsuccessful from the test harness" % options.pidFile
 
         # stop and clean up the runner
         if getattr(self, 'runner', False):
+            if self.local_log:
+                self.app_ctx.dm.getFile(self.remote_log, self.local_log)
+                self.app_ctx.dm.removeFile(self.remote_log)
+
             self.runner.cleanup()
             self.runner = None
 
     def startServers(self, options, debuggerInfo):
         """ Create the servers on the host and start them up """
         savedXre = options.xrePath
         savedUtility = options.utilityPath
         savedProfie = options.profilePath
@@ -223,24 +241,24 @@ class B2GDeviceMochitest(B2GMochitest, M
     def buildURLOptions(self, options, env):
         self.local_log = options.logFile
         options.logFile = self.remote_log
         options.profilePath = self.profile.profile
         super(B2GDeviceMochitest, self).buildURLOptions(options, env)
 
         self.setup_common_options(options)
 
-        options.profilePath = self.remote_profile
+        options.profilePath = self.app_ctx.remote_profile
         options.logFile = self.local_log
 
 
 class B2GDesktopMochitest(B2GMochitest, Mochitest):
 
-    def __init__(self, marionette, profile_data_dir):
-        B2GMochitest.__init__(self, marionette, out_of_process=False, profile_data_dir=profile_data_dir)
+    def __init__(self, marionette_args, profile_data_dir):
+        B2GMochitest.__init__(self, marionette_args, out_of_process=False, profile_data_dir=profile_data_dir)
         Mochitest.__init__(self)
         self.certdbNew = True
 
     def runMarionetteScript(self, marionette, test_script, test_script_args):
         assert(marionette.wait_for_port())
         marionette.start_session()
         marionette.set_context(marionette.CONTEXT_CHROME)
 
@@ -250,16 +268,17 @@ class B2GDesktopMochitest(B2GMochitest, 
             f.close()
         self.marionette.execute_script(test_script,
                                        script_args=test_script_args)
 
     def startTests(self):
         # This is run in a separate thread because otherwise, the app's
         # stdout buffer gets filled (which gets drained only after this
         # function returns, by waitForFinish), which causes the app to hang.
+        self.marionette = Marionette(**self.marionette_args)
         thread = threading.Thread(target=self.runMarionetteScript,
                                   args=(self.marionette,
                                         self.test_script,
                                         self.test_script_args))
         thread.start()
 
     def buildURLOptions(self, options, env):
         super(B2GDesktopMochitest, self).buildURLOptions(options, env)
@@ -277,59 +296,37 @@ class B2GDesktopMochitest(B2GMochitest, 
                             os.path.join(bundlesDir, filename))
 
     def buildProfile(self, options):
         return self.build_profile(options)
 
 
 def run_remote_mochitests(parser, options):
     # create our Marionette instance
-    kwargs = {}
-    if options.emulator:
-        kwargs['emulator'] = options.emulator
-        if options.noWindow:
-            kwargs['noWindow'] = True
-        if options.geckoPath:
-            kwargs['gecko_path'] = options.geckoPath
-        if options.logcat_dir:
-            kwargs['logcat_dir'] = options.logcat_dir
-        if options.busybox:
-            kwargs['busybox'] = options.busybox
-        if options.symbolsPath:
-            kwargs['symbols_path'] = options.symbolsPath
-    # needless to say sdcard is only valid if using an emulator
-    if options.sdcard:
-        kwargs['sdcard'] = options.sdcard
-    if options.b2gPath:
-        kwargs['homedir'] = options.b2gPath
+    marionette_args = {
+        'adb_path': options.adbPath,
+        'emulator': options.emulator,
+        'no_window': options.noWindow,
+        'logdir': options.logdir,
+        'busybox': options.busybox,
+        'symbols_path': options.symbolsPath,
+        'sdcard': options.sdcard,
+        'homedir': options.b2gPath,
+    }
     if options.marionette:
         host, port = options.marionette.split(':')
-        kwargs['host'] = host
-        kwargs['port'] = int(port)
-
-    marionette = Marionette.getMarionetteOrExit(**kwargs)
-
-    if options.emulator:
-        dm = marionette.emulator.dm
-    else:
-        # create the DeviceManager
-        kwargs = {'adbPath': options.adbPath,
-                  'deviceRoot': options.remoteTestRoot}
-        if options.deviceIP:
-            kwargs.update({'host': options.deviceIP,
-                           'port': options.devicePort})
-        dm = DeviceManagerADB(**kwargs)
+        marionette_args['host'] = host
+        marionette_args['port'] = int(port)
 
     options = parser.verifyRemoteOptions(options)
     if (options == None):
         print "ERROR: Invalid options specified, use --help for a list of valid options"
         sys.exit(1)
 
-    mochitest = B2GDeviceMochitest(marionette, dm, options.profile_data_dir, options.xrePath,
-                                   remote_test_root=options.remoteTestRoot,
+    mochitest = B2GDeviceMochitest(marionette_args, options.profile_data_dir, options.xrePath,
                                    remote_log_file=options.remoteLogFile)
 
     options = parser.verifyOptions(options, mochitest)
     if (options == None):
         sys.exit(1)
 
     retVal = 1
     try:
@@ -344,23 +341,22 @@ def run_remote_mochitests(parser, option
         except:
             pass
         retVal = 1
 
     sys.exit(retVal)
 
 def run_desktop_mochitests(parser, options):
     # create our Marionette instance
-    kwargs = {}
+    marionette_args = {}
     if options.marionette:
         host, port = options.marionette.split(':')
-        kwargs['host'] = host
-        kwargs['port'] = int(port)
-    marionette = Marionette.getMarionetteOrExit(**kwargs)
-    mochitest = B2GDesktopMochitest(marionette, options.profile_data_dir)
+        marionette_args['host'] = host
+        marionette_args['port'] = int(port)
+    mochitest = B2GDesktopMochitest(marionette_args, options.profile_data_dir)
 
     # add a -bin suffix if b2g-bin exists, but just b2g was specified
     if options.app[-4:] != '-bin':
         if os.path.isfile("%s-bin" % options.app):
             options.app = "%s-bin" % options.app
 
     options = MochitestOptions.verifyOptions(parser, options, mochitest)
     if options == None:
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/docs/mozrunner.rst
@@ -0,0 +1,177 @@
+:mod:`mozrunner` --- Manage remote and local gecko processes
+============================================================
+
+Mozrunner provides an API to manage a gecko-based application with an
+arbitrary configuration profile. It currently supports local desktop
+binaries such as Firefox and Thunderbird, as well as Firefox OS on
+mobile devices and emulators.
+
+
+Basic usage
+-----------
+
+The simplest way to use mozrunner, is to instantiate a runner, start it
+and then wait for it to finish:
+
+.. code-block:: python
+
+    from mozrunner import FirefoxRunner
+    binary = 'path/to/firefox/binary'
+    runner = FirefoxRunner(binary=binary)
+    runner.start()
+    runner.wait()
+
+This automatically creates and uses a default mozprofile object. If you
+wish to use a specialized or pre-existing profile, you can create a
+:doc:`mozprofile <mozprofile>` object and pass it in:
+
+.. code-block:: python
+
+    from mozprofile import Profile
+    from mozrunner import FirefoxRunner
+    import os
+
+    binary = 'path/to/firefox/binary'
+    profile_path = 'path/to/profile'
+    if os.path.exists(profile_path):
+        profile = Profile.clone(path_from=profile_path)
+    else:
+        profile = Profile(profile=profile_path)
+    runner = FirefoxRunner(binary=binary, profile=profile)
+    runner.start()
+    runner.wait()
+
+
+Handling output
+---------------
+
+By default, mozrunner dumps the output of the gecko process to standard output.
+It is possible to add arbitrary output handlers by passing them in via the
+`process_args` argument. Be careful, passing in a handler overrides the default
+behaviour. So if you want to use a handler in addition to dumping to stdout, you
+need to specify that explicitly. For example:
+
+.. code-block:: python
+
+    from mozrunner import FirefoxRunner
+
+    def handle_output_line(line):
+        do_something(line)
+
+    binary = 'path/to/firefox/binary'
+    process_args = { 'stream': sys.stdout,
+                     'processOutputLine': [handle_output_line] }
+    runner = FirefoxRunner(binary=binary, process_args=process_args)
+
+Mozrunner uses :doc:`mozprocess <mozprocess>` to manage the underlying gecko
+process and handle output. See the :doc:`mozprocess documentation <mozprocess>`
+for all available arguments accepted by `process_args`.
+
+
+Handling timeouts
+-----------------
+
+Sometimes gecko can hang, or maybe it is just taking too long. To handle this case you
+may want to set a timeout. Mozrunner has two kinds of timeouts, the
+traditional `timeout`, and the `outputTimeout`. These get passed into the
+`runner.start()` method. Setting `timeout` will cause gecko to be killed after
+the specified number of seconds, no matter what. Setting `outputTimeout` will cause
+gecko to be killed after the specified number of seconds with no output. In both
+cases the process handler's `onTimeout` callbacks will be triggered.
+
+.. code-block:: python
+
+    from mozrunner import FirefoxRunner
+
+    def on_timeout():
+        print('timed out after 10 seconds with no output!')
+
+    binary = 'path/to/firefox/binary'
+    process_args = { 'onTimeout': on_timeout }
+    runner = FirefoxRunner(binary=binary, process_args=process_args)
+    runner.start(outputTimeout=10)
+    runner.wait()
+
+The `runner.wait()` method also accepts a timeout argument. But unlike the arguments
+to `runner.start()`, this one simply returns from the wait call and does not kill the
+gecko process.
+
+.. code-block:: python
+
+    runner.start(timeout=100)
+
+    waiting = 0
+    while runner.wait(timeout=1) is None:
+        waiting += 1
+        print("Been waiting for %d seconds so far.." % waiting)
+    assert waiting <= 100
+
+
+Using a device runner
+---------------------
+
+The previous examples used a GeckoRuntimeRunner. If you want to control a
+gecko process on a remote device, you need to use a DeviceRunner. The api is
+nearly identical except you don't pass in a binary, instead you create a device
+object. For example, for B2G (Firefox OS) emulators you might do:
+
+.. code-block:: python
+
+    from mozrunner import B2GEmulatorRunner
+
+    b2g_home = 'path/to/B2G'
+    runner = B2GEmulatorRunner(arch='arm', b2g_home=b2g_home)
+    runner.start()
+    runner.wait()
+
+Device runners have a `device` object. Remember that the gecko process runs on
+the device. In the case of the emulator, it is possible to start the
+device independently of the gecko process.
+
+.. code-block:: python
+
+    runner.device.start() # launches the emulator (which also launches gecko)
+    runner.start()        # stops the gecko process, installs the profile, restarts the gecko process
+
+
+Runner API Documentation
+------------------------
+
+Application Runners
+~~~~~~~~~~~~~~~~~~~
+.. automodule:: mozrunner.runners
+   :members:
+
+BaseRunner
+~~~~~~~~~~
+.. autoclass:: mozrunner.base.BaseRunner
+   :members:
+
+GeckoRuntimeRunner
+~~~~~~~~~~~~~~~~~~
+.. autoclass:: mozrunner.base.GeckoRuntimeRunner
+   :show-inheritance:
+   :members:
+
+DeviceRunner
+~~~~~~~~~~~~
+.. autoclass:: mozrunner.base.DeviceRunner
+   :show-inheritance:
+   :members:
+
+Device API Documentation
+------------------------
+
+Generally using the device classes directly shouldn't be required, but in some
+cases it may be desirable.
+
+Device
+~~~~~~
+.. autoclass:: mozrunner.devices.Device
+   :members:
+
+Emulator
+~~~~~~~~
+.. autoclass:: mozrunner.devices.Emulator
+   :show-inheritance:
+   :members:
--- a/testing/mozbase/docs/setuprunning.rst
+++ b/testing/mozbase/docs/setuprunning.rst
@@ -7,9 +7,10 @@ controlled environment such that it can 
 correctly handling the case where the system crashes.
 
 .. toctree::
    :maxdepth: 2
 
    mozfile
    mozprofile
    mozprocess
+   mozrunner
    mozcrash
--- a/testing/mozbase/mozdevice/mozdevice/devicemanager.py
+++ b/testing/mozbase/mozdevice/mozdevice/devicemanager.py
@@ -1,15 +1,14 @@
 # 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 mozlog
-import socket
 import os
 import posixpath
 import re
 import struct
 import StringIO
 import zlib
 
 from Zeroconf import Zeroconf, ServiceBrowser
@@ -373,23 +372,25 @@ class DeviceManager(object):
         :param env: Environment to pass to exec command
         :param cwd: Directory to execute command from
         :param timeout: specified in seconds, defaults to 'default_timeout'
         :param root: Specifies whether command requires root privileges
         """
 
     def shellCheckOutput(self, cmd, env=None, cwd=None, timeout=None, root=False):
         """
-        Executes shell command on device and returns output as a string.
+        Executes shell command on device and returns output as a string. Raises if
+        the return code is non-zero.
 
         :param cmd: Commandline list to execute
         :param env: Environment to pass to exec command
         :param cwd: Directory to execute command from
         :param timeout: specified in seconds, defaults to 'default_timeout'
         :param root: Specifies whether command requires root privileges
+        :raises: DMError
         """
         buf = StringIO.StringIO()
         retval = self.shell(cmd, buf, env=env, cwd=cwd, timeout=timeout, root=root)
         output = str(buf.getvalue()[0:-1]).rstrip()
         buf.close()
         if retval != 0:
             raise DMError("Non-zero return code for command: %s (output: '%s', retval: '%s')" % (cmd, output, retval))
         return output
--- a/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py
+++ b/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py
@@ -24,21 +24,22 @@ class DeviceManagerADB(DeviceManager):
 
     _haveRootShell = False
     _haveSu = False
     _useZip = False
     _logcatNeedsRoot = False
     _pollingInterval = 0.01
     _packageName = None
     _tempDir = None
+    connected = False
     default_timeout = 300
 
     def __init__(self, host=None, port=5555, retryLimit=5, packageName='fennec',
                  adbPath='adb', deviceSerial=None, deviceRoot=None,
-                 logLevel=mozlog.ERROR, **kwargs):
+                 logLevel=mozlog.ERROR, autoconnect=True, **kwargs):
         DeviceManager.__init__(self, logLevel)
         self.host = host
         self.port = port
         self.retryLimit = retryLimit
         self.deviceRoot = deviceRoot
 
         # the path to adb, or 'adb' to assume that it's on the PATH
         self._adbPath = adbPath
@@ -53,38 +54,43 @@ class DeviceManagerADB(DeviceManager):
             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()
+        if autoconnect:
+            self.connect()
 
-        # verify that we can connect to the device. can't continue
-        self._verifyDevice()
+    def connect(self):
+        if not self.connected:
+            # try to connect to the device over tcp/ip if we have a hostname
+            if self.host:
+                self._connectRemoteADB()
 
-        # set up device root
-        self._setupDeviceRoot()
+            # verify that we can connect to the device. can't continue
+            self._verifyDevice()
 
-        # Some commands require root to work properly, even with ADB (e.g.
-        # grabbing APKs out of /data). For these cases, we check whether
-        # we're running as root. If that isn't true, check for the
-        # existence of an su binary
-        self._checkForRoot()
+            # set up device root
+            self._setupDeviceRoot()
 
-        # can we use zip to speed up some file operations? (currently not
-        # required)
-        try:
-            self._verifyZip()
-        except DMError:
-            pass
+            # Some commands require root to work properly, even with ADB (e.g.
+            # grabbing APKs out of /data). For these cases, we check whether
+            # we're running as root. If that isn't true, check for the
+            # existence of an su binary
+            self._checkForRoot()
+
+            # 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()
 
     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. :(
--- a/testing/mozbase/mozdevice/setup.py
+++ b/testing/mozbase/mozdevice/setup.py
@@ -1,16 +1,16 @@
 # 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 setuptools import setup
 
 PACKAGE_NAME = 'mozdevice'
-PACKAGE_VERSION = '0.36'
+PACKAGE_VERSION = '0.37'
 
 deps = ['mozfile >= 1.0',
         'mozlog',
         'moznetwork >= 0.24'
        ]
 
 setup(name=PACKAGE_NAME,
       version=PACKAGE_VERSION,
--- a/testing/mozbase/mozprocess/mozprocess/processhandler.py
+++ b/testing/mozbase/mozprocess/mozprocess/processhandler.py
@@ -688,17 +688,17 @@ falling back to not using job objects fo
             self.proc.kill(sig=sig)
 
             # When we kill the the managed process we also have to wait for the
             # outThread to be finished. Otherwise consumers would have to assume
             # that it still has not completely shutdown.
             return self.wait()
         except AttributeError:
             # Try to print a relevant error message.
-            if not self.proc:
+            if not hasattr(self, 'proc'):
                 print >> sys.stderr, "Unable to kill Process because call to ProcessHandler constructor failed."
             else:
                 raise
 
     def readWithTimeout(self, f, timeout):
         """
         Try to read a line of output from the file object *f*.
 
--- a/testing/mozbase/mozrunner/mozrunner/__init__.py
+++ b/testing/mozbase/mozrunner/mozrunner/__init__.py
@@ -1,11 +1,10 @@
 # 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 .cli import *
 from .errors import *
-from .local import *
-from .local import LocalRunner as Runner
-from .remote import *
+from .runners import *
 
-runners = local_runners
-runners.update(remote_runners)
+import base
+import devices
+import utils
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozrunner/mozrunner/application.py
@@ -0,0 +1,129 @@
+# 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 distutils.spawn import find_executable
+import glob
+import os
+import posixpath
+import sys
+
+from mozdevice import DeviceManagerADB
+from mozprofile import (
+    Profile,
+    FirefoxProfile,
+    MetroFirefoxProfile,
+    ThunderbirdProfile
+)
+
+here = os.path.abspath(os.path.dirname(__file__))
+
+def get_app_context(appname):
+    context_map = { 'default': DefaultContext,
+                    'b2g': B2GContext,
+                    'firefox': FirefoxContext,
+                    'thunderbird': ThunderbirdContext,
+                    'metro': MetroContext }
+    if appname not in context_map:
+        raise KeyError("Application '%s' not supported!" % appname)
+    return context_map[appname]
+
+
+class DefaultContext(object):
+    profile_class = Profile
+
+
+class B2GContext(object):
+    _bindir = None
+    _dm = None
+    _remote_profile = None
+    profile_class = Profile
+
+    def __init__(self, b2g_home=None, adb_path=None):
+        self.homedir = b2g_home or os.environ.get('B2G_HOME')
+        if not self.homedir:
+            raise EnvironmentError('Must define B2G_HOME or pass the b2g_home parameter')
+
+        if not os.path.isdir(self.homedir):
+            raise OSError('Homedir \'%s\' does not exist!' % self.homedir)
+
+        self._adb = adb_path
+        self.update_tools = os.path.join(self.homedir, 'tools', 'update-tools')
+        self.fastboot = self.which('fastboot')
+
+        self.remote_binary = '/system/bin/b2g.sh'
+        self.remote_process = '/system/b2g/b2g'
+        self.remote_bundles_dir = '/system/b2g/distribution/bundles'
+        self.remote_busybox = '/system/bin/busybox'
+        self.remote_profiles_ini = '/data/b2g/mozilla/profiles.ini'
+        self.remote_test_root = '/data/local/tests'
+
+    @property
+    def adb(self):
+        if not self._adb:
+            paths = [os.environ.get('ADB'),
+                     os.environ.get('ADB_PATH'),
+                     self.which('adb')]
+            paths = [p for p in paths if p is not None if os.path.isfile(p)]
+            if not paths:
+                raise OSError('Could not find the adb binary, make sure it is on your' \
+                              'path or set the $ADB_PATH environment variable.')
+            self._adb = paths[0]
+        return self._adb
+
+    @property
+    def bindir(self):
+        if not self._bindir:
+            # TODO get this via build configuration
+            path = os.path.join(self.homedir, 'out', 'host', '*', 'bin')
+            self._bindir = glob.glob(path)[0]
+        return self._bindir
+
+    @property
+    def dm(self):
+        if not self._dm:
+            self._dm = DeviceManagerADB(adbPath=self.adb, autoconnect=False, deviceRoot=self.remote_test_root)
+        return self._dm
+
+    @property
+    def remote_profile(self):
+        if not self._remote_profile:
+            self._remote_profile = posixpath.join(self.remote_test_root, 'profile')
+        return self._remote_profile
+
+
+    def which(self, binary):
+        if self.bindir not in sys.path:
+            sys.path.insert(0, self.bindir)
+
+        return find_executable(binary, os.pathsep.join(sys.path))
+
+    def stop_application(self):
+        self.dm.shellCheckOutput(['stop', 'b2g'])
+
+        # For some reason user.js in the profile doesn't get picked up.
+        # Manually copy it over to prefs.js. See bug 1009730 for more details.
+        self.dm.moveTree(posixpath.join(self.remote_profile, 'user.js'),
+                         posixpath.join(self.remote_profile, 'prefs.js'))
+
+
+class FirefoxContext(object):
+    profile_class = FirefoxProfile
+
+
+class ThunderbirdContext(object):
+    profile_class = ThunderbirdProfile
+
+
+class MetroContext(object):
+    profile_class = MetroFirefoxProfile
+
+    def __init__(self, binary=None):
+        self.binary = binary or os.environ.get('BROWSER_PATH', None)
+
+    def wrap_command(self, command):
+        immersive_helper_path = os.path.join(os.path.dirname(here),
+                                             'resources',
+                                             'metrotestharness.exe')
+        command[:0] = [immersive_helper_path, '-firefoxpath']
+        return command
deleted file mode 100644
--- a/testing/mozbase/mozrunner/mozrunner/base.py
+++ /dev/null
@@ -1,187 +0,0 @@
-#!/usr/bin/env python
-# 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 subprocess
-import traceback
-
-from mozprocess.processhandler import ProcessHandler
-import mozcrash
-import mozlog
-
-from .errors import RunnerNotStartedError
-
-
-# we can replace these methods with 'abc'
-# (http://docs.python.org/library/abc.html) when we require Python 2.6+
-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 Runner(object):
-
-    def __init__(self, profile, clean_profile=True, process_class=None,
-                 kp_kwargs=None, env=None, symbols_path=None):
-        self.clean_profile = clean_profile
-        self.env = env or {}
-        self.kp_kwargs = kp_kwargs or {}
-        self.process_class = process_class or ProcessHandler
-        self.process_handler = None
-        self.profile = profile
-        self.log = mozlog.getLogger('MozRunner')
-        self.symbols_path = symbols_path
-
-    def __del__(self):
-        self.cleanup()
-
-    # Once we can use 'abc' it should become an abstract property
-    @property
-    def command(self):
-        pass
-
-    @property
-    def returncode(self):
-        if self.process_handler:
-            return self.process_handler.poll()
-        else:
-            raise RunnerNotStartedError("returncode retrieved before process started")
-
-    def start(self, debug_args=None, interactive=False, timeout=None, outputTimeout=None):
-        """Run self.command in the proper environment
-
-        returns the process id
-
-        :param debug_args: arguments for the debugger
-        :param interactive: uses subprocess.Popen directly
-        :param timeout: see process_handler.run()
-        :param outputTimeout: see process_handler.run()
-
-        """
-        # ensure the runner is stopped
-        self.stop()
-
-        # ensure the profile exists
-        if not self.profile.exists():
-            self.profile.reset()
-            assert self.profile.exists(), "%s : failure to reset profile" % self.__class__.__name__
-
-        cmd = self.command
-
-        # attach a debugger, if specified
-        if debug_args:
-            cmd = list(debug_args) + cmd
-
-        if interactive:
-            self.process_handler = subprocess.Popen(cmd, env=self.env)
-            # TODO: other arguments
-        else:
-            # this run uses the managed processhandler
-            self.process_handler = self.process_class(cmd, env=self.env, **self.kp_kwargs)
-            self.process_handler.run(timeout, outputTimeout)
-
-        return self.process_handler.pid
-
-    def wait(self, timeout=None):
-        """Wait for the process to exit
-
-        returns the process return code if the process exited,
-        returns -<signal> if the process was killed (Unix only)
-        returns None if the process is still running.
-
-        :param timeout: if not None, will return after timeout seconds.
-                        Use is_running() to determine whether or not a
-                        timeout occured. Timeout is ignored if
-                        interactive was set to True.
-
-        """
-        if self.is_running():
-            # The interactive mode uses directly a Popen process instance. It's
-            # wait() method doesn't have any parameters. So handle it separately.
-            if isinstance(self.process_handler, subprocess.Popen):
-                self.process_handler.wait()
-            else:
-                self.process_handler.wait(timeout)
-
-        elif not self.process_handler:
-            raise RunnerNotStartedError("Wait() called before process started")
-
-        return self.returncode
-
-    def is_running(self):
-        """Checks if the process is running
-
-        returns True if the process is active
-
-        """
-        return self.returncode is None
-
-    def stop(self, sig=None):
-        """Kill the process
-
-        returns -<signal> when the process got killed (Unix only)
-
-        :param sig: Signal used to kill the process, defaults to SIGKILL
-                    (has no effect on Windows).
-
-        """
-        try:
-            if not self.is_running():
-                return
-        except RunnerNotStartedError:
-            return
-
-
-        # The interactive mode uses directly a Popen process instance. It's
-        # kill() method doesn't have any parameters. So handle it separately.
-        if isinstance(self.process_handler, subprocess.Popen):
-            self.process_handler.kill()
-        else:
-            self.process_handler.kill(sig=sig)
-
-        return self.returncode
-
-    def reset(self):
-        """Reset the runner to its default state"""
-        if getattr(self, 'profile', False):
-            self.profile.reset()
-
-    def check_for_crashes(self, dump_directory=None, dump_save_path=None,
-                          test_name=None, quiet=False):
-        """Check for a possible crash and output stack trace
-
-        :param dump_directory: Directory to search for minidump files
-        :param dump_save_path: Directory to save the minidump files to
-        :param test_name: Name to use in the crash output
-        :param quiet: If `True` don't print the PROCESS-CRASH message to stdout
-
-        """
-        if not dump_directory:
-            dump_directory = os.path.join(self.profile.profile, 'minidumps')
-
-        crashed = False
-        try:
-            crashed = mozcrash.check_for_crashes(dump_directory,
-                                                 self.symbols_path,
-                                                 dump_save_path=dump_save_path,
-                                                 test_name=test_name,
-                                                 quiet=quiet)
-        except:
-            traceback.print_exc()
-
-        return crashed
-
-    def cleanup(self):
-        """Cleanup all runner state"""
-        self.stop()
-
-        if getattr(self, 'profile', False) and self.clean_profile:
-            self.profile.cleanup()
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozrunner/mozrunner/base/__init__.py
@@ -0,0 +1,3 @@
+from .runner import BaseRunner
+from .device import DeviceRunner
+from .browser import GeckoRuntimeRunner
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozrunner/mozrunner/base/browser.py
@@ -0,0 +1,74 @@
+# 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 mozinfo
+import os
+import platform
+import sys
+
+from .runner import BaseRunner
+
+
+class GeckoRuntimeRunner(BaseRunner):
+    """
+    The base runner class used for local gecko runtime binaries,
+    such as Firefox and Thunderbird.
+    """
+
+    def __init__(self, binary, cmdargs=None, **runner_args):
+        BaseRunner.__init__(self, **runner_args)
+
+        self.binary = binary
+        self.cmdargs = cmdargs or []
+
+        # allows you to run an instance of Firefox separately from any other instances
+        self.env['MOZ_NO_REMOTE'] = '1'
+        # keeps Firefox attached to the terminal window after it starts
+        self.env['NO_EM_RESTART'] = '1'
+
+        # set the library path if needed on linux
+        if sys.platform == 'linux2' and self.binary.endswith('-bin'):
+            dirname = os.path.dirname(self.binary)
+            if os.environ.get('LD_LIBRARY_PATH', None):
+                self.env['LD_LIBRARY_PATH'] = '%s:%s' % (os.environ['LD_LIBRARY_PATH'], dirname)
+            else:
+                self.env['LD_LIBRARY_PATH'] = dirname
+
+    @property
+    def command(self):
+        command = [self.binary, '-profile', self.profile.profile]
+
+        _cmdargs = [i for i in self.cmdargs
+                    if i != '-foreground']
+        if len(_cmdargs) != len(self.cmdargs):
+            # foreground should be last; see
+            # https://bugzilla.mozilla.org/show_bug.cgi?id=625614
+            self.cmdargs = _cmdargs
+            self.cmdargs.append('-foreground')
+        if mozinfo.isMac and '-foreground' not in self.cmdargs:
+            # runner should specify '-foreground' on Mac; see
+            # https://bugzilla.mozilla.org/show_bug.cgi?id=916512
+            self.cmdargs.append('-foreground')
+
+        # Bug 775416 - Ensure that binary options are passed in first
+        command[1:1] = self.cmdargs
+
+        # If running on OS X 10.5 or older, wrap |cmd| so that it will
+        # be executed as an i386 binary, in case it's a 32-bit/64-bit universal
+        # binary.
+        if mozinfo.isMac and hasattr(platform, 'mac_ver') and \
+                platform.mac_ver()[0][:4] < '10.6':
+            command = ["arch", "-arch", "i386"] + command
+
+        if hasattr(self.app_ctx, 'wrap_command'):
+            command = self.app_ctx.wrap_command(command)
+        return command
+
+    def start(self, *args, **kwargs):
+        # ensure the profile exists
+        if not self.profile.exists():
+            self.profile.reset()
+            assert self.profile.exists(), "%s : failure to reset profile" % self.__class__.__name__
+
+        BaseRunner.start(self, *args, **kwargs)
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozrunner/mozrunner/base/device.py
@@ -0,0 +1,111 @@
+# 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 __future__ import print_function
+
+import datetime
+import re
+import signal
+import sys
+import tempfile
+import time
+
+from .runner import BaseRunner
+
+class DeviceRunner(BaseRunner):
+    """
+    The base runner class used for running gecko on
+    remote devices (or emulators), such as B2G.
+    """
+    def __init__(self, device_class, device_args=None, **kwargs):
+        process_args = kwargs.get('process_args', {})
+        process_args.update({ 'stream': sys.stdout,
+                              'processOutputLine': self.on_output,
+                              'onTimeout': self.on_timeout })
+        kwargs['process_args'] = process_args
+        BaseRunner.__init__(self, **kwargs)
+
+        device_args = device_args or {}
+        self.device = device_class(**device_args)
+
+        process_log = tempfile.NamedTemporaryFile(suffix='pidlog')
+        self._env =  { 'MOZ_CRASHREPORTER': '1',
+                       'MOZ_CRASHREPORTER_NO_REPORT': '1',
+                       'MOZ_CRASHREPORTER_SHUTDOWN': '1',
+                       'MOZ_HIDE_RESULTS_TABLE': '1',
+                       'MOZ_PROCESS_LOG': process_log.name,
+                       'NSPR_LOG_MODULES': 'signaling:5,mtransport:3',
+                       'R_LOG_LEVEL': '5',
+                       'R_LOG_DESTINATION': 'stderr',
+                       'R_LOG_VERBOSE': '1',
+                       'NO_EM_RESTART': '1', }
+        if kwargs.get('env'):
+            self._env.update(kwargs['env'])
+
+        # In this case we need to pass in env as part of the command.
+        # Make this empty so runner doesn't pass anything into the
+        # process class.
+        self.env = None
+
+    @property
+    def command(self):
+        cmd = [self.app_ctx.adb]
+        if self.app_ctx.dm._deviceSerial:
+            cmd.extend(['-s', self.app_ctx.dm._deviceSerial])
+        cmd.append('shell')
+        for k, v in self._env.iteritems():
+            cmd.append('%s=%s' % (k, v))
+        cmd.append(self.app_ctx.remote_binary)
+        return cmd
+
+    def start(self, *args, **kwargs):
+        if not self.device.proc:
+            self.device.start()
+        self.device.setup_profile(self.profile)
+        self.app_ctx.stop_application()
+
+        BaseRunner.start(self, *args, **kwargs)
+
+        timeout = 10 # seconds
+        starttime = datetime.datetime.now()
+        while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout):
+            if self.app_ctx.dm.processExist(self.app_ctx.remote_process):
+                break
+            time.sleep(1)
+        else:
+            print("timed out waiting for '%s' process to start" % self.app_ctx.remote_process)
+
+    def on_output(self, line):
+        match = re.findall(r"TEST-START \| ([^\s]*)", line)
+        if match:
+            self.last_test = match[-1]
+
+    def on_timeout(self, line):
+        self.dm.killProcess(self.app_ctx.remote_process, sig=signal.SIGABRT)
+        timeout = 10 # seconds
+        starttime = datetime.datetime.now()
+        while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout):
+            if not self.app_ctx.dm.processExist(self.app_ctx.remote_process):
+                break
+            time.sleep(1)
+        else:
+            print("timed out waiting for '%s' process to exit" % self.app_ctx.remote_process)
+
+        msg = "%s | application timed out after %s seconds"
+        if self.timeout:
+            timeout = self.timeout
+        else:
+            timeout = self.output_timeout
+            msg = "%s with no output" % msg
+
+        self.log.testFail(msg % (self.last_test, timeout))
+        self.check_for_crashes()
+
+    def check_for_crashes(self):
+        dump_dir = self.device.pull_minidumps()
+        BaseRunner.check_for_crashes(self, dump_directory=dump_dir, test_name=self.last_test)
+
+    def cleanup(self, *args, **kwargs):
+        BaseRunner.cleanup(self, *args, **kwargs)
+        self.device.cleanup()
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozrunner/mozrunner/base/runner.py
@@ -0,0 +1,196 @@
+#!/usr/bin/env python
+# 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 ABCMeta, abstractproperty
+import os
+import subprocess
+import traceback
+
+from mozprocess import ProcessHandler
+import mozcrash
+
+from ..application import DefaultContext
+from ..errors import RunnerNotStartedError
+
+
+class BaseRunner(object):
+    """
+    The base runner class for all mozrunner objects, both local and remote.
+    """
+    __metaclass__ = ABCMeta
+    last_test = 'automation'
+    process_handler = None
+    timeout = None
+    output_timeout = None
+
+    def __init__(self, app_ctx=None, profile=None, clean_profile=True, env=None,
+                 process_class=None, process_args=None, symbols_path=None):
+        self.app_ctx = app_ctx or DefaultContext()
+
+        if isinstance(profile, basestring):
+            self.profile = self.app_ctx.profile_class(profile=profile)
+        else:
+            self.profile = profile or self.app_ctx.profile_class(**getattr(self.app_ctx, 'profile_args', {}))
+
+        # process environment
+        if env is None:
+            self.env = os.environ.copy()
+        else:
+            self.env = env.copy()
+
+        self.clean_profile = clean_profile
+        self.process_class = process_class or ProcessHandler
+        self.process_args = process_args or {}
+        self.symbols_path = symbols_path
+
+    def __del__(self):
+        self.cleanup()
+
+    @abstractproperty
+    def command(self):
+        """Returns the command list to run."""
+        pass
+
+    @property
+    def returncode(self):
+        """
+        The returncode of the process_handler. A value of None
+        indicates the process is still running. A negative
+        value indicates the process was killed with the
+        specified signal.
+
+        :raises: RunnerNotStartedError
+        """
+        if self.process_handler:
+            return self.process_handler.poll()
+        else:
+            raise RunnerNotStartedError("returncode accessed before runner started")
+
+    def start(self, debug_args=None, interactive=False, timeout=None, outputTimeout=None):
+        """
+        Run self.command in the proper environment.
+
+        :param debug_args: arguments for a debugger
+        :param interactive: uses subprocess.Popen directly
+        :param timeout: see process_handler.run()
+        :param outputTimeout: see process_handler.run()
+        :returns: the process id
+        """
+        self.timeout = timeout
+        self.output_timeout = outputTimeout
+        cmd = self.command
+
+        # ensure the runner is stopped
+        self.stop()
+
+        # attach a debugger, if specified
+        if debug_args:
+            cmd = list(debug_args) + cmd
+
+        if interactive:
+            self.process_handler = subprocess.Popen(cmd, env=self.env)
+            # TODO: other arguments
+        else:
+            # this run uses the managed processhandler
+            self.process_handler = self.process_class(cmd, env=self.env, **self.process_args)
+            self.process_handler.run(self.timeout, self.output_timeout)
+
+        return self.process_handler.pid
+
+    def wait(self, timeout=None):
+        """
+        Wait for the process to exit.
+
+        :param timeout: if not None, will return after timeout seconds.
+                        Timeout is ignored if interactive was set to True.
+        :returns: the process return code if process exited normally,
+                  -<signal> if process was killed (Unix only),
+                  None if timeout was reached and the process is still running.
+        :raises: RunnerNotStartedError
+        """
+        if self.is_running():
+            # The interactive mode uses directly a Popen process instance. It's
+            # wait() method doesn't have any parameters. So handle it separately.
+            if isinstance(self.process_handler, subprocess.Popen):
+                self.process_handler.wait()
+            else:
+                self.process_handler.wait(timeout)
+
+        elif not self.process_handler:
+            raise RunnerNotStartedError("Wait() called before process started")
+
+        return self.returncode
+
+    def is_running(self):
+        """
+        Checks if the process is running.
+
+        :returns: True if the process is active
+        """
+        return self.returncode is None
+
+    def stop(self, sig=None):
+        """
+        Kill the process.
+
+        :param sig: Signal used to kill the process, defaults to SIGKILL
+                    (has no effect on Windows).
+        :returns: the process return code if process was already stopped,
+                  -<signal> if process was killed (Unix only)
+        :raises: RunnerNotStartedError
+        """
+        try:
+            if not self.is_running():
+                return self.returncode
+        except RunnerNotStartedError:
+            return
+
+        # The interactive mode uses directly a Popen process instance. It's
+        # kill() method doesn't have any parameters. So handle it separately.
+        if isinstance(self.process_handler, subprocess.Popen):
+            self.process_handler.kill()
+        else:
+            self.process_handler.kill(sig=sig)
+
+        return self.returncode
+
+    def reset(self):
+        """
+        Reset the runner to its default state.
+        """
+        self.stop()
+        self.process_handler = None
+
+    def check_for_crashes(self, dump_directory=None, dump_save_path=None,
+                          test_name=None, quiet=False):
+        """
+        Check for a possible crash and output stack trace.
+
+        :param dump_directory: Directory to search for minidump files
+        :param dump_save_path: Directory to save the minidump files to
+        :param test_name: Name to use in the crash output
+        :param quiet: If `True` don't print the PROCESS-CRASH message to stdout
+        :returns: True if a crash was detected, otherwise False
+        """
+        if not dump_directory:
+            dump_directory = os.path.join(self.profile.profile, 'minidumps')
+
+        crashed = False
+        try:
+            crashed = mozcrash.check_for_crashes(dump_directory,
+                                                 self.symbols_path,
+                                                 dump_save_path=dump_save_path,
+                                                 test_name=test_name,
+                                                 quiet=quiet)
+        except:
+            traceback.print_exc()
+
+        return crashed
+
+    def cleanup(self):
+        """
+        Cleanup all runner state
+        """
+        self.stop()
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozrunner/mozrunner/cli.py
@@ -0,0 +1,193 @@
+# 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 optparse
+import os
+import sys
+
+from mozprofile import MozProfileCLI, Profile
+from .runners import (
+    FirefoxRunner,
+    MetroRunner,
+    ThunderbirdRunner,
+)
+
+from .utils import findInPath
+
+RUNNER_MAP = {
+    'firefox': FirefoxRunner,
+    'metro': MetroRunner,
+    'thunderbird': ThunderbirdRunner,
+}
+
+# Map of debugging programs to information about them
+# from http://mxr.mozilla.org/mozilla-central/source/build/automationutils.py#59
+DEBUGGERS = {'gdb': {'interactive': True,
+                     'args': ['-q', '--args'],},
+             'valgrind': {'interactive': False,
+                          'args': ['--leak-check=full']}
+             }
+
+def debugger_arguments(debugger, arguments=None, interactive=None):
+    """Finds debugger arguments from debugger given and defaults
+
+    :param debugger: name or path to debugger
+    :param arguments: arguments for the debugger, or None to use defaults
+    :param interactive: whether the debugger should run in interactive mode
+
+    """
+    # find debugger executable if not a file
+    executable = debugger
+    if not os.path.exists(executable):
+        executable = findInPath(debugger)
+    if executable is None:
+        raise Exception("Path to '%s' not found" % debugger)
+
+    # if debugger not in dictionary of knowns return defaults
+    dirname, debugger = os.path.split(debugger)
+    if debugger not in DEBUGGERS:
+        return ([executable] + (arguments or []), bool(interactive))
+
+    # otherwise use the dictionary values for arguments unless specified
+    if arguments is None:
+        arguments = DEBUGGERS[debugger].get('args', [])
+    if interactive is None:
+        interactive = DEBUGGERS[debugger].get('interactive', False)
+    return ([executable] + arguments, interactive)
+
+
+class CLI(MozProfileCLI):
+    """Command line interface"""
+
+    module = "mozrunner"
+
+    def __init__(self, args=sys.argv[1:]):
+        self.metadata = getattr(sys.modules[self.module],
+                                'package_metadata',
+                                {})
+        version = self.metadata.get('Version')
+        parser_args = {'description': self.metadata.get('Summary')}
+        if version:
+            parser_args['version'] = "%prog " + version
+        self.parser = optparse.OptionParser(**parser_args)
+        self.add_options(self.parser)
+        (self.options, self.args) = self.parser.parse_args(args)
+
+        if getattr(self.options, 'info', None):
+            self.print_metadata()
+            sys.exit(0)
+
+        # choose appropriate runner and profile classes
+        try:
+            self.runner_class = RUNNER_MAP[self.options.app]
+        except KeyError:
+            self.parser.error('Application "%s" unknown (should be one of "%s")' %
+                              (self.options.app, ', '.join(RUNNER_MAP.keys())))
+
+    def add_options(self, parser):
+        """add options to the parser"""
+
+        # add profile options
+        MozProfileCLI.add_options(self, parser)
+
+        # add runner options
+        parser.add_option('-b', "--binary",
+                          dest="binary", help="Binary path.",
+                          metavar=None, default=None)
+        parser.add_option('--app', dest='app', default='firefox',
+                          help="Application to use [DEFAULT: %default]")
+        parser.add_option('--app-arg', dest='appArgs',
+                          default=[], action='append',
+                          help="provides an argument to the test application")
+        parser.add_option('--debugger', dest='debugger',
+                          help="run under a debugger, e.g. gdb or valgrind")
+        parser.add_option('--debugger-args', dest='debugger_args',
+                          action='store',
+                          help="arguments to the debugger")
+        parser.add_option('--interactive', dest='interactive',
+                          action='store_true',
+                          help="run the program interactively")
+        if self.metadata:
+            parser.add_option("--info", dest="info", default=False,
+                              action="store_true",
+                              help="Print module information")
+
+    ### methods for introspecting data
+
+    def get_metadata_from_egg(self):
+        import pkg_resources
+        ret = {}
+        dist = pkg_resources.get_distribution(self.module)
+        if dist.has_metadata("PKG-INFO"):
+            for line in dist.get_metadata_lines("PKG-INFO"):
+                key, value = line.split(':', 1)
+                ret[key] = value
+        if dist.has_metadata("requires.txt"):
+            ret["Dependencies"] = "\n" + dist.get_metadata("requires.txt")
+        return ret
+
+    def print_metadata(self, data=("Name", "Version", "Summary", "Home-page",
+                                   "Author", "Author-email", "License", "Platform", "Dependencies")):
+        for key in data:
+            if key in self.metadata:
+                print key + ": " + self.metadata[key]
+
+    ### methods for running
+
+    def command_args(self):
+        """additional arguments for the mozilla application"""
+        return map(os.path.expanduser, self.options.appArgs)
+
+    def runner_args(self):
+        """arguments to instantiate the runner class"""
+        return dict(cmdargs=self.command_args(),
+                    binary=self.options.binary)
+
+    def create_runner(self):
+        profile = Profile(**self.profile_args())
+        return self.runner_class(profile=profile, **self.runner_args())
+
+    def run(self):
+        runner = self.create_runner()
+        self.start(runner)
+        runner.cleanup()
+
+    def debugger_arguments(self):
+        """Get the debugger arguments
+
+        returns a 2-tuple of debugger arguments:
+            (debugger_arguments, interactive)
+
+        """
+        debug_args = self.options.debugger_args
+        if debug_args is not None:
+            debug_args = debug_args.split()
+        interactive = self.options.interactive
+        if self.options.debugger:
+            debug_args, interactive = debugger_arguments(self.options.debugger, debug_args, interactive)
+        return debug_args, interactive
+
+    def start(self, runner):
+        """Starts the runner and waits for the application to exit
+
+        It can also happen via a keyboard interrupt. It should be
+        overwritten to provide custom running of the runner instance.
+
+        """
+        # attach a debugger if specified
+        debug_args, interactive = self.debugger_arguments()
+        runner.start(debug_args=debug_args, interactive=interactive)
+        print 'Starting: ' + ' '.join(runner.command)
+        try:
+            runner.wait()
+        except KeyboardInterrupt:
+            runner.stop()
+
+
+def cli(args=sys.argv[1:]):
+    CLI(args).run()
+
+
+if __name__ == '__main__':
+    cli()
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozrunner/mozrunner/devices/__init__.py
@@ -0,0 +1,10 @@
+# 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 emulator import Emulator
+from base import Device
+
+import emulator_battery
+import emulator_geo
+import emulator_screen
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozrunner/mozrunner/devices/base.py
@@ -0,0 +1,227 @@
+from ConfigParser import (
+    ConfigParser,
+    RawConfigParser
+)
+import datetime
+import os
+import posixpath
+import socket
+import subprocess
+import tempfile
+import time
+import traceback
+
+from mozdevice import DMError
+
+class Device(object):
+    def __init__(self, app_ctx, restore=True):
+        self.app_ctx = app_ctx
+        self.dm = self.app_ctx.dm
+        self.restore = restore
+        self.added_files = set()
+        self.backup_files = set()
+
+    @property
+    def remote_profiles(self):
+        """
+        A list of remote profiles on the device.
+        """
+        remote_ini = self.app_ctx.remote_profiles_ini
+        if not self.dm.fileExists(remote_ini):
+            raise Exception("Remote file '%s' not found" % remote_ini)
+
+        local_ini = tempfile.NamedTemporaryFile()
+        self.dm.getFile(remote_ini, local_ini.name)
+        cfg = ConfigParser()
+        cfg.read(local_ini.name)
+
+        profiles = []
+        for section in cfg.sections():
+            if cfg.has_option(section, 'Path'):
+                if cfg.has_option(section, 'IsRelative') and cfg.getint(section, 'IsRelative'):
+                    profiles.append(posixpath.join(posixpath.dirname(remote_ini), \
+                                    cfg.get(section, 'Path')))
+                else:
+                    profiles.append(cfg.get(section, 'Path'))
+        return profiles
+
+    def pull_minidumps(self):
+        """
+        Saves any minidumps found in the remote profile on the local filesystem.
+
+        :returns: Path to directory containing the dumps.
+        """
+        remote_dump_dir = posixpath.join(self.app_ctx.remote_profile, 'minidumps')
+        local_dump_dir = tempfile.mkdtemp()
+        self.dm.getDirectory(remote_dump_dir, local_dump_dir)
+        self.dm.removeDir(remote_dump_dir)
+        return local_dump_dir
+
+    def setup_profile(self, profile):
+        """
+        Copy profile to the device and update the remote profiles.ini
+        to point to the new profile.
+
+        :param profile: mozprofile object to copy over.
+        """
+        self.dm.remount()
+
+        if self.dm.dirExists(self.app_ctx.remote_profile):
+            self.dm.shellCheckOutput(['rm', '-r', self.app_ctx.remote_profile])
+
+        self.dm.pushDir(profile.profile, self.app_ctx.remote_profile)
+
+        extension_dir = os.path.join(profile.profile, 'extensions', 'staged')
+        if os.path.isdir(extension_dir):
+            # Copy the extensions to the B2G bundles dir.
+            # need to write to read-only dir
+            for filename in os.listdir(extension_dir):
+                path = posixpath.join(self.app_ctx.remote_bundles_dir, filename)
+                if self.dm.fileExists(path):
+                    self.dm.shellCheckOutput(['rm', '-rf', path])
+            self.dm.pushDir(extension_dir, self.app_ctx.remote_bundles_dir)
+
+        timeout = 5 # seconds
+        starttime = datetime.datetime.now()
+        while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout):
+            if self.dm.fileExists(self.app_ctx.remote_profiles_ini):
+                break
+            time.sleep(1)
+        else:
+            print "timed out waiting for profiles.ini"
+
+        local_profiles_ini = tempfile.NamedTemporaryFile()
+        self.dm.getFile(self.app_ctx.remote_profiles_ini, local_profiles_ini.name)
+
+        config = ProfileConfigParser()
+        config.read(local_profiles_ini.name)
+        for section in config.sections():
+            if 'Profile' in section:
+                config.set(section, 'IsRelative', 0)
+                config.set(section, 'Path', self.app_ctx.remote_profile)
+
+        new_profiles_ini = tempfile.NamedTemporaryFile()
+        config.write(open(new_profiles_ini.name, 'w'))
+
+        self.backup_file(self.app_ctx.remote_profiles_ini)
+        self.dm.pushFile(new_profiles_ini.name, self.app_ctx.remote_profiles_ini)
+
+    def install_busybox(self, busybox):
+        """
+        Installs busybox on the device.
+
+        :param busybox: Path to busybox binary to install.
+        """
+        self.dm.remount()
+        print 'pushing %s' % self.app_ctx.remote_busybox
+        self.dm.pushFile(busybox, self.app_ctx.remote_busybox, retryLimit=10)
+        # TODO for some reason using dm.shellCheckOutput doesn't work,
+        #      while calling adb shell directly does.
+        args = [self.app_ctx.adb, '-s', self.dm._deviceSerial,
+                'shell', 'cd /system/bin; chmod 555 busybox;' \
+                'for x in `./busybox --list`; do ln -s ./busybox $x; done']
+        adb = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+        adb.wait()
+        self.dm._verifyZip()
+
+    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.
+
+        :param remote_port: The remote port to wait on.
+        """
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        s.bind(("",0))
+        local_port = s.getsockname()[1]
+        s.close()
+
+        self.dm.forward('tcp:%d' % local_port, 'tcp:%d' % remote_port)
+        return local_port
+
+    def wait_for_port(self, port, timeout=300):
+        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', port))
+                data = sock.recv(16)
+                sock.close()
+                if ':' in data:
+                    return True
+            except:
+                traceback.print_exc()
+            time.sleep(1)
+        return False
+
+    def backup_file(self, remote_path):
+        if not self.restore:
+            return
+
+        if self.dm.fileExists(remote_path):
+            self.dm.copyTree(remote_path, '%s.orig' % remote_path)
+            self.backup_files.add(remote_path)
+        else:
+            self.added_files.add(remote_path)
+
+    def cleanup(self):
+        """
+        Cleanup the device.
+        """
+        if not self.restore:
+            return
+
+        try:
+            self.dm._verifyDevice()
+        except DMError:
+            return
+
+        self.dm.remount()
+        # Restore the original profile
+        for added_file in self.added_files:
+            self.dm.removeFile(added_file)
+
+        for backup_file in self.backup_files:
+            if self.dm.fileExists('%s.orig' % backup_file):
+                self.dm.moveTree('%s.orig' % backup_file, backup_file)
+
+        # Delete any bundled extensions
+        extension_dir = posixpath.join(self.app_ctx.remote_profile, 'extensions', 'staged')
+        if self.dm.dirExists(extension_dir):
+            for filename in self.dm.listFiles(extension_dir):
+                try:
+                    self.dm.removeDir(posixpath.join(self.app_ctx.remote_bundles_dir, filename))
+                except DMError:
+                    pass
+        # Remove the test profile
+        self.dm.removeDir(self.app_ctx.remote_profile)
+
+
+class ProfileConfigParser(RawConfigParser):
+    """
+    Class to create profiles.ini config files
+
+    Subclass of RawConfigParser that outputs .ini files in the exact
+    format expected for profiles.ini, which is slightly different
+    than the default format.
+    """
+
+    def optionxform(self, optionstr):
+        return optionstr
+
+    def write(self, fp):
+        if self._defaults:
+            fp.write("[%s]\n" % ConfigParser.DEFAULTSECT)
+            for (key, value) in self._defaults.items():
+                fp.write("%s=%s\n" % (key, str(value).replace('\n', '\n\t')))
+            fp.write("\n")
+        for section in self._sections:
+            fp.write("[%s]\n" % section)
+            for (key, value) in self._sections[section].items():
+                if key == "__name__":
+                    continue
+                if (value is not None) or (self._optcre == self.OPTCRE):
+                    key = "=".join((key, str(value).replace('\n', '\n\t')))
+                fp.write("%s\n" % (key))
+            fp.write("\n")
+
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozrunner/mozrunner/devices/emulator.py
@@ -0,0 +1,258 @@
+# 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 telnetlib import Telnet
+import datetime
+import os
+import shutil
+import subprocess
+import tempfile
+import time
+
+from mozprocess import ProcessHandler
+
+from .base import Device
+from .emulator_battery import EmulatorBattery
+from .emulator_geo import EmulatorGeo
+from .emulator_screen import EmulatorScreen
+from ..utils import uses_marionette
+from ..errors import TimeoutException, ScriptTimeoutException
+
+class ArchContext(object):
+    def __init__(self, arch, context, binary=None):
+        kernel = os.path.join(context.homedir, 'prebuilts', 'qemu-kernel', '%s', '%s')
+        sysdir = os.path.join(context.homedir, 'out', 'target', 'product', '%s')
+        if arch == 'x86':
+            self.binary = os.path.join(context.bindir, 'emulator-x86')
+            self.kernel = kernel % ('x86', 'kernel-qemu')
+            self.sysdir = sysdir % 'generic_x86'
+            self.extra_args = []
+        else:
+            self.binary = os.path.join(context.bindir, 'emulator')
+            self.kernel = kernel % ('arm', 'kernel-qemu-armv7')
+            self.sysdir = sysdir % 'generic'
+            self.extra_args = ['-cpu', 'cortex-a8']
+
+        if binary:
+            self.binary = binary
+
+
+class Emulator(Device):
+    logcat_proc = None
+    port = None
+    proc = None
+    telnet = None
+
+    def __init__(self, app_ctx, arch, resolution=None, sdcard=None, userdata=None,
+                 logdir=None, no_window=None, binary=None):
+        Device.__init__(self, app_ctx)
+
+        self.arch = ArchContext(arch, self.app_ctx, binary=binary)
+        self.resolution = resolution or '320x480'
+        self.sdcard = None
+        if sdcard:
+            self.sdcard = self.create_sdcard(sdcard)
+        self.userdata = os.path.join(self.arch.sysdir, 'userdata.img')
+        if userdata:
+            self.userdata = tempfile.NamedTemporaryFile(prefix='qemu-userdata')
+            shutil.copyfile(userdata, self.userdata)
+        self.logdir = logdir
+        self.no_window = no_window
+
+        self.battery = EmulatorBattery(self)
+        self.geo = EmulatorGeo(self)
+        self.screen = EmulatorScreen(self)
+
+    @property
+    def args(self):
+        """
+        Arguments to pass into the emulator binary.
+        """
+        qemu_args = [self.arch.binary,
+                     '-kernel', self.arch.kernel,
+                     '-sysdir', self.arch.sysdir,
+                     '-data', self.userdata]
+        if self.no_window:
+            qemu_args.append('-no-window')
+        if self.sdcard:
+            qemu_args.extend(['-sdcard', self.sdcard])
+        qemu_args.extend(['-memory', '512',
+                          '-partition-size', '512',
+                          '-verbose',
+                          '-skin', self.resolution,
+                          '-gpu', 'on',
+                          '-qemu'] + self.arch.extra_args)
+        return qemu_args
+
+    def _get_online_devices(self):
+        return set([d[0] for d in self.dm.devices() if d[1] != 'offline'])
+
+    def start(self):
+        """
+        Starts a new emulator.
+        """
+        original_devices = self._get_online_devices()
+
+        qemu_log = None
+        qemu_proc_args = {}
+        if self.logdir:
+            # save output from qemu to logfile
+            qemu_log = os.path.join(self.logdir, 'qemu.log')
+            if os.path.isfile(qemu_log):
+                self._rotate_log(qemu_log)
+            qemu_proc_args['logfile'] = qemu_log
+        else:
+            qemu_proc_args['processOutputLine'] = lambda line: None
+        self.proc = ProcessHandler(self.args, **qemu_proc_args)
+        self.proc.run()
+
+        devices = self._get_online_devices()
+        now = datetime.datetime.now()
+        while (devices - original_devices) == set([]):
+            time.sleep(1)
+            if datetime.datetime.now() - now > datetime.timedelta(seconds=60):
+                raise TimeoutException('timed out waiting for emulator to start')
+            devices = self._get_online_devices()
+        self.connect(devices - original_devices)
+
+    def connect(self, devices=None):
+        """
+        Connects to an already running emulator.
+        """
+        devices = list(devices or self._get_online_devices())
+        serial = [d for d in devices if d.startswith('emulator')][0]
+        self.dm._deviceSerial = serial
+        self.dm.connect()
+        self.port = int(serial[serial.rindex('-')+1:])
+
+        self.geo.set_default_location()
+        self.screen.initialize()
+
+        print self.logdir
+        if self.logdir:
+            # save logcat
+            logcat_log = os.path.join(self.logdir, '%s.log' % serial)
+            if os.path.isfile(logcat_log):
+                self._rotate_log(logcat_log)
+            logcat_args = [self.app_ctx.adb, '-s', '%s' % serial,
+                           'logcat', '-v', 'threadtime']
+            self.logcat_proc = ProcessHandler(logcat_args, logfile=logcat_log)
+            self.logcat_proc.run()
+
+        # setup DNS fix for networking
+        self.app_ctx.dm.shellCheckOutput(['setprop', 'net.dns1', '10.0.2.3'])
+
+    def create_sdcard(self, sdcard_size):
+        """
+        Creates an sdcard partition in the emulator.
+
+        :param sdcard_size: Size of partition to create, e.g '10MB'.
+        """
+        mksdcard = self.app_ctx.which('mksdcard')
+        path = tempfile.mktemp(prefix='sdcard')
+        sdargs = [mksdcard, '-l', 'mySdCard', sdcard_size, path]
+        sd = subprocess.Popen(sdargs, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+        retcode = sd.wait()
+        if retcode:
+            raise Exception('unable to create sdcard: exit code %d: %s'
+                            % (retcode, sd.stdout.read()))
+        return path
+
+    def cleanup(self):
+        """
+        Cleans up and kills the emulator.
+        """
+        Device.cleanup(self)
+        if self.proc:
+            self.proc.kill()
+            self.proc = None
+
+        # Remove temporary sdcard
+        if self.sdcard and os.path.isfile(self.sdcard):
+            os.remove(self.sdcard)
+
+    def _rotate_log(self, srclog, index=1):
+        """
+        Rotate a logfile, by recursively rotating logs further in the sequence,
+        deleting the last file if necessary.
+        """
+        basename = os.path.basename(srclog)
+        basename = basename[:-len('.log')]
+        if index > 1:
+            basename = basename[:-len('.1')]
+        basename = '%s.%d.log' % (basename, index)
+
+        destlog = os.path.join(self.logdir, basename)
+        if os.path.isfile(destlog):
+            if index == 3:
+                os.remove(destlog)
+            else:
+                self._rotate_log(destlog, index+1)
+        shutil.move(srclog, destlog)
+
+    # TODO this function is B2G specific and shouldn't live here
+    @uses_marionette
+    def wait_for_system_message(self, marionette):
+        marionette.set_script_timeout(45000)
+        # Telephony API's won't be available immediately upon emulator
+        # boot; we have to wait for the syste-message-listener-ready
+        # message before we'll be able to use them successfully.  See
+        # bug 792647.
+        print 'waiting for system-message-listener-ready...'
+        try:
+            marionette.execute_async_script("""
+waitFor(
+    function() { marionetteScriptFinished(true); },
+    function() { return isSystemMessageListenerReady(); }
+);
+            """)
+        except ScriptTimeoutException:
+            print 'timed out'
+            # We silently ignore the timeout if it occurs, since
+            # isSystemMessageListenerReady() isn't available on
+            # older emulators.  45s *should* be enough of a delay
+            # to allow telephony API's to work.
+            pass
+        print '...done'
+
+    # TODO this function is B2G specific and shouldn't live here
+    @uses_marionette
+    def wait_for_homescreen(self, marionette):
+        print 'waiting for homescreen...'
+
+        marionette.set_context(marionette.CONTEXT_CONTENT)
+        marionette.execute_async_script("""
+log('waiting for mozbrowserloadend');
+window.addEventListener('mozbrowserloadend', function loaded(aEvent) {
+  log('received mozbrowserloadend for ' + aEvent.target.src);
+  if (aEvent.target.src.indexOf('ftu') != -1 || aEvent.target.src.indexOf('homescreen') != -1) {
+    window.removeEventListener('mozbrowserloadend', loaded);
+    marionetteScriptFinished();
+  }
+});""", script_timeout=120000)
+        print '...done'
+
+    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 __del__(self):
+        if self.telnet:
+            self.telnet.write('exit\n')
+            self.telnet.read_all()
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozrunner/mozrunner/devices/emulator_battery.py
@@ -0,0 +1,53 @@
+# 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)
+
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozrunner/mozrunner/devices/emulator_geo.py
@@ -0,0 +1,17 @@
+# 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 EmulatorGeo(object):
+
+    def __init__(self, emulator):
+        self.emulator = emulator
+
+    def set_default_location(self):
+        self.lon = -122.08769
+        self.lat = 37.41857
+        self.set_location(self.lon, self.lat)
+
+    def set_location(self, lon, lat):
+        self.emulator._run_telnet('geo fix %0.5f %0.5f' % (self.lon, self.lat))
+
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozrunner/mozrunner/devices/emulator_screen.py
@@ -0,0 +1,89 @@
+# 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 EmulatorScreen(object):
+    """Class for screen related emulator commands."""
+
+    SO_PORTRAIT_PRIMARY = 'portrait-primary'
+    SO_PORTRAIT_SECONDARY = 'portrait-secondary'
+    SO_LANDSCAPE_PRIMARY = 'landscape-primary'
+    SO_LANDSCAPE_SECONDARY = 'landscape-secondary'
+
+    def __init__(self, emulator):
+        self.emulator = emulator
+
+    def initialize(self):
+        self.orientation = self.SO_PORTRAIT_PRIMARY
+
+    def _get_raw_orientation(self):
+        """Get the raw value of the current device orientation."""
+        response = self.emulator._run_telnet('sensor get orientation')
+
+        return response[0].split('=')[1].strip()
+
+    def _set_raw_orientation(self, data):
+        """Set the raw value of the specified device orientation."""
+        self.emulator._run_telnet('sensor set orientation %s' % data)
+
+    def get_orientation(self):
+        """Get the current device orientation.
+
+        Returns;
+            orientation -- Orientation of the device. One of:
+                            SO_PORTRAIT_PRIMARY - system buttons at the bottom
+                            SO_PORTRIAT_SECONDARY - system buttons at the top
+                            SO_LANDSCAPE_PRIMARY - system buttons at the right
+                            SO_LANDSCAPE_SECONDARY - system buttons at the left
+
+        """
+        data = self._get_raw_orientation()
+
+        if data == '0:-90:0':
+            orientation = self.SO_PORTRAIT_PRIMARY
+        elif data == '0:90:0':
+            orientation = self.SO_PORTRAIT_SECONDARY
+        elif data == '0:0:90':
+            orientation = self.SO_LANDSCAPE_PRIMARY
+        elif data == '0:0:-90':
+            orientation = self.SO_LANDSCAPE_SECONDARY
+        else:
+            raise ValueError('Unknown orientation sensor value: %s.' % data)
+
+        return orientation
+
+    def set_orientation(self, orientation):
+        """Set the specified device orientation.
+
+        Args
+            orientation -- Orientation of the device. One of:
+                            SO_PORTRAIT_PRIMARY - system buttons at the bottom
+                            SO_PORTRIAT_SECONDARY - system buttons at the top
+                            SO_LANDSCAPE_PRIMARY - system buttons at the right
+                            SO_LANDSCAPE_SECONDARY - system buttons at the left
+        """
+        orientation = SCREEN_ORIENTATIONS[orientation]
+
+        if orientation == self.SO_PORTRAIT_PRIMARY:
+            data = '0:-90:0'
+        elif orientation == self.SO_PORTRAIT_SECONDARY:
+            data = '0:90:0'
+        elif orientation == self.SO_LANDSCAPE_PRIMARY:
+            data = '0:0:90'
+        elif orientation == self.SO_LANDSCAPE_SECONDARY:
+            data = '0:0:-90'
+        else:
+            raise ValueError('Invalid orientation: %s' % orientation)
+
+        self._set_raw_orientation(data)
+
+    orientation = property(get_orientation, set_orientation)
+
+
+SCREEN_ORIENTATIONS = {"portrait": EmulatorScreen.SO_PORTRAIT_PRIMARY,
+                       "landscape": EmulatorScreen.SO_LANDSCAPE_PRIMARY,
+                       "portrait-primary": EmulatorScreen.SO_PORTRAIT_PRIMARY,
+                       "landscape-primary": EmulatorScreen.SO_LANDSCAPE_PRIMARY,
+                       "portrait-secondary": EmulatorScreen.SO_PORTRAIT_SECONDARY,
+                       "landscape-secondary": EmulatorScreen.SO_LANDSCAPE_SECONDARY}
+
--- a/testing/mozbase/mozrunner/mozrunner/errors.py
+++ b/testing/mozbase/mozrunner/mozrunner/errors.py
@@ -1,11 +1,16 @@
 #!/usr/bin/env python
 # 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 RunnerException(Exception):
     """Base exception handler for mozrunner related errors"""
 
-
 class RunnerNotStartedError(RunnerException):
     """Exception handler in case the runner hasn't been started"""
+
+class TimeoutException(RunnerException):
+    """Raised on timeout waiting for targets to start."""
+
+class ScriptTimeoutException(RunnerException):
+    """Raised on timeout waiting for execute_script to finish."""
deleted file mode 100644
--- a/testing/mozbase/mozrunner/mozrunner/local.py
+++ /dev/null
@@ -1,362 +0,0 @@
-#!/usr/bin/env python
-
-# 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 ConfigParser
-import mozinfo
-import optparse
-import os
-import platform
-import subprocess
-import sys
-
-if mozinfo.isMac:
-    from plistlib import readPlist
-
-from mozprofile import Profile, FirefoxProfile, MetroFirefoxProfile, ThunderbirdProfile, MozProfileCLI
-
-from .base import Runner
-from .utils import findInPath, get_metadata_from_egg
-
-
-__all__ = ['CLI',
-           'cli',
-           'LocalRunner',
-           'local_runners',
-           'package_metadata',
-           'FirefoxRunner',
-           'MetroFirefoxRunner',
-           'ThunderbirdRunner']
-
-
-package_metadata = get_metadata_from_egg('mozrunner')
-
-
-# Map of debugging programs to information about them
-# from http://mxr.mozilla.org/mozilla-central/source/build/automationutils.py#59
-debuggers = {'gdb': {'interactive': True,
-                     'args': ['-q', '--args'],},
-             'valgrind': {'interactive': False,
-                          'args': ['--leak-check=full']}
-             }
-
-
-def debugger_arguments(debugger, arguments=None, interactive=None):
-    """Finds debugger arguments from debugger given and defaults
-
-    :param debugger: name or path to debugger
-    :param arguments: arguments for the debugger, or None to use defaults
-    :param interactive: whether the debugger should run in interactive mode
-
-    """
-    # find debugger executable if not a file
-    executable = debugger
-    if not os.path.exists(executable):
-        executable = findInPath(debugger)
-    if executable is None:
-        raise Exception("Path to '%s' not found" % debugger)
-
-    # if debugger not in dictionary of knowns return defaults
-    dirname, debugger = os.path.split(debugger)
-    if debugger not in debuggers:
-        return ([executable] + (arguments or []), bool(interactive))
-
-    # otherwise use the dictionary values for arguments unless specified
-    if arguments is None:
-        arguments = debuggers[debugger].get('args', [])
-    if interactive is None:
-        interactive = debuggers[debugger].get('interactive', False)
-    return ([executable] + arguments, interactive)
-
-
-class LocalRunner(Runner):
-    """Handles all running operations. Finds bins, runs and kills the process"""
-
-    profile_class = Profile # profile class to use by default
-
-    @classmethod
-    def create(cls, binary=None, cmdargs=None, env=None, kp_kwargs=None, profile_args=None,
-               clean_profile=True, process_class=None, **kwargs):
-        profile = cls.profile_class(**(profile_args or {}))
-        return cls(profile, binary=binary, cmdargs=cmdargs, env=env, kp_kwargs=kp_kwargs,
-                                           clean_profile=clean_profile, process_class=process_class, **kwargs)
-
-    def __init__(self, profile, binary, cmdargs=None, env=None,
-                 kp_kwargs=None, clean_profile=None, process_class=None, **kwargs):
-
-        Runner.__init__(self, profile, clean_profile=clean_profile, kp_kwargs=kp_kwargs,
-                        process_class=process_class, env=env, **kwargs)
-
-        # find the binary
-        self.binary = binary
-        if not self.binary:
-            raise Exception("Binary not specified")
-        if not os.path.exists(self.binary):
-            raise OSError("Binary path does not exist: %s" % self.binary)
-
-        # To be safe the absolute path of the binary should be used
-        self.binary = os.path.abspath(self.binary)
-
-        # allow Mac binaries to be specified as an app bundle
-        plist = '%s/Contents/Info.plist' % self.binary
-        if mozinfo.isMac and os.path.exists(plist):
-            info = readPlist(plist)
-            self.binary = os.path.join(self.binary, "Contents/MacOS/",
-                                       info['CFBundleExecutable'])
-
-        self.cmdargs = cmdargs or []
-        _cmdargs = [i for i in self.cmdargs
-                    if i != '-foreground']
-        if len(_cmdargs) != len(self.cmdargs):
-            # foreground should be last; see
-            # https://bugzilla.mozilla.org/show_bug.cgi?id=625614
-            self.cmdargs = _cmdargs
-            self.cmdargs.append('-foreground')
-        if mozinfo.isMac and '-foreground' not in self.cmdargs:
-            # runner should specify '-foreground' on Mac; see
-            # https://bugzilla.mozilla.org/show_bug.cgi?id=916512
-            self.cmdargs.append('-foreground')
-
-        # process environment
-        if env is None:
-            self.env = os.environ.copy()
-        else:
-            self.env = env.copy()
-        # allows you to run an instance of Firefox separately from any other instances
-        self.env['MOZ_NO_REMOTE'] = '1'
-        # keeps Firefox attached to the terminal window after it starts
-        self.env['NO_EM_RESTART'] = '1'
-
-        # set the library path if needed on linux
-        if sys.platform == 'linux2' and self.binary.endswith('-bin'):
-            dirname = os.path.dirname(self.binary)
-            if os.environ.get('LD_LIBRARY_PATH', None):
-                self.env['LD_LIBRARY_PATH'] = '%s:%s' % (os.environ['LD_LIBRARY_PATH'], dirname)
-            else:
-                self.env['LD_LIBRARY_PATH'] = dirname
-
-    @property
-    def command(self):
-        """Returns the command list to run"""
-        commands = [self.binary, '-profile', self.profile.profile]
-
-        # Bug 775416 - Ensure that binary options are passed in first
-        commands[1:1] = self.cmdargs
-
-        # If running on OS X 10.5 or older, wrap |cmd| so that it will
-        # be executed as an i386 binary, in case it's a 32-bit/64-bit universal
-        # binary.
-        if mozinfo.isMac and hasattr(platform, 'mac_ver') and \
-                platform.mac_ver()[0][:4] < '10.6':
-            commands = ["arch", "-arch", "i386"] + commands
-
-        return commands
-
-    def get_repositoryInfo(self):
-        """Read repository information from application.ini and platform.ini"""
-        config = ConfigParser.RawConfigParser()
-        dirname = os.path.dirname(self.binary)
-        repository = { }
-
-        for file, section in [('application', 'App'), ('platform', 'Build')]:
-            config.read(os.path.join(dirname, '%s.ini' % file))
-
-            for key, id in [('SourceRepository', 'repository'),
-                            ('SourceStamp', 'changeset')]:
-                try:
-                    repository['%s_%s' % (file, id)] = config.get(section, key);
-                except:
-                    repository['%s_%s' % (file, id)] = None
-
-        return repository
-
-
-class FirefoxRunner(LocalRunner):
-    """Specialized LocalRunner subclass for running Firefox."""
-
-    profile_class = FirefoxProfile
-
-    def __init__(self, profile, binary=None, **kwargs):
-
-        # if no binary given take it from the BROWSER_PATH environment variable
-        binary = binary or os.environ.get('BROWSER_PATH')
-        LocalRunner.__init__(self, profile, binary, **kwargs)
-
-
-class MetroFirefoxRunner(LocalRunner):
-    """Specialized LocalRunner subclass for running Firefox Metro"""
-
-    profile_class = MetroFirefoxProfile
-
-    # helper application to launch Firefox in Metro mode
-    here = os.path.dirname(os.path.abspath(__file__))
-    immersiveHelperPath = os.path.sep.join([here,
-                                            'resources',
-                                            'metrotestharness.exe'])
-
-    def __init__(self, profile, binary=None, **kwargs):
-
-        # if no binary given take it from the BROWSER_PATH environment variable
-        binary = binary or os.environ.get('BROWSER_PATH')
-        LocalRunner.__init__(self, profile, binary, **kwargs)
-
-        if not os.path.exists(self.immersiveHelperPath):
-            raise OSError('Can not find Metro launcher: %s' % self.immersiveHelperPath)
-
-        if not mozinfo.isWin:
-            raise Exception('Firefox Metro mode is only supported on Windows 8 and onwards')
-
-    @property
-    def command(self):
-       command = LocalRunner.command.fget(self)
-       command[:0] = [self.immersiveHelperPath, '-firefoxpath']
-
-       return command
-
-
-class ThunderbirdRunner(LocalRunner):
-    """Specialized LocalRunner subclass for running Thunderbird"""
-    profile_class = ThunderbirdProfile
-
-
-local_runners = {'firefox': FirefoxRunner,
-                 'metrofirefox' : MetroFirefoxRunner,
-                 'thunderbird': ThunderbirdRunner}
-
-
-class CLI(MozProfileCLI):
-    """Command line interface"""
-
-    module = "mozrunner"
-
-    def __init__(self, args=sys.argv[1:]):
-        self.metadata = getattr(sys.modules[self.module],
-                                'package_metadata',
-                                {})
-        version = self.metadata.get('Version')
-        parser_args = {'description': self.metadata.get('Summary')}
-        if version:
-            parser_args['version'] = "%prog " + version
-        self.parser = optparse.OptionParser(**parser_args)
-        self.add_options(self.parser)
-        (self.options, self.args) = self.parser.parse_args(args)
-
-        if getattr(self.options, 'info', None):
-            self.print_metadata()
-            sys.exit(0)
-
-        # choose appropriate runner and profile classes
-        try:
-            self.runner_class = local_runners[self.options.app]
-        except KeyError:
-            self.parser.error('Application "%s" unknown (should be one of "%s")' %
-                              (self.options.app, ', '.join(local_runners.keys())))
-
-    def add_options(self, parser):
-        """add options to the parser"""
-
-        # add profile options
-        MozProfileCLI.add_options(self, parser)
-
-        # add runner options
-        parser.add_option('-b', "--binary",
-                          dest="binary", help="Binary path.",
-                          metavar=None, default=None)
-        parser.add_option('--app', dest='app', default='firefox',
-                          help="Application to use [DEFAULT: %default]")
-        parser.add_option('--app-arg', dest='appArgs',
-                          default=[], action='append',
-                          help="provides an argument to the test application")
-        parser.add_option('--debugger', dest='debugger',
-                          help="run under a debugger, e.g. gdb or valgrind")
-        parser.add_option('--debugger-args', dest='debugger_args',
-                          action='store',
-                          help="arguments to the debugger")
-        parser.add_option('--interactive', dest='interactive',
-                          action='store_true',
-                          help="run the program interactively")
-        if self.metadata:
-            parser.add_option("--info", dest="info", default=False,
-                              action="store_true",
-                              help="Print module information")
-
-    ### methods for introspecting data
-
-    def get_metadata_from_egg(self):
-        import pkg_resources
-        ret = {}
-        dist = pkg_resources.get_distribution(self.module)
-        if dist.has_metadata("PKG-INFO"):
-            for line in dist.get_metadata_lines("PKG-INFO"):
-                key, value = line.split(':', 1)
-                ret[key] = value
-        if dist.has_metadata("requires.txt"):
-            ret["Dependencies"] = "\n" + dist.get_metadata("requires.txt")
-        return ret
-
-    def print_metadata(self, data=("Name", "Version", "Summary", "Home-page",
-                                   "Author", "Author-email", "License", "Platform", "Dependencies")):
-        for key in data:
-            if key in self.metadata:
-                print key + ": " + self.metadata[key]
-
-    ### methods for running
-
-    def command_args(self):
-        """additional arguments for the mozilla application"""
-        return map(os.path.expanduser, self.options.appArgs)
-
-    def runner_args(self):
-        """arguments to instantiate the runner class"""
-        return dict(cmdargs=self.command_args(),
-                    binary=self.options.binary,
-                    profile_args=self.profile_args())
-
-    def create_runner(self):
-        return self.runner_class.create(**self.runner_args())
-
-    def run(self):
-        runner = self.create_runner()
-        self.start(runner)
-        runner.cleanup()
-
-    def debugger_arguments(self):
-        """Get the debugger arguments
-
-        returns a 2-tuple of debugger arguments:
-            (debugger_arguments, interactive)
-
-        """
-        debug_args = self.options.debugger_args
-        if debug_args is not None:
-            debug_args = debug_args.split()
-        interactive = self.options.interactive
-        if self.options.debugger:
-            debug_args, interactive = debugger_arguments(self.options.debugger, debug_args, interactive)
-        return debug_args, interactive
-
-    def start(self, runner):
-        """Starts the runner and waits for the application to exit
-
-        It can also happen via a keyboard interrupt. It should be
-        overwritten to provide custom running of the runner instance.
-
-        """
-        # attach a debugger if specified
-        debug_args, interactive = self.debugger_arguments()
-        runner.start(debug_args=debug_args, interactive=interactive)
-        print 'Starting: ' + ' '.join(runner.command)
-        try:
-            runner.wait()
-        except KeyboardInterrupt:
-            runner.stop()
-
-
-def cli(args=sys.argv[1:]):
-    CLI(args).run()
-
-
-if __name__ == '__main__':
-    cli()
deleted file mode 100644
--- a/testing/mozbase/mozrunner/mozrunner/remote.py
+++ /dev/null
@@ -1,382 +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 ConfigParser
-import os
-import posixpath
-import re
-import signal
-from StringIO import StringIO
-import subprocess
-import sys
-import tempfile
-import time
-
-from mozdevice import DMError
-import mozfile
-import mozlog
-
-from .base import Runner
-
-
-__all__ = ['B2GRunner',
-           'RemoteRunner',
-           'remote_runners']
-
-
-class RemoteRunner(Runner):
-
-    def __init__(self, profile,
-                       devicemanager,
-                       clean_profile=None,
-                       process_class=None,
-                       env=None,
-                       remote_test_root=None,
-                       restore=True,
-                       **kwargs):
-
-        Runner.__init__(self, profile, clean_profile=clean_profile,
-                        process_class=process_class, env=env, **kwargs)
-        self.log = mozlog.getLogger('RemoteRunner')
-
-        self.dm = devicemanager
-        self.last_test = None
-        self.remote_test_root = remote_test_root or self.dm.getDeviceRoot()
-        self.log.info('using %s as test_root' % self.remote_test_root)
-        self.remote_profile = posixpath.join(self.remote_test_root, 'profile')
-        self.restore = restore
-        self.added_files = set()
-        self.backup_files = set()
-
-    def backup_file(self, remote_path):
-        if not self.restore:
-            return
-
-        if self.dm.fileExists(remote_path):
-            self.dm.shellCheckOutput(['dd', 'if=%s' % remote_path, 'of=%s.orig' % remote_path])
-            self.backup_files.add(remote_path)
-        else:
-            self.added_files.add(remote_path)
-
-    def check_for_crashes(self, last_test=None):
-        last_test = last_test or self.last_test
-        remote_dump_dir = posixpath.join(self.remote_profile, 'minidumps')
-        crashed = False
-
-        self.log.info("checking for crashes in '%s'" % remote_dump_dir)
-        if self.dm.dirExists(remote_dump_dir):
-            local_dump_dir = tempfile.mkdtemp()
-            self.dm.getDirectory(remote_dump_dir, local_dump_dir)
-
-            crashed = Runner.check_for_crashes(self, local_dump_dir, \
-                                               test_name=last_test)
-            mozfile.remove(local_dump_dir)
-            self.dm.removeDir(remote_dump_dir)
-
-        return crashed
-
-    def cleanup(self):
-        if not self.restore:
-            return
-
-        Runner.cleanup(self)
-
-        self.dm.remount()
-        # Restore the original profile
-        for added_file in self.added_files:
-            self.dm.removeFile(added_file)
-
-        for backup_file in self.backup_files:
-            if self.dm.fileExists('%s.orig' % backup_file):
-                self.dm.shellCheckOutput(['dd', 'if=%s.orig' % backup_file, 'of=%s' % backup_file])
-                self.dm.removeFile("%s.orig" % backup_file)
-
-        # Delete any bundled extensions
-        extension_dir = posixpath.join(self.remote_profile, 'extensions', 'staged')
-        if self.dm.dirExists(extension_dir):
-            for filename in self.dm.listFiles(extension_dir):
-                try:
-                    self.dm.removeDir(posixpath.join(self.bundles_dir, filename))
-                except DMError:
-                    pass
-        # Remove the test profile
-        self.dm.removeDir(self.remote_profile)
-
-
-class B2GRunner(RemoteRunner):
-
-    def __init__(self, profile, devicemanager, marionette=None, context_chrome=True,
-                 test_script=None, test_script_args=None,
-                 marionette_port=None, emulator=None, **kwargs):
-
-        remote_test_root = kwargs.get('remote_test_root')
-        if not remote_test_root:
-            kwargs['remote_test_root'] = '/data/local'
-        RemoteRunner.__init__(self, profile, devicemanager, **kwargs)
-        self.log = mozlog.getLogger('B2GRunner')
-
-        tmpfd, processLog = tempfile.mkstemp(suffix='pidlog')
-        os.close(tmpfd)
-        tmp_env = self.env or {}
-        self.env = { 'MOZ_CRASHREPORTER': '1',
-                     'MOZ_CRASHREPORTER_NO_REPORT': '1',
-                     'MOZ_HIDE_RESULTS_TABLE': '1',
-                     'MOZ_PROCESS_LOG': processLog,
-                     'NSPR_LOG_MODULES': 'signaling:5,mtransport:3',
-                     'R_LOG_LEVEL': '5',
-                     'R_LOG_DESTINATION': 'stderr',
-                     'R_LOG_VERBOSE': '1',
-                     'NO_EM_RESTART': '1', }
-        self.env.update(tmp_env)
-        self.last_test = "automation"
-
-        self.marionette = marionette
-        if self.marionette is not None:
-            if marionette_port is None:
-                marionette_port = self.marionette.port
-            elif self.marionette.port != marionette_port:
-                raise ValueError("Got a marionette object and a port but they don't match")
-
-            if emulator is None:
-                emulator = marionette.emulator
-            elif marionette.emulator != emulator:
-                raise ValueError("Got a marionette object and an emulator argument but they don't match")
-
-        self.marionette_port = marionette_port
-        self.emulator = emulator
-
-        self.context_chrome = context_chrome
-        self.test_script = test_script
-        self.test_script_args = test_script_args
-        self.remote_profiles_ini = '/data/b2g/mozilla/profiles.ini'
-        self.bundles_dir = '/system/b2g/distribution/bundles'
-
-    @property
-    def command(self):
-        cmd = [self.dm._adbPath]
-        if self.dm._deviceSerial:
-            cmd.extend(['-s', self.dm._deviceSerial])
-        cmd.append('shell')
-        for k, v in self.env.iteritems():
-            cmd.append("%s=%s" % (k, v))
-        cmd.append('/system/bin/b2g.sh')
-        return cmd
-
-    def start(self, timeout=None, outputTimeout=None):
-        self.timeout = timeout
-        self.outputTimeout = outputTimeout
-        self._setup_remote_profile()
-        # reboot device so it starts up with the proper profile
-        if not self.emulator:
-            self.dm.reboot(wait=True)
-            #wait for wlan to come up
-            if not self._wait_for_net():
-                raise Exception("network did not come up, please configure the network" +
-                                " prior to running before running the automation framework")
-
-        self.dm.shellCheckOutput(['stop', 'b2g'])
-
-        # For some reason user.js in the profile doesn't get picked up.
-        # Manually copy it over to prefs.js. See bug 1009730 for more details.
-        self.dm.moveTree(posixpath.join(self.remote_profile, 'user.js'),
-                         posixpath.join(self.remote_profile, 'prefs.js'))
-
-        self.kp_kwargs.update({'stream': sys.stdout,
-                               'processOutputLine': self.on_output,
-                               'onTimeout': self.on_timeout,})
-        self.process_handler = self.process_class(self.command, **self.kp_kwargs)
-        self.process_handler.run(timeout=timeout, outputTimeout=outputTimeout)
-
-        # Set up port forwarding again for Marionette, since any that
-        # existed previously got wiped out by the reboot.
-        if self.emulator is None:
-            subprocess.Popen([self.dm._adbPath,
-                              'forward',
-                              'tcp:%s' % self.marionette_port,
-                              'tcp:2828']).communicate()
-
-        if self.marionette is not None:
-            self.start_marionette()
-
-        if self.test_script is not None:
-            self.start_tests()
-
-    def start_marionette(self):
-        self.marionette.wait_for_port()
-
-        # start a marionette session
-        session = self.marionette.start_session()
-        if 'b2g' not in session:
-            raise Exception("bad session value %s returned by start_session" % session)
-
-        if self.marionette.emulator:
-            # Disable offline status management (bug 777145), otherwise the network
-            # will be 'offline' when the mochitests start.  Presumably, the network
-            # won't be offline on a real device, so we only do this for emulators.
-            self.marionette.set_context(self.marionette.CONTEXT_CHROME)
-            self.marionette.execute_script("""
-                Components.utils.import("resource://gre/modules/Services.jsm");
-                Services.io.manageOfflineStatus = false;
-                Services.io.offline = false;
-                """)
-
-        if self.context_chrome:
-            self.marionette.set_context(self.marionette.CONTEXT_CHROME)
-        else:
-            self.marionette.set_context(self.marionette.CONTEXT_CONTENT)
-
-
-    def start_tests(self):
-        #self.marionette.execute_script("""
-        #    var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
-        #    var homeUrl = prefs.getCharPref("browser.homescreenURL");
-        #    dump(homeURL + "\n");
-        #""")
-
-        # run the script that starts the tests
-        if os.path.isfile(self.test_script):
-            script = open(self.test_script, 'r')
-            self.marionette.execute_script(script.read(), script_args=self.test_script_args)
-            script.close()
-        elif isinstance(self.test_script, basestring):
-            self.marionette.execute_script(self.test_script, script_args=self.test_script_args)
-
-    def on_output(self, line):
-        match = re.findall(r"TEST-START \| ([^\s]*)", line)
-        if match:
-            self.last_test = match[-1]
-
-    def on_timeout(self):
-        self.dm.killProcess('/system/b2g/b2g', sig=signal.SIGABRT)
-
-        msg = "%s | application timed out after %s seconds"
-
-        if self.timeout:
-            timeout = self.timeout
-        else:
-            timeout = self.outputTimeout
-            msg = "%s with no output" % msg
-
-        self.log.testFail(msg % (self.last_test, timeout))
-        self.check_for_crashes()
-
-    def _get_device_status(self, serial=None):
-        # If we know the device serial number, we look for that,
-        # otherwise we use the (presumably only) device shown in 'adb devices'.
-        serial = serial or self.dm._deviceSerial
-        status = 'unknown'
-
-        proc = subprocess.Popen([self.dm._adbPath, 'devices'], stdout=subprocess.PIPE)
-        line = proc.stdout.readline()
-        while line != '':
-            result = re.match('(.*?)\t(.*)', line)
-            if result:
-                thisSerial = result.group(1)
-                if not serial or thisSerial == serial:
-                    serial = thisSerial
-                    status = result.group(2)
-                    break
-            line = proc.stdout.readline()
-        return (serial, status)
-
-    def _wait_for_net(self):
-        active = False
-        time_out = 0
-        while not active and time_out < 40:
-            proc = subprocess.Popen([self.dm._adbPath, 'shell', '/system/bin/netcfg'], stdout=subprocess.PIPE)
-            proc.stdout.readline() # ignore first line
-            line = proc.stdout.readline()
-            while line != "":
-                if (re.search(r'UP\s+(?:[0-9]{1,3}\.){3}[0-9]{1,3}', line)):
-                    active = True
-                    break
-                line = proc.stdout.readline()
-            time_out += 1
-            time.sleep(1)
-        return active
-
-    def _setup_remote_profile(self):
-        """Copy profile and update the remote profiles.ini to point to the new profile"""
-        self.dm.remount()
-
-        # copy the profile to the device.
-        if self.dm.dirExists(self.remote_profile):
-            self.dm.shellCheckOutput(['rm', '-r', self.remote_profile])
-
-        try:
-            self.dm.pushDir(self.profile.profile, self.remote_profile)
-        except DMError:
-            self.log.error("Automation Error: Unable to copy profile to device.")
-            raise
-
-        extension_dir = os.path.join(self.profile.profile, 'extensions', 'staged')
-        if os.path.isdir(extension_dir):
-            # Copy the extensions to the B2G bundles dir.
-            # need to write to read-only dir
-            for filename in os.listdir(extension_dir):
-                fpath = os.path.join(self.bundles_dir, filename)
-                if self.dm.fileExists(fpath):
-                    self.dm.shellCheckOutput(['rm', '-rf', fpath])
-            try:
-                self.dm.pushDir(extension_dir, self.bundles_dir)
-            except DMError:
-                self.log.error("Automation Error: Unable to copy extensions to device.")
-                raise
-
-        if not self.dm.fileExists(self.remote_profiles_ini):
-            raise DMError("The profiles.ini file '%s' does not exist on the device" % self.remote_profiles_ini)
-
-        local_profiles_ini = tempfile.NamedTemporaryFile()
-        self.dm.getFile(self.remote_profiles_ini, local_profiles_ini.name)
-
-        config = ProfileConfigParser()
-        config.read(local_profiles_ini.name)
-        for section in config.sections():
-            if 'Profile' in section:
-                config.set(section, 'IsRelative', 0)
-                config.set(section, 'Path', self.remote_profile)
-
-        new_profiles_ini = tempfile.NamedTemporaryFile()
-        config.write(open(new_profiles_ini.name, 'w'))
-
-        self.backup_file(self.remote_profiles_ini)
-        self.dm.pushFile(new_profiles_ini.name, self.remote_profiles_ini)
-
-    def cleanup(self):
-        RemoteRunner.cleanup(self)
-        if getattr(self.marionette, 'instance', False):
-            self.marionette.instance.close()
-        del self.marionette
-
-
-class ProfileConfigParser(ConfigParser.RawConfigParser):
-    """Class to create profiles.ini config files
-
-    Subclass of RawConfigParser that outputs .ini files in the exact
-    format expected for profiles.ini, which is slightly different
-    than the default format.
-
-    """
-
-    def optionxform(self, optionstr):
-        return optionstr
-
-    def write(self, fp):
-        if self._defaults:
-            fp.write("[%s]\n" % ConfigParser.DEFAULTSECT)
-            for (key, value) in self._defaults.items():
-                fp.write("%s=%s\n" % (key, str(value).replace('\n', '\n\t')))
-            fp.write("\n")
-        for section in self._sections:
-            fp.write("[%s]\n" % section)
-            for (key, value) in self._sections[section].items():
-                if key == "__name__":
-                    continue
-                if (value is not None) or (self._optcre == self.OPTCRE):
-                    key = "=".join((key, str(value).replace('\n', '\n\t')))
-                fp.write("%s\n" % (key))
-            fp.write("\n")
-
-remote_runners = {'b2g': 'B2GRunner',
-                  'fennec': 'FennecRunner'}
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozrunner/mozrunner/runners.py
@@ -0,0 +1,155 @@
+# 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/.
+
+"""
+This module contains a set of shortcut methods that create runners for commonly
+used Mozilla applications, such as Firefox or B2G emulator.
+"""
+
+from .application import get_app_context
+from .base import DeviceRunner, GeckoRuntimeRunner
+from .devices import Emulator
+
+
+def Runner(*args, **kwargs):
+    """
+    Create a generic GeckoRuntime runner.
+
+    :param binary: Path to binary.
+    :param cmdargs: Arguments to pass into binary.
+    :param profile: Profile object to use.
+    :param env: Environment variables to pass into the gecko process.
+    :param clean_profile: If True, restores profile back to original state.
+    :param process_class: Class used to launch the binary.
+    :param process_args: Arguments to pass into process_class.
+    :param symbols_path: Path to symbol files used for crash analysis.
+    :returns: A generic GeckoRuntimeRunner.
+    """
+    return GeckoRuntimeRunner(*args, **kwargs)
+
+
+def FirefoxRunner(*args, **kwargs):
+    """
+    Create a desktop Firefox runner.
+
+    :param binary: Path to Firefox binary.
+    :param cmdargs: Arguments to pass into binary.
+    :param profile: Profile object to use.
+    :param env: Environment variables to pass into the gecko process.
+    :param clean_profile: If True, restores profile back to original state.
+    :param process_class: Class used to launch the binary.
+    :param process_args: Arguments to pass into process_class.
+    :param symbols_path: Path to symbol files used for crash analysis.
+    :returns: A GeckoRuntimeRunner for Firefox.
+    """
+    kwargs['app_ctx'] = get_app_context('firefox')()
+    return GeckoRuntimeRunner(*args, **kwargs)
+
+
+def ThunderbirdRunner(*args, **kwargs):
+    """
+    Create a desktop Thunderbird runner.
+
+    :param binary: Path to Thunderbird binary.
+    :param cmdargs: Arguments to pass into binary.
+    :param profile: Profile object to use.
+    :param env: Environment variables to pass into the gecko process.
+    :param clean_profile: If True, restores profile back to original state.
+    :param process_class: Class used to launch the binary.
+    :param process_args: Arguments to pass into process_class.
+    :param symbols_path: Path to symbol files used for crash analysis.
+    :returns: A GeckoRuntimeRunner for Thunderbird.
+    """
+    kwargs['app_ctx'] = get_app_context('thunderbird')()
+    return GeckoRuntimeRunner(*args, **kwargs)
+
+
+def MetroRunner(*args, **kwargs):
+    """
+    Create a Windows metro Firefox runner.
+
+    :param binary: Path to metro Firefox binary.
+    :param cmdargs: Arguments to pass into binary.
+    :param profile: Profile object to use.
+    :param env: Environment variables to pass into the gecko process.
+    :param clean_profile: If True, restores profile back to original state.
+    :param process_class: Class used to launch the binary.
+    :param process_args: Arguments to pass into process_class.
+    :param symbols_path: Path to symbol files used for crash analysis.
+    :returns: A GeckoRuntimeRunner for metro Firefox.
+    """
+    kwargs['app_ctx'] = get_app_context('metro')()
+    return GeckoRuntimeRunner(*args, **kwargs)
+
+
+def B2GDesktopRunner(*args, **kwargs):
+    """
+    Create a B2G desktop runner.
+
+    :param binary: Path to b2g desktop binary.
+    :param cmdargs: Arguments to pass into binary.
+    :param profile: Profile object to use.
+    :param env: Environment variables to pass into the gecko process.
+    :param clean_profile: If True, restores profile back to original state.
+    :param process_class: Class used to launch the binary.
+    :param process_args: Arguments to pass into process_class.
+    :param symbols_path: Path to symbol files used for crash analysis.
+    :returns: A GeckoRuntimeRunner for b2g desktop.
+    """
+    # There is no difference between a generic and b2g desktop runner,
+    # but expose a separate entry point for clarity.
+    return Runner(*args, **kwargs)
+
+
+def B2GEmulatorRunner(arch='arm',
+                      b2g_home=None,
+                      adb_path=None,
+                      logdir=None,
+                      binary=None,
+                      no_window=None,
+                      resolution=None,
+                      sdcard=None,
+                      userdata=None,
+                      **kwargs):
+    """
+    Create a B2G emulator runner.
+
+    :param arch: The architecture of the emulator, either 'arm' or 'x86'. Defaults to 'arm'.
+    :param b2g_home: Path to root B2G repository.
+    :param logdir: Path to save logfiles such as logcat and qemu output.
+    :param no_window: Run emulator without a window.
+    :param resolution: Screen resolution to set emulator to, e.g '800x1000'.
+    :param sdcard: Path to local emulated sdcard storage.
+    :param userdata: Path to custom userdata image.
+    :param profile: Profile object to use.
+    :param env: Environment variables to pass into the b2g.sh process.
+    :param clean_profile: If True, restores profile back to original state.
+    :param process_class: Class used to launch the b2g.sh process.
+    :param process_args: Arguments to pass into the b2g.sh process.
+    :param symbols_path: Path to symbol files used for crash analysis.
+    :returns: A DeviceRunner for B2G emulators.
+    """
+    kwargs['app_ctx'] = get_app_context('b2g')(b2g_home, adb_path=adb_path)
+    device_args = { 'app_ctx': kwargs['app_ctx'],
+                    'arch': arch,
+                    'binary': binary,
+                    'resolution': resolution,
+                    'sdcard': sdcard,
+                    'userdata': userdata,
+                    'no_window': no_window,
+                    'logdir': logdir }
+    return DeviceRunner(device_class=Emulator,
+                        device_args=device_args,
+                        **kwargs)
+
+
+runners = {
+ 'default': Runner,
+ 'b2g_desktop': B2GDesktopRunner,
+ 'b2g_emulator': B2GEmulatorRunner,
+ 'firefox': FirefoxRunner,
+ 'metro': MetroRunner,
+ 'thunderbird': ThunderbirdRunner,
+}
+
--- a/testing/mozbase/mozrunner/mozrunner/utils.py
+++ b/testing/mozbase/mozrunner/mozrunner/utils.py
@@ -1,19 +1,20 @@
 #!/usr/bin/env python
 
 # 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/.
 
 """Utility functions for mozrunner"""
 
-__all__ = ['findInPath', 'get_metadata_from_egg']
+__all__ = ['findInPath', 'get_metadata_from_egg', 'uses_marionette']
 
 
+from functools import wraps
 import mozinfo
 import os
 import sys
 
 
 ### python package method metadata by introspection
 try:
     import pkg_resources
@@ -58,8 +59,38 @@ def findInPath(fileName, path=os.environ
             return os.path.join(dir, fileName)
         if mozinfo.isWin:
             if os.path.isfile(os.path.join(dir, fileName + ".exe")):
                 return os.path.join(dir, fileName + ".exe")
 
 if __name__ == '__main__':
     for i in sys.argv[1:]:
         print findInPath(i)
+
+
+def _find_marionette_in_args(*args, **kwargs):
+    try:
+        m = [a for a in args + tuple(kwargs.values()) if hasattr(a, 'session')][0]
+    except IndexError:
+        print("Can only apply decorator to function using a marionette object")
+        raise
+    return m
+
+def uses_marionette(func):
+    """Decorator which creates a marionette session and deletes it
+    afterwards if one doesn't already exist.
+    """
+    @wraps(func)
+    def _(*args, **kwargs):
+        m = _find_marionette_in_args(*args, **kwargs)
+        delete_session = False
+        if not m.session:
+            delete_session = True
+            m.start_session()
+
+        m.set_context(m.CONTEXT_CHROME)
+        ret = func(*args, **kwargs)
+
+        if delete_session:
+            m.delete_session()
+
+        return ret
+    return _
--- a/testing/mozbase/mozrunner/setup.py
+++ b/testing/mozbase/mozrunner/setup.py
@@ -1,22 +1,22 @@
 # 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 sys
-from setuptools import setup
+from setuptools import setup, find_packages
 
 PACKAGE_NAME = 'mozrunner'
-PACKAGE_VERSION = '5.37'
+PACKAGE_VERSION = '6.0'
 
 desc = """Reliable start/stop/configuration of Mozilla Applications (Firefox, Thunderbird, etc.)"""
 
 deps = ['mozcrash >= 0.11',
-        'mozdevice >= 0.30',
+        'mozdevice >= 0.37',
         'mozfile >= 1.0',
         'mozinfo >= 0.7',
         'mozlog >= 1.5',
         'mozprocess >= 0.17',
         'mozprofile >= 0.18',
        ]
 
 # we only support python 2 right now
@@ -34,17 +34,17 @@ setup(name=PACKAGE_NAME,
                    'Programming Language :: Python',
                    'Topic :: Software Development :: Libraries :: Python Modules',
                    ],
       keywords='mozilla',
       author='Mozilla Automation and Tools team',
       author_email='tools@lists.mozilla.org',
       url='https://wiki.mozilla.org/Auto-tools/Projects/Mozbase',
       license='MPL 2.0',
-      packages=['mozrunner'],
+      packages=find_packages(),
       package_data={'mozrunner': [
             'resources/metrotestharness.exe'
       ]},
       zip_safe=False,
       install_requires = deps,
       entry_points="""
       # -*- Entry points: -*-
       [console_scripts]
--- a/testing/profiles/prefs_b2g_unittest.js
+++ b/testing/profiles/prefs_b2g_unittest.js
@@ -1,10 +1,10 @@
 // Prefs specific to b2g mochitests
 
+user_pref("b2g.system_manifest_url","app://test-container.gaiamobile.org/manifest.webapp");
 user_pref("b2g.system_startup_url","app://test-container.gaiamobile.org/index.html");
-user_pref("b2g.system_manifest_url","app://test-container.gaiamobile.org/manifest.webapp");
-user_pref("dom.mozBrowserFramesEnabled", "%(OOP)s");
+user_pref("dom.ipc.browser_frames.oop_by_default", false);
 user_pref("dom.ipc.tabs.disabled", false);
-user_pref("dom.ipc.browser_frames.oop_by_default", false);
+user_pref("dom.mozBrowserFramesEnabled", "%(OOP)s");
 user_pref("dom.mozBrowserFramesWhitelist","app://test-container.gaiamobile.org,http://mochi.test:8888");
+user_pref("dom.testing.datastore_enabled_for_hosted_apps", true);
 user_pref("marionette.force-local", true);
-user_pref("dom.testing.datastore_enabled_for_hosted_apps", true);
--- a/testing/tps/tps/firefoxrunner.py
+++ b/testing/tps/tps/firefoxrunner.py
@@ -74,22 +74,12 @@ class TPSFirefoxRunner(object):
         if profile is None:
             profile = Profile()
         self.profile = profile
 
         if self.binary is None and self.url:
             self.binary = self.download_build()
 
         if self.runner is None:
-            self.runner = FirefoxRunner(self.profile, binary=self.binary)
-
-        self.runner.profile = self.profile
-
-        if env is not None:
-            self.runner.env.update(env)
+            self.runner = FirefoxRunner(profile=self.profile, binary=self.binary, env=env, cmdargs=args)
 
-        if args is not None:
-            self.runner.cmdargs = copy.copy(args)
-
-        self.runner.start()
-        returncode = self.runner.wait(timeout)
-
-        return returncode
+        self.runner.start(timeout=timeout)
+        return self.runner.wait()
--- a/testing/xpcshell/mach_commands.py
+++ b/testing/xpcshell/mach_commands.py
@@ -351,17 +351,17 @@ class B2GXPCShellRunner(MozbuildObject):
         import runtestsb2g
         parser = runtestsb2g.B2GOptions()
         options, args = parser.parse_args([])
 
         options.b2g_path = b2g_home
         options.busybox = busybox or os.environ.get('BUSYBOX')
         options.localLib = self.bin_dir
         options.localBin = self.bin_dir
-        options.logcat_dir = self.xpcshell_dir
+        options.logdir = self.xpcshell_dir
         options.manifest = os.path.join(self.xpcshell_dir, 'xpcshell_b2g.ini')
         options.mozInfo = os.path.join(self.topobjdir, 'mozinfo.json')
         options.objdir = self.topobjdir
         options.symbolsPath = os.path.join(self.distdir, 'crashreporter-symbols'),
         options.testingModulesDir = os.path.join(self.tests_dir, 'modules')
         options.testsRootDir = self.xpcshell_dir
         options.testPath = test_path
         options.use_device_libs = True
--- a/testing/xpcshell/remotexpcshelltests.py
+++ b/testing/xpcshell/remotexpcshelltests.py
@@ -121,23 +121,23 @@ class RemoteXPCShellTestThread(xpcshell.
               self.xpcsCmd]
 
     def testTimeout(self, test_file, proc):
         self.timedout = True
         if not self.retry:
             self.log.error("TEST-UNEXPECTED-FAIL | %s | Test timed out" % test_file)
         self.kill(proc)
 
-    def launchProcess(self, cmd, stdout, stderr, env, cwd):
+    def launchProcess(self, cmd, stdout, stderr, env, cwd, timeout=None):
         self.timedout = False
         cmd.insert(1, self.remoteHere)
         outputFile = "xpcshelloutput"
         with open(outputFile, 'w+') as f:
             try:
-                self.shellReturnCode = self.device.shell(cmd, f)
+                self.shellReturnCode = self.device.shell(cmd, f, timeout=timeout+10)
             except devicemanager.DMError as e:
                 if self.timedout:
                     # If the test timed out, there is a good chance the SUTagent also
                     # timed out and failed to return a return code, generating a
                     # DMError. Ignore the DMError to simplify the error report.
                     self.shellReturnCode = None
                     pass
                 else:
--- a/testing/xpcshell/runtestsb2g.py
+++ b/testing/xpcshell/runtestsb2g.py
@@ -14,20 +14,20 @@ from mozdevice import devicemanagerADB, 
 
 DEVICE_TEST_ROOT = '/data/local/tests'
 
 
 from marionette import Marionette
 
 class B2GXPCShellTestThread(RemoteXPCShellTestThread):
     # Overridden
-    def launchProcess(self, cmd, stdout, stderr, env, cwd):
+    def launchProcess(self, cmd, stdout, stderr, env, cwd, timeout=None):
         try:
             # This returns 1 even when tests pass - hardcode returncode to 0 (bug 773703)
-            outputFile = RemoteXPCShellTestThread.launchProcess(self, cmd, stdout, stderr, env, cwd)
+            outputFile = RemoteXPCShellTestThread.launchProcess(self, cmd, stdout, stderr, env, cwd, timeout=timeout)
             self.shellReturnCode = 0
         except DMError:
             self.shellReturnCode = -1
             outputFile = "xpcshelloutput"
             f = open(outputFile, "a")
             f.write("\n%s" % traceback.format_exc())
             f.close()
         return outputFile
@@ -121,20 +121,20 @@ class B2GOptions(RemoteXPCShellOptions):
                         dest='use_device_libs',
                         help="Don't push .so's")
         defaults['use_device_libs'] = False
         self.add_option("--gecko-path", action="store",
                         type="string", dest="geckoPath",
                         help="the path to a gecko distribution that should "
                         "be installed on the emulator prior to test")
         defaults["geckoPath"] = None
-        self.add_option("--logcat-dir", action="store",
-                        type="string", dest="logcat_dir",
-                        help="directory to store logcat dump files")
-        defaults["logcat_dir"] = None
+        self.add_option("--logdir", action="store",
+                        type="string", dest="logdir",
+                        help="directory to store log files")
+        defaults["logdir"] = None
         self.add_option('--busybox', action='store',
                         type='string', dest='busybox',
                         help="Path to busybox binary to install on device")
         defaults['busybox'] = None
 
         defaults["remoteTestRoot"] = DEVICE_TEST_ROOT
         defaults['dm_trans'] = 'adb'
         defaults['debugger'] = None
@@ -144,33 +144,33 @@ class B2GOptions(RemoteXPCShellOptions):
 
     def verifyRemoteOptions(self, options):
         if options.b2g_path is None:
             self.error("Need to specify a --b2gpath")
 
         if options.geckoPath and not options.emulator:
             self.error("You must specify --emulator if you specify --gecko-path")
 
-        if options.logcat_dir and not options.emulator:
-            self.error("You must specify --emulator if you specify --logcat-dir")
+        if options.logdir and not options.emulator:
+            self.error("You must specify --emulator if you specify --logdir")
         return RemoteXPCShellOptions.verifyRemoteOptions(self, options)
 
 def run_remote_xpcshell(parser, options, args):
     options = parser.verifyRemoteOptions(options)
 
     # Create the Marionette instance
     kwargs = {}
     if options.emulator:
         kwargs['emulator'] = options.emulator
         if options.no_window:
             kwargs['noWindow'] = True
         if options.geckoPath:
             kwargs['gecko_path'] = options.geckoPath
-        if options.logcat_dir:
-            kwargs['logcat_dir'] = options.logcat_dir
+        if options.logdir:
+            kwargs['logdir'] = options.logdir
         if options.busybox:
             kwargs['busybox'] = options.busybox
         if options.symbolsPath:
             kwargs['symbols_path'] = options.symbolsPath
     if options.b2g_path:
         kwargs['homedir'] = options.emu_path or options.b2g_path
     if options.address:
         host, port = options.address.split(':')
--- a/testing/xpcshell/runxpcshelltests.py
+++ b/testing/xpcshell/runxpcshelltests.py
@@ -227,21 +227,23 @@ class XPCShellTestThread(Thread):
                     break
                 self.process_line(line)
 
             if self.saw_proc_start and not self.saw_proc_end:
                 self.has_failure_output = True
 
         return proc.communicate()
 
-    def launchProcess(self, cmd, stdout, stderr, env, cwd):
+    def launchProcess(self, cmd, stdout, stderr, env, cwd, timeout=None):
         """
           Simple wrapper to launch a process.
           On a remote system, this is more complex and we need to overload this function.
         """
+        # timeout is needed by remote and b2g xpcshell to extend the
+        # devicemanager.shell() timeout. It is not used in this function.
         if HAVE_PSUTIL:
             popen_func = psutil.Popen
         else:
             popen_func = Popen
         proc = popen_func(cmd, stdout=stdout, stderr=stderr,
                     env=env, cwd=cwd)
         return proc
 
@@ -618,17 +620,17 @@ class XPCShellTestThread(Thread):
 
         try:
             self.log.info("TEST-INFO | %s | running test ..." % name)
             if self.verbose:
                 self.logCommand(name, completeCmd, test_dir)
 
             startTime = time.time()
             proc = self.launchProcess(completeCmd,
-                stdout=self.pStdout, stderr=self.pStderr, env=self.env, cwd=test_dir)
+                stdout=self.pStdout, stderr=self.pStderr, env=self.env, cwd=test_dir, timeout=testTimeoutInterval)
 
             if self.interactive:
                 self.log.info("TEST-INFO | %s | Process ID: %d" % (name, proc.pid))
 
             stdout, stderr = self.communicate(proc)
 
             if self.interactive:
                 # Not sure what else to do here...
--- a/toolkit/components/maintenanceservice/maintenanceservice.cpp
+++ b/toolkit/components/maintenanceservice/maintenanceservice.cpp
@@ -10,16 +10,20 @@
 
 #include "serviceinstall.h"
 #include "maintenanceservice.h"
 #include "servicebase.h"
 #include "workmonitor.h"
 #include "uachelper.h"
 #include "updatehelper.h"
 
+// Link w/ subsystem window so we don't get a console when executing
+// this binary through the installer.
+#pragma comment(linker, "/SUBSYSTEM:windows")
+
 SERVICE_STATUS gSvcStatus = { 0 }; 
 SERVICE_STATUS_HANDLE gSvcStatusHandle = nullptr; 
 HANDLE gWorkDoneEvent = nullptr;
 HANDLE gThread = nullptr;
 bool gServiceControlStopping = false;
 
 // logs are pretty small, about 20 lines, so 10 seems reasonable.
 #define LOGS_TO_KEEP 10
--- a/toolkit/components/protobuf/moz.build
+++ b/toolkit/components/protobuf/moz.build
@@ -48,8 +48,12 @@ FINAL_LIBRARY = 'toolkitcomps'
 DEFINES['GOOGLE_PROTOBUF_NO_RTTI'] = True
 
 # Suppress warnings in third-party code.
 if CONFIG['GNU_CXX']:
     CXXFLAGS += [
         '-Wno-null-conversion',
         '-Wno-sign-compare',
     ]
+elif CONFIG['_MSC_VER']:
+    CXXFLAGS += [
+        '-wd4099', # mismatched class/struct tags
+    ]