Merge fx-team to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 19 Dec 2014 15:43:34 -0800
changeset 233214 b052018cf239f73ec54b8f0f555ebc6f7ee6e824
parent 233198 8880b5632e2b0da3840a28e6632e82ecfe2544af (current diff)
parent 233006 d9029ab107a801b2e95b5eb562bf8ea571eec5d5 (diff)
child 233215 f1c735d08af80cdd526b813428394c4240aff31d
child 233245 315c981e3f7de1299c50ecee7d2abcaa33645418
child 233288 2e09c000bb8babb3f39170152f37594b1fcd1ecf
child 251343 edbd9d66d644e64b6c9c4604839791a7cc1491ab
push id285
push usernalexander@mozilla.com
push dateSun, 21 Dec 2014 23:30:10 +0000
reviewersmerge
milestone37.0a1
Merge fx-team to m-c a=merge
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -977,17 +977,17 @@ let MozLoopServiceInternal = {
     if (!code || !state) {
       throw new Error("promiseFxAOAuthToken: code and state are required.");
     }
 
     let payload = {
       code: code,
       state: state,
     };
-    return this.hawkRequest(LOOP_SESSION_TYPE.FXA, "/fxa-oauth/token", "POST", payload).then(response => {
+    return this.hawkRequestInternal(LOOP_SESSION_TYPE.FXA, "/fxa-oauth/token", "POST", payload).then(response => {
       return JSON.parse(response.body);
     },
     error => {this._hawkRequestError(error);});
   },
 
   /**
    * Called once gFxAOAuthClient fires onComplete.
    *
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -89,25 +89,20 @@ loop.roomViews = (function(mozL10n) {
       if (event.which === 13) {
         this.handleFormSubmit(event);
       }
     },
 
     handleFormSubmit: function(event) {
       event.preventDefault();
 
-      var newRoomName = this.state.newRoomName;
-
-      if (newRoomName && this.state.roomName != newRoomName) {
-        this.props.dispatcher.dispatch(
-          new sharedActions.RenameRoom({
-            roomToken: this.state.roomToken,
-            newRoomName: newRoomName
-          }));
-      }
+      this.props.dispatcher.dispatch(new sharedActions.RenameRoom({
+        roomToken: this.state.roomToken,
+        newRoomName: this.state.newRoomName
+      }));
     },
 
     handleEmailButtonClick: function(event) {
       event.preventDefault();
 
       this.props.dispatcher.dispatch(
         new sharedActions.EmailRoomUrl({roomUrl: this.state.roomUrl}));
     },
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -89,25 +89,20 @@ loop.roomViews = (function(mozL10n) {
       if (event.which === 13) {
         this.handleFormSubmit(event);
       }
     },
 
     handleFormSubmit: function(event) {
       event.preventDefault();
 
-      var newRoomName = this.state.newRoomName;
-
-      if (newRoomName && this.state.roomName != newRoomName) {
-        this.props.dispatcher.dispatch(
-          new sharedActions.RenameRoom({
-            roomToken: this.state.roomToken,
-            newRoomName: newRoomName
-          }));
-      }
+      this.props.dispatcher.dispatch(new sharedActions.RenameRoom({
+        roomToken: this.state.roomToken,
+        newRoomName: this.state.newRoomName
+      }));
     },
 
     handleEmailButtonClick: function(event) {
       event.preventDefault();
 
       this.props.dispatcher.dispatch(
         new sharedActions.EmailRoomUrl({roomUrl: this.state.roomUrl}));
     },
--- a/browser/components/loop/content/shared/css/common.css
+++ b/browser/components/loop/content/shared/css/common.css
@@ -76,28 +76,31 @@ p {
 
 /* A reset for all button-appearing elements, with the lowest-common
  * denominator of the needed rules.  Intended to be used as a base class
  * together with .btn-*
  */
 
 .btn {
   display: inline-block;
-  overflow: hidden;
   margin: 0;
   padding: 0;
   border: none;
   background: #a5a;
   color: #fff;
   text-align: center;
   text-decoration: none;
+  font-size: .9em;
+  cursor: pointer;
+}
+
+.btn.btn-constrained {
+  overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
-  font-size: .9em;
-  cursor: pointer;
 }
 
 .btn-info {
   background-color: #0096dd;
   border: 1px solid #0095dd;
   color: #fff;
 }
 
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -257,17 +257,17 @@
 
 .call-action-group {
   display: flex;
   padding: 2.5em 4px 0 4px;
   width: 100%;
 }
 
 .call-action-group > .btn {
-  height: 26px;
+  min-height: 26px;
   border-radius: 2px;
   margin: 0 4px;
   min-width: 64px;
 }
 
 .call-action-group .btn-group-chevron,
 .call-action-group .btn-group {
   width: 100%;
--- a/browser/components/loop/content/shared/js/roomStore.js
+++ b/browser/components/loop/content/shared/js/roomStore.js
@@ -149,20 +149,23 @@ loop.store = loop.store || {};
 
     /**
      * Updates current room list when a new room is available.
      *
      * @param {String} eventName     The event name (unused).
      * @param {Object} addedRoomData The added room data.
      */
     _onRoomAdded: function(eventName, addedRoomData) {
-      addedRoomData.participants = [];
-      addedRoomData.ctime = new Date().getTime();
+      addedRoomData.participants = addedRoomData.participants || [];
+      addedRoomData.ctime = addedRoomData.ctime || new Date().getTime();
       this.dispatchAction(new sharedActions.UpdateRoomList({
-        roomList: this._storeState.rooms.concat(new Room(addedRoomData))
+        // Ensure the room isn't part of the list already, then add it.
+        roomList: this._storeState.rooms.filter(function(room) {
+          return addedRoomData.roomToken !== room.roomToken;
+        }).concat(new Room(addedRoomData))
       }));
     },
 
     /**
      * Executed when a room is updated.
      *
      * @param {String} eventName       The event name (unused).
      * @param {Object} updatedRoomData The updated room data.
@@ -415,18 +418,25 @@ loop.store = loop.store || {};
     },
 
     /**
      * Renames a room.
      *
      * @param {sharedActions.RenameRoom} actionData
      */
     renameRoom: function(actionData) {
+      var newRoomName = actionData.newRoomName.trim();
+
+      // Skip update if name is unchanged or empty.
+      if (!newRoomName || this.getStoreState("roomName") === newRoomName) {
+        return;
+      }
+
       this.setStoreState({error: null});
-      this._mozLoop.rooms.rename(actionData.roomToken, actionData.newRoomName,
+      this._mozLoop.rooms.rename(actionData.roomToken, newRoomName,
         function(err) {
           if (err) {
             this.dispatchAction(new sharedActions.RenameRoomError({error: err}));
           }
         }.bind(this));
     },
 
     renameRoomError: function(actionData) {
--- a/browser/components/loop/standalone/content/js/webapp.js
+++ b/browser/components/loop/standalone/content/js/webapp.js
@@ -386,17 +386,17 @@ loop.webapp = (function($, _, OT, mozL10
       var chevronClasses = React.addons.classSet({
         "btn-chevron": true,
         "disabled": this.props.disabled
       });
       return (
         React.DOM.div({className: "standalone-btn-chevron-menu-group"}, 
           React.DOM.div({className: "btn-group-chevron"}, 
             React.DOM.div({className: "btn-group"}, 
-              React.DOM.button({className: "btn btn-large btn-accept", 
+              React.DOM.button({className: "btn btn-constrained btn-large btn-accept", 
                       onClick: this.props.startCall("audio-video"), 
                       disabled: this.props.disabled, 
                       title: mozL10n.get("initiate_audio_video_call_tooltip2")}, 
                 React.DOM.span({className: "standalone-call-btn-text"}, 
                   this.props.caption
                 ), 
                 React.DOM.span({className: "standalone-call-btn-video-icon"})
               ), 
--- a/browser/components/loop/standalone/content/js/webapp.jsx
+++ b/browser/components/loop/standalone/content/js/webapp.jsx
@@ -386,17 +386,17 @@ loop.webapp = (function($, _, OT, mozL10
       var chevronClasses = React.addons.classSet({
         "btn-chevron": true,
         "disabled": this.props.disabled
       });
       return (
         <div className="standalone-btn-chevron-menu-group">
           <div className="btn-group-chevron">
             <div className="btn-group">
-              <button className="btn btn-large btn-accept"
+              <button className="btn btn-constrained btn-large btn-accept"
                       onClick={this.props.startCall("audio-video")}
                       disabled={this.props.disabled}
                       title={mozL10n.get("initiate_audio_video_call_tooltip2")}>
                 <span className="standalone-call-btn-text">
                   {this.props.caption}
                 </span>
                 <span className="standalone-call-btn-video-icon" />
               </button>
--- a/browser/components/loop/test/desktop-local/roomViews_test.js
+++ b/browser/components/loop/test/desktop-local/roomViews_test.js
@@ -123,36 +123,40 @@ describe("loop.roomViews", function () {
       beforeEach(function() {
         view = mountTestComponent();
         view.setState({
           roomToken: "fakeToken",
           roomName: "fakeName"
         });
 
         roomNameBox = view.getDOMNode().querySelector('.input-room-name');
-
-        React.addons.TestUtils.Simulate.change(roomNameBox, { target: {
-          value: "reallyFake"
-        }});
       });
 
       it("should dispatch a RenameRoom action when the focus is lost",
         function() {
+          React.addons.TestUtils.Simulate.change(roomNameBox, { target: {
+            value: "reallyFake"
+          }});
+
           React.addons.TestUtils.Simulate.blur(roomNameBox);
 
           sinon.assert.calledOnce(dispatcher.dispatch);
           sinon.assert.calledWithExactly(dispatcher.dispatch,
             new sharedActions.RenameRoom({
               roomToken: "fakeToken",
               newRoomName: "reallyFake"
             }));
         });
 
       it("should dispatch a RenameRoom action when Enter key is pressed",
         function() {
+          React.addons.TestUtils.Simulate.change(roomNameBox, { target: {
+            value: "reallyFake"
+          }});
+
           TestUtils.Simulate.keyDown(roomNameBox, {key: "Enter", which: 13});
 
           sinon.assert.calledOnce(dispatcher.dispatch);
           sinon.assert.calledWithExactly(dispatcher.dispatch,
             new sharedActions.RenameRoom({
               roomToken: "fakeToken",
               newRoomName: "reallyFake"
             }));
--- a/browser/components/loop/test/shared/roomStore_test.js
+++ b/browser/components/loop/test/shared/roomStore_test.js
@@ -16,44 +16,44 @@ describe("loop.store.Room", function () 
 });
 
 describe("loop.store.RoomStore", function () {
   "use strict";
 
   var sharedActions = loop.shared.actions;
   var sharedUtils = loop.shared.utils;
   var sandbox, dispatcher;
-
-  var fakeRoomList = [{
-    roomToken: "_nxD4V4FflQ",
-    roomUrl: "http://sample/_nxD4V4FflQ",
-    roomName: "First Room Name",
-    maxSize: 2,
-    participants: [],
-    ctime: 1405517546
-  }, {
-    roomToken: "QzBbvGmIZWU",
-    roomUrl: "http://sample/QzBbvGmIZWU",
-    roomName: "Second Room Name",
-    maxSize: 2,
-    participants: [],
-    ctime: 1405517418
-  }, {
-    roomToken: "3jKS_Els9IU",
-    roomUrl: "http://sample/3jKS_Els9IU",
-    roomName: "Third Room Name",
-    maxSize: 3,
-    clientMaxSize: 2,
-    participants: [],
-    ctime: 1405518241
-  }];
+  var fakeRoomList;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
     dispatcher = new loop.Dispatcher();
+    fakeRoomList = [{
+      roomToken: "_nxD4V4FflQ",
+      roomUrl: "http://sample/_nxD4V4FflQ",
+      roomName: "First Room Name",
+      maxSize: 2,
+      participants: [],
+      ctime: 1405517546
+    }, {
+      roomToken: "QzBbvGmIZWU",
+      roomUrl: "http://sample/QzBbvGmIZWU",
+      roomName: "Second Room Name",
+      maxSize: 2,
+      participants: [],
+      ctime: 1405517418
+    }, {
+      roomToken: "3jKS_Els9IU",
+      roomUrl: "http://sample/3jKS_Els9IU",
+      roomName: "Third Room Name",
+      maxSize: 3,
+      clientMaxSize: 2,
+      participants: [],
+      ctime: 1405518241
+    }];
   });
 
   afterEach(function() {
     sandbox.restore();
   });
 
   describe("#constructor", function() {
     it("should throw an error if mozLoop is missing", function() {
@@ -116,16 +116,27 @@ describe("loop.store.RoomStore", functio
             roomName: "New room",
             maxSize: 2,
             participants: [],
             ctime: 1405517546
           });
 
           expect(store.getStoreState().rooms).to.have.length.of(4);
         });
+
+        it("should avoid adding a duplicate room", function() {
+          var sampleRoom = fakeRoomList[0];
+
+          fakeMozLoop.rooms.trigger("add", "add", sampleRoom);
+
+          expect(store.getStoreState().rooms).to.have.length.of(3);
+          expect(store.getStoreState().rooms.reduce(function(count, room) {
+            return count += room.roomToken === sampleRoom.roomToken ? 1 : 0;
+          }, 0)).eql(1);
+        });
       });
 
       describe("update", function() {
         it("should update a room entry", function() {
           fakeMozLoop.rooms.trigger("update", "update", {
             roomToken: "_nxD4V4FflQ",
             roomUrl: "http://sample/_nxD4V4FflQ",
             roomName: "Changed First Room Name",
@@ -534,10 +545,21 @@ describe("loop.store.RoomStore", functio
 
       dispatcher.dispatch(new sharedActions.RenameRoom({
         roomToken: "42abc",
         newRoomName: "silly name"
       }));
 
       expect(store.getStoreState().error).eql(err);
     });
+
+    it("should ensure only submitting a non-empty room name", function() {
+      fakeMozLoop.rooms.rename = sinon.spy();
+
+      dispatcher.dispatch(new sharedActions.RenameRoom({
+        roomToken: "42abc",
+        newRoomName: " \t  \t "
+      }));
+
+      sinon.assert.notCalled(fakeMozLoop.rooms.rename);
+    });
   });
 });
--- a/browser/components/preferences/in-content/search.js
+++ b/browser/components/preferences/in-content/search.js
@@ -85,16 +85,19 @@ var gSearchPane = {
   onTreeSelect: function() {
     document.getElementById("removeEngineButton").disabled =
       gEngineView.selectedIndex == -1 || gEngineView.lastIndex == 0;
   },
 
   onTreeKeyPress: function(aEvent) {
     let index = gEngineView.selectedIndex;
     let tree = document.getElementById("engineList");
+    if (tree.hasAttribute("editing"))
+      return;
+
     if (aEvent.charCode == KeyEvent.DOM_VK_SPACE) {
       // Space toggles the checkbox.
       let newValue = !gEngineView._engineStore.engines[index].shown;
       gEngineView.setCellValue(index, tree.columns.getFirstColumn(),
                                newValue.toString());
     }
     else {
       let isMac = Services.appinfo.OS == "Darwin";
--- a/browser/components/preferences/search.js
+++ b/browser/components/preferences/search.js
@@ -78,16 +78,19 @@ var gSearchPane = {
   onTreeSelect: function() {
     document.getElementById("removeEngineButton").disabled =
       gEngineView.selectedIndex == -1 || gEngineView.lastIndex == 0;
   },
 
   onTreeKeyPress: function(aEvent) {
     let index = gEngineView.selectedIndex;
     let tree = document.getElementById("engineList");
+    if (tree.hasAttribute("editing"))
+      return;
+
     if (aEvent.charCode == KeyEvent.DOM_VK_SPACE) {
       // Space toggles the checkbox.
       let newValue = !gEngineView._engineStore.engines[index].shown;
       gEngineView.setCellValue(index, tree.columns.getFirstColumn(),
                                newValue.toString());
     }
     else {
       let isMac = Services.appinfo.OS == "Darwin";
--- a/browser/devtools/fontinspector/test/browser_fontinspector.html
+++ b/browser/devtools/fontinspector/test/browser_fontinspector.html
@@ -1,30 +1,51 @@
 <!DOCTYPE html>
 
 <style>
   @font-face {
     font-family: bar;
     src: url(bad/font/name.ttf), url(ostrich-regular.ttf) format("truetype");
   }
   @font-face {
+    font-family: barnormal;
+    font-weight: normal;
+    src: url(ostrich-regular.ttf);
+  }
+  @font-face {
+    font-family: bar;
+    font-weight: bold;
+    src: url(ostrich-black.ttf);
+  }
+  @font-face {
     font-family: bar;
     font-weight: 800;
     src: url(ostrich-black.ttf);
   }
-
   body{
     font-family:Arial;
+    font-size: 36px;
   }
   div {
     font-family:Arial;
     font-family:bar;
   }
+  .normal-text {
+    font-family: barnormal;
+    font-weight: normal;
+  }
   .bold-text {
     font-family: bar;
+    font-weight: bold;
+  }
+  .black-text {
+    font-family: bar;
     font-weight: 800;
   }
 </style>
 
 <body>
   BODY
   <div>DIV</div>
+  <div class="normal-text">NORMAL DIV</div>
+  <div class="bold-text">BOLD DIV</div>
+  <div class="black-text">800 DIV</div>
 </body>
--- a/browser/devtools/fontinspector/test/browser_fontinspector.js
+++ b/browser/devtools/fontinspector/test/browser_fontinspector.js
@@ -5,16 +5,29 @@ let tempScope = {};
 let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let TargetFactory = devtools.TargetFactory;
 
 let TEST_URI = "http://mochi.test:8888/browser/browser/devtools/fontinspector/test/browser_fontinspector.html";
 
 let view, viewDoc;
 
+const BASE_URI = "http://mochi.test:8888/browser/browser/devtools/fontinspector/test/"
+
+const FONTS = [
+  {name: "Ostrich Sans Medium", remote: true, url: BASE_URI + "ostrich-regular.ttf",
+   format: "truetype", cssName: "bar"},
+  {name: "Ostrich Sans Black", remote: true, url: BASE_URI + "ostrich-black.ttf",
+   format: "", cssName: "bar"},
+  {name: "Ostrich Sans Black", remote: true, url: BASE_URI + "ostrich-black.ttf",
+   format: "", cssName: "bar"},
+  {name: "Ostrich Sans Medium", remote: true, url: BASE_URI + "ostrich-regular.ttf",
+   format: "", cssName: "barnormal"},
+];
+
 add_task(function*() {
   yield loadTab(TEST_URI);
   let {toolbox, inspector} = yield openInspector();
 
   info("Selecting the test node");
   yield selectNode("body", inspector);
 
   let updated = inspector.once("fontinspector-updated");
@@ -34,42 +47,51 @@ add_task(function*() {
 
   yield testShowAllFonts(inspector);
 
   view = viewDoc = null;
 });
 
 function* testBodyFonts(inspector) {
   let s = viewDoc.querySelectorAll("#all-fonts > section");
-  is(s.length, 2, "Found 2 fonts");
+  is(s.length, 5, "Found 5 fonts");
 
-  // test first web font
-  is(s[0].querySelector(".font-name").textContent,
-     "Ostrich Sans Medium", "font 0: Right font name");
-  ok(s[0].classList.contains("is-remote"),
-     "font 0: is remote");
-  is(s[0].querySelector(".font-url").value,
-     "http://mochi.test:8888/browser/browser/devtools/fontinspector/test/ostrich-regular.ttf",
-     "font 0: right url");
-  is(s[0].querySelector(".font-format").textContent,
-     "truetype", "font 0: right font format");
-  is(s[0].querySelector(".font-css-name").textContent,
-     "bar", "font 0: right css name");
+  for (let i = 0; i < FONTS.length; i++) {
+    let section = s[i];
+    let font = FONTS[i];
+    is(section.querySelector(".font-name").textContent, font.name,
+       "font " + i + " right font name");
+    is(section.classList.contains("is-remote"), font.remote,
+       "font " + i + " remote value correct");
+    is(section.querySelector(".font-url").value, font.url,
+       "font " + i + " url correct");
+    is(section.querySelector(".font-format").hidden, !font.format,
+       "font " + i + " format hidden value correct");
+    is(section.querySelector(".font-format").textContent,
+       font.format, "font " + i + " format correct");
+    is(section.querySelector(".font-css-name").textContent,
+       font.cssName, "font " + i + " css name correct");
+  }
+
+  // test that the bold and regular fonts have different previews
+  let regSrc = s[0].querySelector(".font-preview").src;
+  let boldSrc = s[1].querySelector(".font-preview").src;
+  isnot(regSrc, boldSrc, "preview for bold font is different from regular");
 
   // test system font
-  let font2Name = s[1].querySelector(".font-name").textContent;
-  let font2CssName = s[1].querySelector(".font-css-name").textContent;
+  let localFontName = s[4].querySelector(".font-name").textContent;
+  let localFontCSSName = s[4].querySelector(".font-css-name").textContent;
 
   // On Linux test machines, the Arial font doesn't exist.
   // The fallback is "Liberation Sans"
-  ok((font2Name == "Arial") || (font2Name == "Liberation Sans"),
-     "font 1: Right font name");
-  ok(s[1].classList.contains("is-local"), "font 2: is local");
-  ok((font2CssName == "Arial") || (font2CssName == "Liberation Sans"),
-     "Arial", "font 2: right css name");
+  ok((localFontName == "Arial") || (localFontName == "Liberation Sans"),
+     "local font right font name");
+  ok(s[4].classList.contains("is-local"), "local font is local");
+  ok((localFontCSSName == "Arial") || (localFontCSSName == "Liberation Sans"),
+     "Arial", "local font has right css name");
 }
 
 function* testDivFonts(inspector) {
   let updated = inspector.once("fontinspector-updated");
   yield selectNode("div", inspector);
   yield updated;
 
   let sections1 = viewDoc.querySelectorAll("#all-fonts > section");
@@ -82,10 +104,10 @@ function* testShowAllFonts(inspector) {
   info("testing showing all fonts");
 
   let updated = inspector.once("fontinspector-updated");
   viewDoc.querySelector("#showall").click();
   yield updated;
 
   is(inspector.selection.nodeFront.nodeName, "BODY", "Show all fonts selected the body node");
   let sections = viewDoc.querySelectorAll("#all-fonts > section");
-  is(sections.length, 2, "And font-inspector still shows 2 fonts for body");
+  is(sections.length, 5, "And font-inspector still shows 5 fonts for body");
 }
--- a/browser/devtools/webide/content/prefs.xhtml
+++ b/browser/devtools/webide/content/prefs.xhtml
@@ -25,33 +25,39 @@
     </div>
 
     <h1>&prefs_title;</h1>
 
     <h2>&prefs_general_title;</h2>
 
     <ul>
       <li>
-        <label title="&prefs_options_templatesurl_tooltip;">
-          <span>&prefs_options_templatesurl;</span>
-          <input data-pref="devtools.webide.templatesURL"/>
-        </label>
-      </li>
-      <li>
         <label title="&prefs_options_rememberlastproject_tooltip;">
           <input type="checkbox" data-pref="devtools.webide.restoreLastProject"/>
           <span>&prefs_options_rememberlastproject;</span>
         </label>
       </li>
       <li>
+        <label title="&prefs_options_autoconnectruntime_tooltip;">
+          <input type="checkbox" data-pref="devtools.webide.autoConnectRuntime"/>
+          <span>&prefs_options_autoconnectruntime;</span>
+        </label>
+      </li>
+      <li>
         <label title="&prefs_options_showeditor_tooltip;">
           <input type="checkbox" data-pref="devtools.webide.showProjectEditor"/>
           <span>&prefs_options_showeditor;</span>
         </label>
       </li>
+      <li>
+        <label title="&prefs_options_templatesurl_tooltip;">
+          <span>&prefs_options_templatesurl;</span>
+          <input data-pref="devtools.webide.templatesURL"/>
+        </label>
+      </li>
     </ul>
 
     <h2>&prefs_editor_title;</h2>
 
     <ul>
       <li>
         <label><span>&prefs_options_keybindings;</span>
           <select data-pref="devtools.editor.keymap">
--- a/browser/devtools/webide/content/webide.js
+++ b/browser/devtools/webide/content/webide.js
@@ -89,18 +89,16 @@ let UI = {
     if (autoinstallFxdtAdapters) {
       GetAvailableAddons().then(addons => {
         addons.adapters.install();
       }, console.error);
     }
     Services.prefs.setBoolPref("devtools.webide.autoinstallADBHelper", false);
     Services.prefs.setBoolPref("devtools.webide.autoinstallFxdtAdapters", false);
 
-    this.lastConnectedRuntime = Services.prefs.getCharPref("devtools.webide.lastConnectedRuntime");
-
     if (Services.prefs.getBoolPref("devtools.webide.widget.autoinstall") &&
         !Services.prefs.getBoolPref("devtools.webide.widget.enabled")) {
       Services.prefs.setBoolPref("devtools.webide.widget.enabled", true);
       gDevToolsBrowser.moveWebIDEWidgetInNavbar();
     }
 
     this.setupDeck();
 
@@ -388,20 +386,30 @@ let UI = {
           this.hidePanels();
           this.dismissErrorNotification();
           this.connectToRuntime(r);
         }, true);
       }
     }
   },
 
+  get lastConnectedRuntime() {
+    return Services.prefs.getCharPref("devtools.webide.lastConnectedRuntime");
+  },
+
+  set lastConnectedRuntime(runtime) {
+    Services.prefs.setCharPref("devtools.webide.lastConnectedRuntime", runtime);
+  },
+
   autoConnectRuntime: function () {
     // Automatically reconnect to the previously selected runtime,
-    // if available and has an ID
-    if (AppManager.selectedRuntime || !this.lastConnectedRuntime) {
+    // if available and has an ID and feature is enabled
+    if (AppManager.selectedRuntime ||
+        !Services.prefs.getBoolPref("devtools.webide.autoConnectRuntime") ||
+        !this.lastConnectedRuntime) {
       return;
     }
     let [_, type, id] = this.lastConnectedRuntime.match(/^(\w+):(.+)$/);
 
     type = type.toLowerCase();
 
     // Local connection is mapped to AppManager.runtimeList.other array
     if (type == "local") {
@@ -410,16 +418,18 @@ let UI = {
 
     // We support most runtimes except simulator, that needs to be manually
     // launched
     if (type == "usb" || type == "wifi" || type == "other") {
       for (let runtime of AppManager.runtimeList[type]) {
         // Some runtimes do not expose an id and don't support autoconnect (like
         // remote connection)
         if (runtime.id == id) {
+          // Only want one auto-connect attempt, so clear last runtime value
+          this.lastConnectedRuntime = "";
           this.connectToRuntime(runtime);
         }
       }
     }
   },
 
   connectToRuntime: function(runtime) {
     let name = runtime.name;
@@ -441,18 +451,16 @@ let UI = {
   saveLastConnectedRuntime: function () {
     if (AppManager.selectedRuntime &&
         AppManager.selectedRuntime.id !== undefined) {
       this.lastConnectedRuntime = AppManager.selectedRuntime.type + ":" +
                                   AppManager.selectedRuntime.id;
     } else {
       this.lastConnectedRuntime = "";
     }
-    Services.prefs.setCharPref("devtools.webide.lastConnectedRuntime",
-                               this.lastConnectedRuntime);
   },
 
   _actionsToLog: new Set(),
 
   /**
    * For each new connection, track whether play and debug were ever used.  Only
    * one value is collected for each button, even if they are used multiple
    * times during a connection.
--- a/browser/devtools/webide/webide-prefs.js
+++ b/browser/devtools/webide/webide-prefs.js
@@ -6,16 +6,17 @@
 pref("devtools.webide.showProjectEditor", true);
 pref("devtools.webide.templatesURL", "https://code.cdn.mozilla.net/templates/list.json");
 pref("devtools.webide.autoinstallADBHelper", true);
 #ifdef MOZ_DEV_EDITION
 pref("devtools.webide.autoinstallFxdtAdapters", true);
 #else
 pref("devtools.webide.autoinstallFxdtAdapters", false);
 #endif
+pref("devtools.webide.autoConnectRuntime", true);
 pref("devtools.webide.restoreLastProject", true);
 pref("devtools.webide.enableLocalRuntime", false);
 pref("devtools.webide.addonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/index.json");
 pref("devtools.webide.simulatorAddonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/#VERSION#/#OS#/fxos_#SLASHED_VERSION#_simulator-#OS#-latest.xpi");
 pref("devtools.webide.simulatorAddonID", "fxos_#SLASHED_VERSION#_simulator@mozilla.org");
 pref("devtools.webide.adbAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/adb-helper/#OS#/adbhelper-#OS#-latest.xpi");
 pref("devtools.webide.adbAddonID", "adbhelper@mozilla.org");
 pref("devtools.webide.adaptersAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxdt-adapters/#OS#/fxdt-adapters-#OS#-latest.xpi");
--- a/browser/locales/en-US/chrome/browser/devtools/webide.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/webide.dtd
@@ -109,16 +109,18 @@
 <!ENTITY addons_aboutaddons "Open Add-ons Manager">
 
 <!-- Prefs -->
 <!ENTITY prefs_title "Preferences">
 <!ENTITY prefs_editor_title "Editor">
 <!ENTITY prefs_general_title "General">
 <!ENTITY prefs_restore "Restore Defaults">
 <!ENTITY prefs_manage_components "Manage Extra Components">
+<!ENTITY prefs_options_autoconnectruntime "Reconnect to previous runtime">
+<!ENTITY prefs_options_autoconnectruntime_tooltip "Reconnect to previous runtime when WebIDE starts">
 <!ENTITY prefs_options_rememberlastproject "Remember last project">
 <!ENTITY prefs_options_rememberlastproject_tooltip "Restore previous project when WebIDE starts">
 <!ENTITY prefs_options_templatesurl "Templates URL">
 <!ENTITY prefs_options_templatesurl_tooltip "Index of available templates">
 <!ENTITY prefs_options_showeditor "Show editor">
 <!ENTITY prefs_options_showeditor_tooltip "Show internal editor">
 <!ENTITY prefs_options_tabsize "Tab size">
 <!ENTITY prefs_options_expandtab "Soft tabs">
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -230,16 +230,19 @@ description > html|a {
   background-color: rgba(0,0,0,0.5);
   visibility: hidden;
 }
 
 #dialogBox {
   background-color: #fbfbfb;
   color: #424e5a;
   font-size: 14px;
+  /* `transparent` will use the dialogText color in high-contrast themes and
+     when page colors are disabled */
+  border: 1px solid transparent;
   border-radius: 2.5px;
   box-shadow: 0 2px 4px 0 rgba(0,0,0,0.5);
   display: -moz-box;
   margin: 0;
   padding: 0;
 }
 
 #dialogBox[resizable="true"] {
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -1645,16 +1645,17 @@ public abstract class GeckoApp
                 throw new SessionRestoreException("Could not read from session file");
             }
 
             // If we are doing an OOM restore, parse the session data and
             // stub the restored tabs immediately. This allows the UI to be
             // updated before Gecko has restored.
             if (mShouldRestore) {
                 final JSONArray tabs = new JSONArray();
+                final JSONObject windowObject = new JSONObject();
                 SessionParser parser = new SessionParser() {
                     @Override
                     public void onTabRead(SessionTab sessionTab) {
                         JSONObject tabObject = sessionTab.getTabObject();
 
                         int flags = Tabs.LOADURL_NEW_TAB;
                         flags |= ((isExternalURL || !sessionTab.isSelected()) ? Tabs.LOADURL_DELAY_LOAD : 0);
                         flags |= (tabObject.optBoolean("desktopMode") ? Tabs.LOADURL_DESKTOP : 0);
@@ -1665,35 +1666,40 @@ public abstract class GeckoApp
 
                         try {
                             tabObject.put("tabId", tab.getId());
                         } catch (JSONException e) {
                             Log.e(LOGTAG, "JSON error", e);
                         }
                         tabs.put(tabObject);
                     }
+
+                    @Override
+                    public void onClosedTabsRead(final JSONArray closedTabData) throws JSONException {
+                        windowObject.put("closedTabs", closedTabData);
+                    }
                 };
 
                 if (mPrivateBrowsingSession == null) {
                     parser.parse(sessionString);
                 } else {
                     parser.parse(sessionString, mPrivateBrowsingSession);
                 }
 
                 if (tabs.length() > 0) {
-                    sessionString = new JSONObject().put("windows", new JSONArray().put(new JSONObject().put("tabs", tabs))).toString();
+                    windowObject.put("tabs", tabs);
+                    sessionString = new JSONObject().put("windows", new JSONArray().put(windowObject)).toString();
                 } else {
                     throw new SessionRestoreException("No tabs could be read from session file");
                 }
             }
 
             JSONObject restoreData = new JSONObject();
             restoreData.put("sessionString", sessionString);
             return restoreData.toString();
-
         } catch (JSONException e) {
             throw new SessionRestoreException(e);
         }
     }
 
     @Override
     public synchronized GeckoProfile getProfile() {
         // fall back to default profile if we didn't load a specific one
--- a/mobile/android/base/SessionParser.java
+++ b/mobile/android/base/SessionParser.java
@@ -47,25 +47,38 @@ public abstract class SessionParser {
 
         public JSONObject getTabObject() {
             return mTabObject;
         }
     };
 
     abstract public void onTabRead(SessionTab tab);
 
+    /**
+     * Placeholder method that must be overloaded to handle closedTabs while parsing session data.
+     *
+     * @param closedTabs, JSONArray of recently closed tab entries.
+     * @throws JSONException
+     */
+    public void onClosedTabsRead(final JSONArray closedTabs) throws JSONException{
+    }
+
     public void parse(String... sessionStrings) {
         final LinkedList<SessionTab> sessionTabs = new LinkedList<SessionTab>();
         int totalCount = 0;
         int selectedIndex = -1;
         try {
             for (String sessionString : sessionStrings) {
                 final JSONObject window = new JSONObject(sessionString).getJSONArray("windows").getJSONObject(0);
                 final JSONArray tabs = window.getJSONArray("tabs");
                 final int optSelected = window.optInt("selected", -1);
+                final JSONArray closedTabs = window.optJSONArray("closedTabs");
+                if (closedTabs != null) {
+                    onClosedTabsRead(closedTabs);
+                }
 
                 for (int i = 0; i < tabs.length(); i++) {
                     final JSONObject tab = tabs.getJSONObject(i);
                     final int index = tab.getInt("index");
                     final JSONArray entries = tab.getJSONArray("entries");
                     if (index < 1 || entries.length() < index) {
                         Log.w(LOGTAG, "Session entries and index don't agree.");
                         continue;
--- a/mobile/android/base/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/toolbar/BrowserToolbar.java
@@ -135,17 +135,16 @@ public abstract class BrowserToolbar ext
     protected boolean hasSoftMenuButton;
 
     protected UIMode uiMode;
     protected TabHistoryController tabHistoryController;
 
     private final Paint shadowPaint;
     private final int shadowSize;
 
-    private final LightweightTheme theme;
     private final ToolbarPrefs prefs;
 
     public abstract boolean isAnimating();
 
     protected abstract boolean isTabsButtonOffscreen();
 
     protected abstract void updateNavigationButtons(Tab tab);
 
@@ -174,17 +173,16 @@ public abstract class BrowserToolbar ext
         return toolbar;
     }
 
     protected BrowserToolbar(final Context context, final AttributeSet attrs) {
         super(context, attrs);
         setWillNotDraw(false);
 
         isNewTablet = NewTabletUI.isEnabled(context);
-        theme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();
 
         // BrowserToolbar is attached to BrowserApp only.
         activity = (BrowserApp) context;
 
         // Inflate the content.
         // TODO: Remove the branch when new tablet becomes old tablet.
         if (!isNewTablet) {
             LayoutInflater.from(context).inflate(R.layout.browser_toolbar, this);
--- a/mobile/android/base/toolbar/NavButton.java
+++ b/mobile/android/base/toolbar/NavButton.java
@@ -58,17 +58,17 @@ abstract class NavButton extends ShapedB
         canvas.drawPath(mBorderPath, mBorderPaint);
     }
 
     // The drawable is constructed as per @drawable/url_bar_nav_button.
     @Override
     public void onLightweightThemeChanged() {
         final Drawable drawable;
         if (!NewTabletUI.isEnabled(getContext())) {
-            drawable = mTheme.getDrawable(this);
+            drawable = getTheme().getDrawable(this);
         } else {
             drawable = BrowserToolbar.getLightweightThemeDrawable(this, getResources(), getTheme(),
                     R.color.background_normal);
         }
 
         if (drawable == null) {
             return;
         }
--- a/mobile/android/base/toolbar/ShapedButton.java
+++ b/mobile/android/base/toolbar/ShapedButton.java
@@ -18,24 +18,22 @@ import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.StateListDrawable;
 import android.util.AttributeSet;
 
 public class ShapedButton extends ThemedImageButton
                           implements CanvasDelegate.DrawManager {
-    protected final LightweightTheme mTheme;
 
     protected final Path mPath;
     protected final CanvasDelegate mCanvasDelegate;
 
     public ShapedButton(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();
 
         // Path is clipped.
         mPath = new Path();
 
         final Paint paint = new Paint();
         paint.setAntiAlias(true);
         paint.setColor(getResources().getColor(R.color.canvas_delegate_paint));
         paint.setStrokeWidth(0.0f);
@@ -56,17 +54,17 @@ public class ShapedButton extends Themed
     public void defaultDraw(Canvas canvas) {
         super.draw(canvas);
     }
 
     // The drawable is constructed as per @drawable/shaped_button.
     @Override
     public void onLightweightThemeChanged() {
         final int background = getResources().getColor(R.color.background_tabs);
-        final LightweightThemeDrawable lightWeight = mTheme.getColorDrawable(this, background);
+        final LightweightThemeDrawable lightWeight = getTheme().getColorDrawable(this, background);
 
         if (lightWeight == null)
             return;
 
         lightWeight.setAlpha(34, 34);
 
         final StateListDrawable stateList = new StateListDrawable();
         stateList.addState(PRESSED_ENABLED_STATE_SET, getColorDrawable(R.color.highlight_shaped));
--- a/mobile/android/components/SessionStore.js
+++ b/mobile/android/components/SessionStore.js
@@ -429,16 +429,20 @@ SessionStore.prototype = {
 
     for (let winIndex = 0; winIndex < data.windows.length; ++winIndex) {
       let win = data.windows[winIndex];
       let normalWin = {};
       for (let prop in win) {
         normalWin[prop] = data[prop];
       }
       normalWin.tabs = [];
+
+      // Save normal closed tabs. Forget about private closed tabs.
+      normalWin.closedTabs = win.closedTabs.filter(tab => !tab.isPrivate);
+
       normalData.windows.push(normalWin);
       privateData.windows.push({ tabs: [] });
 
       // Split the session data into private and non-private data objects.
       // Non-private session data will be saved to disk, and private session
       // data will be sent to Java for Android to hold it in memory.
       for (let i = 0; i < win.tabs.length; ++i) {
         let tab = win.tabs[i];
@@ -860,16 +864,21 @@ SessionStore.prototype = {
         // Make sure the browser has its session data for the delay reload
         tab.browser.__SS_data = tabData;
         tab.browser.__SS_restore = true;
         tab.browser.setAttribute("pending", "true");
       }
 
       tab.browser.__SS_extdata = tabData.extData;
     }
+
+    // Restore the closed tabs array on the current window.
+    if (state.windows[0].closedTabs) {
+      this._windows[window.__SSID].closedTabs = state.windows[0].closedTabs;
+    }
   },
 
   getClosedTabCount: function ss_getClosedTabCount(aWindow) {
     if (!aWindow || !aWindow.__SSID || !this._windows[aWindow.__SSID])
       return 0; // not a browser window, or not otherwise tracked by SS.
 
     return this._windows[aWindow.__SSID].closedTabs.length;
   },
@@ -989,36 +998,19 @@ SessionStore.prototype = {
     else
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
   },
 
   restoreLastSession: Task.async(function* (aSessionString) {
     let notifyMessage = "";
 
     try {
-      // Normally, we'll receive the session string from Java, but there are
-      // cases where we may want to restore that Java cannot detect (e.g., if
-      // browser.sessionstore.resume_session_once is true). In these cases, the
-      // session will be read from sessionstore.bak (which is also used for
-      // "tabs from last time").
-      let data = aSessionString;
-
-      if (data == null) {
-        let bytes = yield OS.File.read(this._sessionFileBackup.path);
-        data = JSON.parse(new TextDecoder().decode(bytes) || "");
-      }
-
-      this._restoreWindow(data);
+      this._restoreWindow(aSessionString);
     } catch (e) {
-      if (e instanceof OS.File.Error) {
-        Cu.reportError("SessionStore: " + e.message);
-      } else {
-        Cu.reportError("SessionStore: " + e);
-      }
-
+      Cu.reportError("SessionStore: " + e);
       notifyMessage = "fail";
     }
 
     Services.obs.notifyObservers(null, "sessionstore-windows-restored", notifyMessage);
   }),
 
   removeWindow: function ss_removeWindow(aWindow) {
     if (!aWindow || !aWindow.__SSID || !this._windows[aWindow.__SSID])
--- a/toolkit/devtools/server/actors/styles.js
+++ b/toolkit/devtools/server/actors/styles.js
@@ -35,16 +35,18 @@ exports.PSEUDO_ELEMENTS = PSEUDO_ELEMENT
 const PSEUDO_ELEMENTS_TO_READ = PSEUDO_ELEMENTS.filter(pseudo => {
   return pseudo !== ":before" && pseudo !== ":after";
 });
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 const FONT_PREVIEW_TEXT = "Abc";
 const FONT_PREVIEW_FONT_SIZE = 40;
 const FONT_PREVIEW_FILLSTYLE = "black";
+const NORMAL_FONT_WEIGHT = 400;
+const BOLD_FONT_WEIGHT = 700;
 
 // Predeclare the domnode actor type for use in requests.
 types.addActorType("domnode");
 
 // Predeclare the domstylerule actor type
 types.addActorType("domstylerule");
 
 /**
@@ -271,34 +273,53 @@ var PageStyleActor = protocol.ActorClass
       }
 
       // If this font comes from a @font-face rule
       if (font.rule) {
         fontFace.rule = StyleRuleActor(this, font.rule);
         fontFace.ruleText = font.rule.cssText;
       }
 
+      // Get the weight and style of this font for the preview and sort order
+      let weight = NORMAL_FONT_WEIGHT, style = "";
+      if (font.rule) {
+        weight = font.rule.style.getPropertyValue("font-weight")
+                 || NORMAL_FONT_WEIGHT;
+        if (weight == "bold") {
+          weight = BOLD_FONT_WEIGHT;
+        } else if (weight == "normal") {
+          weight = NORMAL_FONT_WEIGHT;
+        }
+        style = font.rule.style.getPropertyValue("font-style") || "";
+      }
+      fontFace.weight = weight;
+      fontFace.style = style;
+
       if (options.includePreviews) {
         let opts = {
           previewText: options.previewText,
           previewFontSize: options.previewFontSize,
+          fontStyle: weight + " " + style,
           fillStyle: options.previewFillStyle
         }
         let { dataURL, size } = getFontPreviewData(font.CSSFamilyName,
                                                    contentDocument, opts);
         fontFace.preview = {
           data: LongStringActor(this.conn, dataURL),
           size: size
         };
       }
       fontsArray.push(fontFace);
     }
 
     // @font-face fonts at the top, then alphabetically, then by weight
     fontsArray.sort(function(a, b) {
+      return a.weight > b.weight ? 1 : -1;
+    });
+    fontsArray.sort(function(a, b) {
       if (a.CSSFamilyName == b.CSSFamilyName) {
         return 0;
       }
       return a.CSSFamilyName > b.CSSFamilyName ? 1 : -1;
     });
     fontsArray.sort(function(a, b) {
       if ((a.rule && b.rule) || (!a.rule && !b.rule)) {
         return 0;
--- a/toolkit/mozapps/extensions/internal/AddonRepository.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonRepository.jsm
@@ -839,16 +839,21 @@ this.AddonRepository = {
         if (result == null)
           continue;
 
         // Ignore add-on if it wasn't actually requested
         let idIndex = ids.indexOf(result.addon.id);
         if (idIndex == -1)
           continue;
 
+        // Ignore add-on if the add-on manager doesn't know about its type:
+        if (!(result.addon.type in AddonManager.addonTypes)) {
+          continue;
+        }
+
         results.push(result);
         // Ignore this add-on from now on
         ids.splice(idIndex, 1);
       }
 
       // Include any compatibility overrides for addons not hosted by the
       // remote repository.
       for each (let addonCompat in aCompatData) {
@@ -1084,20 +1089,18 @@ this.AddonRepository = {
               break;
             case 3:
               addon.type = "dictionary";
               break;
             case 4:
               addon.type = "search";
               break;
             case 5:
-              addon.type = "langpack";
-              break;
             case 6:
-              addon.type = "langpack-addon";
+              addon.type = "locale";
               break;
             case 7:
               addon.type = "plugin";
               break;
             case 8:
               addon.type = "api";
               break;
             case 9:
@@ -1298,16 +1301,20 @@ this.AddonRepository = {
       if (result == null)
         continue;
 
       // Ignore add-on missing a required attribute
       let requiredAttributes = ["id", "name", "version", "type", "creator"];
       if (requiredAttributes.some(function parseAddons_attributeFilter(aAttribute) !result.addon[aAttribute]))
         continue;
 
+      // Ignore add-on with a type AddonManager doesn't understand:
+      if (!(result.addon.type in AddonManager.addonTypes))
+        continue;
+
       // Add only if the add-on is compatible with the platform
       if (!result.addon.isPlatformCompatible)
         continue;
 
       // Add only if there was an xpi compatible with this OS or there was a
       // way to purchase the add-on
       if (!result.xpiURL && !result.addon.purchaseURL)
         continue;
--- a/toolkit/mozapps/extensions/test/browser/browser_searching.xml
+++ b/toolkit/mozapps/extensions/test/browser/browser_searching.xml
@@ -28,16 +28,47 @@
         <max_version>*</max_version>
       </application>
     </compatible_applications>
     <compatible_os>ALL</compatible_os>
     <install size="1">http://example.com/addon1.xpi</install>
   </addon>
   <addon>
     <name>FAIL</name>
+    <type id='9'>lightweight theme</type>
+    <guid>addon12345@tests.mozilla.org</guid>
+    <version>1.0</version>
+    <authors>
+      <author>
+        <name>Test Creator</name>
+        <link>http://example.com/creator.html</link>
+      </author>
+    </authors>
+    <status id='4'>Public</status>
+    <summary>Addon with uninstallable type shouldn't be visible in search</summary>
+    <description>Test description</description>
+    <compatible_applications>
+      <application>
+        <name>Firefox</name>
+        <appID>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</appID>
+        <min_version>0</min_version>
+        <max_version>*</max_version>
+      </application>
+      <application>
+        <name>SeaMonkey</name>
+        <appID>{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}</appID>
+        <min_version>0</min_version>
+        <max_version>*</max_version>
+      </application>
+    </compatible_applications>
+    <compatible_os>ALL</compatible_os>
+    <install size="1">http://example.com/addon1.xpi</install>
+  </addon>
+  <addon>
+    <name>FAIL</name>
     <type id='1'>Extension</type>
     <guid>install1@tests.mozilla.org</guid>
     <version>1.0</version>
     <authors>
       <author>
         <name>Test Creator</name>
         <link>http://example.com/creator.html</link>
       </author>
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_cache.xml
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_cache.xml
@@ -93,17 +93,17 @@
     <weekly_downloads>3332</weekly_downloads>
     <daily_users>4442</daily_users>
     <last_updated epoch="9">1970-01-01T00:00:09Z</last_updated>
     <install size="9">http://localhost:4444/repo/2/install.xpi</install>
   </addon>
 
   <addon>
     <name>Repo Add-on 3</name>
-    <type id="9999">Unknown</type>
+    <type id="2">Theme</type>
     <guid>test_AddonRepository_3@tests.mozilla.org</guid>
     <version>2.3</version>
     <icon size="32">http://localhost/repo/3/icon.png</icon>
     <previews>
       <preview primary="1">
         <full type="image/png">http://localhost:4444/repo/3/firstFull.png</full>
         <thumbnail type="image/png">http://localhost:4444/repo/3/firstThumbnail.png</thumbnail>
         <caption>Repo Add-on 3 - First Caption</caption>
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_getAddonsByIDs.xml
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_getAddonsByIDs.xml
@@ -164,17 +164,17 @@
   </addon>
 
   <!-- Passes even though guid matches already installed add-on,
        type is unknown, no defined author elements, status is not Public,
        no compatible applications matched, no installs compatible with OS
    -->
   <addon>
     <name>PASS</name>
-    <type id="9999">Unknown</type>
+    <type id="2">Theme</type>
     <guid>test_AddonRepository_1@tests.mozilla.org</guid>
     <version>1.4</version>
     <status id="9999">Unknown</status>
     <compatible_applications>
       <application>
         <appID>unknown@tests.mozilla.org</appID>
         <min_version>1</min_version>
         <max_version>1</max_version>
--- a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository.js
@@ -100,16 +100,17 @@ var GET_RESULTS = [{
                             minVersion: 0.2,
                             maxVersion: 0.3,
                             appID: "xpcshell@tests.mozilla.org",
                             appMinVersion: 5.0,
                             appMaxVersion: 6.0
                           }]
 }, {
   id:                     "test_AddonRepository_1@tests.mozilla.org",
+  type:                   "theme",
   version:                "1.4",
   repositoryStatus:       9999,
   icons:                  {}
 }];
 
 // Results of retrieveRecommendedAddons and searchAddons
 var SEARCH_RESULTS = [{
   id:                     "test1@tests.mozilla.org",
--- a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js
@@ -147,16 +147,17 @@ const REPOSITORY_ADDONS = [{
   reviewURL:              BASE_URL + "/repo/2/review.html",
   totalDownloads:         2222,
   weeklyDownloads:        3332,
   dailyUsers:             4442,
   sourceURI:              BASE_URL + "/repo/2/install.xpi",
   repositoryStatus:       9
 }, {
   id:                     ADDON_IDS[2],
+  type:                   "theme",
   name:                   "Repo Add-on 3",
   version:                "2.3",
   iconURL:                BASE_URL + "/repo/3/icon.png",
   icons:                  { "32": BASE_URL + "/repo/3/icon.png" },
   screenshots:            [{
                             url:          BASE_URL + "/repo/3/firstFull.png",
                             thumbnailURL: BASE_URL + "/repo/3/firstThumbnail.png",
                             caption:      "Repo Add-on 3 - First Caption"