Merge m-c to b2ginbound, a=merge
authorWes Kocher <wkocher@mozilla.com>
Mon, 15 Jun 2015 18:35:18 -0700
changeset 279691 f42cf1d7c1bb44a806ff6a5ba71ef2afaf699666
parent 279690 454b81979ce820175c7cb8e044e3915ad3e79c30 (current diff)
parent 279688 ce863f9d8864c5014f8f39d295649d6c4e3ffbf5 (diff)
child 279692 a7c7b9345df78324da69a3ad0e4c2b9a47a8e136
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to b2ginbound, a=merge
browser/devtools/performance/test/browser_timeline-filters.js
--- a/browser/components/dirprovider/DirectoryProvider.cpp
+++ b/browser/components/dirprovider/DirectoryProvider.cpp
@@ -201,36 +201,54 @@ AppendDistroSearchDirs(nsIProperties* aD
       }
     }
   }
 }
 
 NS_IMETHODIMP
 DirectoryProvider::GetFiles(const char *aKey, nsISimpleEnumerator* *aResult)
 {
+  /**
+   * We want to preserve the following order, since the search service loads
+   * engines in first-loaded-wins order.
+   *   - distro search plugin locations (Loaded by the search service using
+   *     NS_APP_DISTRIBUTION_SEARCH_DIR_LIST)
+   *
+   *   - engines shipped in chrome (Loaded from jar files by the search
+   *     service)
+   *
+   *   Then other locations, from NS_APP_SEARCH_DIR_LIST:
+   *   - extension search plugin locations (prepended below using
+   *     NS_NewUnionEnumerator)
+   *   - user search plugin locations (profile)
+   *   - app search plugin location (shipped engines)
+   */
+
   nsresult rv;
 
+  if (!strcmp(aKey, NS_APP_DISTRIBUTION_SEARCH_DIR_LIST)) {
+    nsCOMPtr<nsIProperties> dirSvc
+      (do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID));
+    if (!dirSvc)
+      return NS_ERROR_FAILURE;
+
+    nsCOMArray<nsIFile> distroFiles;
+    AppendDistroSearchDirs(dirSvc, distroFiles);
+
+    return NS_NewArrayEnumerator(aResult, distroFiles);
+  }
+
   if (!strcmp(aKey, NS_APP_SEARCH_DIR_LIST)) {
     nsCOMPtr<nsIProperties> dirSvc
       (do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID));
     if (!dirSvc)
       return NS_ERROR_FAILURE;
 
     nsCOMArray<nsIFile> baseFiles;
 
-    /**
-     * We want to preserve the following order, since the search service loads
-     * engines in first-loaded-wins order.
-     *   - extension search plugin locations (prepended below using
-     *     NS_NewUnionEnumerator)
-     *   - distro search plugin locations
-     *   - user search plugin locations (profile)
-     *   - app search plugin location (shipped engines)
-     */
-    AppendDistroSearchDirs(dirSvc, baseFiles);
     AppendFileKey(NS_APP_USER_SEARCH_DIR, dirSvc, baseFiles);
     AppendFileKey(NS_APP_SEARCH_DIR, dirSvc, baseFiles);
 
     nsCOMPtr<nsISimpleEnumerator> baseEnum;
     rv = NS_NewArrayEnumerator(getter_AddRefs(baseEnum), baseFiles);
     if (NS_FAILED(rv))
       return rv;
 
--- a/browser/devtools/commandline/test/browser.ini
+++ b/browser/devtools/commandline/test/browser.ini
@@ -65,16 +65,17 @@ support-files =
   browser_cmd_jsb_script.jsi
 [browser_cmd_listen.js]
 [browser_cmd_media.js]
 support-files =
   browser_cmd_media.html
 [browser_cmd_pagemod_export.js]
 support-files =
   browser_cmd_pagemod_export.html
+[browser_cmd_paintflashing.js]
 [browser_cmd_pref1.js]
 [browser_cmd_pref2.js]
 [browser_cmd_pref3.js]
 [browser_cmd_restart.js]
 [browser_cmd_rulers.js]
 [browser_cmd_screenshot.js]
 support-files =
   browser_cmd_screenshot.html
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_paintflashing.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+* http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the paintflashing command correctly sets its state.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/commandline/" +
+                 "test/browser_cmd_cookie.html";
+
+function test() {
+  return Task.spawn(testTask).then(finish, helpers.handleError);
+}
+
+let tests = {
+  testInput: function(options) {
+    let toggleCommand = options.requisition.system.commands.get("paintflashing toggle");
+
+    let actions = [
+      {
+        command: "paintflashing on",
+        isChecked: true,
+        label: "checked after on"
+      },
+      {
+        command: "paintflashing off",
+        isChecked: false,
+        label: "unchecked after off"
+      },
+      {
+        command: "paintflashing toggle",
+        isChecked: true,
+        label: "checked after toggle"
+      },
+      {
+        command: "paintflashing toggle",
+        isChecked: false,
+        label: "unchecked after toggle"
+      }
+    ];
+
+    return helpers.audit(options, actions.map(spec => ({
+      setup: spec.command,
+      exec: {},
+      post: () => is(toggleCommand.state.isChecked(), spec.isChecked, spec.label)
+    })));
+  },
+};
+
+function* testTask() {
+  let options = yield helpers.openTab(TEST_URI);
+  yield helpers.openToolbar(options);
+
+  yield helpers.runTests(options, tests);
+
+  yield helpers.closeToolbar(options);
+  yield helpers.closeTab(options);
+}
--- a/browser/devtools/performance/modules/logic/waterfall-utils.js
+++ b/browser/devtools/performance/modules/logic/waterfall-utils.js
@@ -2,33 +2,38 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 /**
  * Utility functions for collapsing markers into a waterfall.
  */
 
-loader.lazyRequireGetter(this, "getBlueprintFor",
-  "devtools/performance/marker-utils", true);
+loader.lazyRequireGetter(this, "MarkerUtils",
+  "devtools/performance/marker-utils");
 
 /**
  * Collapses markers into a tree-like structure.
  * @param object markerNode
  * @param array markersList
+ * @param array filter
  */
-function collapseMarkersIntoNode({ markerNode, markersList }) {
+function collapseMarkersIntoNode({ markerNode, markersList, filter }) {
   let { getCurrentParentNode, collapseMarker, addParentNode, popParentNode } = createParentNodeFactory(markerNode);
 
   for (let i = 0, len = markersList.length; i < len; i++) {
     let curr = markersList[i];
 
+    // If this marker type should not be displayed, just skip
+    if (!MarkerUtils.isMarkerValid(curr, filter)) {
+      continue;
+    }
+
     let parentNode = getCurrentParentNode();
-    let blueprint = getBlueprintFor(curr);
-
+    let blueprint = MarkerUtils.getBlueprintFor(curr);
     let collapse = blueprint.collapseFunc || (() => null);
     let peek = distance => markersList[i + distance];
 
     let collapseInfo = collapse(parentNode, curr, peek);
     if (collapseInfo) {
       let { collapse, toParent, finalize } = collapseInfo;
 
       // If `toParent` is an object, use it as the next parent marker
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -131,14 +131,15 @@ support-files =
 [browser_profiler_tree-view-03.js]
 [browser_profiler_tree-view-04.js]
 [browser_profiler_tree-view-05.js]
 [browser_profiler_tree-view-06.js]
 [browser_profiler_tree-view-07.js]
 [browser_profiler_tree-view-08.js]
 [browser_profiler_tree-view-09.js]
 [browser_profiler_tree-view-10.js]
-[browser_timeline-filters.js]
+[browser_timeline-filters-01.js]
+[browser_timeline-filters-02.js]
 [browser_timeline-waterfall-background.js]
 [browser_timeline-waterfall-generic.js]
 [browser_timeline-waterfall-rerender.js]
 [browser_timeline-waterfall-sidebar.js]
 skip-if = os == 'linux' # Bug 1161817
rename from browser/devtools/performance/test/browser_timeline-filters.js
rename to browser/devtools/performance/test/browser_timeline-filters-01.js
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_timeline-filters-02.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests markers filtering mechanism.
+ */
+
+const URL = EXAMPLE_URL + "doc_innerHTML.html";
+
+function* spawnTest() {
+  let { panel } = yield initPerformance(URL);
+  let { $, $$, EVENTS, PerformanceController, OverviewView, WaterfallView } = panel.panelWin;
+
+  yield startRecording(panel);
+  ok(true, "Recording has started.");
+
+  yield waitUntil(() => {
+    let markers = PerformanceController.getCurrentRecording().getMarkers();
+    return markers.some(m => m.name == "Parse HTML") &&
+           markers.some(m => m.name == "Javascript");
+  });
+
+  let waterfallRendered = WaterfallView.once(EVENTS.WATERFALL_RENDERED);
+  yield stopRecording(panel);
+
+  $("#filter-button").click();
+  let filterJS = $("menuitem[marker-type=Javascript]");
+
+  yield waterfallRendered;
+
+  ok($(".waterfall-marker-bar[type=Javascript]"), "Found at least one 'Javascript' marker");
+  ok(!$(".waterfall-marker-bar[type='Parse HTML']"), "Found no Parse HTML markers as they are nested still");
+
+  EventUtils.synthesizeMouseAtCenter(filterJS, {type: "mouseup"}, panel.panelWin);
+  yield Promise.all([
+    WaterfallView.once(EVENTS.WATERFALL_RENDERED),
+    once(filterJS, "command")
+  ]);
+
+  ok(!$(".waterfall-marker-bar[type=Javascript]"), "Javascript markers are all hidden.");
+  ok($(".waterfall-marker-bar[type='Parse HTML']"),
+    "Found at least one 'Parse HTML' marker still visible after hiding JS markers");
+
+  yield teardown(panel);
+  finish();
+}
--- a/browser/devtools/performance/views/details-waterfall.js
+++ b/browser/devtools/performance/views/details-waterfall.js
@@ -108,16 +108,20 @@ let WaterfallView = Heritage.extend(Deta
     });
   },
 
   /**
    * Called whenever an observed pref is changed.
    */
   _onObservedPrefChange: function(_, prefName) {
     this._hiddenMarkers = PerformanceController.getPref("hidden-markers");
+
+    // Clear the cache as we'll need to recompute the collapsed
+    // marker model
+    this._cache = new WeakMap();
   },
 
   /**
    * Called when MarkerDetails view emits an event to view source.
    */
   _onViewSource: function (_, file, line) {
     gToolbox.viewSourceInDebugger(file, line);
   },
@@ -132,16 +136,17 @@ let WaterfallView = Heritage.extend(Deta
       return cached;
     }
 
     let rootMarkerNode = WaterfallUtils.makeParentMarkerNode({ name: "(root)" });
 
     WaterfallUtils.collapseMarkersIntoNode({
       markerNode: rootMarkerNode,
       markersList: markers,
+      filter: this._hiddenMarkers
     });
 
     this._cache.set(markers, rootMarkerNode);
     return rootMarkerNode;
   },
 
   /**
    * Renders the waterfall tree.
--- a/browser/devtools/shared/test/browser_telemetry_button_paintflashing.js
+++ b/browser/devtools/shared/test/browser_telemetry_button_paintflashing.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 const TEST_URI = "data:text/html;charset=utf-8," +
   "<p>browser_telemetry_button_paintflashing.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(function*() {
@@ -25,37 +27,38 @@ add_task(function*() {
 });
 
 function* testButton(toolbox, Telemetry) {
   info("Testing command-button-paintflashing");
 
   let button = toolbox.doc.querySelector("#command-button-paintflashing");
   ok(button, "Captain, we have the button");
 
-  yield delayedClicks(button, 4);
+  yield* delayedClicks(toolbox, button, 4);
   checkResults("_PAINTFLASHING_", Telemetry);
 }
 
-function delayedClicks(node, clicks) {
-  return new Promise(resolve => {
-    let clicked = 0;
+function* delayedClicks(toolbox, node, clicks) {
+  for (let i = 0; i < clicks; i++) {
+    yield new Promise(resolve => {
+      // See TOOL_DELAY for why we need setTimeout here
+      setTimeout(() => resolve(), TOOL_DELAY);
+    });
 
-    // See TOOL_DELAY for why we need setTimeout here
-    setTimeout(function delayedClick() {
-      info("Clicking button " + node.id);
-      node.click();
-      clicked++;
+    // this event will fire once the command execution starts and
+    // the output object is created
+    let clicked = toolbox._requisition.commandOutputManager.onOutput.once();
 
-      if (clicked >= clicks) {
-        resolve(node);
-      } else {
-        setTimeout(delayedClick, TOOL_DELAY);
-      }
-    }, TOOL_DELAY);
-  });
+    info("Clicking button " + node.id);
+    node.click();
+
+    let outputEvent = yield clicked;
+    // promise gets resolved once execution finishes and output is ready
+    yield outputEvent.output.promise;
+  }
 }
 
 function checkResults(histIdFocus, Telemetry) {
   let result = Telemetry.prototype.telemetryInfo;
 
   for (let [histId, value] of Iterator(result)) {
     if (histId.startsWith("DEVTOOLS_INSPECTOR_") ||
         !histId.includes(histIdFocus)) {
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -511,20 +511,16 @@ size. -->
 <!ENTITY home_remote_tabs_many_hidden_devices "&formatD; devices hidden">
 <!-- Localization note (home_remote_tabs_hidden_devices_title) : This is the
      title of a dialog; we expect more than one device. -->
 <!ENTITY home_remote_tabs_hidden_devices_title "Hidden devices">
 <!-- Localization note (home_remote_tabs_unhide_selected_devices) : This is
      the text of a button; we expect more than one device. -->
 <!ENTITY home_remote_tabs_unhide_selected_devices "Unhide selected devices">
 
-<!ENTITY private_browsing_title "Private Browsing">
-<!ENTITY private_tabs_panel_empty_desc "Your private tabs will show up here. While we don\'t keep any of your browsing history or cookies, bookmarks and files that you download will still be saved on your device.">
-<!ENTITY private_tabs_panel_learn_more "Want to learn more?">
-
 <!ENTITY remote_tabs_panel_moved_title "Where did my tabs go?">
 <!ENTITY remote_tabs_panel_moved_desc "We\'ve moved your tabs from other devices into a panel on your home page that can be easily accessed every time you open a new tab.">
 <!ENTITY remote_tabs_panel_moved_link "Take me to my new panel.">
 
 <!ENTITY pin_site_dialog_hint "Enter a search keyword">
 
 <!ENTITY filepicker_title "Choose File">
 <!ENTITY filepicker_audio_title "Choose or record a sound">
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8791f24bc4c5a736583cee6efcb82bd7133416c6
GIT binary patch
literal 1660
zc$`I43p7-D7#}3BgpxehJ3^&SC5^O`sYM<W((2*Pm^*jo-Z4U+Q)wbagyML$@=D%H
zv>4>g3W+EYODyBD#1gWyckVc6&-w0mzwiJ5e&6@|e&?PWPO|$&T2fgOhr>x*SrQ%4
zcL4ej#YIqmQ&{LI4kx^YWaD%iO(7`B4TjxlFd5u5fXYCPjCw5IJOmL6h`=yEX0ccR
z1fwY#{J#c3V=C+hAsZVTXquOo2ZHWoDt1eS$utZF!Vq+gOhW*y9z&r8Fon*-rtmue
zGzJA?0#tVa6MhE}rce<I4W_VI91aJ9I8-Kcb0TJZs#F1dlL<B#MZsi&LHs6YnHz{u
z84L=---S%&^M@c$09%lPdIS*wX*4E<hHQ=o5lkE=N*jXwsdOfT;e|3JgPRfwCc63g
zq4gj(g?~lEh|$n)2+A3iMX(*enBM{8Nn`l<_;`7FQ7|TazqtrZrh8EMCm4A&05^3&
zTVX&1+lxU)Q*31fV~z$)CdwQpdwNm?dJ3>00)i}b9A936$XMV;W1?!PWY%V(-$8+o
zgPkKzRL{=$xAMBS-l68gxa&@GW!uk2cC2lzw}zMrJrdFU<KI4pSfxBN@VV(*kU`q_
z#aI*k%O4X@+)wV>BB_4jTK?$r%+l)0^k75a+!U!N#??seLdxGt?Vgtvha(JqvS%L>
zt!XmmDu)8zb%l;gU);aY?Uw8BxAn}6_s1+0svV^y=v=Qqi=%Z<0bxA&3nbimsUTUr
zs&-rH{hc>tDUD-y98{w!M&@5Xb3WMhwyFV#6S;0hBsc{K71W6+G?HX-FLGHn>&(#~
z*_Tcfrqtr~lPRXIvbrwLPPQgC<z!{<GlQ2#;lWMC+idGj_C-yv2J98jJa#))C#oKI
zVD-s#-&gD8Ix4YKs_J<G!r>aszt;LFVHTgok-ojwZ&ycWw<vv0DrSzDcWY?al}MDu
z`!=MqriIJe1N+NNWZb=VgcLb7ea-&<rht19_v7!Z0iGwTrKEUO+rF#k{rO$Coo0#4
zGF3X33uC$OPg;16&zQ0sD@y1ZE31pSu{sj3cgiZttGeNgEgI^zwI&G<<whv!dvMx1
z$MlUkbDY_X3@R@sk*CiUscuonH>s~tvsb9MRL`b{S(x3_`blCje2Y%E-VQm-RB_@C
z-?zGY1%tlEx>11U{=bHcUP-i0<iy@_%6pnc2bBZ#4DosifQE@4UTRJ;Nof*xvO4`!
z)`C<}2*YLMcu+I9o+i8Q9AlrWm6mu^NH;6S_>CQDl$azG-u(#IFE65>ou*au@(t(I
zlAM8B?%m*X=aT>e!IYyciKIL3&brZ%=6IGM&0$L_r}bKfo?#pzkz{bTKFd2xBO&V}
zD(}9B&nACORp2sNA<p=${`B4t*8cle?8QWDer}t2O3aoh1oU~e8(e+fU1u3xc3Q73
zVfWBv7P%~FHj|k8W6+jxV5s%`?Ti$Kp0|!)%z6q^<kw?Dm-mwzBNEb_tiv7juW&8p
z4h)C1m3~T;2sMl=7Y=QIP+Acg{h_Pq7FqU?MVw-A`*3_ve_?}OLtMCu(v|IxfxYvF
z?(xx4jprc6)R_lQ>-$!Sq@@K-=3I<v`Au(x4J{ezLKix1>}@^N58wNC(V{IsWKVNT
zjx1c)p;`@XpDhSvw^oVP30H$c8BO_%_wyrUH6vZ+wm$Y|6HI5OVkgVT*p`<wM~etk
z$JPAQyw~WHIiJ=^Ie3CtN=`_(s@h?(2P!`8(!5^>crh%6QjehwZI8i5dSclmXJC(c
z+bd%9CGQa_qoMSBgD#B;tznsBO|;>NeSk%@ONlBlGLa0jiQXNZE64rkIvjGBX1aQ)
z%L)4`Zy7D|J~<nRey)soUbbA<FjyK`y13HnDE`Deuxc^vXr;P&U&Ph1{`r~JqTL58
zfvq||)$9{pYvYAdl0{`#*?O)uQ`X`E75MR{qKQtw^0a?sG-pyxrweVw(ccHo>a-oP
I=oC5lKUgUBPyhe`
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..42ecdeb764c3c9126b3e58e293e31867ab4610a9
GIT binary patch
literal 2010
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e62cdbe137ce32dfb52bf157edbf74c7bd10291c
GIT binary patch
literal 2872
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
GP53{Dv_1*|
--- a/mobile/android/base/resources/layout/private_tabs_panel.xml
+++ b/mobile/android/base/resources/layout/private_tabs_panel.xml
@@ -1,55 +1,30 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:gecko="http://schemas.android.com/apk/res-auto">
 
-    <ScrollView android:layout_width="match_parent"
-                android:layout_height="match_parent">
-
-        <LinearLayout android:id="@+id/private_tabs_empty"
-                      style="@style/PrivateTabsPanelFrame"
-                      android:layout_width="@dimen/private_tabs_panel_empty_width"
-                      android:layout_height="@dimen/private_tabs_panel_empty_height">
-
-            <LinearLayout style="@style/TabsPanelSection.PrivateTabs.Header"
-                          android:layout_width="match_parent"
-                          android:layout_height="wrap_content">
-
-                <TextView android:id="@+id/private_tabs_empty_header"
-                          style="@style/TabsPanelItem.TextAppearance.Header.PrivateTabs"
-                          android:layout_width="match_parent"
-                          android:layout_height="wrap_content"
-                          android:text="@string/private_browsing_title"/>
+    <FrameLayout android:id="@+id/private_tabs_empty"
+                 android:layout_width="match_parent"
+                 android:layout_height="match_parent">
 
-                <TextView style="@style/TabsPanelItem.TextAppearance"
-                          android:layout_width="match_parent"
-                          android:layout_height="wrap_content"
-                          android:text="@string/private_tabs_panel_empty_desc"/>
-
-            </LinearLayout>
-
-            <LinearLayout style="@style/TabsPanelSection.PrivateTabs"
-                          android:layout_width="match_parent"
-                          android:layout_height="match_parent">
+        <!-- TODO: Remove the negative marginTop once we get rid of the 
+                   browser chrome at the bottom of the tabs tray. 
+                   Bug 1161638 -->
+        <ImageView android:layout_height="wrap_content"
+                   android:layout_width="wrap_content"
+                   android:src="@drawable/private_masq"
+                   android:layout_gravity="center"
+                   android:layout_marginTop="-10dp"/>
 
-                <TextView android:id="@+id/private_tabs_learn_more"
-                          style="@style/TabsPanelItem.TextAppearance.Linkified.LearnMore"
-                          android:layout_width="match_parent"
-                          android:text="@string/private_tabs_panel_learn_more"/>
-
-            </LinearLayout>
-
-        </LinearLayout>
-
-    </ScrollView>
+    </FrameLayout>
 
     <!-- Note: for an unknown reason, scrolling in the TabsLayout
          does not work unless it is laid out after the empty view. -->
     <view class="org.mozilla.gecko.tabs.TabsPanel$TabsLayout"
           android:id="@+id/private_tabs_layout"
           style="@style/TabsLayout"
           android:layout_width="match_parent"
           android:layout_height="match_parent"
--- a/mobile/android/base/resources/values-land/styles.xml
+++ b/mobile/android/base/resources/values-land/styles.xml
@@ -14,39 +14,19 @@
          <item name="android:nextFocusDown">@+id/close</item>
     </style>
 
     <style name="TabsItemClose">
          <item name="android:nextFocusUp">@+id/info</item>
     </style>
 
     <!-- Tabs panel -->
-    <style name="PrivateTabsPanelFrame">
-        <item name="android:paddingLeft">16dp</item>
-        <item name="android:paddingRight">16dp</item>
-        <item name="android:orientation">horizontal</item>
-    </style>
-
     <style name="TabsPanelSection" parent="TabsPanelSectionBase">
         <item name="android:layout_weight">1</item>
     </style>
 
-    <style name="TabsPanelSection.PrivateTabs.Header">
-        <item name="android:paddingTop">10dp</item>
-    </style>
-
     <style name="TabsPanelItem">
         <item name="android:layout_marginBottom">20dp</item>
         <item name="android:layout_gravity">left</item>
         <item name="android:gravity">left</item>
     </style>
 
-    <style name="TabsPanelItem.TextAppearance.Header.PrivateTabs">
-        <item name="android:visibility">gone</item>
-    </style>
-
-    <style name="TabsPanelItem.TextAppearance.Linkified.LearnMore">
-        <item name="android:layout_height">match_parent</item>
-        <item name="android:gravity">center</item>
-        <item name="android:layout_gravity">center</item>
-    </style>
-
 </resources>
--- a/mobile/android/base/resources/values-large-land-v11/styles.xml
+++ b/mobile/android/base/resources/values-large-land-v11/styles.xml
@@ -35,25 +35,13 @@
     </style>
 
     <!-- Tabs panel -->
     <style name="TabsPanelSection" parent="TabsPanelSectionBase">
         <item name="android:layout_marginLeft">20dp</item>
         <item name="android:layout_marginRight">20dp</item>
     </style>
 
-    <style name="TabsPanelSection.PrivateTabs">
-        <!-- We set values in tablet portrait. -->
-    </style>
-
-    <style name="TabsPanelSection.PrivateTabs.Header">
-        <!-- We set values in tablet portrait. -->
-    </style>
-
     <style name="TabsPanelItem" parent="TabsPanelItemBase">
         <!-- To override the values-land style. -->
     </style>
 
-    <style name="TabsPanelItem.TextAppearance.Linkified.LearnMore">
-        <item name="android:layout_height">wrap_content</item>
-    </style>
-
 </resources>
--- a/mobile/android/base/resources/values-large-v11/dimens.xml
+++ b/mobile/android/base/resources/values-large-v11/dimens.xml
@@ -15,19 +15,16 @@
 
     <dimen name="browser_toolbar_site_security_height">60dp</dimen>
     <dimen name="browser_toolbar_site_security_width">34dp</dimen>
     <dimen name="browser_toolbar_site_security_margin_right">1dp</dimen>
     <!-- We primarily use padding (instead of margins) to increase the hit area. -->
     <dimen name="browser_toolbar_site_security_padding_vertical">21dp</dimen>
     <dimen name="browser_toolbar_site_security_padding_horizontal">8dp</dimen>
 
-    <dimen name="private_tabs_panel_empty_width">300dp</dimen>
-    <dimen name="private_tabs_panel_empty_height">@dimen/wrap_content</dimen>
-
     <dimen name="tabs_counter_size">26sp</dimen>
     <dimen name="panel_grid_view_column_width">200dp</dimen>
 
     <dimen name="reading_list_row_height">96dp</dimen>
     <dimen name="reading_list_row_padding_right">15dp</dimen>
 
     <dimen name="tab_queue_container_width">360dp</dimen>
 
--- a/mobile/android/base/resources/values-large-v11/styles.xml
+++ b/mobile/android/base/resources/values-large-v11/styles.xml
@@ -121,43 +121,13 @@
         <item name="android:lineSpacingMultiplier">1.3</item>
     </style>
 
     <style name="Widget.HomeBanner">
         <item name="android:paddingLeft">32dp</item>
         <item name="android:paddingRight">32dp</item>
     </style>
 
-    <!-- Tabs panel -->
-    <style name="PrivateTabsPanelFrame">
-        <item name="android:orientation">vertical</item>
-
-        <!-- We want to center the content on the screen, not in
-             the View, so add padding to compensate for the header. -->
-        <item name="android:layout_gravity">center</item>
-        <item name="android:paddingBottom">@dimen/browser_toolbar_height</item>
-    </style>
-
-    <style name="TabsPanelSection.PrivateTabs">
-        <item name="android:layout_weight">1</item>
-        <item name="android:layout_marginLeft">20dp</item>
-        <item name="android:layout_marginRight">20dp</item>
-    </style>
-
-    <style name="TabsPanelSection.PrivateTabs.Header">
-        <item name="android:paddingTop">10dp</item>
-    </style>
-
-    <style name="TabsPanelItem.TextAppearance.Header.PrivateTabs">
-        <item name="android:visibility">visible</item>
-    </style>
-
-    <style name="TabsPanelItem.TextAppearance.Linkified.LearnMore">
-        <item name="android:layout_height">match_parent</item>
-        <item name="android:gravity">center</item>
-        <item name="android:layout_gravity">center</item>
-    </style>
-
     <style name="TextAppearance.UrlBar.Title" parent="TextAppearance.Medium">
         <item name="android:textSize">16sp</item>
     </style>
 
 </resources>
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -135,18 +135,16 @@
     <dimen name="remote_tab_child_row_height">64dp</dimen>
     <dimen name="remote_tab_group_row_height">26dp</dimen>
     <dimen name="searchpreferences_icon_size">32dp</dimen>
     <dimen name="tab_thumbnail_height">90dp</dimen>
     <dimen name="tab_thumbnail_width">160dp</dimen>
     <dimen name="tabs_counter_size">22sp</dimen>
     <dimen name="tabs_panel_indicator_width">60dp</dimen>
     <dimen name="tabs_panel_list_padding">16dip</dimen>
-    <dimen name="private_tabs_panel_empty_width">@dimen/match_parent</dimen>
-    <dimen name="private_tabs_panel_empty_height">@dimen/match_parent</dimen>
     <dimen name="tabs_list_divider_height">2dp</dimen>
     <dimen name="tabs_strip_height">40dp</dimen>
     <dimen name="tabs_strip_button_width">100dp</dimen>
     <dimen name="tabs_strip_button_padding">18dp</dimen>
     <dimen name="tabs_strip_shadow_size">1dp</dimen>
     <dimen name="tabs_layout_horizontal_height">156dp</dimen>
     <dimen name="text_selection_handle_width">47dp</dimen>
     <dimen name="text_selection_handle_height">58dp</dimen>
--- a/mobile/android/base/resources/values/styles.xml
+++ b/mobile/android/base/resources/values/styles.xml
@@ -515,41 +515,26 @@
          <item name="android:nextFocusRight">@+id/close</item>
     </style>
 
     <style name="TabsItemClose">
          <item name="android:nextFocusLeft">@+id/info</item>
     </style>
 
     <!-- Tabs panel -->
-    <style name="PrivateTabsPanelFrame">
-        <item name="android:paddingLeft">16dp</item>
-        <item name="android:paddingRight">16dp</item>
-        <item name="android:paddingTop">32dp</item>
-        <item name="android:orientation">vertical</item>
-    </style>
-
     <style name="TabsPanelSectionBase">
         <item name="android:orientation">vertical</item>
         <item name="android:layout_marginLeft">40dp</item>
         <item name="android:layout_marginRight">40dp</item>
     </style>
 
     <style name="TabsPanelSection" parent="TabsPanelSectionBase">
         <!-- We set values in landscape. -->
     </style>
 
-    <style name="TabsPanelSection.PrivateTabs">
-        <!-- We set values on tablet. -->
-    </style>
-
-    <style name="TabsPanelSection.PrivateTabs.Header">
-        <!-- We set values on landscape and tablet. -->
-    </style>
-
     <style name="TabsPanelItemBase">
         <item name="android:layout_marginBottom">28dp</item>
         <item name="android:layout_gravity">center</item>
         <item name="android:gravity">center</item>
     </style>
 
     <style name="TabsPanelItem" parent="TabsPanelItemBase">
         <!-- We set values in landscape. -->
@@ -561,30 +546,22 @@
         <item name="android:lineSpacingMultiplier">1.35</item>
     </style>
 
     <style name="TabsPanelItem.TextAppearance.Header">
         <item name="android:textSize">18sp</item>
         <item name="android:layout_marginBottom">16dp</item>
     </style>
 
-    <style name="TabsPanelItem.TextAppearance.Header.PrivateTabs">
-        <!-- We change these values on landscape and tablet. -->
-    </style>
-
     <style name="TabsPanelItem.TextAppearance.Linkified">
         <item name="android:clickable">true</item>
         <item name="android:focusable">true</item>
         <item name="android:textColor">#0292D6</item>
     </style>
 
-    <style name="TabsPanelItem.TextAppearance.Linkified.LearnMore">
-        <item name="android:layout_height">wrap_content</item>
-    </style>
-
     <style name="Widget.RemoteTabsItemView" parent="Widget.TwoLinePageRow"/>
 
     <style name="Widget.RemoteTabsClientView" parent="Widget.TwoLinePageRow">
         <item name="android:background">@color/about_page_header_grey</item>
     </style>
 
     <style name="Widget.RemoteTabsListView" parent="Widget.HomeListView">
         <item name="android:childDivider">#E7ECF0</item>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -413,21 +413,16 @@
   <string name="home_remote_tabs_need_to_sign_in">&home_remote_tabs_need_to_sign_in;</string>
   <string name="home_remote_tabs_need_to_finish_migrating">&home_remote_tabs_need_to_finish_migrating;</string>
   <string name="home_remote_tabs_trouble_verifying">&home_remote_tabs_trouble_verifying;</string>
   <string name="home_remote_tabs_need_to_verify">&home_remote_tabs_need_to_verify;</string>
   <string name="home_remote_tabs_one_hidden_device">&home_remote_tabs_one_hidden_device;</string>
   <string name="home_remote_tabs_many_hidden_devices">&home_remote_tabs_many_hidden_devices;</string>
   <string name="home_remote_tabs_hidden_devices_title">&home_remote_tabs_hidden_devices_title;</string>
   <string name="home_remote_tabs_unhide_selected_devices">&home_remote_tabs_unhide_selected_devices;</string>
-  <string name="private_browsing_title">&private_browsing_title;</string>
-  <string name="private_tabs_panel_empty_desc">&private_tabs_panel_empty_desc;</string>
-  <string name="private_tabs_panel_learn_more">&private_tabs_panel_learn_more;</string>
-  <!-- https://support.mozilla.org/%LOCALE%/kb/mobile-private-browsing-browse-web-without-saving-syncing-info -->
-  <string name="private_tabs_panel_learn_more_link">https://support.mozilla.org/&formatS1;/kb/mobile-private-browsing-browse-web-without-saving-syncing-info</string>
   <string name="pin_site_dialog_hint">&pin_site_dialog_hint;</string>
 
   <string name="remote_tabs_panel_moved_title">&remote_tabs_panel_moved_title;</string>
   <string name="remote_tabs_panel_moved_desc">&remote_tabs_panel_moved_desc;</string>
   <string name="remote_tabs_panel_moved_link">&remote_tabs_panel_moved_link;</string>
   <string name="remote_tabs_never_synced">&remote_tabs_never_synced;</string>
 
   <string name="filepicker_title">&filepicker_title;</string>
--- a/mobile/android/base/tabs/PrivateTabsPanel.java
+++ b/mobile/android/base/tabs/PrivateTabsPanel.java
@@ -1,68 +1,48 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko.tabs;
 
-import java.util.Locale;
-
-import org.mozilla.gecko.Locales;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.tabs.TabsPanel.CloseAllPanelView;
 import org.mozilla.gecko.tabs.TabsPanel.TabsLayout;
 
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 
 /**
  * A container that wraps the private tabs {@link android.widget.AdapterView} and empty
  * {@link android.view.View} to manage both of their visibility states by changing the visibility of
  * this container as calling {@link android.widget.AdapterView#setVisibility} does not affect the
  * empty View's visibility.
  */
 class PrivateTabsPanel extends FrameLayout implements CloseAllPanelView {
-    private TabsPanel tabsPanel;
-
     private final TabsLayout tabsLayout;
 
     public PrivateTabsPanel(Context context, AttributeSet attrs) {
         super(context, attrs);
 
         LayoutInflater.from(context).inflate(R.layout.private_tabs_panel, this);
         tabsLayout = (TabsLayout) findViewById(R.id.private_tabs_layout);
 
-        final LinearLayout emptyTabsFrame = (LinearLayout) findViewById(R.id.private_tabs_empty);
+        final ViewGroup emptyTabsFrame = (ViewGroup) findViewById(R.id.private_tabs_empty);
         tabsLayout.setEmptyView(emptyTabsFrame);
-
-        final View learnMore = findViewById(R.id.private_tabs_learn_more);
-        learnMore.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                final String locale = Locales.getLanguageTag(Locale.getDefault());
-                final String url =
-                        getResources().getString(R.string.private_tabs_panel_learn_more_link, locale);
-                Tabs.getInstance().loadUrlInTab(url);
-                if (tabsPanel != null) {
-                    tabsPanel.autoHidePanel();
-                }
-            }
-        });
     }
 
     @Override
     public void setTabsPanel(TabsPanel panel) {
-        tabsPanel = panel;
         tabsLayout.setTabsPanel(panel);
     }
 
     @Override
     public void show() {
         tabsLayout.show();
         setVisibility(View.VISIBLE);
     }
--- a/mobile/android/components/DirectoryProvider.js
+++ b/mobile/android/components/DirectoryProvider.js
@@ -13,16 +13,17 @@ Cu.import("resource://gre/modules/XPCOMU
 
 // -----------------------------------------------------------------------
 // Directory Provider for special browser folders and files
 // -----------------------------------------------------------------------
 
 const NS_APP_CACHE_PARENT_DIR = "cachePDir";
 const NS_APP_SEARCH_DIR       = "SrchPlugns";
 const NS_APP_SEARCH_DIR_LIST  = "SrchPluginsDL";
+const NS_APP_DISTRIBUTION_SEARCH_DIR_LIST = "SrchPluginsDistDL";
 const NS_APP_USER_SEARCH_DIR  = "UsrSrchPlugns";
 const NS_XPCOM_CURRENT_PROCESS_DIR = "XCurProcD";
 const XRE_APP_DISTRIBUTION_DIR = "XREAppDist";
 const XRE_UPDATE_ROOT_DIR     = "UpdRootD";
 const ENVVAR_UPDATE_DIR       = "UPDATES_DIRECTORY";
 const WEBAPPS_DIR             = "webappsDir";
 const DOWNLOAD_DIR            = "DfltDwnld";
 
@@ -142,37 +143,42 @@ DirectoryProvider.prototype = {
     // We didn't append the locale dir - try the default one.
     let defLocale = Services.prefs.getCharPref("distribution.searchplugins.defaultLocale");
     let defLocalePlugins = localePlugins.clone();
     if (defLocalePlugins.exists())
       array.push(defLocalePlugins);
   },
 
   getFiles: function(prop) {
-    if (prop != NS_APP_SEARCH_DIR_LIST)
-      return;
+    if (prop != NS_APP_SEARCH_DIR_LIST &&
+        prop != NS_APP_DISTRIBUTION_SEARCH_DIR_LIST)
+      return null;
 
     let result = [];
 
-    /**
-     * We want to preserve the following order, since the search service loads
-     * engines in first-loaded-wins order.
-     *   - distro search plugin locations
-     *   - user search plugin locations (profile)
-     *   - app search plugin location (shipped engines)
-     */
-    this._appendDistroSearchDirs(result);
+    if (prop == NS_APP_DISTRIBUTION_SEARCH_DIR_LIST) {
+      this._appendDistroSearchDirs(result);
+    }
+    else {
+      /**
+       * We want to preserve the following order, since the search service
+       * loads engines in first-loaded-wins order.
+       *   - distro search plugin locations (loaded separately by the search
+       *     service)
+       *   - user search plugin locations (profile)
+       *   - app search plugin location (shipped engines)
+       */
+      let appUserSearchDir = FileUtils.getDir(NS_APP_USER_SEARCH_DIR, [], false);
+      if (appUserSearchDir.exists())
+        result.push(appUserSearchDir);
 
-    let appUserSearchDir = FileUtils.getDir(NS_APP_USER_SEARCH_DIR, [], false);
-    if (appUserSearchDir.exists())
-      result.push(appUserSearchDir);
-
-    let appSearchDir = FileUtils.getDir(NS_APP_SEARCH_DIR, [], false);
-    if (appSearchDir.exists())
-      result.push(appSearchDir);
+      let appSearchDir = FileUtils.getDir(NS_APP_SEARCH_DIR, [], false);
+      if (appSearchDir.exists())
+        result.push(appSearchDir);
+    }
 
     return {
       QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
       hasMoreElements: function() {
         return result.length > 0;
       },
       getNext: function() {
         return result.shift();
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -46,16 +46,17 @@ XPCOMUtils.defineLazyGetter(this, "gEnco
 const MODE_RDONLY   = 0x01;
 const MODE_WRONLY   = 0x02;
 const MODE_CREATE   = 0x08;
 const MODE_APPEND   = 0x10;
 const MODE_TRUNCATE = 0x20;
 
 // Directory service keys
 const NS_APP_SEARCH_DIR_LIST  = "SrchPluginsDL";
+const NS_APP_DISTRIBUTION_SEARCH_DIR_LIST = "SrchPluginsDistDL";
 const NS_APP_USER_SEARCH_DIR  = "UsrSrchPlugns";
 const NS_APP_SEARCH_DIR       = "SrchPlugns";
 const NS_APP_USER_PROFILE_50_DIR = "ProfD";
 
 // Search engine "locations". If this list is changed, be sure to update
 // the engine's _isDefault function accordingly.
 const SEARCH_APP_DIR = 1;
 const SEARCH_PROFILE_DIR = 2;
@@ -3480,37 +3481,53 @@ SearchService.prototype = {
     let cacheEnabled = getBoolPref(BROWSER_SEARCH_PREF + "cache.enabled", true);
     if (cacheEnabled) {
       let cacheFile = getDir(NS_APP_USER_PROFILE_50_DIR);
       cacheFile.append("search.json");
       if (cacheFile.exists())
         cache = this._readCacheFile(cacheFile);
     }
 
-    let loadDirs = [], chromeURIs = [], chromeFiles = [];
-
+    let chromeURIs = [], chromeFiles = [];
     let loadFromJARs = false;
     try {
       loadFromJARs = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF)
                              .getBoolPref("loadFromJars");
     } catch (ex) {}
 
     if (loadFromJARs)
       [chromeFiles, chromeURIs] = this._findJAREngines();
 
-    let locations = getDir(NS_APP_SEARCH_DIR_LIST, Ci.nsISimpleEnumerator);
+    let distDirs = [];
+    let locations;
+    try {
+      locations = getDir(NS_APP_DISTRIBUTION_SEARCH_DIR_LIST,
+                         Ci.nsISimpleEnumerator);
+    } catch (e) {
+      // NS_APP_DISTRIBUTION_SEARCH_DIR_LIST is defined by each app
+      // so this throws during unit tests (but not xpcshell tests).
+      locations = {hasMoreElements: () => false};
+    }
+    while (locations.hasMoreElements()) {
+      let dir = locations.getNext().QueryInterface(Ci.nsIFile);
+      if (dir.directoryEntries.hasMoreElements())
+        distDirs.push(dir);
+    }
+
+    let otherDirs = [];
+    locations = getDir(NS_APP_SEARCH_DIR_LIST, Ci.nsISimpleEnumerator);
     while (locations.hasMoreElements()) {
       let dir = locations.getNext().QueryInterface(Ci.nsIFile);
       if (loadFromJARs && dir.equals(getDir(NS_APP_SEARCH_DIR)))
         continue;
       if (dir.directoryEntries.hasMoreElements())
-        loadDirs.push(dir);
+        otherDirs.push(dir);
     }
 
-    let toLoad = chromeFiles.concat(loadDirs);
+    let toLoad = chromeFiles.concat(distDirs, otherDirs);
 
     function modifiedDir(aDir) {
       return (!cache.directories || !cache.directories[aDir.path] ||
               cache.directories[aDir.path].lastModifiedTime != aDir.lastModifiedTime);
     }
 
     function notInCachePath(aPathToLoad)
       cachePaths.indexOf(aPathToLoad.path) == -1;
@@ -3523,20 +3540,22 @@ SearchService.prototype = {
                        cache.locale != getLocale() ||
                        cache.buildID != buildID ||
                        cachePaths.length != toLoad.length ||
                        toLoad.some(notInCachePath) ||
                        toLoad.some(modifiedDir);
 
     if (!cacheEnabled || rebuildCache) {
       LOG("_loadEngines: Absent or outdated cache. Loading engines from disk.");
-      loadDirs.forEach(this._loadEnginesFromDir, this);
+      distDirs.forEach(this._loadEnginesFromDir, this);
 
       this._loadFromChromeURLs(chromeURIs);
 
+      otherDirs.forEach(this._loadEnginesFromDir, this);
+
       if (cacheEnabled)
         this._buildCache();
       return;
     }
 
     LOG("_loadEngines: loading from cache directories");
     for each (let dir in cache.directories)
       this._loadEnginesFromCache(dir);
@@ -3556,53 +3575,81 @@ SearchService.prototype = {
       // See if we have a cache file so we don't have to parse a bunch of XML.
       let cache = {};
       let cacheEnabled = getBoolPref(BROWSER_SEARCH_PREF + "cache.enabled", true);
       if (cacheEnabled) {
         let cacheFilePath = OS.Path.join(OS.Constants.Path.profileDir, "search.json");
         cache = yield checkForSyncCompletion(this._asyncReadCacheFile(cacheFilePath));
       }
 
-      let loadDirs = [], chromeURIs = [], chromeFiles = [];
-
+      let chromeURIs = [], chromeFiles = [];
       let loadFromJARs = false;
       try {
         loadFromJARs = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF)
                                .getBoolPref("loadFromJars");
       } catch (ex) {}
 
       if (loadFromJARs) {
         Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "find-jar-engines");
         [chromeFiles, chromeURIs] =
           yield checkForSyncCompletion(this._asyncFindJAREngines());
       }
 
+      // Get the non-empty distribution directories into distDirs...
+      let distDirs = [];
+      let locations;
+      try {
+        locations = getDir(NS_APP_DISTRIBUTION_SEARCH_DIR_LIST,
+                           Ci.nsISimpleEnumerator);
+      } catch (e) {
+        // NS_APP_DISTRIBUTION_SEARCH_DIR_LIST is defined by each app
+        // so this throws during unit tests (but not xpcshell tests).
+        locations = {hasMoreElements: () => false};
+      }
+      while (locations.hasMoreElements()) {
+        let dir = locations.getNext().QueryInterface(Ci.nsIFile);
+        let iterator = new OS.File.DirectoryIterator(dir.path,
+                                                     { winPattern: "*.xml" });
+        try {
+          // Add dir to distDirs if it contains any files.
+          yield checkForSyncCompletion(iterator.next());
+          distDirs.push(dir);
+        } catch (ex if ex.result != Cr.NS_ERROR_ALREADY_INITIALIZED) {
+          // Catch for StopIteration exception.
+        } finally {
+          iterator.close();
+        }
+      }
+
       // Add the non-empty directories of NS_APP_SEARCH_DIR_LIST to
-      // loadDirs...
-      let locations = getDir(NS_APP_SEARCH_DIR_LIST, Ci.nsISimpleEnumerator);
+      // otherDirs...
+      let otherDirs = [];
+      locations = getDir(NS_APP_SEARCH_DIR_LIST, Ci.nsISimpleEnumerator);
       while (locations.hasMoreElements()) {
         let dir = locations.getNext().QueryInterface(Ci.nsIFile);
         // ... but skip the application directory if we are loading from JAR.
+        // Applications shipping JAR engines don't ship plain text
+        // engine files anymore.
         if (loadFromJARs && dir.equals(getDir(NS_APP_SEARCH_DIR)))
           continue;
 
         let iterator = new OS.File.DirectoryIterator(dir.path,
                                                      { winPattern: "*.xml" });
         try {
-          // Add dir to loadDirs if it contains any files.
+          // Add dir to otherDirs if it contains any files.
           yield checkForSyncCompletion(iterator.next());
-          loadDirs.push(dir);
+          otherDirs.push(dir);
         } catch (ex if ex.result != Cr.NS_ERROR_ALREADY_INITIALIZED) {
           // Catch for StopIteration exception.
         } finally {
           iterator.close();
         }
       }
 
-      let toLoad = chromeFiles.concat(loadDirs);
+      let toLoad = chromeFiles.concat(distDirs, otherDirs);
       function hasModifiedDir(aList) {
         return Task.spawn(function() {
           let modifiedDir = false;
 
           for (let dir of aList) {
             if (!cache.directories || !cache.directories[dir.path]) {
               modifiedDir = true;
               break;
@@ -3631,24 +3678,29 @@ SearchService.prototype = {
                          cache.buildID != buildID ||
                          cachePaths.length != toLoad.length ||
                          toLoad.some(notInCachePath) ||
                          (yield checkForSyncCompletion(hasModifiedDir(toLoad)));
 
       if (!cacheEnabled || rebuildCache) {
         LOG("_asyncLoadEngines: Absent or outdated cache. Loading engines from disk.");
         let engines = [];
-        for (let loadDir of loadDirs) {
+        for (let loadDir of distDirs) {
           let enginesFromDir =
             yield checkForSyncCompletion(this._asyncLoadEnginesFromDir(loadDir));
           engines = engines.concat(enginesFromDir);
         }
         let enginesFromURLs =
            yield checkForSyncCompletion(this._asyncLoadFromChromeURLs(chromeURIs));
         engines = engines.concat(enginesFromURLs);
+        for (let loadDir of otherDirs) {
+          let enginesFromDir =
+            yield checkForSyncCompletion(this._asyncLoadEnginesFromDir(loadDir));
+          engines = engines.concat(enginesFromDir);
+        }
 
         for (let engine of engines) {
           this._addEngineToStore(engine);
         }
         if (cacheEnabled)
           this._buildCache();
         return;
       }
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/data/engine-addon.xml
@@ -0,0 +1,8 @@
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>addon</ShortName>
+<Description>addon</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Url type="text/html" method="GET" template="http://searchtest.local">
+  <Param name="search" value="{searchTerms}"/>
+</Url>
+</SearchPlugin>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/data/engine-override.xml
@@ -0,0 +1,8 @@
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>bug645970</ShortName>
+<Description>override</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Url type="text/html" method="GET" template="http://searchtest.local">
+  <Param name="search" value="{searchTerms}"/>
+</Url>
+</SearchPlugin>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/data/install.rdf
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>search-engine@tests.mozilla.org</em:id>
+    <em:unpack>true</em:unpack>
+    <em:version>1.0</em:version>
+
+    <em:targetApplication>
+      <Description>
+        <em:id>toolkit@mozilla.org</em:id>
+        <em:minVersion>0</em:minVersion>
+        <em:maxVersion>*</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+    <!-- Front End MetaData -->
+    <em:name>Search Engine</em:name>
+
+  </Description>
+</RDF>
--- a/toolkit/components/search/tests/xpcshell/head_search.js
+++ b/toolkit/components/search/tests/xpcshell/head_search.js
@@ -92,16 +92,100 @@ function configureToLoadJarEngines(loadF
   // Ensure a test engine exists in the app dir anyway.
   let dir = Services.dirsvc.get(NS_APP_SEARCH_DIR, Ci.nsIFile);
   if (!dir.exists())
     dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
   do_get_file("data/engine-app.xml").copyTo(dir, "app.xml");
 }
 
 /**
+ * Fake the installation of an add-on in the profile, by creating the
+ * directory and registering it with the directory service.
+ */
+function installAddonEngine(name = "engine-addon")
+{
+  const XRE_EXTENSIONS_DIR_LIST = "XREExtDL";
+  const gProfD = do_get_profile().QueryInterface(Ci.nsILocalFile);
+
+  let dir = gProfD.clone();
+  dir.append("extensions");
+  if (!dir.exists())
+    dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+
+  dir.append("search-engine@tests.mozilla.org");
+  dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+
+  do_get_file("data/install.rdf").copyTo(dir, "install.rdf");
+  let addonDir = dir.clone();
+  dir.append("searchplugins");
+  dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+  do_get_file("data/" + name + ".xml").copyTo(dir, "bug645970.xml");
+
+  Services.dirsvc.registerProvider({
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider,
+                                           Ci.nsIDirectoryServiceProvider2]),
+
+    getFile: function (prop, persistant) {
+      throw Cr.NS_ERROR_FAILURE;
+    },
+
+    getFiles: function (prop) {
+      let result = [];
+
+      switch (prop) {
+      case XRE_EXTENSIONS_DIR_LIST:
+        result.push(addonDir);
+        break;
+      default:
+        throw Cr.NS_ERROR_FAILURE;
+      }
+
+      return {
+        QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
+        hasMoreElements: () => result.length > 0,
+        getNext: () => result.shift()
+      };
+    }
+  });
+}
+
+/**
+ * Copy the engine-distribution.xml engine to a fake distribution
+ * created in the profile, and registered with the directory service.
+ */
+function installDistributionEngine()
+{
+  const XRE_APP_DISTRIBUTION_DIR = "XREAppDist";
+
+  const gProfD = do_get_profile().QueryInterface(Ci.nsILocalFile);
+
+  let dir = gProfD.clone();
+  dir.append("distribution");
+  dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+  let distDir = dir.clone();
+
+  dir.append("searchplugins");
+  dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+
+  dir.append("common");
+  dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+
+  do_get_file("data/engine-override.xml").copyTo(dir, "bug645970.xml");
+
+  Services.dirsvc.registerProvider({
+    getFile: function(aProp, aPersistent) {
+      aPersistent.value = true;
+      if (aProp == XRE_APP_DISTRIBUTION_DIR)
+        return distDir.clone();
+      return null;
+    }
+  });
+}
+
+/**
  * Clean the profile of any metadata files left from a previous run.
  */
 function removeMetadata()
 {
   let file = gProfD.clone();
   file.append("search-metadata.json");
   if (file.exists()) {
     file.remove(false);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_async_addon.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+  do_test_pending();
+
+  removeMetadata();
+  removeCacheFile();
+
+  do_load_manifest("data/chrome.manifest");
+
+  configureToLoadJarEngines();
+  installAddonEngine();
+
+  do_check_false(Services.search.isInitialized);
+
+  Services.search.init(function search_initialized(aStatus) {
+    do_check_true(Components.isSuccessCode(aStatus));
+    do_check_true(Services.search.isInitialized);
+
+    // test the add-on engine is loaded in addition to our jar engine
+    let engines = Services.search.getEngines();
+    do_check_eq(engines.length, 2);
+
+    // test jar engine is loaded ok.
+    let engine = Services.search.getEngineByName("addon");
+    do_check_neq(engine, null);
+
+    do_check_eq(engine.description, "addon");
+
+    do_test_finished();
+  });
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_async_addon_no_override.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+  do_test_pending();
+
+  removeMetadata();
+  removeCacheFile();
+
+  do_load_manifest("data/chrome.manifest");
+
+  configureToLoadJarEngines();
+  installAddonEngine("engine-override");
+
+  do_check_false(Services.search.isInitialized);
+
+  Services.search.init(function search_initialized(aStatus) {
+    do_check_true(Components.isSuccessCode(aStatus));
+    do_check_true(Services.search.isInitialized);
+
+    // test the add-on engine isn't overriding our jar engine
+    let engines = Services.search.getEngines();
+    do_check_eq(engines.length, 1);
+
+    // test jar engine is loaded ok.
+    let engine = Services.search.getEngineByName("bug645970");
+    do_check_neq(engine, null);
+
+    do_check_eq(engine.description, "bug645970");
+
+    do_test_finished();
+  });
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_async_distribution.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+  do_test_pending();
+
+  removeMetadata();
+  removeCacheFile();
+
+  do_load_manifest("data/chrome.manifest");
+
+  configureToLoadJarEngines();
+  installDistributionEngine();
+
+  do_check_false(Services.search.isInitialized);
+
+  Services.search.init(function search_initialized(aStatus) {
+    do_check_true(Components.isSuccessCode(aStatus));
+    do_check_true(Services.search.isInitialized);
+
+    // test that the engine from the distribution overrides our jar engine
+    let engines = Services.search.getEngines();
+    do_check_eq(engines.length, 1);
+
+    let engine = Services.search.getEngineByName("bug645970");
+    do_check_neq(engine, null);
+
+    // check the engine we have is actually the one from the distribution
+    do_check_eq(engine.description, "override");
+
+    do_test_finished();
+  });
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_async_profile_engine.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const NS_APP_USER_SEARCH_DIR  = "UsrSrchPlugns";
+
+function run_test() {
+  do_test_pending();
+
+  removeMetadata();
+  removeCacheFile();
+
+  do_load_manifest("data/chrome.manifest");
+
+  configureToLoadJarEngines();
+
+  // Copy an engine in [profile]/searchplugin/ and ensure it's not
+  // overriding the same file from a jar.
+  // The description in the file we are copying is 'profile'.
+  let dir = Services.dirsvc.get(NS_APP_USER_SEARCH_DIR, Ci.nsIFile);
+  if (!dir.exists())
+    dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+  do_get_file("data/engine-override.xml").copyTo(dir, "bug645970.xml");
+
+  do_check_false(Services.search.isInitialized);
+
+  Services.search.init(function search_initialized(aStatus) {
+    do_check_true(Components.isSuccessCode(aStatus));
+    do_check_true(Services.search.isInitialized);
+
+    // test engines from dir are not loaded.
+    let engines = Services.search.getEngines();
+    do_check_eq(engines.length, 1);
+
+    // test jar engine is loaded ok.
+    let engine = Services.search.getEngineByName("bug645970");
+    do_check_neq(engine, null);
+
+    do_check_eq(engine.description, "bug645970");
+
+    do_test_finished();
+  });
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_sync_addon.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+  removeMetadata();
+  removeCacheFile();
+
+  do_load_manifest("data/chrome.manifest");
+
+  configureToLoadJarEngines();
+  installAddonEngine();
+
+  do_check_false(Services.search.isInitialized);
+
+  // test the add-on engine is loaded in addition to our jar engine
+  let engines = Services.search.getEngines();
+  do_check_eq(engines.length, 2);
+
+  do_check_true(Services.search.isInitialized);
+
+  // test jar engine is loaded ok.
+  let engine = Services.search.getEngineByName("addon");
+  do_check_neq(engine, null);
+
+  do_check_eq(engine.description, "addon");
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_sync_addon_no_override.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+  removeMetadata();
+  removeCacheFile();
+
+  do_load_manifest("data/chrome.manifest");
+
+  configureToLoadJarEngines();
+  installAddonEngine("engine-override");
+
+  do_check_false(Services.search.isInitialized);
+
+  // test the add-on engine isn't overriding our jar engine
+  let engines = Services.search.getEngines();
+  do_check_eq(engines.length, 1);
+
+  do_check_true(Services.search.isInitialized);
+
+  // test jar engine is loaded ok.
+  let engine = Services.search.getEngineByName("bug645970");
+  do_check_neq(engine, null);
+
+  do_check_eq(engine.description, "bug645970");
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_sync_distribution.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+  removeMetadata();
+  removeCacheFile();
+
+  do_load_manifest("data/chrome.manifest");
+
+  configureToLoadJarEngines();
+  installDistributionEngine();
+
+  do_check_false(Services.search.isInitialized);
+
+  // test that the engine from the distribution overrides our jar engine
+  let engines = Services.search.getEngines();
+  do_check_eq(engines.length, 1);
+
+  do_check_true(Services.search.isInitialized);
+
+  let engine = Services.search.getEngineByName("bug645970");
+  do_check_neq(engine, null);
+
+  // check the engine we have is actually the one from the distribution
+  do_check_eq(engine.description, "override");
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_sync_profile_engine.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const NS_APP_USER_SEARCH_DIR  = "UsrSrchPlugns";
+
+function run_test() {
+  removeMetadata();
+  removeCacheFile();
+
+  do_load_manifest("data/chrome.manifest");
+
+  configureToLoadJarEngines();
+
+  // Copy an engine in [profile]/searchplugin/ and ensure it's not
+  // overriding the same file from a jar.
+  // The description in the file we are copying is 'profile'.
+  let dir = Services.dirsvc.get(NS_APP_USER_SEARCH_DIR, Ci.nsIFile);
+  if (!dir.exists())
+    dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+  do_get_file("data/engine-override.xml").copyTo(dir, "bug645970.xml");
+
+  do_check_false(Services.search.isInitialized);
+
+  // test engines from dir are not loaded.
+  let engines = Services.search.getEngines();
+  do_check_eq(engines.length, 1);
+
+  do_check_true(Services.search.isInitialized);
+
+  // test jar engine is loaded ok.
+  let engine = Services.search.getEngineByName("bug645970");
+  do_check_neq(engine, null);
+
+  do_check_eq(engine.description, "bug645970");
+}
--- a/toolkit/components/search/tests/xpcshell/xpcshell.ini
+++ b/toolkit/components/search/tests/xpcshell/xpcshell.ini
@@ -3,25 +3,28 @@ head = head_search.js
 tail =
 firefox-appdir = browser
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 support-files =
   data/chrome.manifest
   data/engine.src
   data/engine.xml
   data/engine2.xml
+  data/engine-addon.xml
+  data/engine-override.xml
   data/engine-app.xml
   data/engine-fr.xml
   data/engineMaker.sjs
   data/engine-rel-searchform.xml
   data/engine-rel-searchform-post.xml
   data/engine-rel-searchform-purpose.xml
   data/engineImages.xml
   data/ico-size-16x16-png.ico
   data/invalid-engine.xml
+  data/install.rdf
   data/search-metadata.json
   data/search.json
   data/search.sqlite
   data/searchSuggestions.sjs
   data/searchTest.jar
 
 [test_nocache.js]
 [test_645970.js]
@@ -53,14 +56,22 @@ support-files =
 [test_SearchStaticData.js]
 [test_addEngine_callback.js]
 [test_multipleIcons.js]
 [test_resultDomain.js]
 [test_serialize_file.js]
 [test_searchSuggest.js]
 [test_async.js]
 [test_async_app.js]
+[test_async_addon.js]
+[test_async_addon_no_override.js]
+[test_async_distribution.js]
+[test_async_profile_engine.js]
 [test_sync.js]
 [test_sync_app.js]
+[test_sync_addon.js]
+[test_sync_addon_no_override.js]
+[test_sync_distribution.js]
 [test_sync_fallback.js]
 [test_sync_delay_fallback.js]
+[test_sync_profile_engine.js]
 [test_rel_searchform.js]
 [test_selectedEngine.js]
--- a/toolkit/devtools/gcli/commands/paintflashing.js
+++ b/toolkit/devtools/gcli/commands/paintflashing.js
@@ -107,19 +107,19 @@ exports.items = [
             return gcli.hiddenByChromePref();
           },
           description: l10n.lookup("paintflashingChromeDesc"),
         }
       ]
     }],
     exec: function*(args, context) {
       if (!args.chrome) {
-        const value = yield context.updateExec("paintflashing_server --state on");
-        isContentPaintFlashing = value;
-        onPaintFlashingChanged(context.environment.target, value);
+        const output = yield context.updateExec("paintflashing_server --state on");
+        isContentPaintFlashing = output.data;
+        onPaintFlashingChanged(context.environment.target, output.data);
       }
       else {
         setPaintFlashing(context.environment.chromeWindow, "on");
       }
     }
   },
   {
     item: "command",
@@ -137,19 +137,19 @@ exports.items = [
             return gcli.hiddenByChromePref();
           },
           description: l10n.lookup("paintflashingChromeDesc"),
         }
       ]
     }],
     exec: function*(args, context) {
       if (!args.chrome) {
-        const value = yield context.updateExec("paintflashing_server --state off");
-        isContentPaintFlashing = value;
-        onPaintFlashingChanged(context.environment.target, value);
+        const output = yield context.updateExec("paintflashing_server --state off");
+        isContentPaintFlashing = output.data;
+        onPaintFlashingChanged(context.environment.target, output.data);
       }
       else {
         setPaintFlashing(context.environment.chromeWindow, "off");
       }
     }
   },
   {
     item: "command",
@@ -162,19 +162,19 @@ exports.items = [
       isChecked: () => isContentPaintFlashing,
       onChange: (_, handler) => eventEmitter.on("changed", handler),
       offChange: (_, handler) => eventEmitter.off("changed", handler),
     },
     tooltipText: l10n.lookup("paintflashingTooltip"),
     description: l10n.lookup("paintflashingToggleDesc"),
     manual: l10n.lookup("paintflashingManual"),
     exec: function*(args, context) {
-      const value = yield context.updateExec("paintflashing_server --state toggle");
-      isContentPaintFlashing = value;
-      onPaintFlashingChanged(context.environment.target, value);
+      const output = yield context.updateExec("paintflashing_server --state toggle");
+      isContentPaintFlashing = output.data;
+      onPaintFlashingChanged(context.environment.target, output.data);
     }
   },
   {
     item: "command",
     runAt: "server",
     name: "paintflashing_server",
     hidden: true,
     params: [
--- a/toolkit/devtools/server/docs/protocol.js.md
+++ b/toolkit/devtools/server/docs/protocol.js.md
@@ -196,16 +196,18 @@ Moving right along, let's say you want t
           incrementor.increment();
         }
         return incrementors;
     }, {
         request: { incrementors: Arg(0, "array:incrementor") },
         response: { incrementors: RetVal("array:incrementor") }
     })
 
+You can use an iterator in place of an array as an argument or return value, and the library will handle the conversion automatically.
+
 Or maybe you want to return a dictionary where one item is a incrementor.  To do this you need to tell the type system which members of the dictionary need custom marshallers:
 
     protocol.types.addDictType("contrivedObject", {
         incrementor: "incrementor",
         incrementorArray: "array:incrementor"
     });
 
     reallyContrivedExample: method(function() {
--- a/toolkit/devtools/server/protocol.js
+++ b/toolkit/devtools/server/protocol.js
@@ -104,16 +104,21 @@ types.getType = function(type) {
 /**
  * Don't allow undefined when writing primitive types to packets.  If
  * you want to allow undefined, use a nullable type.
  */
 function identityWrite(v) {
   if (v === undefined) {
     throw Error("undefined passed where a value is required");
   }
+  // This has to handle iterator->array conversion because arrays of
+  // primitive types pass through here.
+  if (v && typeof (v) === "object" && Symbol.iterator in v) {
+    return [...v];
+  }
   return v;
 }
 
 /**
  * Add a type to the type system.
  *
  * When registering a type, you can provide `read` and `write` methods.
  *
@@ -185,18 +190,18 @@ types.addArrayType = function(subtype) {
   let name = "array:" + subtype.name;
 
   // Arrays of primitive types are primitive types themselves.
   if (subtype.primitive) {
     return types.addType(name);
   }
   return types.addType(name, {
     category: "array",
-    read: (v, ctx) => v.map(i => subtype.read(i, ctx)),
-    write: (v, ctx) => v.map(i => subtype.write(i, ctx))
+    read: (v, ctx) => [...v].map(i => subtype.read(i, ctx)),
+    write: (v, ctx) => [...v].map(i => subtype.write(i, ctx))
   });
 };
 
 /**
  * Add a dict type to the type system.  This allows you to serialize
  * a JS object that contains non-primitive subtypes.
  *
  * Properties of the value that aren't included in the specializations
--- a/toolkit/devtools/server/tests/unit/test_protocol_children.js
+++ b/toolkit/devtools/server/tests/unit/test_protocol_children.js
@@ -77,16 +77,29 @@ let ChildActor = protocol.ActorClass({
   getIDDetail: method(function() {
     return this;
   }, {
     response: {
       idDetail: RetVal("childActor#actorid")
     }
   }),
 
+  getIntArray: method(function(inputArray) {
+    // Test that protocol.js converts an iterator to an array.
+    let f = function*() {
+      for (let i of inputArray) {
+        yield 2 * i;
+      }
+    };
+    return f();
+  }, {
+    request: { inputArray: Arg(0, "array:number") },
+    response: RetVal("array:number")
+  }),
+
   getSibling: method(function(id) {
     return this.parent().getChild(id);
   }, {
     request: { id: Arg(0) },
     response: { sibling: RetVal("childActor") }
   }),
 
   emitEvents: method(function() {
@@ -188,16 +201,28 @@ let RootActor = protocol.ActorClass({
 
   getChildren: method(function(ids) {
     return ids.map(id => this.getChild(id));
   }, {
     request: { ids: Arg(0, "array:string") },
     response: { children: RetVal("array:childActor") },
   }),
 
+  getChildren2: method(function(ids) {
+    let f = function*() {
+      for (let c of ids) {
+        yield c;
+      }
+    };
+    return f();
+  }, {
+    request: { ids: Arg(0, "array:childActor") },
+    response: { children: RetVal("array:childActor") },
+  }),
+
   getManyChildren: method(function() {
     return {
       foo: "bar", // note that this isn't in the specialization array.
       child5: this.getChild("child5"),
       more: [ this.getChild("child6"), this.getChild("child7") ]
     }
   }, {
     response: RetVal("manyChildrenDict")
@@ -437,16 +462,44 @@ function run_test()
       trace.expectReceive({"foo":"bar","child5":{"actor":"<actorid>","childID":"child5"},"more":[{"actor":"<actorid>","childID":"child6"},{"actor":"<actorid>","childID":"child7"}],"from":"<actorid>"});
 
       // Check all the crazy stuff we did in getManyChildren
       do_check_eq(ret.foo, "bar");
       do_check_eq(ret.child5.childID, "child5");
       do_check_eq(ret.more[0].childID, "child6");
       do_check_eq(ret.more[1].childID, "child7");
     }).then(() => {
+      // Test accepting a generator.
+      let f = function*() {
+        for (let i of [1, 2, 3, 4, 5]) {
+          yield i;
+        }
+      };
+      return childFront.getIntArray(f());
+    }).then((ret) => {
+      do_check_eq(ret.length, 5);
+      let expected = [2, 4, 6, 8, 10];
+      for (let i = 0; i < 5; ++i) {
+        do_check_eq(ret[i], expected[i]);
+      }
+    }).then(() => {
+      return rootFront.getChildren(["child1", "child2"]);
+    }).then(ids => {
+      let f = function*() {
+        for (let id of ids) {
+          yield id;
+        }
+      };
+      return rootFront.getChildren2(f());
+    }).then(ret => {
+      do_check_eq(ret.length, 2);
+      do_check_true(ret[0] === childFront);
+      do_check_true(ret[1] !== childFront);
+      do_check_true(ret[1] instanceof ChildFront);
+    }).then(() => {
       client.close(() => {
         do_test_finished();
       });
     }).then(null, err => {
       do_report_unexpected_exception(err, "Failure executing test");
     });
   });
   do_test_pending();
--- a/xpcom/io/nsAppDirectoryServiceDefs.h
+++ b/xpcom/io/nsAppDirectoryServiceDefs.h
@@ -43,16 +43,17 @@
 #define NS_APP_RES_DIR                          "ARes"
 #define NS_APP_CHROME_DIR                       "AChrom"
 #define NS_APP_PLUGINS_DIR                      "APlugns"       // Deprecated - use NS_APP_PLUGINS_DIR_LIST
 #define NS_APP_SEARCH_DIR                       "SrchPlugns"
 
 #define NS_APP_CHROME_DIR_LIST                  "AChromDL"
 #define NS_APP_PLUGINS_DIR_LIST                 "APluginsDL"
 #define NS_APP_SEARCH_DIR_LIST                  "SrchPluginsDL"
+#define NS_APP_DISTRIBUTION_SEARCH_DIR_LIST     "SrchPluginsDistDL"
 
 // --------------------------------------------------------------------------------------
 // Files and directories which exist on a per-profile basis
 // These locations are typically provided by the profile mgr
 // --------------------------------------------------------------------------------------
 
 // In a shared profile environment, prefixing a profile-relative
 // key with NS_SHARED returns a location that is shared by