Merge m-c to inbound, a=merge
authorWes Kocher <wkocher@mozilla.com>
Wed, 23 Nov 2016 16:18:24 -0800
changeset 324147 5e87f33d7a227bbc74f04ed82c36e67ef734aa03
parent 324146 fcbb8882491faa45227d254056173080b2418da6 (current diff)
parent 324004 34fce7c12173bdd6dda54c2ebf6d344252f1ac48 (diff)
child 324148 53a9ff4a083a2c790d25939a598ec8378e77abf4
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
reviewersmerge
milestone53.0a1
Merge m-c to inbound, a=merge
build/application.ini
devtools/client/storage/test/browser_storage_dynamic_updates.js
testing/docker/desktop1604-test/bin/run-wizard
testing/marionette/client/marionette_driver/marionette.py
testing/marionette/harness/marionette/runner/base.py
testing/talos/talos/tests/svgx/composite-scale-opacity.svg
testing/talos/talos/tests/svgx/composite-scale-rotate-opacity.svg
testing/talos/talos/tests/svgx/composite-scale-rotate.svg
testing/talos/talos/tests/svgx/composite-scale.svg
testing/talos/talos/tests/svgx/gearflowers.svg
toolkit/locales/en-US/chrome/passwordmgr/passwordmgr.properties
--- a/accessible/interfaces/ia2/Makefile.in
+++ b/accessible/interfaces/ia2/Makefile.in
@@ -68,17 +68,19 @@ MIDL_UNUSED_GENERATED_FILES = \
   $(MIDL_LIBRARIES:%.idl=%.c) \
   $(NULL)
 
 EMBED_MANIFEST_AT = 2
 
 INSTALL_TARGETS += midl
 midl_FILES := $(filter %.h %_i.c,$(MIDL_GENERATED_FILES))
 midl_DEST = $(DIST)/include
-midl_TARGET := export
+midl_TARGET := midl
+
+export:: midl
 
 include $(topsrcdir)/config/rules.mk
 
 # generate list of to-be-generated files that are missing
 # but ignore special file dlldata.c
 missing:=$(strip $(foreach onefile,$(strip $(subst dlldata.c,,$(MIDL_GENERATED_FILES))),$(if $(wildcard $(onefile)),,$(onefile))))
 
 missing_base:=$(sort $(basename $(subst _p.c,,$(subst _i.c,,$(missing)))))
--- a/accessible/interfaces/msaa/Makefile.in
+++ b/accessible/interfaces/msaa/Makefile.in
@@ -42,9 +42,11 @@ midl_exports := \
     ISimpleDOMNode_i.c \
     ISimpleDOMText.h \
     ISimpleDOMText_i.c \
     $(NULL)
 
 INSTALL_TARGETS += midl_exports
 midl_exports_FILES := $(midl_exports)
 midl_exports_DEST = $(DIST)/include
-midl_exports_TARGET := export
+midl_exports_TARGET := midl
+
+export:: midl
--- a/accessible/xpcom/AccEventGen.py
+++ b/accessible/xpcom/AccEventGen.py
@@ -218,11 +218,11 @@ def get_conf(conf_file):
         mozpath.join(buildconfig.topsrcdir, 'xpcom', 'base'),
     ]
     return conf, inc_dir
 
 def gen_files(fd, conf_file, xpidllex, xpidlyacc):
     deps = set()
     conf, inc_dir = get_conf(conf_file)
     deps.update(print_header_file(fd, conf, inc_dir))
-    with open('xpcAccEvents.cpp', 'w') as cpp_fd:
+    with open(os.path.join(os.path.dirname(fd.name), 'xpcAccEvents.cpp'), 'w') as cpp_fd:
         deps.update(print_cpp_file(cpp_fd, conf, inc_dir))
     return deps
--- a/browser/app/moz.build
+++ b/browser/app/moz.build
@@ -85,10 +85,10 @@ if CONFIG['HAVE_CLOCK_MONOTONIC']:
 
 if CONFIG['MOZ_GPSD']:
     DEFINES['MOZ_GPSD'] = True
 
 if CONFIG['MOZ_LINUX_32_SSE2_STARTUP_ERROR']:
     DEFINES['MOZ_LINUX_32_SSE2_STARTUP_ERROR'] = True
 
 for icon in ('firefox', 'document', 'newwindow', 'newtab', 'pbmode'):
-    DEFINES[icon.upper() + '_ICO'] = '"%s/dist/branding/%s.ico"' % (
-        TOPOBJDIR, icon)
+    DEFINES[icon.upper() + '_ICO'] = '"%s/%s/%s.ico"' % (
+        TOPSRCDIR, CONFIG['MOZ_BRANDING_DIRECTORY'], icon)
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -919,19 +919,16 @@ TabManager.for = function(extension) {
 /* eslint-disable mozilla/balanced-listeners */
 extensions.on("shutdown", (type, extension) => {
   tabManagers.delete(extension);
 });
 /* eslint-enable mozilla/balanced-listeners */
 
 // Manages mapping between XUL windows and extension window IDs.
 global.WindowManager = {
-  _windows: new WeakMap(),
-  _nextId: 0,
-
   // Note: These must match the values in windows.json.
   WINDOW_ID_NONE: -1,
   WINDOW_ID_CURRENT: -2,
 
   get topWindow() {
     return Services.wm.getMostRecentWindow("navigator:browser");
   },
 
@@ -960,22 +957,21 @@ global.WindowManager = {
     if (options.width !== null || options.height !== null) {
       let width = options.width !== null ? options.width : window.outerWidth;
       let height = options.height !== null ? options.height : window.outerHeight;
       window.resizeTo(width, height);
     }
   },
 
   getId(window) {
-    if (this._windows.has(window)) {
-      return this._windows.get(window);
+    if (!window.QueryInterface) {
+      return null;
     }
-    let id = this._nextId++;
-    this._windows.set(window, id);
-    return id;
+    return window.QueryInterface(Ci.nsIInterfaceRequestor)
+                 .getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
   },
 
   getWindow(id, context) {
     if (id == this.WINDOW_ID_CURRENT) {
       return currentWindow(context);
     }
 
     for (let window of WindowListManager.browserWindows(true)) {
--- a/browser/components/migration/MigrationUtils.jsm
+++ b/browser/components/migration/MigrationUtils.jsm
@@ -256,17 +256,17 @@ this.MigratorPrototype = {
       }
     };
 
     let collectQuantityTelemetry = () => {
       try {
         for (let resourceType of Object.keys(MigrationUtils._importQuantities)) {
           let histogramId =
             "FX_MIGRATION_" + resourceType.toUpperCase() + "_QUANTITY";
-          let histogram = Services.telemetry.getKeyedHistogram(histogramId);
+          let histogram = Services.telemetry.getKeyedHistogramById(histogramId);
           histogram.add(this.getKey(), MigrationUtils._importQuantities[resourceType]);
         }
       } catch (ex) { /* Telemetry is exception-happy */ }
     };
 
     // Called either directly or through the bookmarks import callback.
     let doMigrate = Task.async(function*() {
       let resourcesGroupedByItems = new Map();
rename from build/application.ini
rename to build/application.ini.in
--- a/build/moz.build
+++ b/build/moz.build
@@ -13,34 +13,17 @@ SPHINX_TREES['buildsystem'] = 'docs'
 if CONFIG['OS_ARCH'] == 'WINNT':
     DIRS += ['win32']
 else:
     DIRS += ['unix']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     DIRS += ['annotationProcessors']
 
-for var in ('GRE_MILESTONE', 'MOZ_APP_VERSION', 'MOZ_APP_BASENAME',
-            'MOZ_APP_VENDOR', 'MOZ_APP_ID', 'MAR_CHANNEL_ID',
-            'ACCEPTED_MAR_CHANNEL_IDS', 'MOZ_APP_REMOTINGNAME'):
-    DEFINES[var] = CONFIG[var]
-
-if CONFIG['MOZ_APP_DISPLAYNAME'] != CONFIG['MOZ_APP_BASENAME']:
-    DEFINES['MOZ_APP_DISPLAYNAME'] = CONFIG['MOZ_APP_DISPLAYNAME']
-
-if CONFIG['MOZ_BUILD_APP'] == 'browser':
-    DEFINES['MOZ_BUILD_APP_IS_BROWSER'] = True
-
-if CONFIG['MOZ_APP_PROFILE']:
-    DEFINES['MOZ_APP_PROFILE'] = CONFIG['MOZ_APP_PROFILE']
-
-for var in ('MOZ_CRASHREPORTER', 'MOZ_PROFILE_MIGRATOR',
-            'MOZ_APP_STATIC_INI'):
-    if CONFIG[var]:
-        DEFINES[var] = True
+DEFINES['ACCEPTED_MAR_CHANNEL_IDS'] = CONFIG['ACCEPTED_MAR_CHANNEL_IDS']
 
 if CONFIG['MOZ_BUILD_APP'] == 'browser':
     PYTHON_UNITTEST_MANIFESTS += [
         'compare-mozconfig/python.ini',
     ]
 
 if CONFIG['ENABLE_TESTS'] or CONFIG['MOZ_DMD']:
     FINAL_TARGET_FILES += ['/tools/rb/fix_stack_using_bpsyms.py']
@@ -58,27 +41,55 @@ FINAL_TARGET_FILES += ['/.gdbinit']
 FINAL_TARGET_PP_FILES += ['.gdbinit_python.in']
 OBJDIR_FILES += ['!/dist/bin/.gdbinit_python']
 
 # Install the clang-cl runtime library for ASAN next to the binaries we produce.
 if CONFIG['MOZ_ASAN'] and CONFIG['CLANG_CL']:
     FINAL_TARGET_FILES += ['%' + CONFIG['MOZ_CLANG_RT_ASAN_LIB_PATH']]
 
 if CONFIG['MOZ_APP_BASENAME']:
-    FINAL_TARGET_PP_FILES += ['application.ini']
+    appini_defines = {
+        'TOPOBJDIR': TOPOBJDIR,
+    }
+
+    for var in ('GRE_MILESTONE', 'MOZ_APP_VERSION', 'MOZ_APP_BASENAME',
+                'MOZ_APP_VENDOR', 'MOZ_APP_ID', 'MAR_CHANNEL_ID',
+                'MOZ_APP_REMOTINGNAME'):
+        appini_defines[var] = CONFIG[var]
+
+    if CONFIG['MOZ_APP_DISPLAYNAME'] != CONFIG['MOZ_APP_BASENAME']:
+        appini_defines['MOZ_APP_DISPLAYNAME'] = CONFIG['MOZ_APP_DISPLAYNAME']
+
+    if CONFIG['MOZ_BUILD_APP'] == 'browser':
+        appini_defines['MOZ_BUILD_APP_IS_BROWSER'] = True
+
+    if CONFIG['MOZ_APP_PROFILE']:
+        appini_defines['MOZ_APP_PROFILE'] = CONFIG['MOZ_APP_PROFILE']
+
+    for var in ('MOZ_CRASHREPORTER', 'MOZ_PROFILE_MIGRATOR',
+                'MOZ_APP_STATIC_INI'):
+        if CONFIG[var]:
+            appini_defines[var] = True
+
+    GENERATED_FILES += ['application.ini']
+
+    appini = GENERATED_FILES['application.ini']
+    appini.script = '../python/mozbuild/mozbuild/action/preprocessor.py:generate'
+    appini.inputs = ['application.ini.in']
+    appini.flags = ['-D%s=%s' % (k, '1' if v is True else v)
+                    for k, v in appini_defines.iteritems()]
+    FINAL_TARGET_FILES += ['!application.ini']
     if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android' and CONFIG['MOZ_UPDATER']:
         FINAL_TARGET_PP_FILES += ['update-settings.ini']
 
     if CONFIG['MOZ_APP_STATIC_INI']:
         GENERATED_FILES += ['application.ini.h']
         appini = GENERATED_FILES['application.ini.h']
         appini.script = 'appini_header.py'
-        appini.inputs = ['!/dist/bin/application.ini']
-
-DEFINES['TOPOBJDIR'] = TOPOBJDIR
+        appini.inputs = ['!application.ini']
 
 # NOTE: Keep .gdbinit in the topsrcdir for people who run gdb from the topsrcdir.
 OBJDIR_FILES += ['/.gdbinit']
 
 # Put a .lldbinit in the bin directory and the objdir, to be picked up
 # automatically by LLDB when we debug executables using either of those two
 # directories as the current working directory.  The .lldbinit file will
 # load $(topsrcdir)/.lldbinit, which is where the actual debugging commands are.
--- a/config/faster/rules.mk
+++ b/config/faster/rules.mk
@@ -103,8 +103,10 @@ ACDEFINES += -DBUILD_FASTER
 # FasterMake/RecursiveMake backend, this dependency is handled in the top-level
 # Makefile.
 ifndef FASTER_RECURSIVE_MAKE
 $(TOPOBJDIR)/config/makefiles/xpidl/xpidl: $(TOPOBJDIR)/install-dist_idl
 endif
 # It also requires all the install manifests for dist/bin to have been processed
 # because it adds interfaces.manifest references with buildlist.py.
 $(TOPOBJDIR)/config/makefiles/xpidl/xpidl: $(addprefix install-,$(filter dist/bin%,$(INSTALL_MANIFESTS)))
+
+$(TOPOBJDIR)/build/application.ini: $(TOPOBJDIR)/buildid.h $(TOPOBJDIR)/source-repo.h
--- a/devtools/client/locales/en-US/storage.properties
+++ b/devtools/client/locales/en-US/storage.properties
@@ -30,16 +30,17 @@ tree.labels.cookies=Cookies
 tree.labels.localStorage=Local Storage
 tree.labels.sessionStorage=Session Storage
 tree.labels.indexedDB=Indexed DB
 tree.labels.Cache=Cache Storage
 
 # LOCALIZATION NOTE (table.headers.*.*):
 # These strings are the header names of the columns in the Storage Table for
 # each type of storage available through the Storage Tree to the side.
+table.headers.cookies.uniqueKey=Unique key
 table.headers.cookies.name=Name
 table.headers.cookies.path=Path
 table.headers.cookies.host=Domain
 table.headers.cookies.expires=Expires on
 table.headers.cookies.value=Value
 table.headers.cookies.lastAccessed=Last accessed on
 table.headers.cookies.creationTime=Created on
 
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -135,17 +135,16 @@ skip-if = (os == 'linux' && e10s && debu
 [browser_net_send-beacon-other-tab.js]
 [browser_net_simple-init.js]
 [browser_net_simple-request-data.js]
 skip-if = true # Bug 1258809
 [browser_net_simple-request-details.js]
 skip-if = true # Bug 1258809
 [browser_net_simple-request.js]
 [browser_net_sort-01.js]
-skip-if = (e10s && debug && os == 'mac') # Bug 1253037
 [browser_net_sort-02.js]
 [browser_net_sort-03.js]
 [browser_net_statistics-01.js]
 [browser_net_statistics-02.js]
 [browser_net_statistics-03.js]
 [browser_net_status-codes.js]
 [browser_net_streaming-response.js]
 [browser_net_throttle.js]
--- a/devtools/client/shared/widgets/TableWidget.js
+++ b/devtools/client/shared/widgets/TableWidget.js
@@ -610,27 +610,36 @@ TableWidget.prototype = {
     this.menupopup.addEventListener("command", this.onPopupCommand);
     popupset.appendChild(this.menupopup);
     this.populateMenuPopup();
   },
 
   /**
    * Populates the header context menu with the names of the columns along with
    * displaying which columns are hidden or visible.
+   *
+   * @param {Array} privateColumns=[]
+   *        An array of column names that should never appear in the table. This
+   *        allows us to e.g. have an invisible compound primary key for a
+   *        table's rows.
    */
-  populateMenuPopup: function () {
+  populateMenuPopup: function (privateColumns = []) {
     if (!this.menupopup) {
       return;
     }
 
     while (this.menupopup.firstChild) {
       this.menupopup.firstChild.remove();
     }
 
     for (let column of this.columns.values()) {
+      if (privateColumns.includes(column.id)) {
+        continue;
+      }
+
       let menuitem = this.document.createElementNS(XUL_NS, "menuitem");
       menuitem.setAttribute("label", column.header.getAttribute("value"));
       menuitem.setAttribute("data-id", column.id);
       menuitem.setAttribute("type", "checkbox");
       menuitem.setAttribute("checked", !column.wrapper.getAttribute("hidden"));
       if (column.id == this.uniqueId) {
         menuitem.setAttribute("disabled", "true");
       }
@@ -658,26 +667,31 @@ TableWidget.prototype = {
       disabled[disabled.length - 1].removeAttribute("disabled");
     }
   },
 
   /**
    * Creates the columns in the table. Without calling this method, data cannot
    * be inserted into the table unless `initialColumns` was supplied.
    *
-   * @param {object} columns
+   * @param {Object} columns
    *        A key value pair representing the columns of the table. Where the
    *        key represents the id of the column and the value is the displayed
    *        label in the header of the column.
-   * @param {string} sortOn
+   * @param {String} sortOn
    *        The id of the column on which the table will be initially sorted on.
-   * @param {array} hiddenColumns
+   * @param {Array} hiddenColumns
    *        Ids of all the columns that are hidden by default.
+   * @param {Array} privateColumns=[]
+   *        An array of column names that should never appear in the table. This
+   *        allows us to e.g. have an invisible compound primary key for a
+   *        table's rows.
    */
-  setColumns: function (columns, sortOn = this.sortedOn, hiddenColumns = []) {
+  setColumns: function (columns, sortOn = this.sortedOn, hiddenColumns = [],
+                        privateColumns = []) {
     for (let column of this.columns.values()) {
       column.destroy();
     }
 
     this.columns.clear();
 
     if (!(sortOn in columns)) {
       sortOn = null;
@@ -697,23 +711,24 @@ TableWidget.prototype = {
         sortOn = id;
       }
 
       if (this.firstColumn && id == this.firstColumn) {
         continue;
       }
 
       this.columns.set(id, new Column(this, id, columns[id]));
-      if (hiddenColumns.indexOf(id) > -1) {
+      if (hiddenColumns.includes(id) || privateColumns.includes(id)) {
+        // Hide the column.
         this.columns.get(id).toggleColumn();
       }
     }
     this.sortedOn = sortOn;
     this.sortBy(this.sortedOn);
-    this.populateMenuPopup();
+    this.populateMenuPopup(privateColumns);
   },
 
   /**
    * Returns true if the passed string or the row json object corresponds to the
    * selected item in the table.
    */
   isSelected: function (item) {
     if (typeof item == "object") {
@@ -773,16 +788,21 @@ TableWidget.prototype = {
       return;
     }
 
     if (this.items.has(item[this.uniqueId])) {
       this.update(item);
       return;
     }
 
+    if (this.editBookmark && !this.items.has(this.editBookmark)) {
+      // Key has been updated... update bookmark.
+      this.editBookmark = item[this.uniqueId];
+    }
+
     let index = this.columns.get(this.sortedOn).push(item);
     for (let [key, column] of this.columns) {
       if (key != this.sortedOn) {
         column.insertAt(item, index);
       }
       column.updateZebra();
     }
     this.items.set(item[this.uniqueId], item);
--- a/devtools/client/storage/test/browser.ini
+++ b/devtools/client/storage/test/browser.ini
@@ -24,17 +24,19 @@ support-files =
 [browser_storage_cookies_delete_all.js]
 [browser_storage_cookies_domain.js]
 [browser_storage_cookies_edit.js]
 [browser_storage_cookies_edit_keyboard.js]
 [browser_storage_cookies_tab_navigation.js]
 [browser_storage_delete.js]
 [browser_storage_delete_all.js]
 [browser_storage_delete_tree.js]
-[browser_storage_dynamic_updates.js]
+[browser_storage_dynamic_updates_cookies.js]
+[browser_storage_dynamic_updates_localStorage.js]
+[browser_storage_dynamic_updates_sessionStorage.js]
 [browser_storage_empty_objectstores.js]
 [browser_storage_indexeddb_delete.js]
 [browser_storage_indexeddb_delete_blocked.js]
 [browser_storage_localstorage_edit.js]
 [browser_storage_localstorage_error.js]
 [browser_storage_overflow.js]
 [browser_storage_search.js]
 [browser_storage_search_keyboard_trap.js]
--- a/devtools/client/storage/test/browser_storage_basic.js
+++ b/devtools/client/storage/test/browser_storage_basic.js
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+/* import-globals-from head.js */
+
 // Basic test to assert that the storage tree and table corresponding to each
 // item in the storage tree is correctly displayed
 
 // Entries that should be present in the tree for this test
 // Format for each entry in the array :
 // [
 //   ["path", "to", "tree", "item"], - The path to the tree item to click formed
 //                                     by id of each item
@@ -16,20 +18,33 @@
 // ]
 // These entries are formed by the cookies, local storage, session storage and
 // indexedDB entries created in storage-listings.html,
 // storage-secured-iframe.html and storage-unsecured-iframe.html
 
 "use strict";
 
 const testCases = [
-  [["cookies", "test1.example.org"],
-   ["c1", "cs2", "c3", "uc1"]],
-  [["cookies", "sectest1.example.org"],
-   ["uc1", "cs2", "sc1"]],
+  [
+    ["cookies", "test1.example.org"],
+    [
+      getCookieId("c1", "test1.example.org", "/browser"),
+      getCookieId("cs2", ".example.org", "/"),
+      getCookieId("c3", "test1.example.org", "/"),
+      getCookieId("uc1", ".example.org", "/")
+    ]
+  ],
+  [
+    ["cookies", "sectest1.example.org"],
+    [
+      getCookieId("uc1", ".example.org", "/"),
+      getCookieId("cs2", ".example.org", "/"),
+      getCookieId("sc1", "sectest1.example.org", "/browser/devtools/client/storage/test/")
+    ]
+  ],
   [["localStorage", "http://test1.example.org"],
    ["ls1", "ls2"]],
   [["localStorage", "http://sectest1.example.org"],
    ["iframe-u-ls1"]],
   [["localStorage", "https://sectest1.example.org"],
    ["iframe-s-ls1"]],
   [["sessionStorage", "http://test1.example.org"],
    ["ss1"]],
--- a/devtools/client/storage/test/browser_storage_cookies_delete_all.js
+++ b/devtools/client/storage/test/browser_storage_cookies_delete_all.js
@@ -16,18 +16,18 @@ function* performDelete(store, rowName, 
   let menuDeleteAllFromItem = contextMenu.querySelector(
     "#storage-table-popup-delete-all-from");
 
   let storeName = store.join(" > ");
 
   yield selectTreeItem(store);
 
   let eventWait = gUI.once("store-objects-updated");
+  let cells = getRowCells(rowName, true);
 
-  let cells = getRowCells(rowName);
   yield waitForContextMenu(contextMenu, cells.name, () => {
     info(`Opened context menu in ${storeName}, row '${rowName}'`);
     if (deleteAll) {
       menuDeleteAllItem.click();
     } else {
       menuDeleteAllFromItem.click();
       let hostName = cells.host.value;
       ok(menuDeleteAllFromItem.getAttribute("label").includes(hostName),
@@ -38,34 +38,64 @@ function* performDelete(store, rowName, 
   yield eventWait;
 }
 
 add_task(function* () {
   yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html");
 
   info("test state before delete");
   yield checkState([
-    [["cookies", "test1.example.org"], ["c1", "c3", "cs2", "uc1"]],
-    [["cookies", "sectest1.example.org"], ["cs2", "sc1", "uc1"]],
+    [
+      ["cookies", "test1.example.org"], [
+        getCookieId("c1", "test1.example.org", "/browser"),
+        getCookieId("c3", "test1.example.org", "/"),
+        getCookieId("cs2", ".example.org", "/"),
+        getCookieId("uc1", ".example.org", "/")
+      ]
+    ],
+    [
+      ["cookies", "sectest1.example.org"], [
+        getCookieId("cs2", ".example.org", "/"),
+        getCookieId("sc1", "sectest1.example.org",
+                    "/browser/devtools/client/storage/test/"),
+        getCookieId("uc1", ".example.org", "/")
+      ]
+    ],
   ]);
 
   info("delete all from domain");
   // delete only cookies that match the host exactly
-  yield performDelete(["cookies", "test1.example.org"], "c1", false);
+  let id = getCookieId("c1", "test1.example.org", "/browser");
+  yield performDelete(["cookies", "test1.example.org"], id, false);
 
   info("test state after delete all from domain");
   yield checkState([
     // Domain cookies (.example.org) must not be deleted.
-    [["cookies", "test1.example.org"], ["cs2", "uc1"]],
-    [["cookies", "sectest1.example.org"], ["cs2", "sc1", "uc1"]],
+    [
+      ["cookies", "test1.example.org"],
+      [
+        getCookieId("cs2", ".example.org", "/"),
+        getCookieId("uc1", ".example.org", "/")
+      ]
+    ],
+    [
+      ["cookies", "sectest1.example.org"],
+      [
+        getCookieId("cs2", ".example.org", "/"),
+        getCookieId("uc1", ".example.org", "/"),
+        getCookieId("sc1", "sectest1.example.org",
+                    "/browser/devtools/client/storage/test/"),
+      ]
+    ],
   ]);
 
   info("delete all");
   // delete all cookies for host, including domain cookies
-  yield performDelete(["cookies", "sectest1.example.org"], "uc1", true);
+  id = getCookieId("uc1", ".example.org", "/");
+  yield performDelete(["cookies", "sectest1.example.org"], id, true);
 
   info("test state after delete all");
   yield checkState([
     // Domain cookies (.example.org) are deleted too, so deleting in sectest1
     // also removes stuff from test1.
     [["cookies", "test1.example.org"], []],
     [["cookies", "sectest1.example.org"], []],
   ]);
--- a/devtools/client/storage/test/browser_storage_cookies_domain.js
+++ b/devtools/client/storage/test/browser_storage_cookies_domain.js
@@ -8,14 +8,22 @@
 
 // Test that cookies with domain equal to full host name are listed.
 // E.g., ".example.org" vs. example.org). Bug 1149497.
 
 add_task(function* () {
   yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies.html");
 
   yield checkState([
-    [["cookies", "test1.example.org"],
-      ["test1", "test2", "test3", "test4", "test5"]],
+    [
+      ["cookies", "test1.example.org"],
+      [
+        getCookieId("test1", ".test1.example.org", "/browser"),
+        getCookieId("test2", "test1.example.org", "/browser"),
+        getCookieId("test3", ".test1.example.org", "/browser"),
+        getCookieId("test4", "test1.example.org", "/browser"),
+        getCookieId("test5", ".test1.example.org", "/browser")
+      ]
+    ],
   ]);
 
   yield finishTests();
 });
--- a/devtools/client/storage/test/browser_storage_cookies_edit.js
+++ b/devtools/client/storage/test/browser_storage_cookies_edit.js
@@ -5,18 +5,25 @@
 // Basic test to check the editing of cookies.
 
 "use strict";
 
 add_task(function* () {
   yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies.html");
   showAllColumns(true);
 
-  yield editCell("test3", "name", "newTest3");
-  yield editCell("newTest3", "path", "/");
-  yield editCell("newTest3", "host", "test1.example.org");
-  yield editCell("newTest3", "expires", "Tue, 14 Feb 2040 17:41:14 GMT");
-  yield editCell("newTest3", "value", "newValue3");
-  yield editCell("newTest3", "isSecure", "true");
-  yield editCell("newTest3", "isHttpOnly", "true");
+  let id = getCookieId("test3", ".test1.example.org", "/browser");
+  yield editCell(id, "name", "newTest3");
+
+  id = getCookieId("newTest3", ".test1.example.org", "/browser");
+  yield editCell(id, "host", "test1.example.org");
+
+  id = getCookieId("newTest3", "test1.example.org", "/browser");
+  yield editCell(id, "path", "/");
+
+  id = getCookieId("newTest3", "test1.example.org", "/");
+  yield editCell(id, "expires", "Tue, 14 Feb 2040 17:41:14 GMT");
+  yield editCell(id, "value", "newValue3");
+  yield editCell(id, "isSecure", "true");
+  yield editCell(id, "isHttpOnly", "true");
 
   yield finishTests();
 });
--- a/devtools/client/storage/test/browser_storage_cookies_edit_keyboard.js
+++ b/devtools/client/storage/test/browser_storage_cookies_edit_keyboard.js
@@ -5,19 +5,20 @@
 // Basic test to check the editing of cookies with the keyboard.
 
 "use strict";
 
 add_task(function* () {
   yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies.html");
   showAllColumns(true);
 
-  yield startCellEdit("test4", "name");
+  let id = getCookieId("test4", "test1.example.org", "/browser");
+  yield startCellEdit(id, "name");
   yield typeWithTerminator("test6", "VK_TAB");
+  yield typeWithTerminator(".example.org", "VK_TAB");
   yield typeWithTerminator("/", "VK_TAB");
-  yield typeWithTerminator(".example.org", "VK_TAB");
   yield typeWithTerminator("Tue, 25 Dec 2040 12:00:00 GMT", "VK_TAB");
   yield typeWithTerminator("test6value", "VK_TAB");
   yield typeWithTerminator("false", "VK_TAB");
   yield typeWithTerminator("false", "VK_TAB");
 
   yield finishTests();
 });
--- a/devtools/client/storage/test/browser_storage_cookies_tab_navigation.js
+++ b/devtools/client/storage/test/browser_storage_cookies_tab_navigation.js
@@ -5,17 +5,18 @@
 // Basic test to check cookie table tab navigation.
 
 "use strict";
 
 add_task(function* () {
   yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies.html");
   showAllColumns(true);
 
-  yield startCellEdit("test1", "name");
+  let id = getCookieId("test1", ".test1.example.org", "/browser");
+  yield startCellEdit(id, "name");
 
   PressKeyXTimes("VK_TAB", 18);
   is(getCurrentEditorValue(), "value3",
      "We have tabbed to the correct cell.");
 
   PressKeyXTimes("VK_TAB", 18, {shiftKey: true});
   is(getCurrentEditorValue(), "test1",
      "We have shift-tabbed to the correct cell.");
--- a/devtools/client/storage/test/browser_storage_delete.js
+++ b/devtools/client/storage/test/browser_storage_delete.js
@@ -8,18 +8,20 @@
 
 // Test deleting storage items
 
 const TEST_CASES = [
   [["localStorage", "http://test1.example.org"],
     "ls1", "name"],
   [["sessionStorage", "http://test1.example.org"],
     "ss1", "name"],
-  [["cookies", "test1.example.org"],
-    "c1", "name"],
+  [
+    ["cookies", "test1.example.org"],
+    getCookieId("c1", "test1.example.org", "/browser"), "name"
+  ],
   [["indexedDB", "http://test1.example.org", "idb1", "obj1"],
     1, "name"],
   [["Cache", "http://test1.example.org", "plop"],
     MAIN_DOMAIN + "404_cached_file.js", "url"],
 ];
 
 add_task(function* () {
   yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html");
@@ -36,17 +38,17 @@ add_task(function* () {
     let row = getRowCells(rowName);
     ok(gUI.table.items.has(rowName), `There is a row '${rowName}' in ${treeItemName}`);
 
     let eventWait = gUI.once("store-objects-updated");
 
     yield waitForContextMenu(contextMenu, row[cellToClick], () => {
       info(`Opened context menu in ${treeItemName}, row '${rowName}'`);
       menuDeleteItem.click();
-      let truncatedRowName = String(rowName).substr(0, 16);
+      let truncatedRowName = String(rowName).replace(SEPARATOR_GUID, "-").substr(0, 16);
       ok(menuDeleteItem.getAttribute("label").includes(truncatedRowName),
         `Context menu item label contains '${rowName}' (maybe truncated)`);
     });
 
     yield eventWait;
 
     ok(!gUI.table.items.has(rowName),
       `There is no row '${rowName}' in ${treeItemName} after deletion`);
--- a/devtools/client/storage/test/browser_storage_delete_tree.js
+++ b/devtools/client/storage/test/browser_storage_delete_tree.js
@@ -12,17 +12,25 @@ add_task(function* () {
   yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html");
 
   let contextMenu = gPanelWindow.document.getElementById("storage-tree-popup");
   let menuDeleteAllItem = contextMenu.querySelector(
     "#storage-tree-popup-delete-all");
 
   info("test state before delete");
   yield checkState([
-    [["cookies", "test1.example.org"], ["c1", "c3", "cs2", "uc1"]],
+    [
+      ["cookies", "test1.example.org"],
+      [
+        getCookieId("c1", "test1.example.org", "/browser"),
+        getCookieId("cs2", ".example.org", "/"),
+        getCookieId("c3", "test1.example.org", "/"),
+        getCookieId("uc1", ".example.org", "/")
+      ]
+    ],
     [["localStorage", "http://test1.example.org"], ["ls1", "ls2"]],
     [["sessionStorage", "http://test1.example.org"], ["ss1"]],
     [["indexedDB", "http://test1.example.org", "idb1", "obj1"], [1, 2, 3]],
     [["Cache", "http://test1.example.org", "plop"],
       [MAIN_DOMAIN + "404_cached_file.js", MAIN_DOMAIN + "browser_storage_basic.js"]],
   ]);
 
   info("do the delete");
new file mode 100644
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_dynamic_updates_cookies.js
@@ -0,0 +1,188 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// Test dynamic updates in the storage inspector for cookies.
+
+add_task(function* () {
+  yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-updates.html");
+
+  gUI.tree.expandAll();
+
+  ok(gUI.sidebar.hidden, "Sidebar is initially hidden");
+  let c1id = getCookieId("c1", "test1.example.org", "/browser");
+  yield selectTableItem(c1id);
+
+  // test that value is something initially
+  let initialValue = [[
+    {name: "c1", value: "1.2.3.4.5.6.7"},
+    {name: "c1.Path", value: "/browser"}
+  ], [
+    {name: "c1", value: "Array"},
+    {name: "c1.0", value: "1"},
+    {name: "c1.6", value: "7"}
+  ]];
+
+  // test that value is something initially
+  let finalValue = [[
+    {name: "c1", value: '{"foo": 4,"bar":6}'},
+    {name: "c1.Path", value: "/browser"}
+  ], [
+    {name: "c1", value: "Object"},
+    {name: "c1.foo", value: "4"},
+    {name: "c1.bar", value: "6"}
+  ]];
+
+  // Check that sidebar shows correct initial value
+  yield findVariableViewProperties(initialValue[0], false);
+
+  yield findVariableViewProperties(initialValue[1], true);
+  // Check if table shows correct initial value
+
+  yield checkState([
+    [
+      ["cookies", "test1.example.org"],
+      [
+        getCookieId("c1", "test1.example.org", "/browser"),
+        getCookieId("c2", "test1.example.org", "/browser")
+      ]
+    ],
+  ]);
+  checkCell(c1id, "value", "1.2.3.4.5.6.7");
+
+  gWindow.addCookie("c1", '{"foo": 4,"bar":6}', "/browser");
+  yield gUI.once("sidebar-updated");
+
+  yield findVariableViewProperties(finalValue[0], false);
+  yield findVariableViewProperties(finalValue[1], true);
+
+  yield checkState([
+    [
+      ["cookies", "test1.example.org"],
+      [
+        getCookieId("c1", "test1.example.org", "/browser"),
+        getCookieId("c2", "test1.example.org", "/browser")
+      ]
+    ],
+  ]);
+  checkCell(c1id, "value", '{"foo": 4,"bar":6}');
+
+  // Add a new entry
+  gWindow.addCookie("c3", "booyeah");
+
+  // Wait once for update and another time for value fetching
+  yield gUI.once("store-objects-updated");
+  yield gUI.once("store-objects-updated");
+
+  yield checkState([
+    [
+      ["cookies", "test1.example.org"],
+      [
+        getCookieId("c1", "test1.example.org", "/browser"),
+        getCookieId("c2", "test1.example.org", "/browser"),
+        getCookieId("c3", "test1.example.org",
+                    "/browser/devtools/client/storage/test/")
+      ]
+    ],
+  ]);
+  let c3id = getCookieId("c3", "test1.example.org",
+                         "/browser/devtools/client/storage/test/");
+  checkCell(c3id, "value", "booyeah");
+
+  // Add another
+  gWindow.addCookie("c4", "booyeah");
+
+  // Wait once for update and another time for value fetching
+  yield gUI.once("store-objects-updated");
+  yield gUI.once("store-objects-updated");
+
+  yield checkState([
+    [
+      ["cookies", "test1.example.org"],
+      [
+        getCookieId("c1", "test1.example.org", "/browser"),
+        getCookieId("c2", "test1.example.org", "/browser"),
+        getCookieId("c3", "test1.example.org",
+                    "/browser/devtools/client/storage/test/"),
+        getCookieId("c4", "test1.example.org",
+                    "/browser/devtools/client/storage/test/")
+      ]
+    ],
+  ]);
+  let c4id = getCookieId("c4", "test1.example.org",
+                         "/browser/devtools/client/storage/test/");
+  checkCell(c4id, "value", "booyeah");
+
+  // Removing cookies
+  gWindow.removeCookie("c1", "/browser");
+
+  yield gUI.once("sidebar-updated");
+
+  yield checkState([
+    [
+      ["cookies", "test1.example.org"],
+      [
+        getCookieId("c2", "test1.example.org", "/browser"),
+        getCookieId("c3", "test1.example.org",
+                    "/browser/devtools/client/storage/test/"),
+        getCookieId("c4", "test1.example.org",
+                    "/browser/devtools/client/storage/test/")
+      ]
+    ],
+  ]);
+
+  ok(!gUI.sidebar.hidden, "Sidebar still visible for next row");
+
+  // Check if next element's value is visible in sidebar
+  yield findVariableViewProperties([{name: "c2", value: "foobar"}]);
+
+  // Keep deleting till no rows
+  gWindow.removeCookie("c3");
+
+  yield gUI.once("store-objects-updated");
+
+  yield checkState([
+    [
+      ["cookies", "test1.example.org"],
+      [
+        getCookieId("c2", "test1.example.org", "/browser"),
+        getCookieId("c4", "test1.example.org",
+                    "/browser/devtools/client/storage/test/")
+      ]
+    ],
+  ]);
+
+  // Check if next element's value is visible in sidebar
+  yield findVariableViewProperties([{name: "c2", value: "foobar"}]);
+
+  gWindow.removeCookie("c2", "/browser");
+
+  yield gUI.once("sidebar-updated");
+
+  yield checkState([
+    [
+      ["cookies", "test1.example.org"],
+      [
+        getCookieId("c4", "test1.example.org",
+                    "/browser/devtools/client/storage/test/")
+      ]
+    ],
+  ]);
+
+  // Check if next element's value is visible in sidebar
+  yield findVariableViewProperties([{name: "c4", value: "booyeah"}]);
+
+  gWindow.removeCookie("c4");
+
+  yield gUI.once("store-objects-updated");
+
+  yield checkState([
+    [["cookies", "test1.example.org"], [ ]],
+  ]);
+
+  ok(gUI.sidebar.hidden, "Sidebar is hidden when no rows");
+
+  yield finishTests();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_dynamic_updates_localStorage.js
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// Test dynamic updates in the storage inspector for localStorage.
+
+add_task(function* () {
+  yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-updates.html");
+
+  gUI.tree.expandAll();
+
+  ok(gUI.sidebar.hidden, "Sidebar is initially hidden");
+
+  yield checkState([
+    [
+      ["localStorage", "http://test1.example.org"],
+      ["ls1", "ls2", "ls3", "ls4", "ls5", "ls6", "ls7"]
+    ],
+  ]);
+
+  gWindow.localStorage.removeItem("ls4");
+
+  yield gUI.once("store-objects-updated");
+
+  yield checkState([
+    [
+      ["localStorage", "http://test1.example.org"],
+      ["ls1", "ls2", "ls3", "ls5", "ls6", "ls7"]
+    ],
+  ]);
+
+  gWindow.localStorage.setItem("ls4", "again");
+
+  yield gUI.once("store-objects-updated");
+  yield gUI.once("store-objects-updated");
+
+  yield checkState([
+    [
+      ["localStorage", "http://test1.example.org"],
+      ["ls1", "ls2", "ls3", "ls4", "ls5", "ls6", "ls7"]
+    ],
+  ]);
+  // Updating a row
+  gWindow.localStorage.setItem("ls2", "ls2-changed");
+
+  yield gUI.once("store-objects-updated");
+  yield gUI.once("store-objects-updated");
+
+  checkCell("ls2", "value", "ls2-changed");
+
+  // Clearing items. Bug 1233497 makes it so that we can no longer yield
+  // CPOWs from Tasks. We work around this by calling clear via a ContentTask
+  // instead.
+  yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+    return Task.spawn(content.wrappedJSObject.clear);
+  });
+
+  yield gUI.once("store-objects-cleared");
+
+  yield checkState([
+    [
+      ["localStorage", "http://test1.example.org"],
+      [ ]
+    ],
+  ]);
+
+  yield finishTests();
+});
rename from devtools/client/storage/test/browser_storage_dynamic_updates.js
rename to devtools/client/storage/test/browser_storage_dynamic_updates_sessionStorage.js
--- a/devtools/client/storage/test/browser_storage_dynamic_updates.js
+++ b/devtools/client/storage/test/browser_storage_dynamic_updates_sessionStorage.js
@@ -1,213 +1,84 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+// Test dynamic updates in the storage inspector for sessionStorage.
+
 add_task(function* () {
   yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-updates.html");
 
-  let $ = id => gPanelWindow.document.querySelector(id);
-  let $$ = sel => gPanelWindow.document.querySelectorAll(sel);
-
   gUI.tree.expandAll();
 
   ok(gUI.sidebar.hidden, "Sidebar is initially hidden");
-  yield selectTableItem("c1");
 
-  // test that value is something initially
-  let initialValue = [[
-    {name: "c1", value: "1.2.3.4.5.6.7"},
-    {name: "c1.Path", value: "/browser"}
-  ], [
-    {name: "c1", value: "Array"},
-    {name: "c1.0", value: "1"},
-    {name: "c1.6", value: "7"}
-  ]];
-
-  // test that value is something initially
-  let finalValue = [[
-    {name: "c1", value: '{"foo": 4,"bar":6}'},
-    {name: "c1.Path", value: "/browser"}
-  ], [
-    {name: "c1", value: "Object"},
-    {name: "c1.foo", value: "4"},
-    {name: "c1.bar", value: "6"}
-  ]];
-  // Check that sidebar shows correct initial value
-  yield findVariableViewProperties(initialValue[0], false);
-  yield findVariableViewProperties(initialValue[1], true);
-  // Check if table shows correct initial value
-  ok($("#value [data-id='c1'].table-widget-cell"), "cell is present");
-  is($("#value [data-id='c1'].table-widget-cell").value, "1.2.3.4.5.6.7",
-       "correct initial value in table");
-  gWindow.addCookie("c1", '{"foo": 4,"bar":6}', "/browser");
-  yield gUI.once("sidebar-updated");
-
-  yield findVariableViewProperties(finalValue[0], false);
-  yield findVariableViewProperties(finalValue[1], true);
-  ok($("#value [data-id='c1'].table-widget-cell"),
-     "cell is present after update");
-  is($("#value [data-id='c1'].table-widget-cell").value, '{"foo": 4,"bar":6}',
-     "correct final value in table");
-
-  // Add a new entry
-  is($$("#value .table-widget-cell").length, 2,
-     "Correct number of rows before update 0");
-
-  gWindow.addCookie("c3", "booyeah");
-
-  // Wait once for update and another time for value fetching
-  yield gUI.once("store-objects-updated");
-  yield gUI.once("store-objects-updated");
-
-  is($$("#value .table-widget-cell").length, 3,
-     "Correct number of rows after update 1");
-
-  // Add another
-  gWindow.addCookie("c4", "booyeah");
-
-  // Wait once for update and another time for value fetching
-  yield gUI.once("store-objects-updated");
-  yield gUI.once("store-objects-updated");
-
-  is($$("#value .table-widget-cell").length, 4,
-     "Correct number of rows after update 2");
-
-  // Removing cookies
-  gWindow.removeCookie("c1", "/browser");
-
-  yield gUI.once("sidebar-updated");
-
-  is($$("#value .table-widget-cell").length, 3,
-     "Correct number of rows after delete update 3");
-
-  ok(!$("#c1"), "Correct row got deleted");
-
-  ok(!gUI.sidebar.hidden, "Sidebar still visible for next row");
-
-  // Check if next element's value is visible in sidebar
-  yield findVariableViewProperties([{name: "c2", value: "foobar"}]);
-
-  // Keep deleting till no rows
-
-  gWindow.removeCookie("c3");
-
-  yield gUI.once("store-objects-updated");
-
-  is($$("#value .table-widget-cell").length, 2,
-     "Correct number of rows after delete update 4");
-
-  // Check if next element's value is visible in sidebar
-  yield findVariableViewProperties([{name: "c2", value: "foobar"}]);
-
-  gWindow.removeCookie("c2", "/browser");
-
-  yield gUI.once("sidebar-updated");
-
-  yield findVariableViewProperties([{name: "c4", value: "booyeah"}]);
-
-  is($$("#value .table-widget-cell").length, 1,
-     "Correct number of rows after delete update 5");
-
-  gWindow.removeCookie("c4");
-
-  yield gUI.once("store-objects-updated");
-
-  is($$("#value .table-widget-cell").length, 0,
-     "Correct number of rows after delete update 6");
-  ok(gUI.sidebar.hidden, "Sidebar is hidden when no rows");
-
-  // Testing in local storage
-  yield selectTreeItem(["localStorage", "http://test1.example.org"]);
-
-  is($$("#value .table-widget-cell").length, 7,
-     "Correct number of rows after delete update 7");
-
-  ok($(".table-widget-cell[data-id='ls4']"), "ls4 exists before deleting");
-
-  gWindow.localStorage.removeItem("ls4");
-
-  yield gUI.once("store-objects-updated");
-
-  is($$("#value .table-widget-cell").length, 6,
-     "Correct number of rows after delete update 8");
-  ok(!$(".table-widget-cell[data-id='ls4']"),
-     "ls4 does not exists after deleting");
-
-  gWindow.localStorage.setItem("ls4", "again");
-
-  yield gUI.once("store-objects-updated");
-  yield gUI.once("store-objects-updated");
-
-  is($$("#value .table-widget-cell").length, 7,
-     "Correct number of rows after delete update 9");
-  ok($(".table-widget-cell[data-id='ls4']"),
-     "ls4 came back after adding it again");
-
-  // Updating a row
-  gWindow.localStorage.setItem("ls2", "ls2-changed");
-
-  yield gUI.once("store-objects-updated");
-  yield gUI.once("store-objects-updated");
-
-  is($("#value [data-id='ls2']").value, "ls2-changed",
-      "Value got updated for local storage");
-
-  // Testing in session storage
-  yield selectTreeItem(["sessionStorage", "http://test1.example.org"]);
-
-  is($$("#value .table-widget-cell").length, 3,
-     "Correct number of rows for session storage");
+  yield checkState([
+    [
+      ["sessionStorage", "http://test1.example.org"],
+      ["ss1", "ss2", "ss3"]
+    ],
+  ]);
 
   gWindow.sessionStorage.setItem("ss4", "new-item");
 
   yield gUI.once("store-objects-updated");
   yield gUI.once("store-objects-updated");
 
-  is($$("#value .table-widget-cell").length, 4,
-     "Correct number of rows after session storage update");
+  yield checkState([
+    [
+      ["sessionStorage", "http://test1.example.org"],
+      ["ss1", "ss2", "ss3", "ss4"]
+    ],
+  ]);
 
   // deleting item
 
   gWindow.sessionStorage.removeItem("ss3");
 
   yield gUI.once("store-objects-updated");
 
   gWindow.sessionStorage.removeItem("ss1");
 
   yield gUI.once("store-objects-updated");
 
-  is($$("#value .table-widget-cell").length, 2,
-     "Correct number of rows after removing items from session storage");
+  yield checkState([
+    [
+      ["sessionStorage", "http://test1.example.org"],
+      ["ss2", "ss4"]
+    ],
+  ]);
 
   yield selectTableItem("ss2");
 
   ok(!gUI.sidebar.hidden, "sidebar is visible");
 
   // Checking for correct value in sidebar before update
   yield findVariableViewProperties([{name: "ss2", value: "foobar"}]);
 
   gWindow.sessionStorage.setItem("ss2", "changed=ss2");
 
   yield gUI.once("sidebar-updated");
 
-  is($("#value [data-id='ss2']").value, "changed=ss2",
-      "Value got updated for session storage in the table");
+  checkCell("ss2", "value", "changed=ss2");
 
   yield findVariableViewProperties([{name: "ss2", value: "changed=ss2"}]);
 
   // Clearing items. Bug 1233497 makes it so that we can no longer yield
   // CPOWs from Tasks. We work around this by calling clear via a ContentTask
   // instead.
   yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
     return Task.spawn(content.wrappedJSObject.clear);
   });
 
   yield gUI.once("store-objects-cleared");
 
-  is($$("#value .table-widget-cell").length, 0,
-     "Table should be cleared");
+  yield checkState([
+    [
+      ["sessionStorage", "http://test1.example.org"],
+      [ ]
+    ],
+  ]);
 
   yield finishTests();
 });
--- a/devtools/client/storage/test/browser_storage_sidebar.js
+++ b/devtools/client/storage/test/browser_storage_sidebar.js
@@ -15,32 +15,32 @@
 "use strict";
 
 const testCases = [
   {
     location: ["cookies", "sectest1.example.org"],
     sidebarHidden: true
   },
   {
-    location: "cs2",
+    location: getCookieId("cs2", ".example.org", "/"),
     sidebarHidden: false
   },
   {
     sendEscape: true
   },
   {
-    location: "cs2",
+    location: getCookieId("cs2", ".example.org", "/"),
     sidebarHidden: false
   },
   {
-    location: "uc1",
+    location: getCookieId("uc1", ".example.org", "/"),
     sidebarHidden: false
   },
   {
-    location: "uc1",
+    location: getCookieId("uc1", ".example.org", "/"),
     sidebarHidden: false
   },
 
   {
     location: ["localStorage", "http://sectest1.example.org"],
     sidebarHidden: true
   },
   {
--- a/devtools/client/storage/test/browser_storage_values.js
+++ b/devtools/client/storage/test/browser_storage_values.js
@@ -12,44 +12,48 @@
 //   true if the check is to be made in the parsed value section
 // ]
 
 "use strict";
 
 const LONG_WORD = "a".repeat(1000);
 
 const testCases = [
-  ["cs2", [
+  [getCookieId("cs2", ".example.org", "/"), [
     {name: "cs2", value: "sessionCookie"},
     {name: "cs2.Path", value: "/"},
     {name: "cs2.HostOnly", value: "false"},
     {name: "cs2.HttpOnly", value: "false"},
     {name: "cs2.Domain", value: ".example.org"},
     {name: "cs2.Expires", value: "Session"},
     {name: "cs2.Secure", value: "false"},
   ]],
-  ["c1", [
+  [getCookieId("c1", "test1.example.org", "/browser"), [
     {name: "c1", value: JSON.stringify(["foo", "Bar", {foo: "Bar"}])},
     {name: "c1.Path", value: "/browser"},
     {name: "c1.HostOnly", value: "true"},
     {name: "c1.HttpOnly", value: "false"},
     {name: "c1.Domain", value: "test1.example.org"},
     {name: "c1.Expires", value: new Date(2000000000000).toUTCString()},
     {name: "c1.Secure", value: "false"},
   ]],
   [null, [
     {name: "c1", value: "Array"},
     {name: "c1.0", value: "foo"},
     {name: "c1.1", value: "Bar"},
     {name: "c1.2", value: "Object"},
     {name: "c1.2.foo", value: "Bar"},
   ], true],
-  ["c_encoded", [
-    {name: "c_encoded", value: encodeURIComponent(JSON.stringify({foo: {foo1: "bar"}}))}
-  ]],
+  [
+    getCookieId("c_encoded", "test1.example.org",
+                "/browser/devtools/client/storage/test/"),
+    [
+      {name: "c_encoded", value: encodeURIComponent(JSON.stringify({foo: {foo1: "bar"}}))}
+    ]
+  ],
   [null, [
     {name: "c_encoded", value: "Object"},
     {name: "c_encoded.foo", value: "Object"},
     {name: "c_encoded.foo.foo1", value: "bar"}
   ], true],
   [["localStorage", "http://test1.example.org"]],
   ["ls2", [
     {name: "ls2", value: "foobar-2"}
--- a/devtools/client/storage/test/head.js
+++ b/devtools/client/storage/test/head.js
@@ -19,16 +19,21 @@ const DUMPEMIT_PREF = "devtools.dump.emi
 const DEBUGGERLOG_PREF = "devtools.debugger.log";
 // Allows Cache API to be working on usage `http` test page
 const CACHES_ON_HTTP_PREF = "dom.caches.testing.enabled";
 const PATH = "browser/devtools/client/storage/test/";
 const MAIN_DOMAIN = "http://test1.example.org/" + PATH;
 const ALT_DOMAIN = "http://sectest1.example.org/" + PATH;
 const ALT_DOMAIN_SECURED = "https://sectest1.example.org:443/" + PATH;
 
+// GUID to be used as a separator in compound keys. This must match the same
+// constant in devtools/server/actors/storage.js,
+// devtools/client/storage/ui.js and devtools/server/tests/browser/head.js
+const SEPARATOR_GUID = "{9d414cc5-8319-0a04-0586-c0a6ae01670a}";
+
 var gToolbox, gPanelWindow, gWindow, gUI;
 
 // Services.prefs.setBoolPref(DUMPEMIT_PREF, true);
 // Services.prefs.setBoolPref(DEBUGGERLOG_PREF, true);
 
 Services.prefs.setBoolPref(STORAGE_PREF, true);
 Services.prefs.setBoolPref(CACHES_ON_HTTP_PREF, true);
 registerCleanupFunction(() => {
@@ -500,20 +505,27 @@ function* selectTreeItem(ids) {
 
 /**
  * Click selects a row in the table.
  *
  * @param {String} id
  *        The id of the row in the table widget
  */
 function* selectTableItem(id) {
-  let selector = ".table-widget-cell[data-id='" + id + "']";
+  let table = gUI.table;
+  let selector = ".table-widget-column#" + table.uniqueId +
+                 " .table-widget-cell[value='" + id + "']";
   let target = gPanelWindow.document.querySelector(selector);
+
   ok(target, "table item found with ids " + id);
 
+  if (!target) {
+    showAvailableIds();
+  }
+
   yield click(target);
   yield gUI.once("sidebar-updated");
 }
 
 /**
  * Wait for eventName on target.
  * @param {Object} target An observable object that either supports on/off or
  * addEventListener/removeEventListener
@@ -581,32 +593,49 @@ function getRowValues(id, includeHidden 
 function getRowCells(id, includeHidden = false) {
   let doc = gPanelWindow.document;
   let table = gUI.table;
   let item = doc.querySelector(".table-widget-column#" + table.uniqueId +
                                " .table-widget-cell[value='" + id + "']");
 
   if (!item) {
     ok(false, "Row id '" + id + "' exists");
+
+    showAvailableIds();
   }
 
-  let index = table.columns.get(table.uniqueId).visibleCellNodes.indexOf(item);
+  let index = table.columns.get(table.uniqueId).cellNodes.indexOf(item);
   let cells = {};
 
   for (let [name, column] of [...table.columns]) {
     if (!includeHidden && column.column.parentNode.hidden) {
       continue;
     }
-    cells[name] = column.visibleCellNodes[index];
+    cells[name] = column.cellNodes[index];
   }
 
   return cells;
 }
 
 /**
+ * Show available ids.
+ */
+function showAvailableIds() {
+  let doc = gPanelWindow.document;
+  let table = gUI.table;
+
+  info("Available ids:");
+  let cells = doc.querySelectorAll(".table-widget-column#" + table.uniqueId +
+                                   " .table-widget-cell");
+  for (let cell of cells) {
+    info("  - " + cell.getAttribute("value"));
+  }
+}
+
+/**
  * Get a cell value.
  *
  * @param {String} id
  *        The uniqueId of the row.
  * @param {String} column
  *        The id of the column
  *
  * @yield {String}
@@ -793,19 +822,28 @@ function* checkState(state) {
     let storeName = store.join(" > ");
     info(`Selecting tree item ${storeName}`);
     yield selectTreeItem(store);
 
     let items = gUI.table.items;
 
     is(items.size, names.length,
       `There is correct number of rows in ${storeName}`);
+
+    if (names.length === 0) {
+      showAvailableIds();
+    }
+
     for (let name of names) {
       ok(items.has(name),
         `There is item with name '${name}' in ${storeName}`);
+
+      if (!items.has(name)) {
+        showAvailableIds();
+      }
     }
   }
 }
 
 /**
  * Checks if document's active element is within the given element.
  * @param  {HTMLDocument}  doc document with active element in question
  * @param  {DOMNode}       container element tested on focus containment
@@ -833,8 +871,12 @@ var focusSearchBoxUsingShortcut = Task.a
   synthesizeKeyShortcut(strings.GetStringFromName("storage.filter.key"));
 
   yield focused;
 
   if (callback) {
     callback();
   }
 });
+
+function getCookieId(name, domain, path) {
+  return `${name}${SEPARATOR_GUID}${domain}${SEPARATOR_GUID}${path}`;
+}
--- a/devtools/client/storage/test/storage-updates.html
+++ b/devtools/client/storage/test/storage-updates.html
@@ -33,18 +33,20 @@ window.removeCookie = function(name, pat
 };
 
 /**
  * We keep this method here even though these items are automatically cleared
  * after the test is complete. this is so that the store-objects-cleared event
  * can be tested.
  */
 window.clear = function*() {
+  localStorage.clear();
+  dump("removed localStorage from " + document.location + "\n");
+
   sessionStorage.clear();
-
   dump("removed sessionStorage from " + document.location + "\n");
 };
 
 window.onload = function() {
   addCookie("c1", "1.2.3.4.5.6.7", "/browser");
   addCookie("c2", "foobar", "/browser");
 
   localStorage.setItem("ls1", "testing");
--- a/devtools/client/storage/ui.js
+++ b/devtools/client/storage/ui.js
@@ -7,16 +7,22 @@
 
 const {Task} = require("devtools/shared/task");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {LocalizationHelper, ELLIPSIS} = require("devtools/shared/l10n");
 const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
 const JSOL = require("devtools/client/shared/vendor/jsol");
 const {KeyCodes} = require("devtools/client/shared/keycodes");
 
+// GUID to be used as a separator in compound keys. This must match the same
+// constant in devtools/server/actors/storage.js,
+// devtools/client/storage/test/head.js and
+// devtools/server/tests/browser/head.js
+const SEPARATOR_GUID = "{9d414cc5-8319-0a04-0586-c0a6ae01670a}";
+
 loader.lazyRequireGetter(this, "TreeWidget",
                          "devtools/client/shared/widgets/TreeWidget", true);
 loader.lazyRequireGetter(this, "TableWidget",
                          "devtools/client/shared/widgets/TableWidget", true);
 loader.lazyRequireGetter(this, "ViewHelpers",
                          "devtools/client/shared/widgets/view-helpers");
 loader.lazyImporter(this, "VariablesView",
   "resource://devtools/client/shared/widgets/VariablesView.jsm");
@@ -31,23 +37,16 @@ const GENERIC_VARIABLES_VIEW_SETTINGS = 
   lazyEmpty: true,
    // ms
   lazyEmptyDelay: 10,
   searchEnabled: true,
   searchPlaceholder: L10N.getStr("storage.search.placeholder"),
   preventDescriptorModifiers: true
 };
 
-// Columns which are hidden by default in the storage table
-const HIDDEN_COLUMNS = [
-  "creationTime",
-  "isDomain",
-  "isSecure"
-];
-
 const REASON = {
   NEW_ROW: "new-row",
   NEXT_50_ITEMS: "next-50-items",
   POPULATE: "populate",
   UPDATE: "update"
 };
 
 const COOKIE_KEY_MAP = {
@@ -781,43 +780,53 @@ StorageUI.prototype = {
    */
   resetColumns: function* (type, host, subtype) {
     this.table.host = host;
     this.table.datatype = type;
 
     let uniqueKey = null;
     let columns = {};
     let editableFields = [];
+    let hiddenFields = [];
+    let privateFields = [];
     let fields = yield this.getCurrentActor().getFields(subtype);
 
     fields.forEach(f => {
       if (!uniqueKey) {
         this.table.uniqueId = uniqueKey = f.name;
       }
 
       if (f.editable) {
         editableFields.push(f.name);
       }
 
+      if (f.hidden) {
+        hiddenFields.push(f.name);
+      }
+
+      if (f.private) {
+        privateFields.push(f.name);
+      }
+
       columns[f.name] = f.name;
       let columnName;
       try {
         columnName = L10N.getStr("table.headers." + type + "." + f.name);
       } catch (e) {
         columnName = COOKIE_KEY_MAP[f.name];
       }
 
       if (!columnName) {
         console.error("Unable to localize table header type:" + type + " key:" + f.name);
       } else {
         columns[f.name] = columnName;
       }
     });
 
-    this.table.setColumns(columns, null, HIDDEN_COLUMNS);
+    this.table.setColumns(columns, null, hiddenFields, privateFields);
     this.hideSidebar();
 
     yield this.makeFieldsEditable(editableFields);
   },
 
   /**
    * Populates or updates the rows in the storage table.
    *
@@ -922,20 +931,23 @@ StorageUI.prototype = {
     // IndexedDB only supports removing items from object stores (level 4 of the tree)
     if (!actor.removeItem || (type === "indexedDB" && selectedItem.length !== 4)) {
       event.preventDefault();
       return;
     }
 
     let rowId = this.table.contextMenuRowId;
     let data = this.table.items.get(rowId);
-    let name = addEllipsis(data[this.table.uniqueId]);
+    let name = data[this.table.uniqueId];
+
+    let separatorRegex = new RegExp(SEPARATOR_GUID, "g");
+    let label = addEllipsis((name + "").replace(separatorRegex, "-"));
 
     this._tablePopupDelete.setAttribute("label",
-      L10N.getFormatStr("storage.popupMenu.deleteLabel", name));
+      L10N.getFormatStr("storage.popupMenu.deleteLabel", label));
 
     if (type === "cookies") {
       let host = addEllipsis(data.host);
 
       this._tablePopupDeleteAllFrom.hidden = false;
       this._tablePopupDeleteAllFrom.setAttribute("label",
         L10N.getFormatStr("storage.popupMenu.deleteAllFromLabel", host));
     } else {
--- a/devtools/server/actors/storage.js
+++ b/devtools/server/actors/storage.js
@@ -10,16 +10,22 @@ const protocol = require("devtools/share
 const {LongStringActor} = require("devtools/server/actors/string");
 const {DebuggerServer} = require("devtools/server/main");
 const Services = require("Services");
 const promise = require("promise");
 const {isWindowIncluded} = require("devtools/shared/layout/utils");
 const specs = require("devtools/shared/specs/storage");
 const { Task } = require("devtools/shared/task");
 
+// GUID to be used as a separator in compound keys. This must match the same
+// constant in devtools/client/storage/ui.js,
+// devtools/client/storage/test/head.js and
+// devtools/server/tests/browser/head.js
+const SEPARATOR_GUID = "{9d414cc5-8319-0a04-0586-c0a6ae01670a}";
+
 loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm");
 loader.lazyImporter(this, "Sqlite", "resource://gre/modules/Sqlite.jsm");
 
 // We give this a funny name to avoid confusion with the global
 // indexedDB.
 loader.lazyGetter(this, "indexedDBForStorage", () => {
   // On xpcshell, we can't instantiate indexedDB without crashing
   try {
@@ -462,24 +468,26 @@ StorageActors.createActor({
   },
 
   toStoreObject(cookie) {
     if (!cookie) {
       return null;
     }
 
     return {
+      uniqueKey: `${cookie.name}${SEPARATOR_GUID}${cookie.host}` +
+                 `${SEPARATOR_GUID}${cookie.path}`,
       name: cookie.name,
+      host: cookie.host || "",
       path: cookie.path || "",
-      host: cookie.host || "",
 
       // because expires is in seconds
       expires: (cookie.expires || 0) * 1000,
 
-      // because it is in micro seconds
+      // because creationTime is in micro seconds
       creationTime: cookie.creationTime / 1000,
 
       // - do -
       lastAccessed: cookie.lastAccessed / 1000,
       value: new LongStringActor(this.conn, cookie.value || ""),
       isDomain: cookie.isDomain,
       isSecure: cookie.isSecure,
       isHttpOnly: cookie.isHttpOnly
@@ -490,17 +498,20 @@ StorageActors.createActor({
     this.hostVsStores.set(host, new Map());
     let doc = this.storageActor.document;
 
     let cookies = this.getCookiesFromHost(host, doc.nodePrincipal
                                                    .originAttributes);
 
     for (let cookie of cookies) {
       if (this.isCookieAtHost(cookie, host)) {
-        this.hostVsStores.get(host).set(cookie.name, cookie);
+        let uniqueKey = `${cookie.name}${SEPARATOR_GUID}${cookie.host}` +
+                        `${SEPARATOR_GUID}${cookie.path}`;
+
+        this.hostVsStores.get(host).set(uniqueKey, cookie);
       }
     }
   },
 
   /**
    * Notification observer for "cookie-change".
    *
    * @param subject
@@ -523,40 +534,49 @@ StorageActors.createActor({
     let hosts = this.getMatchingHosts(subject);
     let data = {};
 
     switch (action) {
       case "added":
       case "changed":
         if (hosts.length) {
           for (let host of hosts) {
-            this.hostVsStores.get(host).set(subject.name, subject);
-            data[host] = [subject.name];
+            let uniqueKey = `${subject.name}${SEPARATOR_GUID}${subject.host}` +
+                            `${SEPARATOR_GUID}${subject.path}`;
+
+            this.hostVsStores.get(host).set(uniqueKey, subject);
+            data[host] = [uniqueKey];
           }
           this.storageActor.update(action, "cookies", data);
         }
         break;
 
       case "deleted":
         if (hosts.length) {
           for (let host of hosts) {
-            this.hostVsStores.get(host).delete(subject.name);
-            data[host] = [subject.name];
+            let uniqueKey = `${subject.name}${SEPARATOR_GUID}${subject.host}` +
+                            `${SEPARATOR_GUID}${subject.path}`;
+
+            this.hostVsStores.get(host).delete(uniqueKey);
+            data[host] = [uniqueKey];
           }
           this.storageActor.update("deleted", "cookies", data);
         }
         break;
 
       case "batch-deleted":
         if (hosts.length) {
           for (let host of hosts) {
             let stores = [];
             for (let cookie of subject) {
-              this.hostVsStores.get(host).delete(cookie.name);
-              stores.push(cookie.name);
+              let uniqueKey = `${cookie.name}${SEPARATOR_GUID}${cookie.host}` +
+                              `${SEPARATOR_GUID}${cookie.path}`;
+
+              this.hostVsStores.get(host).delete(uniqueKey);
+              stores.push(uniqueKey);
             }
             data[host] = stores;
           }
           this.storageActor.update("deleted", "cookies", data);
         }
         break;
 
       case "cleared":
@@ -568,25 +588,27 @@ StorageActors.createActor({
         }
         break;
     }
     return null;
   },
 
   getFields: Task.async(function* () {
     return [
-      { name: "name", editable: 1},
-      { name: "path", editable: 1},
-      { name: "host", editable: 1},
-      { name: "expires", editable: 1},
-      { name: "lastAccessed", editable: 0},
-      { name: "value", editable: 1},
-      { name: "isDomain", editable: 0},
-      { name: "isSecure", editable: 1},
-      { name: "isHttpOnly", editable: 1}
+      { name: "uniqueKey", editable: false, private: true },
+      { name: "name", editable: true, hidden: false },
+      { name: "host", editable: true, hidden: false },
+      { name: "path", editable: true, hidden: false },
+      { name: "expires", editable: true, hidden: false },
+      { name: "lastAccessed", editable: false, hidden: false },
+      { name: "creationTime", editable: false, hidden: true },
+      { name: "value", editable: true, hidden: false },
+      { name: "isDomain", editable: false, hidden: true },
+      { name: "isSecure", editable: true, hidden: true },
+      { name: "isHttpOnly", editable: true, hidden: false }
     ];
   }),
 
   /**
    * Pass the editItem command from the content to the chrome process.
    *
    * @param {Object} data
    *        See editCookie() for format details.
@@ -698,17 +720,17 @@ var cookieHelpers = {
   /**
    * Apply the results of a cookie edit.
    *
    * @param {Object} data
    *        An object in the following format:
    *        {
    *          host: "http://www.mozilla.org",
    *          field: "value",
-   *          key: "name",
+   *          editCookie: "name",
    *          oldValue: "%7BHello%7D",
    *          newValue: "%7BHelloo%7D",
    *          items: {
    *            name: "optimizelyBuckets",
    *            path: "/",
    *            host: ".mozilla.org",
    *            expires: "Mon, 02 Jun 2025 12:37:37 GMT",
    *            creationTime: "Tue, 18 Nov 2014 16:21:18 GMT",
@@ -722,20 +744,23 @@ var cookieHelpers = {
    */
   editCookie(data) {
     let {field, oldValue, newValue} = data;
     let origName = field === "name" ? oldValue : data.items.name;
     let origHost = field === "host" ? oldValue : data.items.host;
     let origPath = field === "path" ? oldValue : data.items.path;
     let cookie = null;
 
-    let enumerator = Services.cookies.getCookiesFromHost(origHost, data.originAttributes || {});
+    let enumerator =
+      Services.cookies.getCookiesFromHost(origHost, data.originAttributes || {});
     while (enumerator.hasMoreElements()) {
       let nsiCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
-      if (nsiCookie.name === origName && nsiCookie.host === origHost) {
+      if (nsiCookie.name === origName &&
+          nsiCookie.host === origHost &&
+          nsiCookie.path === origPath) {
         cookie = {
           host: nsiCookie.host,
           path: nsiCookie.path,
           name: nsiCookie.name,
           value: nsiCookie.value,
           isSecure: nsiCookie.isSecure,
           isHttpOnly: nsiCookie.isHttpOnly,
           isSession: nsiCookie.isSession,
@@ -745,17 +770,17 @@ var cookieHelpers = {
         break;
       }
     }
 
     if (!cookie) {
       return;
     }
 
-    // If the date is expired set it for 1 minute in the future.
+    // If the date is expired set it for 10 seconds in the future.
     let now = new Date();
     if (!cookie.isSession && (cookie.expires * 1000) <= now) {
       let tenSecondsFromNow = (now.getTime() + 10 * 1000) / 1000;
 
       cookie.expires = tenSecondsFromNow;
     }
 
     switch (field) {
@@ -799,32 +824,44 @@ var cookieHelpers = {
       cookie.isHttpOnly,
       cookie.isSession,
       cookie.isSession ? MAX_COOKIE_EXPIRY : cookie.expires,
       cookie.originAttributes
     );
   },
 
   _removeCookies(host, opts = {}) {
+    // We use a uniqueId to emulate compound keys for cookies. We need to
+    // extract the cookie name to remove the correct cookie.
+    if (opts.name) {
+      let split = opts.name.split(SEPARATOR_GUID);
+
+      opts.name = split[0];
+      opts.path = split[2];
+    }
+
     function hostMatches(cookieHost, matchHost) {
       if (cookieHost == null) {
         return matchHost == null;
       }
       if (cookieHost.startsWith(".")) {
         return ("." + matchHost).endsWith(cookieHost);
       }
       return cookieHost == host;
     }
 
-    let enumerator = Services.cookies.getCookiesFromHost(host, opts.originAttributes || {});
+    let enumerator =
+      Services.cookies.getCookiesFromHost(host, opts.originAttributes || {});
+
     while (enumerator.hasMoreElements()) {
       let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
       if (hostMatches(cookie.host, host) &&
           (!opts.name || cookie.name === opts.name) &&
-          (!opts.domain || cookie.host === opts.domain)) {
+          (!opts.domain || cookie.host === opts.domain) &&
+          (!opts.path || cookie.path === opts.path)) {
         Services.cookies.remove(
           cookie.host,
           cookie.name,
           cookie.path,
           false,
           cookie.originAttributes
         );
       }
@@ -1026,18 +1063,18 @@ function getObjectForLocalOrSessionStora
       this.hostVsStores = new Map();
       for (let window of this.windows) {
         this.populateStoresForHost(this.getHostName(window.location), window);
       }
     },
 
     getFields: Task.async(function* () {
       return [
-        { name: "name", editable: 1},
-        { name: "value", editable: 1}
+        { name: "name", editable: true },
+        { name: "value", editable: true }
       ];
     }),
 
     /**
      * Edit localStorage or sessionStorage fields.
      *
      * @param {Object} data
      *        See editCookie() for format details.
@@ -1207,18 +1244,18 @@ StorageActors.createActor({
     return {
       url: String(request.url),
       status: String(response.statusText),
     };
   }),
 
   getFields: Task.async(function* () {
     return [
-      { name: "url", editable: 0 },
-      { name: "status", editable: 0 }
+      { name: "url", editable: false },
+      { name: "status", editable: false }
     ];
   }),
 
   getHostName(location) {
     if (!location.host) {
       return location.href;
     }
     return location.protocol + "//" + location.host;
@@ -1731,36 +1768,36 @@ StorageActors.createActor({
     }
   },
 
   getFields: Task.async(function* (subType) {
     switch (subType) {
       // Detail of database
       case "database":
         return [
-          { name: "objectStore", editable: 0 },
-          { name: "keyPath", editable: 0 },
-          { name: "autoIncrement", editable: 0 },
-          { name: "indexes", editable: 0 },
+          { name: "objectStore", editable: false },
+          { name: "keyPath", editable: false },
+          { name: "autoIncrement", editable: false },
+          { name: "indexes", editable: false },
         ];
 
       // Detail of object store
       case "object store":
         return [
-          { name: "name", editable: 0 },
-          { name: "value", editable: 0 }
+          { name: "name", editable: false },
+          { name: "value", editable: false }
         ];
 
       // Detail of indexedDB for one origin
       default:
         return [
-          { name: "db", editable: 0 },
-          { name: "origin", editable: 0 },
-          { name: "version", editable: 0 },
-          { name: "objectStores", editable: 0 },
+          { name: "db", editable: false },
+          { name: "origin", editable: false },
+          { name: "version", editable: false },
+          { name: "objectStores", editable: false },
         ];
     }
   })
 });
 
 var indexedDBHelpers = {
   backToChild(...args) {
     let mm = Cc["@mozilla.org/globalmessagemanager;1"]
@@ -2489,16 +2526,17 @@ let StorageActor = protocol.ActorClassWi
       // items from changed instead.
       this.removeNamesFromUpdateList("changed", storeType,
                                      this.boundUpdate.added[storeType]);
     } else if (action == "deleted") {
       // If any item got delete, or a host got delete, no point in sending
       // added or changed update
       this.removeNamesFromUpdateList("added", storeType, data);
       this.removeNamesFromUpdateList("changed", storeType, data);
+
       for (let host in data) {
         if (data[host].length == 0 && this.boundUpdate.added &&
             this.boundUpdate.added[storeType] &&
             this.boundUpdate.added[storeType][host]) {
           delete this.boundUpdate.added[storeType][host];
         }
         if (data[host].length == 0 && this.boundUpdate.changed &&
             this.boundUpdate.changed[storeType] &&
--- a/devtools/server/tests/browser/browser.ini
+++ b/devtools/server/tests/browser/browser.ini
@@ -6,16 +6,17 @@ support-files =
   animation.html
   doc_allocations.html
   doc_force_cc.html
   doc_force_gc.html
   doc_innerHTML.html
   doc_perf.html
   navigate-first.html
   navigate-second.html
+  storage-cookies-same-name.html
   storage-dynamic-windows.html
   storage-listings.html
   storage-unsecured-iframe.html
   storage-updates.html
   storage-secured-iframe.html
   stylesheets-nested-iframes.html
   timeline-iframe-child.html
   timeline-iframe-parent.html
@@ -75,16 +76,17 @@ skip-if = true # Needs to be updated for
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
 [browser_perf-recording-actor-02.js]
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
 [browser_perf-samples-01.js]
 [browser_perf-samples-02.js]
 #[browser_perf-front-profiler-01.js] bug 1077464
 #[browser_perf-front-profiler-05.js] bug 1077464
 #[browser_perf-front-profiler-06.js]
+[browser_storage_cookies-duplicate-names.js]
 [browser_storage_dynamic_windows.js]
 [browser_storage_listings.js]
 [browser_storage_updates.js]
 [browser_stylesheets_getTextEmpty.js]
 [browser_stylesheets_nested-iframes.js]
 [browser_timeline.js]
 [browser_timeline_actors.js]
 [browser_timeline_iframes.js]
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/browser_storage_cookies-duplicate-names.js
@@ -0,0 +1,105 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the storage panel is able to display multiple cookies with the same
+// name (and different paths).
+
+const {StorageFront} = require("devtools/shared/fronts/storage");
+Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/storage-helpers.js", this);
+
+const TESTDATA = {
+  "test1.example.org": [
+    {
+      name: "name",
+      value: "value1",
+      expires: 0,
+      path: "/",
+      host: "test1.example.org",
+      isDomain: false,
+      isSecure: false,
+    },
+    {
+      name: "name",
+      value: "value2",
+      expires: 0,
+      path: "/path2/",
+      host: "test1.example.org",
+      isDomain: false,
+      isSecure: false,
+    },
+    {
+      name: "name",
+      value: "value3",
+      expires: 0,
+      path: "/path3/",
+      host: "test1.example.org",
+      isDomain: false,
+      isSecure: false,
+    }
+  ]
+};
+
+add_task(function* () {
+  yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies-same-name.html");
+
+  initDebuggerServer();
+  let client = new DebuggerClient(DebuggerServer.connectPipe());
+  let form = yield connectDebuggerClient(client);
+  let front = StorageFront(client, form);
+  let data = yield front.listStores();
+
+  ok(data.cookies, "Cookies storage actor is present");
+
+  yield testCookies(data.cookies);
+  yield clearStorage();
+
+  // Forcing GC/CC to get rid of docshells and windows created by this test.
+  forceCollections();
+  yield client.close();
+  forceCollections();
+  DebuggerServer.destroy();
+  forceCollections();
+});
+
+function testCookies(cookiesActor) {
+  let numHosts = Object.keys(cookiesActor.hosts).length;
+  is(numHosts, 1, "Correct number of host entries for cookies");
+  return testCookiesObjects(0, cookiesActor.hosts, cookiesActor);
+}
+
+var testCookiesObjects = Task.async(function* (index, hosts, cookiesActor) {
+  let host = Object.keys(hosts)[index];
+  let matchItems = data => {
+    is(data.total, TESTDATA[host].length,
+       "Number of cookies in host " + host + " matches");
+    for (let item of data.data) {
+      let found = false;
+      for (let toMatch of TESTDATA[host]) {
+        if (item.name === toMatch.name &&
+            item.host === toMatch.host &&
+            item.path === toMatch.path) {
+          found = true;
+          ok(true, "Found cookie " + item.name + " in response");
+          is(item.value.str, toMatch.value, "The value matches.");
+          is(item.expires, toMatch.expires, "The expiry time matches.");
+          is(item.path, toMatch.path, "The path matches.");
+          is(item.host, toMatch.host, "The host matches.");
+          is(item.isSecure, toMatch.isSecure, "The isSecure value matches.");
+          is(item.isDomain, toMatch.isDomain, "The isDomain value matches.");
+          break;
+        }
+      }
+      ok(found, "cookie " + item.name + " should exist in response");
+    }
+  };
+
+  ok(!!TESTDATA[host], "Host is present in the list : " + host);
+  matchItems(yield cookiesActor.getStoreObjects(host));
+  if (index == Object.keys(hosts).length - 1) {
+    return;
+  }
+  yield testCookiesObjects(++index, hosts, cookiesActor);
+});
--- a/devtools/server/tests/browser/browser_storage_dynamic_windows.js
+++ b/devtools/server/tests/browser/browser_storage_dynamic_windows.js
@@ -61,16 +61,17 @@ function markOutMatched(toBeEmptied, dat
      "At least one storage type should be present");
   for (let storageType in toBeEmptied) {
     if (!data[storageType]) {
       continue;
     }
     info("Testing for " + storageType);
     for (let host in data[storageType]) {
       ok(toBeEmptied[storageType][host], "Host " + host + " found");
+
       if (!deleted) {
         for (let item of data[storageType][host]) {
           let index = toBeEmptied[storageType][host].indexOf(item);
           ok(index > -1, "Item found - " + item);
           if (index > -1) {
             toBeEmptied[storageType][host].splice(index, 1);
           }
         }
@@ -82,80 +83,39 @@ function markOutMatched(toBeEmptied, dat
       }
     }
     if (!Object.keys(toBeEmptied[storageType]).length) {
       delete toBeEmptied[storageType];
     }
   }
 }
 
-// function testReload(front) {
-//   info("Testing if reload works properly");
-
-//   let shouldBeEmptyFirst = Cu.cloneInto(beforeReload,  {});
-//   let shouldBeEmptyLast = Cu.cloneInto(beforeReload,  {});
-//   return new Promise(resolve => {
-
-//     let onStoresUpdate = data => {
-//       info("in stores update of testReload");
-//       // This might be second time stores update is happening, in which case,
-//       // data.deleted will be null.
-//       // OR.. This might be the first time on a super slow machine where both
-//       // data.deleted and data.added is missing in the first update.
-//       if (data.deleted) {
-//         markOutMatched(shouldBeEmptyFirst, data.deleted, true);
-//       }
-
-//       if (!Object.keys(shouldBeEmptyFirst).length) {
-//         info("shouldBeEmptyFirst is empty now");
-//       }
-
-//       // stores-update call might not have data.added for the first time on
-//       // slow machines, in which case, data.added will be null
-//       if (data.added) {
-//         markOutMatched(shouldBeEmptyLast, data.added);
-//       }
-
-//       if (!Object.keys(shouldBeEmptyLast).length) {
-//         info("Everything to be received is received.");
-//         endTestReloaded();
-//       }
-//     };
-
-//     let endTestReloaded = () => {
-//       front.off("stores-update", onStoresUpdate);
-//       resolve();
-//     };
-
-//     front.on("stores-update", onStoresUpdate);
-
-//     content.location.reload();
-//   });
-// }
-
 function testAddIframe(front) {
   info("Testing if new iframe addition works properly");
   return new Promise(resolve => {
     let shouldBeEmpty = {
       localStorage: {
         "https://sectest1.example.org": ["iframe-s-ls1"]
       },
       sessionStorage: {
         "https://sectest1.example.org": ["iframe-s-ss1"]
       },
       cookies: {
-        "sectest1.example.org": ["sc1"]
+        "sectest1.example.org": [
+          getCookieId("sc1", "sectest1.example.org",
+                      "/browser/devtools/server/tests/browser/")
+        ]
       },
       indexedDB: {
         // empty because indexed db creation happens after the page load, so at
         // the time of window-ready, there was no indexed db present.
         "https://sectest1.example.org": []
       },
       Cache: {
-        "https://sectest1.example.org":[]
+        "https://sectest1.example.org": []
       }
     };
 
     let onStoresUpdate = data => {
       info("checking if the hosts list is correct for this iframe addition");
 
       markOutMatched(shouldBeEmpty, data.added);
 
--- a/devtools/server/tests/browser/browser_storage_listings.js
+++ b/devtools/server/tests/browser/browser_storage_listings.js
@@ -30,25 +30,16 @@ const storeMap = {
       },
       {
         name: "c3",
         value: "foobar-2",
         expires: 2000000001000,
         path: "/",
         host: "test1.example.org",
         isDomain: false,
-        isSecure: false,
-      },
-      {
-        name: "uc1",
-        value: "foobar",
-        host: ".example.org",
-        path: "/",
-        expires: 0,
-        isDomain: true,
         isSecure: true,
       }
     ],
     "sectest1.example.org": [
       {
         name: "uc1",
         value: "foobar",
         host: ".example.org",
@@ -332,28 +323,29 @@ function* testStores(data) {
   ok(data.indexedDB, "Indexed DB storage actor is present");
   yield testCookies(data.cookies);
   yield testLocalStorage(data.localStorage);
   yield testSessionStorage(data.sessionStorage);
   yield testIndexedDB(data.indexedDB);
 }
 
 function testCookies(cookiesActor) {
-  is(Object.keys(cookiesActor.hosts).length, 2, "Correct number of host entries for cookies");
+  is(Object.keys(cookiesActor.hosts).length, 2,
+                 "Correct number of host entries for cookies");
   return testCookiesObjects(0, cookiesActor.hosts, cookiesActor);
 }
 
 var testCookiesObjects = Task.async(function* (index, hosts, cookiesActor) {
   let host = Object.keys(hosts)[index];
   let matchItems = data => {
     let cookiesLength = 0;
     for (let secureCookie of storeMap.cookies[host]) {
-       if (secureCookie.isSecure) {
-          ++cookiesLength;
-       }
+      if (secureCookie.isSecure) {
+        ++cookiesLength;
+      }
     }
     // Any secure cookies did not get stored in the database.
     is(data.total, storeMap.cookies[host].length - cookiesLength,
        "Number of cookies in host " + host + " matches");
     for (let item of data.data) {
       let found = false;
       for (let toMatch of storeMap.cookies[host]) {
         if (item.name == toMatch.name) {
--- a/devtools/server/tests/browser/browser_storage_updates.js
+++ b/devtools/server/tests/browser/browser_storage_updates.js
@@ -22,17 +22,22 @@ const TESTS = [
       win.addCookie("c2", "foobar2");
 
       info('win.localStorage.setItem("l1", "foobar1")');
       win.localStorage.setItem("l1", "foobar1");
     },
     expected: {
       added: {
         cookies: {
-          "test1.example.org": ["c1", "c2"]
+          "test1.example.org": [
+            getCookieId("c1", "test1.example.org",
+                        "/browser/devtools/server/tests/browser/"),
+            getCookieId("c2", "test1.example.org",
+                        "/browser/devtools/server/tests/browser/")
+          ]
         },
         localStorage: {
           "http://test1.example.org": ["l1"]
         }
       }
     }
   },
 
@@ -43,17 +48,20 @@ const TESTS = [
       win.addCookie("c1", "new_foobar1");
 
       info('win.localStorage.setItem("l2", "foobar2")');
       win.localStorage.setItem("l2", "foobar2");
     },
     expected: {
       changed: {
         cookies: {
-          "test1.example.org": ["c1"]
+          "test1.example.org": [
+            getCookieId("c1", "test1.example.org",
+                        "/browser/devtools/server/tests/browser/"),
+          ]
         }
       },
       added: {
         localStorage: {
           "http://test1.example.org": ["l2"]
         }
       }
     }
@@ -69,17 +77,20 @@ const TESTS = [
       win.localStorage.removeItem("l1");
 
       info('win.localStorage.setItem("l3", "foobar3")');
       win.localStorage.setItem("l3", "foobar3");
     },
     expected: {
       deleted: {
         cookies: {
-          "test1.example.org": ["c2"]
+          "test1.example.org": [
+            getCookieId("c2", "test1.example.org",
+                        "/browser/devtools/server/tests/browser/"),
+          ]
         },
         localStorage: {
           "http://test1.example.org": ["l1"]
         }
       },
       added: {
         localStorage: {
           "http://test1.example.org": ["l3"]
@@ -107,30 +118,36 @@ const TESTS = [
       win.sessionStorage.setItem("s2", "foobar2");
 
       info('win.localStorage.setItem("l3", "new_foobar3")');
       win.localStorage.setItem("l3", "new_foobar3");
     },
     expected: {
       added: {
         cookies: {
-          "test1.example.org": ["c3"]
+          "test1.example.org": [
+            getCookieId("c3", "test1.example.org",
+                        "/browser/devtools/server/tests/browser/"),
+          ]
         },
         sessionStorage: {
           "http://test1.example.org": ["s1", "s2"]
         }
       },
       changed: {
         localStorage: {
           "http://test1.example.org": ["l3"]
         }
       },
       deleted: {
         cookies: {
-          "test1.example.org": ["c1"]
+          "test1.example.org": [
+            getCookieId("c1", "test1.example.org",
+                        "/browser/devtools/server/tests/browser/"),
+          ]
         },
         localStorage: {
           "http://test1.example.org": ["l2"]
         }
       }
     }
   },
 
@@ -153,17 +170,20 @@ const TESTS = [
   {
     action: function (win) {
       info("win.clearCookies()");
       win.clearCookies();
     },
     expected: {
       deleted: {
         cookies: {
-          "test1.example.org": ["c3"]
+          "test1.example.org": [
+            getCookieId("c3", "test1.example.org",
+                        "/browser/devtools/server/tests/browser/"),
+          ]
         }
       }
     }
   }
 ];
 
 function markOutMatched(toBeEmptied, data) {
   if (!Object.keys(toBeEmptied).length) {
--- a/devtools/server/tests/browser/head.js
+++ b/devtools/server/tests/browser/head.js
@@ -1,12 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+"use strict";
+
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cu = Components.utils;
 
 const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const {DebuggerClient} = require("devtools/shared/client/main");
 const {DebuggerServer} = require("devtools/server/main");
@@ -14,16 +18,21 @@ const {defer} = require("promise");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const Services = require("Services");
 
 const PATH = "browser/devtools/server/tests/browser/";
 const MAIN_DOMAIN = "http://test1.example.org/" + PATH;
 const ALT_DOMAIN = "http://sectest1.example.org/" + PATH;
 const ALT_DOMAIN_SECURED = "https://sectest1.example.org:443/" + PATH;
 
+// GUID to be used as a separator in compound keys. This must match the same
+// constant in devtools/server/actors/storage.js,
+// devtools/client/storage/ui.js and devtools/client/storage/test/head.js
+const SEPARATOR_GUID = "{9d414cc5-8319-0a04-0586-c0a6ae01670a}";
+
 // All tests are asynchronous.
 waitForExplicitFinish();
 
 /**
  * Add a new test tab in the browser and load the given url.
  * @param {String} url The url to be loaded in the new tab
  * @return a promise that resolves to the new browser that the document
  *         is loaded in. Note that we cannot return the document
@@ -89,17 +98,16 @@ function connectDebuggerClient(client) {
  * @param {String} eventName
  * @param {Boolean} useCapture Optional, for addEventListener/removeEventListener
  * @return A promise that resolves when the event has been handled
  */
 function once(target, eventName, useCapture = false) {
   info("Waiting for event: '" + eventName + "' on " + target + ".");
 
   return new Promise(resolve => {
-
     for (let [add, remove] of [
       ["addEventListener", "removeEventListener"],
       ["addListener", "removeListener"],
       ["on", "off"]
     ]) {
       if ((add in target) && (remove in target)) {
         target[add](eventName, function onEvent(...aArgs) {
           info("Got event: '" + eventName + "' on " + target + ".");
@@ -132,29 +140,34 @@ function forceCollections() {
 function getMockTabActor(win) {
   return {
     window: win,
     isRootActor: true
   };
 }
 
 registerCleanupFunction(function tearDown() {
+  Services.cookies.removeAll();
+
   while (gBrowser.tabs.length > 1) {
     gBrowser.removeCurrentTab();
   }
 });
 
 function idleWait(time) {
   return DevToolsUtils.waitForTime(time);
 }
 
 function busyWait(time) {
   let start = Date.now();
+  // eslint-disable-next-line
   let stack;
-  while (Date.now() - start < time) { stack = Components.stack; }
+  while (Date.now() - start < time) {
+    stack = Components.stack;
+  }
 }
 
 /**
  * Waits until a predicate returns true.
  *
  * @param function predicate
  *        Invoked once in a while until it returns true.
  * @param number interval [optional]
@@ -167,37 +180,44 @@ function waitUntil(predicate, interval =
   return new Promise(resolve => {
     setTimeout(function () {
       waitUntil(predicate).then(() => resolve(true));
     }, interval);
   });
 }
 
 function waitForMarkerType(front, types, predicate,
-  unpackFun = (name, data) => data.markers,
-  eventName = "timeline-data")
-{
+                           unpackFun = (name, data) => data.markers,
+                           eventName = "timeline-data") {
   types = [].concat(types);
-  predicate = predicate || function () { return true; };
+  predicate = predicate || function () {
+    return true;
+  };
   let filteredMarkers = [];
   let { promise, resolve } = defer();
 
   info("Waiting for markers of type: " + types);
 
   function handler(name, data) {
     if (typeof name === "string" && name !== "markers") {
       return;
     }
 
     let markers = unpackFun(name, data);
     info("Got markers: " + JSON.stringify(markers, null, 2));
 
-    filteredMarkers = filteredMarkers.concat(markers.filter(m => types.indexOf(m.name) !== -1));
+    filteredMarkers = filteredMarkers.concat(
+      markers.filter(m => types.indexOf(m.name) !== -1));
 
-    if (types.every(t => filteredMarkers.some(m => m.name === t)) && predicate(filteredMarkers)) {
+    if (types.every(t => filteredMarkers.some(m => m.name === t)) &&
+        predicate(filteredMarkers)) {
       front.off(eventName, handler);
       resolve(filteredMarkers);
     }
   }
   front.on(eventName, handler);
 
   return promise;
 }
+
+function getCookieId(name, domain, path) {
+  return `${name}${SEPARATOR_GUID}${domain}${SEPARATOR_GUID}${path}`;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/storage-cookies-same-name.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Storage inspector cookies with duplicate names</title>
+</head>
+<body onload="createCookies()">
+<script type="application/javascript;version=1.7">
+"use strict";
+function createCookies() {
+  document.cookie = "name=value1;path=/;";
+  document.cookie = "name=value2;path=/path2/;";
+  document.cookie = "name=value3;path=/path3/;";
+}
+
+window.removeCookie = function (name) {
+  document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
+};
+
+window.clearCookies = function () {
+  let cookies = document.cookie;
+  for (let cookie of cookies.split(";")) {
+    removeCookie(cookie.split("=")[0]);
+  }
+};
+</script>
+</body>
+</html>
--- a/devtools/shared/webconsole/network-monitor.js
+++ b/devtools/shared/webconsole/network-monitor.js
@@ -1520,16 +1520,17 @@ NetworkMonitor.prototype = {
 function NetworkMonitorChild(outerWindowID, messageManager, conn, owner) {
   this.outerWindowID = outerWindowID;
   this.conn = conn;
   this.owner = owner;
   this._messageManager = messageManager;
   this._onNewEvent = this._onNewEvent.bind(this);
   this._onUpdateEvent = this._onUpdateEvent.bind(this);
   this._netEvents = new Map();
+  this._msgName = `debug:${this.conn.prefix}netmonitor`;
 }
 
 exports.NetworkMonitorChild = NetworkMonitorChild;
 
 NetworkMonitorChild.prototype = {
   owner: null,
   _netEvents: null,
   _saveRequestAndResponseBodies: true,
@@ -1537,51 +1538,49 @@ NetworkMonitorChild.prototype = {
 
   get saveRequestAndResponseBodies() {
     return this._saveRequestAndResponseBodies;
   },
 
   set saveRequestAndResponseBodies(val) {
     this._saveRequestAndResponseBodies = val;
 
-    this._messageManager.sendAsyncMessage("debug:netmonitor", {
+    this._messageManager.sendAsyncMessage(this._msgName, {
       action: "setPreferences",
       preferences: {
         saveRequestAndResponseBodies: this._saveRequestAndResponseBodies,
       },
     });
   },
 
   get throttleData() {
     return this._throttleData;
   },
 
   set throttleData(val) {
     this._throttleData = val;
 
-    this._messageManager.sendAsyncMessage("debug:netmonitor", {
+    this._messageManager.sendAsyncMessage(this._msgName, {
       action: "setPreferences",
       preferences: {
         throttleData: this._throttleData,
       },
     });
   },
 
   init: function () {
     this.conn.setupInParent({
       module: "devtools/shared/webconsole/network-monitor",
       setupParent: "setupParentProcess"
     });
 
     let mm = this._messageManager;
-    mm.addMessageListener("debug:netmonitor:newEvent",
-                          this._onNewEvent);
-    mm.addMessageListener("debug:netmonitor:updateEvent",
-                          this._onUpdateEvent);
-    mm.sendAsyncMessage("debug:netmonitor", {
+    mm.addMessageListener(`${this._msgName}:newEvent`, this._onNewEvent);
+    mm.addMessageListener(`${this._msgName}:updateEvent`, this._onUpdateEvent);
+    mm.sendAsyncMessage(this._msgName, {
       outerWindowID: this.outerWindowID,
       action: "start",
     });
   },
 
   _onNewEvent: DevToolsUtils.makeInfallible(function _onNewEvent(msg) {
     let {id, event} = msg.data;
 
@@ -1595,35 +1594,32 @@ NetworkMonitorChild.prototype = {
     this._netEvents.set(id, Cu.getWeakReference(actor));
   }),
 
   _onUpdateEvent: DevToolsUtils.makeInfallible(function _onUpdateEvent(msg) {
     let {id, method, args} = msg.data;
     let weakActor = this._netEvents.get(id);
     let actor = weakActor ? weakActor.get() : null;
     if (!actor) {
-      console.error("Received debug:netmonitor:updateEvent for unknown " +
-                    "event ID: " + id);
+      console.error(`Received ${this._msgName}:updateEvent for unknown event ID: ${id}`);
       return;
     }
     if (!(method in actor)) {
-      console.error("Received debug:netmonitor:updateEvent unsupported " +
-                    "method: " + method);
+      console.error(`Received ${this._msgName}:updateEvent unsupported ` +
+                    `method: ${method}`);
       return;
     }
     actor[method].apply(actor, args);
   }),
 
   destroy: function () {
     let mm = this._messageManager;
     try {
-      mm.removeMessageListener("debug:netmonitor:newEvent",
-                               this._onNewEvent);
-      mm.removeMessageListener("debug:netmonitor:updateEvent",
-                               this._onUpdateEvent);
+      mm.removeMessageListener(`${this._msgName}:newEvent`, this._onNewEvent);
+      mm.removeMessageListener(`${this._msgName}:updateEvent`, this._onUpdateEvent);
     } catch (e) {
       // On b2g, when registered to a new root docshell,
       // all message manager functions throw when trying to call them during
       // message-manager-disconnect event.
       // As there is no attribute/method on message manager to know
       // if they are still usable or not, we can only catch the exception...
     }
     this._netEvents.clear();
@@ -1642,28 +1638,31 @@ NetworkMonitorChild.prototype = {
  * The child process has a NetworkMonitorChild instance that is listening for
  * all network logging from the main process. The net monitor shim is used to
  * proxy the data to the WebConsoleActor instance of the child process.
  *
  * @constructor
  * @param nsIMessageManager messageManager
  *        The message manager for the child app process. This is used for
  *        communication with the NetworkMonitorChild instance of the process.
+ * @param string msgName
+ *        The message name to be used for this connection.
  */
-function NetworkEventActorProxy(messageManager) {
+function NetworkEventActorProxy(messageManager, msgName) {
   this.id = gSequenceId();
   this.messageManager = messageManager;
+  this._msgName = msgName;
 }
 exports.NetworkEventActorProxy = NetworkEventActorProxy;
 
 NetworkEventActorProxy.methodFactory = function (method) {
   return DevToolsUtils.makeInfallible(function () {
     let args = Array.slice(arguments);
     let mm = this.messageManager;
-    mm.sendAsyncMessage("debug:netmonitor:updateEvent", {
+    mm.sendAsyncMessage(`${this._msgName}:updateEvent`, {
       id: this.id,
       method: method,
       args: args,
     });
   }, "NetworkEventActorProxy." + method);
 };
 
 NetworkEventActorProxy.prototype = {
@@ -1673,17 +1672,17 @@ NetworkEventActorProxy.prototype = {
    *
    * @param object event
    *        Object describing the network request.
    * @return object
    *         This object.
    */
   init: DevToolsUtils.makeInfallible(function (event) {
     let mm = this.messageManager;
-    mm.sendAsyncMessage("debug:netmonitor:newEvent", {
+    mm.sendAsyncMessage(`${this._msgName}:newEvent`, {
       id: this.id,
       event: event,
     });
     return this;
   }),
 };
 
 (function () {
@@ -1721,38 +1720,39 @@ exports.setupParentProcess = setupParent
  * monitor needs to run there when debugging tabs that are in the child.
  *
  * @param nsIMessageManager mm
  *        The message manager for the browser we're filtering on.
  * @param string prefix
  *        The RDP connection prefix that uniquely identifies the connection.
  */
 function NetworkMonitorParent(mm, prefix) {
+  this._msgName = `debug:${prefix}netmonitor`;
   this.onNetMonitorMessage = this.onNetMonitorMessage.bind(this);
   this.onNetworkEvent = this.onNetworkEvent.bind(this);
   this.setMessageManager(mm);
 }
 
 NetworkMonitorParent.prototype = {
   netMonitor: null,
   messageManager: null,
 
   setMessageManager(mm) {
     if (this.messageManager) {
       let oldMM = this.messageManager;
-      oldMM.removeMessageListener("debug:netmonitor", this.onNetMonitorMessage);
+      oldMM.removeMessageListener(this._msgName, this.onNetMonitorMessage);
     }
     this.messageManager = mm;
     if (mm) {
-      mm.addMessageListener("debug:netmonitor", this.onNetMonitorMessage);
+      mm.addMessageListener(this._msgName, this.onNetMonitorMessage);
     }
   },
 
   /**
-   * Handler for "debug:netmonitor" messages received through the message manager
+   * Handler for `debug:${prefix}netmonitor` messages received through the message manager
    * from the content process.
    *
    * @param object msg
    *        Message from the content.
    */
   onNetMonitorMessage: DevToolsUtils.makeInfallible(function (msg) {
     let {action} = msg.json;
     // Pipe network monitor data from parent to child via the message manager.
@@ -1797,17 +1797,17 @@ NetworkMonitorParent.prototype = {
    *
    * @param object event
    *        Object describing the network request.
    * @return object
    *         A NetworkEventActorProxy instance which is notified when further
    *         data about the request is available.
    */
   onNetworkEvent: DevToolsUtils.makeInfallible(function (event) {
-    return new NetworkEventActorProxy(this.messageManager).init(event);
+    return new NetworkEventActorProxy(this.messageManager, this._msgName).init(event);
   }),
 
   destroy: function () {
     this.setMessageManager(null);
 
     if (this.netMonitor) {
       this.netMonitor.destroy();
       this.netMonitor = null;
new file mode 100644
--- /dev/null
+++ b/dom/html/test/file_fullscreen-table.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="UTF-8">
+  <title>Test for Bug 1223561</title>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+  <script type="text/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+<table style="background-color: green"></table>
+<script>
+"use strict";
+
+function ok(condition, msg) {
+  opener.ok(condition, "[table] " + msg);
+}
+
+function is(a, b, msg) {
+  opener.is(a, b, "[table] " + msg);
+}
+
+function info(msg) {
+  opener.info("[table] " + msg);
+}
+
+const gTable = document.querySelector("table");
+
+function begin() {
+  info("The default background of window should be white");
+  addFullscreenChangeContinuation("enter", enteredFullscreen);
+  assertWindowPureColor(window, "white");
+  gTable.requestFullscreen();
+}
+
+function enteredFullscreen() {
+  info("The table with green background should be in fullscreen");
+  assertWindowPureColor(window, "green");
+  gTable.style = "background: transparent";
+  info("When the table becames transparent, the black backdrop should appear");
+  assertWindowPureColor(window, "black");
+  addFullscreenChangeContinuation("exit", exitedFullscreen);
+  document.exitFullscreen();
+}
+
+function exitedFullscreen() {
+  opener.nextTest();
+}
+</script>
+</body>
+</html>
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -48,38 +48,16 @@ support-files =
   file_bug209275_2.html
   file_bug209275_3.html
   file_bug297761.html
   file_bug417760.png
   file_bug893537.html
   file_bug1260704.png
   file_formSubmission_img.jpg
   file_formSubmission_text.txt
-  file_fullscreen-api.html
-  file_fullscreen-backdrop.html
-  file_fullscreen-denied-inner.html
-  file_fullscreen-denied.html
-  file_fullscreen-esc-exit-inner.html
-  file_fullscreen-esc-exit.html
-  file_fullscreen-hidden.html
-  file_fullscreen-lenient-setters.html
-  file_fullscreen-multiple-inner.html
-  file_fullscreen-multiple.html
-  file_fullscreen-navigation.html
-  file_fullscreen-nested.html
-  file_fullscreen-prefixed.html
-  file_fullscreen-plugins.html
-  file_fullscreen-rollback.html
-  file_fullscreen-scrollbar.html
-  file_fullscreen-selector.html
-  file_fullscreen-svg-element.html
-  file_fullscreen-top-layer.html
-  file_fullscreen-unprefix-disabled-inner.html
-  file_fullscreen-unprefix-disabled.html
-  file_fullscreen-utils.js
   file_iframe_sandbox_a_if1.html
   file_iframe_sandbox_a_if10.html
   file_iframe_sandbox_a_if11.html
   file_iframe_sandbox_a_if12.html
   file_iframe_sandbox_a_if13.html
   file_iframe_sandbox_a_if14.html
   file_iframe_sandbox_a_if15.html
   file_iframe_sandbox_a_if16.html
@@ -456,16 +434,40 @@ support-files =
 [test_formSubmission.html]
 skip-if = toolkit == 'android' #TIMED_OUT
 [test_formSubmission2.html]
 skip-if = toolkit == 'android'
 [test_formelements.html]
 [test_fullscreen-api.html]
 tags = fullscreen
 skip-if = toolkit == 'android'
+support-files =
+  file_fullscreen-api.html
+  file_fullscreen-backdrop.html
+  file_fullscreen-denied-inner.html
+  file_fullscreen-denied.html
+  file_fullscreen-esc-exit-inner.html
+  file_fullscreen-esc-exit.html
+  file_fullscreen-hidden.html
+  file_fullscreen-lenient-setters.html
+  file_fullscreen-multiple-inner.html
+  file_fullscreen-multiple.html
+  file_fullscreen-navigation.html
+  file_fullscreen-nested.html
+  file_fullscreen-prefixed.html
+  file_fullscreen-plugins.html
+  file_fullscreen-rollback.html
+  file_fullscreen-scrollbar.html
+  file_fullscreen-selector.html
+  file_fullscreen-svg-element.html
+  file_fullscreen-table.html
+  file_fullscreen-top-layer.html
+  file_fullscreen-unprefix-disabled-inner.html
+  file_fullscreen-unprefix-disabled.html
+  file_fullscreen-utils.js
 [test_fullscreen-api-race.html]
 tags = fullscreen
 skip-if = toolkit == 'android' # just copy the conditions from the test above
 [test_hidden.html]
 [test_html_attributes_reflection.html]
 [test_htmlcollection.html]
 [test_iframe_sandbox_general.html]
 tags = openwindow
--- a/dom/html/test/test_fullscreen-api.html
+++ b/dom/html/test/test_fullscreen-api.html
@@ -40,16 +40,17 @@ var gTestWindows = [
   "file_fullscreen-scrollbar.html",
   "file_fullscreen-selector.html",
   "file_fullscreen-top-layer.html",
   "file_fullscreen-backdrop.html",
   "file_fullscreen-nested.html",
   "file_fullscreen-prefixed.html",
   "file_fullscreen-unprefix-disabled.html",
   "file_fullscreen-lenient-setters.html",
+  "file_fullscreen-table.html",
 ];
 
 var testWindow = null;
 var gTestIndex = 0;
 
 function finish() {
   SimpleTest.finish();
 }
--- a/dom/media/mediasource/test/test_PlayEvents.html
+++ b/dom/media/mediasource/test/test_PlayEvents.html
@@ -16,19 +16,16 @@ SimpleTest.waitForExplicitFinish();
 // 1. Load 1.6s of data and ensure that canplay event is fired.
 // 2. Load data to have a complete buffered range from 0 to duration and ensure that canplaythrough is fired.
 // 3. Seek to an area with no buffered data, and ensure that readyState goes back to HAVE_METADATA
 // 4. Load 1.6s of data at the seek position and ensure that canplay is fired and that readyState is now HAVE_FUTURE_DATA
 // 5. Start playing video and check that once it reaches a position with no data, readyState goes back to HAVE_CURRENT_DATA and waiting event is fired.
 // 6. Add 1.6s of data once video element fired waiting, that canplay is fired once readyState is HAVE_FUTURE_DATA.
 // 7. Finally load data to the end and ensure that canplaythrough is fired and that readyState is now HAVE_ENOUGH_DATA
 
-// FIXME: bug 1319293
-addMSEPrefs(["media.dormant-on-pause-timeout-ms", -1]);
-
 runWithMSE(function(ms, el) {
   el.controls = true;
   once(ms, 'sourceopen').then(function() {
     // Log events for debugging.
     var events = ["suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata",
                   "loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort",
                   "waiting", "pause", "durationchange", "seeking", "seeked"];
     function logEvent(e) {
--- a/dom/media/platforms/agnostic/VorbisDecoder.cpp
+++ b/dom/media/platforms/agnostic/VorbisDecoder.cpp
@@ -149,17 +149,17 @@ VorbisDataDecoder::ProcessDecode(MediaRa
 MediaResult
 VorbisDataDecoder::DoDecode(MediaRawData* aSample)
 {
   MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
 
   const unsigned char* aData = aSample->Data();
   size_t aLength = aSample->Size();
   int64_t aOffset = aSample->mOffset;
-  uint64_t aTstampUsecs = aSample->mTime;
+  int64_t aTstampUsecs = aSample->mTime;
   int64_t aTotalFrames = 0;
 
   MOZ_ASSERT(mPacketCount >= 3);
 
   if (!mLastFrameTime || mLastFrameTime.ref() != aSample->mTime) {
     // We are starting a new block.
     mFrames = 0;
     mLastFrameTime = Some(aSample->mTime);
--- a/dom/media/platforms/android/MediaCodecDataDecoder.cpp
+++ b/dom/media/platforms/android/MediaCodecDataDecoder.cpp
@@ -368,22 +368,31 @@ MediaCodecDataDecoder::Init()
            InitPromise::CreateAndReject(
                NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
 }
 
 nsresult
 MediaCodecDataDecoder::InitDecoder(Surface::Param aSurface)
 {
   mDecoder = CreateDecoder(mMimeType);
+
   if (!mDecoder) {
     INVOKE_CALLBACK(Error,
                     MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__));
     return NS_ERROR_FAILURE;
   }
 
+  // Check if the video codec supports adaptive playback or not.
+  if (aSurface && java::HardwareCodecCapabilityUtils::CheckSupportsAdaptivePlayback(
+                    mDecoder, nsCString(TranslateMimeType(mMimeType)))) {
+      // TODO: may need to find a way to not use hard code to decide the max w/h.
+      mFormat->SetInteger(MediaFormat::KEY_MAX_WIDTH, 1920);
+      mFormat->SetInteger(MediaFormat::KEY_MAX_HEIGHT, 1080);
+  }
+
   MediaCrypto::LocalRef crypto = MediaDrmProxy::GetMediaCrypto(mDrmStubId);
   bool hascrypto = !!crypto;
   LOG("Has(%d) MediaCrypto (%s)", hascrypto, NS_ConvertUTF16toUTF8(mDrmStubId).get());
   nsresult rv;
   NS_ENSURE_SUCCESS(rv = mDecoder->Configure(mFormat, aSurface, crypto, 0), rv);
   NS_ENSURE_SUCCESS(rv = mDecoder->Start(), rv);
 
   NS_ENSURE_SUCCESS(rv = ResetInputBuffers(), rv);
--- a/editor/txmgr/tests/TestTXMgr.cpp
+++ b/editor/txmgr/tests/TestTXMgr.cpp
@@ -250,17 +250,17 @@ int32_t sAggregateBatchTestRedoOrderArr[
         127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140,
         281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294,
         295, 296, 297, 298, 299, 300, 301, 448, 449, 450, 451, 452, 453, 454,
         455, 456, 457, 458 };
 
 class TestTransaction : public nsITransaction
 {
 protected:
-  virtual ~TestTransaction() {}
+  virtual ~TestTransaction() = default;
 
 public:
 
   TestTransaction() {}
 
   NS_DECL_ISUPPORTS
 };
 
@@ -283,80 +283,78 @@ protected:
   int32_t mFlags;
 
 public:
 
   explicit SimpleTransaction(int32_t aFlags=NONE_FLAG)
     : mVal(++sConstructorCount), mFlags(aFlags)
   {}
 
-  virtual ~SimpleTransaction()
-  {
-  }
+  ~SimpleTransaction() override = default;
 
-  NS_IMETHOD DoTransaction()
+  NS_IMETHOD DoTransaction() override
   {
     //
     // Make sure DoTransaction() is called in the order we expect!
     // Notice that we don't check to see if we go past the end of the array.
     // This is done on purpose since we want to crash if the order array is out
     // of date.
     //
     if (sDoOrderArr) {
       EXPECT_EQ(mVal, sDoOrderArr[sDoCount]);
     }
 
     ++sDoCount;
 
     return (mFlags & THROWS_DO_ERROR_FLAG) ? NS_ERROR_FAILURE : NS_OK;
   }
 
-  NS_IMETHOD UndoTransaction()
+  NS_IMETHOD UndoTransaction() override
   {
     //
     // Make sure UndoTransaction() is called in the order we expect!
     // Notice that we don't check to see if we go past the end of the array.
     // This is done on purpose since we want to crash if the order array is out
     // of date.
     //
     if (sUndoOrderArr) {
       EXPECT_EQ(mVal, sUndoOrderArr[sUndoCount]);
     }
 
     ++sUndoCount;
 
     return (mFlags & THROWS_UNDO_ERROR_FLAG) ? NS_ERROR_FAILURE : NS_OK;
   }
 
-  NS_IMETHOD RedoTransaction()
+  NS_IMETHOD RedoTransaction() override
   {
     //
     // Make sure RedoTransaction() is called in the order we expect!
     // Notice that we don't check to see if we go past the end of the array.
     // This is done on purpose since we want to crash if the order array is out
     // of date.
     //
     if (sRedoOrderArr) {
       EXPECT_EQ(mVal, sRedoOrderArr[sRedoCount]);
     }
 
     ++sRedoCount;
 
     return (mFlags & THROWS_REDO_ERROR_FLAG) ? NS_ERROR_FAILURE : NS_OK;
   }
 
-  NS_IMETHOD GetIsTransient(bool *aIsTransient)
+  NS_IMETHOD GetIsTransient(bool *aIsTransient) override
   {
     if (aIsTransient) {
       *aIsTransient = (mFlags & TRANSIENT_FLAG) ? true : false;
     }
     return NS_OK;
   }
 
-  NS_IMETHOD Merge(nsITransaction *aTransaction, bool *aDidMerge)
+  NS_IMETHOD Merge(nsITransaction *aTransaction, bool *aDidMerge) override
   {
     if (aDidMerge) {
       *aDidMerge = (mFlags & MERGE_FLAG) ? true : false;
     }
     return NS_OK;
   }
 };
 
@@ -398,21 +396,19 @@ public:
     mNumber             = 1;
     mFlags              = aFlags & (~ALL_ERROR_FLAGS);
     mErrorFlags         = aFlags & ALL_ERROR_FLAGS;
     mTXMgr              = aTXMgr;
     mMaxLevel           = aMaxLevel;
     mNumChildrenPerNode = aNumChildrenPerNode;
   }
 
-  virtual ~AggregateTransaction()
-  {
-  }
+  ~AggregateTransaction() override = default;
 
-  NS_IMETHOD DoTransaction()
+  NS_IMETHOD DoTransaction() override
   {
     if (mLevel >= mMaxLevel) {
       // Only leaf nodes can throw errors!
       mFlags |= mErrorFlags;
     }
 
     nsresult rv = SimpleTransaction::DoTransaction();
     if (NS_FAILED(rv)) {
@@ -474,17 +470,17 @@ class TestTransactionFactory
 public:
   virtual TestTransaction *create(nsITransactionManager *txmgr, int32_t flags) = 0;
 };
 
 class SimpleTransactionFactory : public TestTransactionFactory
 {
 public:
 
-  TestTransaction *create(nsITransactionManager *txmgr, int32_t flags)
+  TestTransaction *create(nsITransactionManager *txmgr, int32_t flags) override
   {
     return (TestTransaction *)new SimpleTransaction(flags);
   }
 };
 
 class AggregateTransactionFactory : public TestTransactionFactory
 {
 private:
@@ -497,17 +493,17 @@ public:
 
   AggregateTransactionFactory(int32_t aMaxLevel, int32_t aNumChildrenPerNode,
                               int32_t aFixedFlags=NONE_FLAG)
       : mMaxLevel(aMaxLevel), mNumChildrenPerNode(aNumChildrenPerNode),
         mFixedFlags(aFixedFlags)
   {
   }
 
-  virtual TestTransaction *create(nsITransactionManager *txmgr, int32_t flags)
+  TestTransaction *create(nsITransactionManager *txmgr, int32_t flags) override
   {
     return (TestTransaction *)new AggregateTransaction(txmgr, mMaxLevel,
                                                        mNumChildrenPerNode,
                                                        flags | mFixedFlags);
   }
 };
 
 void
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -353,17 +353,17 @@ public:
   CreateForContent(Endpoint<PCompositorBridgeParent>&& aEndpoint);
 
   struct LayerTreeState {
     LayerTreeState();
     ~LayerTreeState();
     RefPtr<Layer> mRoot;
     RefPtr<GeckoContentController> mController;
     APZCTreeManagerParent* mApzcTreeManagerParent;
-    CompositorBridgeParent* mParent;
+    RefPtr<CompositorBridgeParent> mParent;
     LayerManagerComposite* mLayerManager;
     // Pointer to the CrossProcessCompositorBridgeParent. Used by APZCs to share
     // their FrameMetrics with the corresponding child process that holds
     // the PCompositorBridgeChild
     CrossProcessCompositorBridgeParent* mCrossProcessParent;
     TargetConfig mTargetConfig;
     APZTestData mApzTestData;
     LayerTransactionParent* mLayerTree;
--- a/hal/Hal.cpp
+++ b/hal/Hal.cpp
@@ -259,105 +259,105 @@ public:
 
   void BroadcastCachedInformation() {
     this->BroadcastInformation(mInfo);
   }
 
 protected:
   virtual void GetCurrentInformationInternal(InfoType*) = 0;
 
-  virtual void OnNotificationsDisabled() {
+  void OnNotificationsDisabled() override {
     mHasValidCache = false;
   }
 
 private:
   InfoType                mInfo;
   bool                    mHasValidCache;
 };
 
 class BatteryObserversManager : public CachingObserversManager<BatteryInformation>
 {
 protected:
-  void EnableNotifications() {
+  void EnableNotifications() override {
     PROXY_IF_SANDBOXED(EnableBatteryNotifications());
   }
 
-  void DisableNotifications() {
+  void DisableNotifications() override {
     PROXY_IF_SANDBOXED(DisableBatteryNotifications());
   }
 
-  void GetCurrentInformationInternal(BatteryInformation* aInfo) {
+  void GetCurrentInformationInternal(BatteryInformation* aInfo) override {
     PROXY_IF_SANDBOXED(GetCurrentBatteryInformation(aInfo));
   }
 };
 
 static BatteryObserversManager&
 BatteryObservers()
 {
   static BatteryObserversManager sBatteryObservers;
   AssertMainThread();
   return sBatteryObservers;
 }
 
 class NetworkObserversManager : public CachingObserversManager<NetworkInformation>
 {
 protected:
-  void EnableNotifications() {
+  void EnableNotifications() override {
     PROXY_IF_SANDBOXED(EnableNetworkNotifications());
   }
 
-  void DisableNotifications() {
+  void DisableNotifications() override {
     PROXY_IF_SANDBOXED(DisableNetworkNotifications());
   }
 
-  void GetCurrentInformationInternal(NetworkInformation* aInfo) {
+  void GetCurrentInformationInternal(NetworkInformation* aInfo) override {
     PROXY_IF_SANDBOXED(GetCurrentNetworkInformation(aInfo));
   }
 };
 
 static NetworkObserversManager&
 NetworkObservers()
 {
   static NetworkObserversManager sNetworkObservers;
   AssertMainThread();
   return sNetworkObservers;
 }
 
 class WakeLockObserversManager : public ObserversManager<WakeLockInformation>
 {
 protected:
-  void EnableNotifications() {
+  void EnableNotifications() override {
     PROXY_IF_SANDBOXED(EnableWakeLockNotifications());
   }
 
-  void DisableNotifications() {
+  void DisableNotifications() override {
     PROXY_IF_SANDBOXED(DisableWakeLockNotifications());
   }
 };
 
 static WakeLockObserversManager&
 WakeLockObservers()
 {
   static WakeLockObserversManager sWakeLockObservers;
   AssertMainThread();
   return sWakeLockObservers;
 }
 
 class ScreenConfigurationObserversManager : public CachingObserversManager<ScreenConfiguration>
 {
 protected:
-  void EnableNotifications() {
+  void EnableNotifications() override {
     PROXY_IF_SANDBOXED(EnableScreenConfigurationNotifications());
   }
 
-  void DisableNotifications() {
+  void DisableNotifications() override {
     PROXY_IF_SANDBOXED(DisableScreenConfigurationNotifications());
   }
 
-  void GetCurrentInformationInternal(ScreenConfiguration* aInfo) {
+  void GetCurrentInformationInternal(ScreenConfiguration* aInfo) override {
     PROXY_IF_SANDBOXED(GetCurrentScreenConfiguration(aInfo));
   }
 };
 
 static ScreenConfigurationObserversManager&
 ScreenConfigurationObservers()
 {
   AssertMainThread();
@@ -444,21 +444,21 @@ void SetScreenBrightness(double aBrightn
 {
   AssertMainThread();
   PROXY_IF_SANDBOXED(SetScreenBrightness(clamped(aBrightness, 0.0, 1.0)));
 }
 
 class SystemClockChangeObserversManager : public ObserversManager<int64_t>
 {
 protected:
-  void EnableNotifications() {
+  void EnableNotifications() override {
     PROXY_IF_SANDBOXED(EnableSystemClockChangeNotifications());
   }
 
-  void DisableNotifications() {
+  void DisableNotifications() override {
     PROXY_IF_SANDBOXED(DisableSystemClockChangeNotifications());
   }
 };
 
 static SystemClockChangeObserversManager&
 SystemClockChangeObservers()
 {
   static SystemClockChangeObserversManager sSystemClockChangeObservers;
@@ -484,21 +484,21 @@ void
 NotifySystemClockChange(const int64_t& aClockDeltaMS)
 {
   SystemClockChangeObservers().BroadcastInformation(aClockDeltaMS);
 }
 
 class SystemTimezoneChangeObserversManager : public ObserversManager<SystemTimezoneChangeInformation>
 {
 protected:
-  void EnableNotifications() {
+  void EnableNotifications() override {
     PROXY_IF_SANDBOXED(EnableSystemTimezoneChangeNotifications());
   }
 
-  void DisableNotifications() {
+  void DisableNotifications() override {
     PROXY_IF_SANDBOXED(DisableSystemTimezoneChangeNotifications());
   }
 };
 
 static SystemTimezoneChangeObserversManager&
 SystemTimezoneChangeObservers()
 {
   static SystemTimezoneChangeObserversManager sSystemTimezoneChangeObservers;
--- a/ipc/ipdl/Makefile.in
+++ b/ipc/ipdl/Makefile.in
@@ -10,18 +10,22 @@ ifdef COMPILE_ENVIRONMENT
 # This file is generated by the moz.build backend.
 include ipdlsrcs.mk
 
 include $(topsrcdir)/config/rules.mk
 
 
 # NB: the IPDL compiler manages .ipdl-->.h/.cpp dependencies itself,
 # which is why we don't have explicit .h/.cpp targets here
-export:: $(ALL_IPDLSRCS)
+ipdl: $(ALL_IPDLSRCS)
 	$(PYTHON) $(topsrcdir)/config/pythonpath.py \
 	  $(PLY_INCLUDE) \
 	  $(srcdir)/ipdl.py \
 	  --outheaders-dir=_ipdlheaders \
 	  --outcpp-dir=. \
 	  $(IPDLDIRS:%=-I%) \
 	  $^
+
+.PHONY: ipdl
+
+export:: ipdl
 endif
 
--- a/ipc/ipdl/ipdl/type.py
+++ b/ipc/ipdl/ipdl/type.py
@@ -418,17 +418,17 @@ class StructType(_CompoundType):
         _CompoundType.__init__(self)
         self.qname = qname
         self.fields = fields            # [ Type ]
 
     def isStruct(self):   return True
     def itercomponents(self):
         for f in self.fields:
             yield f
-    
+
     def name(self): return self.qname.baseid
     def fullname(self): return str(self.qname)
 
 class UnionType(_CompoundType):
     def __init__(self, qname, components):
         _CompoundType.__init__(self)
         self.qname = qname
         self.components = components    # [ Type ]
@@ -547,17 +547,17 @@ def errormsg(loc, fmt, *args):
 
 ##--------------------
 class SymbolTable:
     def __init__(self, errors):
         self.errors = errors
         self.scopes = [ { } ]   # stack({})
         self.globalScope = self.scopes[0]
         self.currentScope = self.globalScope
-    
+
     def enterScope(self, node):
         assert (isinstance(self.scopes[0], dict)
                 and self.globalScope is self.scopes[0])
         assert (isinstance(self.currentScope, dict))
 
         if not hasattr(node, 'symtab'):
             node.symtab = { }
 
@@ -838,17 +838,17 @@ class GatherDecls(TcheckVisitor):
         self.symtab.exitScope(sd)
 
     def visitUnionDecl(self, ud):
         utype = ud.decl.type
 
         # If we've already processed this union, don't do it again.
         if len(utype.components):
             return
-        
+
         for c in ud.components:
             cdecl = self.symtab.lookup(str(c))
             if cdecl is None:
                 self.error(c.loc, "unknown component type `%s' of union `%s'",
                            str(c), ud.name)
                 continue
             utype.components.append(self._canonicalType(cdecl.type, c))
 
@@ -925,17 +925,17 @@ class GatherDecls(TcheckVisitor):
 
             if not (ctordecl and ctordecl.type.isCtor()):
                 self.error(
                     managed.loc,
                     "constructor declaration required for managed protocol `%s' (managed by protocol `%s')",
                     mgdname, p.name)
 
         p.states = { }
-        
+
         if len(p.transitionStmts):
             p.startStates = [ ts for ts in p.transitionStmts
                               if ts.state.start ]
             if 0 == len(p.startStates):
                 p.startStates = [ p.transitionStmts[0] ]
 
         # declare implicit "any", "dead", and "dying" states
         self.declare(loc=State.ANY.loc,
@@ -961,30 +961,30 @@ class GatherDecls(TcheckVisitor):
             self.seentriggers = set()
             trans.accept(self)
 
         if not (p.decl.type.stateless
                 or (p.decl.type.isToplevel()
                     and None is self.symtab.lookup(_DELETE_MSG))):
             # add a special state |state DEAD: null goto DEAD;|
             deadtrans = TransitionStmt.makeNullStmt(State.DEAD)
-            p.states[State.DEAD] = deadtrans           
+            p.states[State.DEAD] = deadtrans
             if p.decl.type.hasReentrantDelete:
                 dyingtrans = TransitionStmt.makeNullStmt(State.DYING)
                 p.states[State.DYING] = dyingtrans
 
         # visit the message decls once more and resolve the state names
         # attached to actor params and returns
         def resolvestate(loc, actortype):
             assert actortype.isIPDL() and actortype.isActor()
 
             # already resolved this guy's state
             if isinstance(actortype.state, Decl):
                 return
-            
+
             if actortype.state is None:
                 # we thought this was a C++ type until type checking,
                 # when we realized it was an IPDL actor type.  But
                 # that means that the actor wasn't specified to be in
                 # any particular state
                 actortype.state = State.ANY
 
             statename = actortype.state.name
@@ -1177,17 +1177,17 @@ class GatherDecls(TcheckVisitor):
         mname = t.msg
         if t in self.seentriggers:
             self.error(loc, "trigger `%s' appears multiple times", t.msg)
         self.seentriggers.add(t)
 
         mdecl = self.symtab.lookup(mname)
         if mdecl is not None and mdecl.type.isIPDL() and mdecl.type.isProtocol():
             mdecl = self.symtab.lookup(mname +'Constructor')
-        
+
         if mdecl is None:
             self.error(loc, "message `%s' has not been declared", mname)
         elif not mdecl.type.isMessage():
             self.error(
                 loc,
                 "`%s' should have message type, but instead has type `%s'",
                 mname, mdecl.type.typename())
         else:
@@ -1242,17 +1242,17 @@ def checkcycles(p, stack=None):
 
     if stack is None:
         stack = []
 
     for cp in p.manages:
         # special case for self-managed protocols
         if cp is p:
             continue
-        
+
         if cp in stack:
             return [stack + [p, cp]]
         cycles += checkcycles(cp, stack + [p])
 
     return cycles
 
 def formatcycles(cycles):
     r = []
@@ -1323,17 +1323,17 @@ class CheckTypes(TcheckVisitor):
     def visitUnionDecl(self, ud):
         if not fullyDefined(ud.decl.type):
             self.error(ud.decl.loc,
                        "union `%s' is only partially defined", ud.name)
 
 
     def visitProtocol(self, p):
         self.ptype = p.decl.type
-        
+
         # check that we require no more "power" than our manager protocols
         ptype, pname = p.decl.type, p.decl.shortname
 
         if len(p.spawnsStmts) and not ptype.isToplevel():
             self.error(p.decl.loc,
                        "protocol `%s' is not top-level and so cannot declare |spawns|",
                        pname)
 
@@ -1364,17 +1364,17 @@ class CheckTypes(TcheckVisitor):
                     p.decl.loc,
                    "managed protocol `%s' requires a `delete()' message to be declared",
                     p.name)
         else:
             cycles = checkcycles(p.decl.type)
             if cycles:
                 self.error(
                     p.decl.loc,
-                    "cycle(s) detected in manager/manages heirarchy: %s",
+                    "cycle(s) detected in manager/manages hierarchy: %s",
                     formatcycles(cycles))
 
         if 1 == len(ptype.managers) and ptype is ptype.manager():
             self.error(
                 p.decl.loc,
                 "top-level protocol `%s' cannot manage itself",
                 p.name)
 
@@ -1449,17 +1449,17 @@ class CheckTypes(TcheckVisitor):
             self.error(
                 loc,
                 "|manages| declaration in protocol `%s' does not match any |manager| declaration in protocol `%s'",
                 pname, mgsname)
 
 
     def visitManager(self, mgr):
         # FIXME/bug 541126: check that the protocol graph is acyclic
-        
+
         pdecl = mgr.of.decl
         ptype, pname = pdecl.type, pdecl.shortname
 
         mgrdecl = mgr.decl
         mgrtype, mgrname = mgrdecl.type, mgrdecl.shortname
 
         # we added this information; sanity check it
         assert ptype.isManagedBy(mgrtype)
@@ -1518,29 +1518,29 @@ class CheckTypes(TcheckVisitor):
             self.error(
                 loc,
                 "message `%s' in protocol `%s' requests compression but is not async or is special (ctor or dtor)",
                 mname[:-len('constructor')], pname)
 
         if mtype.isCtor() and not ptype.isManagerOf(mtype.constructedType()):
             self.error(
                 loc,
-                "ctor for protocol `%s', which is not managed by protocol `%s'", 
+                "ctor for protocol `%s', which is not managed by protocol `%s'",
                 mname[:-len('constructor')], pname)
 
 
     def visitTransition(self, t):
         _YNC = [ ASYNC, SYNC ]
 
         loc = t.loc
         impliedDirection, impliedSems = {
             SEND: [ OUT, _YNC ], RECV: [ IN, _YNC ],
             CALL: [ OUT, INTR ],  ANSWER: [ IN, INTR ],
          } [t.trigger]
-        
+
         if (OUT is impliedDirection and t.msg.type.isIn()
             or IN is impliedDirection and t.msg.type.isOut()
             or _YNC is impliedSems and t.msg.type.isInterrupt()
             or INTR is impliedSems and (not t.msg.type.isInterrupt())):
             mtype = t.msg.type
 
             self.error(
                 loc, "%s %s message `%s' is not `%s'd",
@@ -2142,17 +2142,17 @@ direction as trigger |t|'''
             return '[error]'
 
         T1 = stateName(T1)
         T2 = stateName(T2)
         U1 = stateName(U1)
         U2 = stateName(U2)
 
         return T1, M1.msg.progname, U1, T2, M2.msg.progname, U2
-        
+
 
     def reportRaceError(self, loc, S, t1Seq, t2Seq):
         T1, M1, U1, T2, M2, U2 = self._normalizeTransitionSequences(t1Seq, t2Seq)
         self.error(
             loc,
 """in protocol `%(P)s', the sequence of events
      parent:    +--`send %(M1)s'-->( state `%(T1)s' )--`recv %(M2)s'-->( state %(U1)s )
                /
--- a/ipc/ipdl/test/ipdl/IPDLCompile.py
+++ b/ipc/ipdl/test/ipdl/IPDLCompile.py
@@ -18,17 +18,17 @@ class IPDLCompile:
 
         tmpoutdir = tempfile.mkdtemp(prefix='ipdl_unit_test')
 
         try:
             self.argv.extend([
                 '-d', tmpoutdir,
                 self.specfilename
             ])
-            
+
             proc = subprocess.Popen(args=self.argv,
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE)
             self.stdout, self.stderr = proc.communicate()
 
             self.returncode = proc.returncode
             assert self.returncode is not None
 
@@ -64,12 +64,12 @@ exception being raised.'''
         assert self.completed()
 
         return None is not re.search(r'Traceback (most recent call last):',
                                      self.stderr)
 
     def ok(self):
         '''Return True iff compiling self.specstring was successful.'''
         assert self.completed()
-        
+
         return (not self.exception()
                 and not self.error()
                 and (0 == self.returncode))
--- a/ipc/ipdl/test/ipdl/runtests.py
+++ b/ipc/ipdl/test/ipdl/runtests.py
@@ -17,29 +17,29 @@ class IPDLTestCase(unittest.TestCase):
     def mkFailMsg(self):
         return '''
 ### Command: %s
 ### stderr:
 %s'''% (' '.join(self.compile.argv), self.compile.stderr)
 
     def shortDescription(self):
         return '%s test of "%s"'% (self.__class__.__name__, self.filename)
-    
+
 
 class OkTestCase(IPDLTestCase):
     '''An invocation of the IPDL compiler on a valid specification.
 The IPDL compiler should not produce errors or exceptions.'''
 
     def __init__(self, ipdlargv, filename):
         IPDLTestCase.__init__(self, ipdlargv, filename)
 
     def checkPassed(self):
         self.assertTrue(self.compile.ok(), self.mkFailMsg())
 
-  
+
 class ErrorTestCase(IPDLTestCase):
     '''An invocation of the IPDL compiler on an *invalid* specification.
 The IPDL compiler *should* produce errors but not exceptions.'''
 
     def __init__(self, ipdlargv, filename):
         IPDLTestCase.__init__(self, ipdlargv, filename)
 
     def checkPassed(self):
--- a/layout/generic/nsPlaceholderFrame.cpp
+++ b/layout/generic/nsPlaceholderFrame.cpp
@@ -201,20 +201,36 @@ nsPlaceholderFrame::GetParentStyleContex
     nsStyleContext* sc =
       PresContext()->FrameManager()->GetDisplayContentsStyleFor(parentContent);
     if (sc) {
       *aProviderFrame = nullptr;
       return sc;
     }
   }
 
+  nsIFrame* parentFrame = GetParent();
+  // Placeholder of backdrop frame is a child of the corresponding top
+  // layer frame, and its style context inherits from that frame. In
+  // case of table, the top layer frame is the table wrapper frame.
+  // However, it will be skipped in CorrectStyleParentFrame below, so
+  // we need to handle it specially here.
+  if ((GetStateBits() & PLACEHOLDER_FOR_TOPLAYER) &&
+      parentFrame->GetType() == nsGkAtoms::tableWrapperFrame) {
+    MOZ_ASSERT(mOutOfFlowFrame->GetType() == nsGkAtoms::backdropFrame,
+               "Only placeholder of backdrop frame can be put inside "
+               "a table wrapper frame");
+    *aProviderFrame = parentFrame;
+    return parentFrame->StyleContext();
+  }
+
   // Lie about our pseudo so we can step out of all anon boxes and
   // pseudo-elements.  The other option would be to reimplement the
   // {ib} split gunk here.
-  *aProviderFrame = CorrectStyleParentFrame(GetParent(), nsGkAtoms::placeholderFrame);
+  *aProviderFrame = CorrectStyleParentFrame(parentFrame,
+                                            nsGkAtoms::placeholderFrame);
   return *aProviderFrame ? (*aProviderFrame)->StyleContext() : nullptr;
 }
 
 
 #ifdef DEBUG
 static void
 PaintDebugPlaceholder(nsIFrame* aFrame, DrawTarget* aDrawTarget,
                       const nsRect& aDirtyRect, nsPoint aPt)
--- a/layout/style/res/ua.css
+++ b/layout/style/res/ua.css
@@ -17,17 +17,21 @@
 }
 
 *|*::-moz-inline-table {
   display: inline-table !important;
   box-sizing: border-box; /* XXX do we really want this? */
 }
 
 *|*::-moz-table-wrapper {
+  /* The inherited properties here need to be safe to have on both the
+   * table and the table wrapper, generally because code ignores them
+   * for the table. */
   display: inherit !important; /* table or inline-table */
+  -moz-top-layer: inherit !important;
   margin: inherit ! important;
   padding: 0 ! important;
   border: none ! important;
   float: inherit;
   clear: inherit;
   position: inherit;
   top: inherit;
   right: inherit;
--- a/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconDownloader.java
+++ b/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconDownloader.java
@@ -2,16 +2,18 @@
  * 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.icons.loader;
 
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.icons.decoders.FaviconDecoder;
 import org.mozilla.gecko.icons.decoders.LoadFaviconResult;
 import org.mozilla.gecko.icons.IconRequest;
 import org.mozilla.gecko.icons.IconResponse;
@@ -77,129 +79,140 @@ public class IconDownloader implements I
     }
 
     /**
      * Download the Favicon from the given URL and pass it to the decoder function.
      *
      * @param targetFaviconURL URL of the favicon to download.
      * @return A LoadFaviconResult containing the bitmap(s) extracted from the downloaded file, or
      *         null if no or corrupt data was received.
-     * @throws IOException If attempts to fully read the stream result in such an exception, such as
-     *                     in the event of a transient connection failure.
-     * @throws URISyntaxException If the underlying call to tryDownload retries and raises such an
-     *                            exception trying a fallback URL.
      */
     @VisibleForTesting
-    LoadFaviconResult downloadAndDecodeImage(Context context, String targetFaviconURL) throws IOException, URISyntaxException {
+    @Nullable
+    LoadFaviconResult downloadAndDecodeImage(Context context, String targetFaviconURL) {
         // Try the URL we were given.
         final HttpURLConnection connection = tryDownload(targetFaviconURL);
         if (connection == null) {
             return null;
         }
 
         InputStream stream = null;
 
         // Decode the image from the fetched response.
         try {
             stream = connection.getInputStream();
             return decodeImageFromResponse(context, stream, connection.getHeaderFieldInt("Content-Length", -1));
+        } catch (IOException e) {
+            Log.d(LOGTAG, "IOException while reading and decoding ixon", e);
+            return null;
         } finally {
             // Close the stream and free related resources.
             IOUtils.safeStreamClose(stream);
             connection.disconnect();
         }
     }
 
     /**
      * Helper method for trying the download request to grab a Favicon.
      *
      * @param faviconURI URL of Favicon to try and download
      * @return The HttpResponse containing the downloaded Favicon if successful, null otherwise.
      */
-    private HttpURLConnection tryDownload(String faviconURI) throws URISyntaxException, IOException {
+    @Nullable
+    private HttpURLConnection tryDownload(String faviconURI) {
         final HashSet<String> visitedLinkSet = new HashSet<>();
         visitedLinkSet.add(faviconURI);
         return tryDownloadRecurse(faviconURI, visitedLinkSet);
     }
 
     /**
      * Try to download from the favicon URL and recursively follow redirects.
      */
-    private HttpURLConnection tryDownloadRecurse(String faviconURI, HashSet<String> visited) throws URISyntaxException, IOException {
+    @Nullable
+    private HttpURLConnection tryDownloadRecurse(String faviconURI, HashSet<String> visited) {
         if (visited.size() == MAX_REDIRECTS_TO_FOLLOW) {
             return null;
         }
 
-        final HttpURLConnection connection = connectTo(faviconURI);
+        HttpURLConnection connection = null;
+
+        try {
+            connection = connectTo(faviconURI);
 
-        // Was the response a failure?
-        final int status = connection.getResponseCode();
+            // Was the response a failure?
+            final int status = connection.getResponseCode();
+
+            // Handle HTTP status codes requesting a redirect.
+            if (status >= 300 && status < 400) {
+                final String newURI = connection.getHeaderField("Location");
 
-        // Handle HTTP status codes requesting a redirect.
-        if (status >= 300 && status < 400) {
-            final String newURI = connection.getHeaderField("Location");
+                // Handle mad web servers.
+                try {
+                    if (newURI == null || newURI.equals(faviconURI)) {
+                        return null;
+                    }
 
-            // Handle mad web servers.
-            try {
-                if (newURI == null || newURI.equals(faviconURI)) {
-                    return null;
+                    if (visited.contains(newURI)) {
+                        // Already been redirected here - abort.
+                        return null;
+                    }
+
+                    visited.add(newURI);
+                } finally {
+                    connection.disconnect();
                 }
 
-                if (visited.contains(newURI)) {
-                    // Already been redirected here - abort.
-                    return null;
-                }
+                return tryDownloadRecurse(newURI, visited);
+            }
 
-                visited.add(newURI);
-            } finally {
+            if (status >= 400) {
+                // Client or Server error. Let's not retry loading from this URL again for some time.
+                FailureCache.get().rememberFailure(faviconURI);
+
+                connection.disconnect();
+                return null;
+            }
+        } catch (IOException | URISyntaxException e) {
+            if (connection != null) {
                 connection.disconnect();
             }
-
-            return tryDownloadRecurse(newURI, visited);
-        }
-
-        if (status >= 400) {
-            // Client or Server error. Let's not retry loading from this URL again for some time.
-            FailureCache.get().rememberFailure(faviconURI);
-
-            connection.disconnect();
             return null;
         }
 
         return connection;
     }
 
     @VisibleForTesting
-    HttpURLConnection connectTo(String faviconURI) throws URISyntaxException, IOException {
+    @NonNull
+    HttpURLConnection connectTo(String uri) throws URISyntaxException, IOException {
         final HttpURLConnection connection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(
-                new URI(faviconURI));
+                new URI(uri));
 
         connection.setRequestProperty("User-Agent", GeckoAppShell.getGeckoInterface().getDefaultUAString());
 
         // We implemented or own way of following redirects back when this code was using HttpClient.
         // Nowadays we should let HttpUrlConnection do the work - assuming that it doesn't follow
         // redirects in loops forever.
         connection.setInstanceFollowRedirects(false);
 
-        connection.connect();
-
         return connection;
     }
 
     /**
      * Copies the favicon stream to a buffer and decodes downloaded content into bitmaps using the
      * FaviconDecoder.
      *
      * @param stream to decode
      * @param contentLength as reported by the server (or -1)
      * @return A LoadFaviconResult containing the bitmap(s) extracted from the downloaded file, or
      *         null if no or corrupt data were received.
      * @throws IOException If attempts to fully read the stream result in such an exception, such as
      *                     in the event of a transient connection failure.
      */
+    @Nullable
     private LoadFaviconResult decodeImageFromResponse(Context context, InputStream stream, int contentLength) throws IOException {
         // This may not be provided, but if it is, it's useful.
         final int bufferSize;
         if (contentLength > 0) {
             // The size was reported and sane, so let's use that.
             // Integer overflow should not be a problem for Favicon sizes...
             bufferSize = contentLength + 1;
         } else {
--- a/mobile/android/base/java/org/mozilla/gecko/media/JellyBeanAsyncCodec.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/JellyBeanAsyncCodec.java
@@ -1,14 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.media;
 
+import org.mozilla.gecko.util.HardwareCodecCapabilityUtils;
+
 import android.media.MediaCodec;
 import android.media.MediaCrypto;
 import android.media.MediaFormat;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.util.Log;
@@ -293,16 +295,25 @@ final class JellyBeanAsyncCodec implemen
         mCallbackSender = new CallbackSender(looper, callbacks);
         if (DEBUG) Log.d(LOGTAG, "setCallbacks(): sender=" + mCallbackSender);
     }
 
     @Override
     public void configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags) {
         assertCallbacks();
 
+        // Video decoder should config with adaptive playback capability.
+        if (surface != null) {
+            if (HardwareCodecCapabilityUtils.checkSupportsAdaptivePlayback(
+                    mCodec, format.getString(MediaFormat.KEY_MIME))) {
+                // TODO: may need to find a way to not use hard code to decide the max w/h.
+                format.setInteger(MediaFormat.KEY_MAX_WIDTH, 1920);
+                format.setInteger(MediaFormat.KEY_MAX_HEIGHT, 1080);
+            }
+        }
         mCodec.configure(format, surface, crypto, flags);
     }
 
     private void assertCallbacks() {
         if (mCallbackSender == null) {
             throw new IllegalStateException(LOGTAG + ": callback must be supplied with setCallbacks().");
         }
     }
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsGridLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsGridLayout.java
@@ -1,712 +1,134 @@
 /* -*- 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 org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.tabs.TabsPanel.TabsLayout;
-import org.mozilla.gecko.widget.themed.ThemedRelativeLayout;
+import org.mozilla.gecko.widget.GridSpacingDecoration;
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.os.Build;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.helper.ItemTouchHelper;
 import android.util.AttributeSet;
-import android.util.SparseArray;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.AbsListView;
-import android.widget.AdapterView;
-import android.widget.Button;
-import android.widget.GridView;
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.animation.ValueAnimator;
+import android.util.Log;
 
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A tabs layout implementation for the tablet redesign (bug 1014156) and later ported to mobile (bug 1193745).
- */
-
-class TabsGridLayout extends GridView
-                     implements TabsLayout,
-                                Tabs.OnTabsChangedListener {
-
+public class TabsGridLayout extends TabsLayout {
     private static final String LOGTAG = "Gecko" + TabsGridLayout.class.getSimpleName();
 
-    public static final int ANIM_DELAY_MULTIPLE_MS = 20;
-    private static final int ANIM_TIME_MS = 200;
-    private static final DecelerateInterpolator ANIM_INTERPOLATOR = new DecelerateInterpolator();
-
-    private final SparseArray<PointF> tabLocations = new SparseArray<PointF>();
-    private final boolean isPrivate;
-    private final TabsLayoutAdapter tabsAdapter;
-    private final int columnWidth;
-    private TabsPanel tabsPanel;
-    private int lastSelectedTabId;
-
-    public TabsGridLayout(final Context context, final AttributeSet attrs) {
-        super(context, attrs, R.attr.tabGridLayoutViewStyle);
-
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsLayout);
-        isPrivate = (a.getInt(R.styleable.TabsLayout_tabs, 0x0) == 1);
-        a.recycle();
-
-        tabsAdapter = new TabsGridLayoutAdapter(context);
-        setAdapter(tabsAdapter);
-
-        setRecyclerListener(new RecyclerListener() {
-            @Override
-            public void onMovedToScrapHeap(View view) {
-                TabsLayoutItemView item = (TabsLayoutItemView) view;
-                item.setThumbnail(null);
-            }
-        });
-
-        // The clipToPadding setting in the styles.xml doesn't seem to be working (bug 1101784)
-        // so lets set it manually in code for the moment as it's needed for the padding animation
-        setClipToPadding(false);
+    private GridSpacingDecoration spacingDecoration;
+    private final int desiredItemWidth;
+    private final int desiredHorizontalItemSpacing;
+    private final int minHorizontalItemSpacing;
+    private final int verticalItemPadding;
 
-        setVerticalFadingEdgeEnabled(false);
-
-        final Resources resources = getResources();
-        columnWidth = resources.getDimensionPixelSize(R.dimen.tab_panel_column_width);
-
-        final int padding = resources.getDimensionPixelSize(R.dimen.tab_panel_grid_padding);
-        final int paddingTop = resources.getDimensionPixelSize(R.dimen.tab_panel_grid_padding_top);
-
-        // Lets set double the top padding on the bottom so that the last row shows up properly!
-        // Your demise, GridView, cannot come fast enough.
-        final int paddingBottom = paddingTop * 2;
-
-        setPadding(padding, paddingTop, padding, paddingBottom);
+    public TabsGridLayout(Context context, AttributeSet attrs) {
+        super(context, attrs, R.layout.tabs_layout_item_view);
 
-        setOnItemClickListener(new OnItemClickListener() {
-            @Override
-            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-                final TabsLayoutItemView tabView = (TabsLayoutItemView) view;
-                final int tabId = tabView.getTabId();
-                final Tab tab = Tabs.getInstance().selectTab(tabId);
-                if (tab == null) {
-                    return;
-                }
-                autoHidePanel();
-                Tabs.getInstance().notifyListeners(tab, Tabs.TabEvents.OPENED_FROM_TABS_TRAY);
-            }
-        });
+        final Resources resources = context.getResources();
 
-        TabSwipeGestureListener mSwipeListener = new TabSwipeGestureListener();
-        setOnTouchListener(mSwipeListener);
-        setOnScrollListener(mSwipeListener.makeScrollListener());
-    }
+        // Actual span count is updated in onSizeChanged.
+        setLayoutManager(new GridLayoutManager(context, 1));
 
-    private void populateTabLocations(final Tab removedTab) {
-        tabLocations.clear();
-
-        final int firstPosition = getFirstVisiblePosition();
-        final int lastPosition = getLastVisiblePosition();
-        final int numberOfColumns = getNumColumns();
-        final int childCount = getChildCount();
-        final int removedPosition = tabsAdapter.getPositionForTab(removedTab);
-
-        for (int x = 1, i = (removedPosition - firstPosition) + 1; i < childCount; i++, x++) {
-            final View child = getChildAt(i);
-            if (child != null) {
-                // Reset the transformations here in case the user is swiping tabs away fast and they swipe a tab
-                // before the last animation has finished (bug 1179195).
-                resetTransforms(child);
-
-                tabLocations.append(x, new PointF(child.getX(), child.getY()));
-            }
-        }
-
-        final boolean firstChildOffScreen = ((firstPosition > 0) || getChildAt(0).getY() < 0);
-        final boolean lastChildVisible = (lastPosition - childCount == firstPosition - 1);
-        final boolean oneItemOnLastRow = (lastPosition % numberOfColumns == 0);
-        if (firstChildOffScreen && lastChildVisible && oneItemOnLastRow) {
-            // We need to set the view's bottom padding to prevent a sudden jump as the
-            // last item in the row is being removed. We then need to remove the padding
-            // via a sweet animation
-
-            final int removedHeight = getChildAt(0).getMeasuredHeight();
-            final int verticalSpacing =
-                    getResources().getDimensionPixelOffset(R.dimen.tab_panel_grid_vspacing);
+        desiredItemWidth = resources.getDimensionPixelSize(R.dimen.tab_panel_item_width);
+        desiredHorizontalItemSpacing = resources.getDimensionPixelSize(R.dimen.tab_panel_grid_ideal_item_hspacing);
+        minHorizontalItemSpacing = resources.getDimensionPixelOffset(R.dimen.tab_panel_grid_min_item_hspacing);
+        verticalItemPadding = resources.getDimensionPixelSize(R.dimen.tab_panel_grid_item_vpadding);
+        final int viewPaddingHorizontal = resources.getDimensionPixelSize(R.dimen.tab_panel_grid_hpadding);
+        final int viewPaddingVertical = resources.getDimensionPixelSize(R.dimen.tab_panel_grid_vpadding);
 
-            ValueAnimator paddingAnimator = ValueAnimator.ofInt(getPaddingBottom() + removedHeight + verticalSpacing, getPaddingBottom());
-            paddingAnimator.setDuration(ANIM_TIME_MS * 2);
-
-            paddingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+        setPadding(viewPaddingHorizontal, viewPaddingVertical, viewPaddingHorizontal, viewPaddingVertical);
+        setClipToPadding(false);
+        setScrollBarStyle(SCROLLBARS_OUTSIDE_OVERLAY);
 
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (Integer) animation.getAnimatedValue());
-                }
-            });
-            paddingAnimator.start();
-        }
-    }
+        setItemAnimator(new TabsGridLayoutAnimator());
 
-    @Override
-    public void setTabsPanel(TabsPanel panel) {
-        tabsPanel = panel;
-    }
-
-    @Override
-    public void show() {
-        setVisibility(View.VISIBLE);
-        Tabs.getInstance().refreshThumbnails();
-        Tabs.registerOnTabsChangedListener(this);
-        refreshTabsData();
-
-        final Tab currentlySelectedTab = Tabs.getInstance().getSelectedTab();
-        final int position =  currentlySelectedTab != null ? tabsAdapter.getPositionForTab(currentlySelectedTab) : -1;
-        if (position != -1) {
-            final boolean selectionChanged = lastSelectedTabId != currentlySelectedTab.getId();
-            final boolean positionIsVisible = position >= getFirstVisiblePosition() && position <= getLastVisiblePosition();
-
-            if (selectionChanged || !positionIsVisible) {
-                smoothScrollToPosition(position);
+        // A TouchHelper handler for swipe to close.
+        final TabsTouchHelperCallback callback = new TabsTouchHelperCallback(this) {
+            @Override
+            protected float alphaForItemSwipeDx(float dX, int distanceToAlphaMin) {
+                return 1f - 2f * Math.abs(dX) / distanceToAlphaMin;
             }
-        }
-    }
-
-    @Override
-    public void hide() {
-        lastSelectedTabId = Tabs.getInstance().getSelectedTab().getId();
-        setVisibility(View.GONE);
-        Tabs.unregisterOnTabsChangedListener(this);
-        GeckoAppShell.notifyObservers("Tab:Screenshot:Cancel", "");
-        tabsAdapter.clear();
-    }
-
-    @Override
-    public boolean shouldExpand() {
-        return true;
-    }
-
-    private void autoHidePanel() {
-        tabsPanel.autoHidePanel();
-    }
-
-    @Override
-    public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
-        switch (msg) {
-            case ADDED:
-                // Refresh only if panel is shown. show() will call refreshTabsData() later again.
-                if (tabsPanel.isShown()) {
-                    // Refresh the list to make sure the new tab is added in the right position.
-                    refreshTabsData();
-                }
-                break;
-
-            case CLOSED:
-
-                // This is limited to >= ICS as animations on GB devices are generally pants
-                if (Build.VERSION.SDK_INT >= 11 && tabsAdapter.getCount() > 0) {
-                    animateRemoveTab(tab);
-                }
-
-                final Tabs tabsInstance = Tabs.getInstance();
-
-                if (tabsAdapter.removeTab(tab)) {
-                    if (tab.isPrivate() == isPrivate && tabsAdapter.getCount() > 0) {
-                        int selected = tabsAdapter.getPositionForTab(tabsInstance.getSelectedTab());
-                        updateSelectedStyle(selected);
-                    }
-                    if (!tab.isPrivate()) {
-                        // Make sure we always have at least one normal tab
-                        final Iterable<Tab> tabs = tabsInstance.getTabsInOrder();
-                        boolean removedTabIsLastNormalTab = true;
-                        for (Tab singleTab : tabs) {
-                            if (!singleTab.isPrivate()) {
-                                removedTabIsLastNormalTab = false;
-                                break;
-                            }
-                        }
-                        if (removedTabIsLastNormalTab) {
-                            tabsInstance.addTab();
-                        }
-                    }
-                }
-                break;
-
-            case SELECTED:
-                // Update the selected position, then fall through...
-                updateSelectedPosition();
-            case UNSELECTED:
-                // We just need to update the style for the unselected tab...
-            case THUMBNAIL:
-            case TITLE:
-            case RECORDING_CHANGE:
-            case AUDIO_PLAYING_CHANGE:
-                View view = getChildAt(tabsAdapter.getPositionForTab(tab) - getFirstVisiblePosition());
-                if (view == null)
-                    return;
-
-                ((TabsLayoutItemView) view).assignValues(tab);
-                break;
-        }
-    }
-
-    // Updates the selected position in the list so that it will be scrolled to the right place.
-    private void updateSelectedPosition() {
-        int selected = tabsAdapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
-        updateSelectedStyle(selected);
-
-        if (selected != -1) {
-            setSelection(selected);
-        }
-    }
-
-    /**
-     * Updates the selected/unselected style for the tabs.
-     *
-     * @param selected position of the selected tab
-     */
-    private void updateSelectedStyle(final int selected) {
-        post(new Runnable() {
-            @Override
-            public void run() {
-                final int displayCount = tabsAdapter.getCount();
-
-                for (int i = 0; i < displayCount; i++) {
-                    final Tab tab = tabsAdapter.getItem(i);
-                    final boolean checked = displayCount == 1 || i == selected;
-                    final View tabView = getViewForTab(tab);
-                    if (tabView != null) {
-                        ((TabsLayoutItemView) tabView).setChecked(checked);
-                    }
-                    // setItemChecked doesn't exist until API 11, despite what the API docs say!
-                    setItemChecked(i, checked);
-                }
-            }
-        });
-    }
-
-    private void refreshTabsData() {
-        // Store a different copy of the tabs, so that we don't have to worry about
-        // accidentally updating it on the wrong thread.
-        ArrayList<Tab> tabData = new ArrayList<>();
-
-        Iterable<Tab> allTabs = Tabs.getInstance().getTabsInOrder();
-        for (Tab tab : allTabs) {
-            if (tab.isPrivate() == isPrivate)
-                tabData.add(tab);
-        }
-
-        tabsAdapter.setTabs(tabData);
-        updateSelectedPosition();
-    }
-
-    private void resetTransforms(View view) {
-        view.setAlpha(1);
-        view.setTranslationX(0);
-        view.setTranslationY(0);
-
-        ((TabsLayoutItemView) view).setCloseVisible(true);
+        };
+        final ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
+        touchHelper.attachToRecyclerView(this);
     }
 
     @Override
     public void closeAll() {
-
         autoHidePanel();
 
-        if (getChildCount() == 0) {
+        closeAllTabs();
+    }
+
+    @Override
+    protected boolean addAtIndexRequiresScroll(int index) {
+        final GridLayoutManager layoutManager = (GridLayoutManager) getLayoutManager();
+        final int spanCount = layoutManager.getSpanCount();
+        final int firstVisibleIndex = layoutManager.findFirstVisibleItemPosition();
+        // When you add an item at the first visible position to a GridLayoutManager and there's
+        // room to scroll, RecyclerView scrolls the new position to anywhere from near the bottom of
+        // its row to completely offscreen (for unknown reasons), so we need to scroll to fix that.
+        // We also scroll when the item being added is the only item on the final row.
+        return index == firstVisibleIndex ||
+                (index == getAdapter().getItemCount() - 1 && index % spanCount == 0);
+    }
+
+    private void updateSpacingDecoration(int horizontalItemSpacing) {
+        if (spacingDecoration != null) {
+            removeItemDecoration(spacingDecoration);
+        }
+        spacingDecoration = new GridSpacingDecoration(horizontalItemSpacing, verticalItemPadding);
+        addItemDecoration(spacingDecoration);
+        updateSelectedPosition();
+    }
+
+    @Override
+    public void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        if (w == oldw) {
             return;
         }
 
-        final Iterable<Tab> tabs = Tabs.getInstance().getTabsInOrder();
-        for (Tab tab : tabs) {
-            // In the normal panel we want to close all tabs (both private and normal),
-            // but in the private panel we only want to close private tabs.
-            if (!isPrivate || tab.isPrivate()) {
-                Tabs.getInstance().closeTab(tab, false);
-            }
-        }
-    }
-
-    private View getViewForTab(Tab tab) {
-        final int position = tabsAdapter.getPositionForTab(tab);
-        return getChildAt(position - getFirstVisiblePosition());
-    }
-
-    void closeTab(View v) {
-        if (tabsAdapter.getCount() == 1) {
-            autoHidePanel();
-        }
-
-        TabsLayoutItemView itemView = (TabsLayoutItemView) v.getTag();
-        Tab tab = Tabs.getInstance().getTab(itemView.getTabId());
-
-        Tabs.getInstance().closeTab(tab, true);
-    }
-
-    private void animateRemoveTab(final Tab removedTab) {
-        final int removedPosition = tabsAdapter.getPositionForTab(removedTab);
-
-        final View removedView = getViewForTab(removedTab);
-
-        // The removed position might not have a matching child view
-        // when it's not within the visible range of positions in the strip.
-        if (removedView == null) {
-            return;
-        }
-        final int removedHeight = removedView.getMeasuredHeight();
-
-        populateTabLocations(removedTab);
-
-        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
-            @Override
-            public boolean onPreDraw() {
-                getViewTreeObserver().removeOnPreDrawListener(this);
-                // We don't animate the removed child view (it just disappears)
-                // but we still need its size to animate all affected children
-                // within the visible viewport.
-                final int childCount = getChildCount();
-                final int firstPosition = getFirstVisiblePosition();
-                final int numberOfColumns = getNumColumns();
-
-                final List<Animator> childAnimators = new ArrayList<>();
-
-                PropertyValuesHolder translateX, translateY;
-                for (int x = 0, i = removedPosition - firstPosition; i < childCount; i++, x++) {
-                    final View child = getChildAt(i);
-                    ObjectAnimator animator;
-
-                    if (i % numberOfColumns == numberOfColumns - 1) {
-                        // Animate X & Y
-                        translateX = PropertyValuesHolder.ofFloat("translationX", -(columnWidth * numberOfColumns), 0);
-                        translateY = PropertyValuesHolder.ofFloat("translationY", removedHeight, 0);
-                        animator = ObjectAnimator.ofPropertyValuesHolder(child, translateX, translateY);
-                    } else {
-                        // Just animate X
-                        translateX = PropertyValuesHolder.ofFloat("translationX", columnWidth, 0);
-                        animator = ObjectAnimator.ofPropertyValuesHolder(child, translateX);
-                    }
-                    animator.setStartDelay(x * ANIM_DELAY_MULTIPLE_MS);
-                    childAnimators.add(animator);
-                }
-
-                final AnimatorSet animatorSet = new AnimatorSet();
-                animatorSet.playTogether(childAnimators);
-                animatorSet.setDuration(ANIM_TIME_MS);
-                animatorSet.setInterpolator(ANIM_INTERPOLATOR);
-                animatorSet.start();
+        final GridLayoutManager layoutManager = (GridLayoutManager) getLayoutManager();
 
-                // Set the starting position of the child views - because we are delaying the start
-                // of the animation, we need to prevent the items being drawn in their final position
-                // prior to the animation starting
-                for (int x = 1, i = (removedPosition - firstPosition) + 1; i < childCount; i++, x++) {
-                    final View child = getChildAt(i);
-
-                    final PointF targetLocation = tabLocations.get(x + 1);
-                    if (targetLocation == null) {
-                        continue;
-                    }
-
-                    child.setX(targetLocation.x);
-                    child.setY(targetLocation.y);
-                }
-
-                return true;
-            }
-        });
-    }
-
-
-    private void animateCancel(final View view) {
-        PropertyAnimator animator = new PropertyAnimator(ANIM_TIME_MS);
-        animator.attach(view, PropertyAnimator.Property.ALPHA, 1);
-        animator.attach(view, PropertyAnimator.Property.TRANSLATION_X, 0);
-
-        animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
-            @Override
-            public void onPropertyAnimationStart() {
-            }
-
-            @Override
-            public void onPropertyAnimationEnd() {
-                TabsLayoutItemView tab = (TabsLayoutItemView) view;
-                tab.setCloseVisible(true);
-            }
-        });
-
-        animator.start();
-    }
-
-    private class TabsGridLayoutAdapter extends TabsLayoutAdapter {
-
-        final private Button.OnClickListener mCloseClickListener;
-
-        public TabsGridLayoutAdapter(Context context) {
-            super(context, R.layout.tabs_layout_item_view);
-
-            mCloseClickListener = new Button.OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    closeTab(v);
-                }
-            };
-        }
-
-        @Override
-        TabsLayoutItemView newView(int position, ViewGroup parent) {
-            final TabsLayoutItemView item = super.newView(position, parent);
-
-            item.setCloseOnClickListener(mCloseClickListener);
-            ((ThemedRelativeLayout) item.findViewById(R.id.wrapper)).setPrivateMode(isPrivate);
-
-            return item;
-        }
-
-        @Override
-        public void bindView(TabsLayoutItemView view, Tab tab) {
-            super.bindView(view, tab);
-
-            // If we're recycling this view, there's a chance it was transformed during
-            // the close animation. Remove any of those properties.
-            resetTransforms(view);
-        }
-    }
-
-    private class TabSwipeGestureListener implements View.OnTouchListener {
-        // same value the stock browser uses for after drag animation velocity in pixels/sec
-        // http://androidxref.com/4.0.4/xref/packages/apps/Browser/src/com/android/browser/NavTabScroller.java#61
-        private static final float MIN_VELOCITY = 750;
-
-        private final int mSwipeThreshold;
-        private final int mMinFlingVelocity;
+        final int nonPaddingWidth = w - getPaddingLeft() - getPaddingRight();
+        // We lay out the tabs so that the outer two tab edges are butted up against the
+        // RecyclerView padding, and then all other tab edges get their own padding, so
+        // nonPaddingWidth in terms of tab width w and tab spacing s for n tabs is
+        //   n * w + (n - 1) * s
+        // Solving for n gives the formulas below.
+        final int idealSpacingSpanCount = Math.max(1,
+                (nonPaddingWidth + desiredHorizontalItemSpacing) / (desiredItemWidth + desiredHorizontalItemSpacing));
+        final int maxSpanCount = Math.max(1,
+                (nonPaddingWidth + minHorizontalItemSpacing) / (desiredItemWidth + minHorizontalItemSpacing));
 
-        private final int mMaxFlingVelocity;
-        private VelocityTracker mVelocityTracker;
-
-        private int mTabWidth = 1;
-
-        private View mSwipeView;
-        private Runnable mPendingCheckForTap;
-
-        private float mSwipeStartX;
-        private boolean mSwiping;
-        private boolean mEnabled;
-
-        public TabSwipeGestureListener() {
-            mEnabled = true;
-
-            ViewConfiguration vc = ViewConfiguration.get(TabsGridLayout.this.getContext());
-            mSwipeThreshold = vc.getScaledTouchSlop();
-            mMinFlingVelocity = (int) (TabsGridLayout.this.getContext().getResources().getDisplayMetrics().density * MIN_VELOCITY);
-            mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
-        }
-
-        public void setEnabled(boolean enabled) {
-            mEnabled = enabled;
-        }
-
-        public OnScrollListener makeScrollListener() {
-            return new OnScrollListener() {
-                @Override
-                public void onScrollStateChanged(AbsListView view, int scrollState) {
-                    setEnabled(scrollState != GridView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
-                }
-
-                @Override
-                public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
-
-                }
-            };
-        }
-
-        @Override
-        public boolean onTouch(View view, MotionEvent e) {
-            if (!mEnabled) {
-                return false;
-            }
-
-            switch (e.getActionMasked()) {
-                case MotionEvent.ACTION_DOWN: {
-                    // Check if we should set pressed state on the
-                    // touched view after a standard delay.
-                    triggerCheckForTap();
-
-                    final float x = e.getRawX();
-                    final float y = e.getRawY();
-
-                    // Find out which view is being touched
-                    mSwipeView = findViewAt(x, y);
-
-                    if (mSwipeView != null) {
-                        if (mTabWidth < 2) {
-                            mTabWidth = mSwipeView.getWidth();
-                        }
-
-                        mSwipeStartX = e.getRawX();
-
-                        mVelocityTracker = VelocityTracker.obtain();
-                        mVelocityTracker.addMovement(e);
-                    }
-
-                    view.onTouchEvent(e);
-                    return true;
-                }
-
-                case MotionEvent.ACTION_UP: {
-                    if (mSwipeView == null) {
-                        break;
-                    }
-
-                    cancelCheckForTap();
-                    mSwipeView.setPressed(false);
+        // General caution note: span count can change here at a point where some ItemDecorations
+        // have been computed and some have not, and Android doesn't recompute ItemDecorations after
+        // a setSpanCount call, so we need to always remove and then add back our spacingDecoration
+        // (whose computations depend on spanCount) in order to get a full layout recompute.
+        if (idealSpacingSpanCount == maxSpanCount) {
+            layoutManager.setSpanCount(idealSpacingSpanCount);
+            updateSpacingDecoration(desiredHorizontalItemSpacing);
+        } else {
+            // We're gaining a column by decreasing the item spacing - this actually turns out to be
+            // necessary to fit three columns in landscape mode on many phones.  It also allows us
+            // to match the span counts produced by the previous GridLayout implementation.
+            layoutManager.setSpanCount(maxSpanCount);
 
-                    if (!mSwiping) {
-                        final TabsLayoutItemView item = (TabsLayoutItemView) mSwipeView;
-                        final int tabId = item.getTabId();
-                        final Tab tab = Tabs.getInstance().selectTab(tabId);
-                        if (tab != null) {
-                            autoHidePanel();
-                            Tabs.getInstance().notifyListeners(tab, Tabs.TabEvents.OPENED_FROM_TABS_TRAY);
-                        }
-
-                        mVelocityTracker.recycle();
-                        mVelocityTracker = null;
-                        break;
-                    }
-
-                    mVelocityTracker.addMovement(e);
-                    mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
-
-                    float velocityX = Math.abs(mVelocityTracker.getXVelocity());
-
-                    boolean dismiss = false;
-
-                    float deltaX = mSwipeView.getTranslationX();
-
-                    if (Math.abs(deltaX) > mTabWidth / 2) {
-                        dismiss = true;
-                    } else if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity) {
-                        dismiss = mSwiping && (deltaX * mVelocityTracker.getYVelocity() > 0);
-                    }
-                    if (dismiss) {
-                        closeTab(mSwipeView.findViewById(R.id.close));
-                    } else {
-                        animateCancel(mSwipeView);
-                    }
-                    mVelocityTracker.recycle();
-                    mVelocityTracker = null;
-                    mSwipeView = null;
-
-                    mSwipeStartX = 0;
-                    mSwiping = false;
-                }
-
-                case MotionEvent.ACTION_MOVE: {
-                    if (mSwipeView == null || mVelocityTracker == null) {
-                        break;
-                    }
-
-                    mVelocityTracker.addMovement(e);
-
-                    float delta = e.getRawX() - mSwipeStartX;
-
-                    boolean isScrollingX = Math.abs(delta) > mSwipeThreshold;
-                    boolean isSwipingToClose = isScrollingX;
-
-                    // If we're actually swiping, make sure we don't
-                    // set pressed state on the swiped view.
-                    if (isScrollingX) {
-                        cancelCheckForTap();
-                    }
-
-                    if (isSwipingToClose) {
-                        mSwiping = true;
-                        TabsGridLayout.this.requestDisallowInterceptTouchEvent(true);
-
-                        ((TabsLayoutItemView) mSwipeView).setCloseVisible(false);
-
-                        // Stops listview from highlighting the touched item
-                        // in the list when swiping.
-                        MotionEvent cancelEvent = MotionEvent.obtain(e);
-                        cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
-                                (e.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
-                        TabsGridLayout.this.onTouchEvent(cancelEvent);
-                        cancelEvent.recycle();
-                    }
-
-                    if (mSwiping) {
-                        mSwipeView.setTranslationX(delta);
-
-                        mSwipeView.setAlpha(Math.min(1f, 1f - 2f * Math.abs(delta) / mTabWidth));
-
-                        return true;
-                    }
-
-                    break;
+            // Increase the spacing as much as we can without giving up our increased span count.
+            for (int spacing = minHorizontalItemSpacing + 1; spacing <= desiredHorizontalItemSpacing; spacing++) {
+                if (maxSpanCount * desiredItemWidth + (maxSpanCount - 1) * spacing > nonPaddingWidth) {
+                    updateSpacingDecoration(spacing - 1);
+                    return;
                 }
             }
-            return false;
-        }
-
-        private View findViewAt(float rawX, float rawY) {
-            Rect rect = new Rect();
-
-            int[] listViewCoords = new int[2];
-            TabsGridLayout.this.getLocationOnScreen(listViewCoords);
-
-            int x = (int) rawX - listViewCoords[0];
-            int y = (int) rawY - listViewCoords[1];
-
-            for (int i = 0; i < TabsGridLayout.this.getChildCount(); i++) {
-                View child = TabsGridLayout.this.getChildAt(i);
-                child.getHitRect(rect);
-
-                if (rect.contains(x, y)) {
-                    return child;
-                }
-            }
-
-            return null;
-        }
-
-        private void triggerCheckForTap() {
-            if (mPendingCheckForTap == null) {
-                mPendingCheckForTap = new CheckForTap();
-            }
-
-            TabsGridLayout.this.postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
-        }
-
-        private void cancelCheckForTap() {
-            if (mPendingCheckForTap == null) {
-                return;
-            }
-
-            TabsGridLayout.this.removeCallbacks(mPendingCheckForTap);
-        }
-
-        private class CheckForTap implements Runnable {
-            @Override
-            public void run() {
-                if (!mSwiping && mSwipeView != null && mEnabled) {
-                    mSwipeView.setPressed(true);
-                }
-            }
+            // We should never get here if our calculations above were correct.
+            Log.e(LOGTAG, "Span count calculation error");
+            updateSpacingDecoration(minHorizontalItemSpacing);
         }
     }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsGridLayoutAnimator.java
@@ -0,0 +1,21 @@
+/* -*- 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 org.mozilla.gecko.widget.DefaultItemAnimatorBase;
+
+import android.support.v7.widget.RecyclerView;
+
+class TabsGridLayoutAnimator extends DefaultItemAnimatorBase {
+    public TabsGridLayoutAnimator() {
+        setSupportsChangeAnimations(false);
+    }
+
+    @Override
+    protected boolean preAnimateRemoveImpl(final RecyclerView.ViewHolder holder) {
+        return false;
+    }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayout.java
@@ -25,26 +25,26 @@ public abstract class TabsLayout extends
         Tabs.OnTabsChangedListener,
         RecyclerViewClickSupport.OnItemClickListener,
         TabsTouchHelperCallback.DismissListener {
 
     private static final String LOGTAG = "Gecko" + TabsLayout.class.getSimpleName();
 
     private final boolean isPrivate;
     private TabsPanel tabsPanel;
-    private final TabsLayoutRecyclerAdapter tabsAdapter;
+    private final TabsLayoutAdapter tabsAdapter;
 
     public TabsLayout(Context context, AttributeSet attrs, int itemViewLayoutResId) {
         super(context, attrs);
 
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsLayout);
         isPrivate = (a.getInt(R.styleable.TabsLayout_tabs, 0x0) == 1);
         a.recycle();
 
-        tabsAdapter = new TabsLayoutRecyclerAdapter(context, itemViewLayoutResId, isPrivate,
+        tabsAdapter = new TabsLayoutAdapter(context, itemViewLayoutResId, isPrivate,
                 /* close on click listener */
                 new Button.OnClickListener() {
                     @Override
                     public void onClick(View v) {
                         // The view here is the close button, which has a reference
                         // to the parent TabsLayoutItemView in its tag, hence the getTag() call.
                         TabsLayoutItemView itemView = (TabsLayoutItemView) v.getTag();
                         closeTab(itemView);
@@ -96,18 +96,18 @@ public abstract class TabsLayout extends
 
     @Override
     public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
         switch (msg) {
             case ADDED:
                 final int tabIndex = Integer.parseInt(data);
                 tabsAdapter.notifyTabInserted(tab, tabIndex);
                 if (addAtIndexRequiresScroll(tabIndex)) {
-                    // (The current Tabs implementation updates the SELECTED tab *after* this
-                    // call to ADDED, so don't just call updateSelectedPosition().)
+                    // (The SELECTED tab is updated *after* this call to ADDED, so don't just call
+                    // updateSelectedPosition().)
                     scrollToPosition(tabIndex);
                 }
                 break;
 
             case CLOSED:
                 if (tab.isPrivate() == isPrivate && tabsAdapter.getItemCount() > 0) {
                     tabsAdapter.removeTab(tab);
                 }
@@ -119,38 +119,44 @@ public abstract class TabsLayout extends
             case TITLE:
             case RECORDING_CHANGE:
             case AUDIO_PLAYING_CHANGE:
                 tabsAdapter.notifyTabChanged(tab);
                 break;
         }
     }
 
-    // Addition of a tab at selected positions (dependent on LayoutManager) will result in a tab
-    // being added out of view - return true if index is such a position.
+    /**
+     * Addition of a tab at selected positions (dependent on LayoutManager) will result in a tab
+     * being added out of view - return true if {@code index} is such a position.
+     */
     abstract protected boolean addAtIndexRequiresScroll(int index);
 
+    protected int getSelectedAdapterPosition() {
+        return tabsAdapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
+    }
+
     @Override
     public void onItemClicked(RecyclerView recyclerView, int position, View v) {
         final TabsLayoutItemView item = (TabsLayoutItemView) v;
         final int tabId = item.getTabId();
         final Tab tab = Tabs.getInstance().selectTab(tabId);
         if (tab == null) {
             // The tab that was clicked no longer exists in the tabs list (which can happen if you
             // tap on a tab while its remove animation is running), so ignore the click.
             return;
         }
 
         autoHidePanel();
         Tabs.getInstance().notifyListeners(tab, Tabs.TabEvents.OPENED_FROM_TABS_TRAY);
     }
 
-    // Updates the selected position in the list so that it will be scrolled to the right place.
-    private void updateSelectedPosition() {
-        final int selected = tabsAdapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
+    /** Updates the selected position in the list so that it will be scrolled to the right place. */
+    protected void updateSelectedPosition() {
+        final int selected = getSelectedAdapterPosition();
         if (selected != NO_POSITION) {
             scrollToPosition(selected);
         }
     }
 
     private void refreshTabsData() {
         // Store a different copy of the tabs, so that we don't have to worry about
         // accidentally updating it on the wrong thread.
@@ -194,16 +200,25 @@ public abstract class TabsLayout extends
         }
     }
 
     @Override
     public void onItemDismiss(View view) {
         closeTab(view);
     }
 
+    @Override
+    public void onChildAttachedToWindow(View child) {
+        // Make sure we reset any attributes that may have been animated in this child's previous
+        // incarnation.
+        child.setTranslationX(0);
+        child.setTranslationY(0);
+        child.setAlpha(1);
+    }
+
     private Tab getTabForView(View view) {
         if (view == null) {
             return null;
         }
         return Tabs.getInstance().getTab(((TabsLayoutItemView) view).getTabId());
     }
 
     @Override
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutAdapter.java
@@ -1,100 +1,127 @@
 /* -*- 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 org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 
 import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.BaseAdapter;
+import android.widget.Button;
 
 import java.util.ArrayList;
 
-// Adapter to bind tabs into a list
-public class TabsLayoutAdapter extends BaseAdapter {
-    public static final String LOGTAG = "Gecko" + TabsLayoutAdapter.class.getSimpleName();
+public class TabsLayoutAdapter
+        extends RecyclerView.Adapter<TabsLayoutAdapter.TabsListViewHolder> {
+
+    private static final String LOGTAG = "Gecko" + TabsLayoutAdapter.class.getSimpleName();
+
+    private final int tabLayoutId;
+    private @NonNull ArrayList<Tab> tabs;
+    private final LayoutInflater inflater;
+    private final boolean isPrivate;
+    // Click listener for the close button on itemViews.
+    private final Button.OnClickListener closeOnClickListener;
 
-    private final Context mContext;
-    private final int mTabLayoutId;
-    private ArrayList<Tab> mTabs;
-    private final LayoutInflater mInflater;
+    // The TabsLayoutItemView takes care of caching its own Views, so we don't need to do anything
+    // here except not be abstract.
+    public static class TabsListViewHolder extends RecyclerView.ViewHolder {
+        public TabsListViewHolder(View itemView) {
+            super(itemView);
+        }
+    }
 
-    public TabsLayoutAdapter (Context context, int tabLayoutId) {
-        mContext = context;
-        mInflater = LayoutInflater.from(mContext);
-        mTabLayoutId = tabLayoutId;
+    public TabsLayoutAdapter(Context context, int tabLayoutId, boolean isPrivate,
+                             Button.OnClickListener closeOnClickListener) {
+        inflater = LayoutInflater.from(context);
+        this.tabLayoutId = tabLayoutId;
+        this.isPrivate = isPrivate;
+        this.closeOnClickListener = closeOnClickListener;
+        tabs = new ArrayList<>(0);
+    }
+
+    /* package */ final void setTabs(@NonNull ArrayList<Tab> tabs) {
+        this.tabs = tabs;
+        notifyDataSetChanged();
+    }
+
+    /* package */ final void clear() {
+        tabs = new ArrayList<>(0);
+        notifyDataSetChanged();
     }
 
-    final void setTabs (ArrayList<Tab> tabs) {
-        mTabs = tabs;
-        notifyDataSetChanged(); // Be sure to call this whenever mTabs changes.
+    /* package */ final boolean removeTab(Tab tab) {
+        final int position = getPositionForTab(tab);
+        if (position == -1) {
+            return false;
+        }
+        tabs.remove(position);
+        notifyItemRemoved(position);
+        return true;
+    }
+
+    /* package */ final int getPositionForTab(Tab tab) {
+        if (tab == null) {
+            return -1;
+        }
+
+        return tabs.indexOf(tab);
     }
 
-    final boolean removeTab (Tab tab) {
-        boolean tabRemoved = mTabs.remove(tab);
-        if (tabRemoved) {
-            notifyDataSetChanged(); // Be sure to call this whenever mTabs changes.
+    /* package */ void notifyTabChanged(Tab tab) {
+        final int position = getPositionForTab(tab);
+        if (position != -1) {
+            notifyItemChanged(position);
         }
-        return tabRemoved;
     }
 
-    final void clear() {
-        mTabs = null;
-
-        notifyDataSetChanged(); // Be sure to call this whenever mTabs changes.
+    /* package */ void notifyTabInserted(Tab tab, int index) {
+        if (index >= 0 && index <= tabs.size()) {
+            tabs.add(index, tab);
+            notifyItemInserted(index);
+        } else {
+            // Add to the end.
+            tabs.add(tab);
+            notifyItemInserted(tabs.size() - 1);
+            // index == -1 is a valid way to add to the end, the other cases are errors.
+            if (index != -1) {
+                Log.e(LOGTAG, "Tab was inserted at an invalid position: " + Integer.toString(index));
+            }
+        }
     }
 
     @Override
-    public int getCount() {
-        return (mTabs == null ? 0 : mTabs.size());
-    }
-
-    @Override
-    public Tab getItem(int position) {
-        return mTabs.get(position);
+    public int getItemCount() {
+        return tabs.size();
     }
 
-    @Override
-    public long getItemId(int position) {
-        return position;
-    }
-
-    final int getPositionForTab(Tab tab) {
-        if (mTabs == null || tab == null)
-            return -1;
-
-        return mTabs.indexOf(tab);
+    private Tab getItem(int position) {
+        return tabs.get(position);
     }
 
     @Override
-    public boolean isEnabled(int position) {
-        return true;
+    public void onBindViewHolder(TabsListViewHolder viewHolder, int position) {
+        final Tab tab = getItem(position);
+        final TabsLayoutItemView itemView = (TabsLayoutItemView) viewHolder.itemView;
+        itemView.assignValues(tab);
+        // Be careful (re)setting position values here: bind is called on each notifyItemChanged,
+        // so you could be stomping on values that have been set in support of other animations
+        // that are already underway.
     }
 
     @Override
-    final public TabsLayoutItemView getView(int position, View convertView, ViewGroup parent) {
-        final TabsLayoutItemView view;
-        if (convertView == null) {
-            view = newView(position, parent);
-        } else {
-            view = (TabsLayoutItemView) convertView;
-        }
-        final Tab tab = mTabs.get(position);
-        bindView(view, tab);
-        return view;
+    public TabsListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        final TabsLayoutItemView viewItem = (TabsLayoutItemView) inflater.inflate(tabLayoutId, parent, false);
+        viewItem.setPrivateMode(isPrivate);
+        viewItem.setCloseOnClickListener(closeOnClickListener);
+
+        return new TabsListViewHolder(viewItem);
     }
-
-    TabsLayoutItemView newView(int position, ViewGroup parent) {
-        return (TabsLayoutItemView) mInflater.inflate(mTabLayoutId, parent, false);
-    }
-
-    void bindView(TabsLayoutItemView view, Tab tab) {
-        view.assignValues(tab);
-    }
-}
\ No newline at end of file
+}
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsListLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsListLayout.java
@@ -27,17 +27,23 @@ public class TabsListLayout extends Tabs
     public TabsListLayout(Context context, AttributeSet attrs) {
         super(context, attrs, R.layout.tabs_list_item_view);
 
         setHasFixedSize(true);
 
         setLayoutManager(new LinearLayoutManager(context));
 
         // A TouchHelper handler for swipe to close.
-        final TabsTouchHelperCallback callback = new TabsTouchHelperCallback(this);
+        final TabsTouchHelperCallback callback = new TabsTouchHelperCallback(this) {
+            @Override
+            protected float alphaForItemSwipeDx(float dX, int distanceToAlphaMin) {
+                return Math.max(0.1f,
+                        Math.min(1f, 1f - 2f * Math.abs(dX) / distanceToAlphaMin));
+            }
+        };
         final ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
         touchHelper.attachToRecyclerView(this);
 
         setItemAnimator(new TabsListLayoutAnimator(ANIMATION_DURATION));
     }
 
     @Override
     public void closeAll() {
@@ -101,18 +107,9 @@ public class TabsListLayout extends Tabs
             cascadeDelay += ANIMATION_CASCADE_DELAY;
         }
     }
 
     @Override
     protected boolean addAtIndexRequiresScroll(int index) {
         return index == 0 || index == getAdapter().getItemCount() - 1;
     }
-
-    @Override
-    public void onChildAttachedToWindow(View child) {
-        // Make sure we reset any attributes that may have been animated in this child's previous
-        // incarnation.
-        child.setTranslationX(0);
-        child.setTranslationY(0);
-        child.setAlpha(1);
-    }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsTouchHelperCallback.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsTouchHelperCallback.java
@@ -5,17 +5,17 @@
 
 package org.mozilla.gecko.tabs;
 
 import android.graphics.Canvas;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.helper.ItemTouchHelper;
 import android.view.View;
 
-class TabsTouchHelperCallback extends ItemTouchHelper.Callback {
+abstract class TabsTouchHelperCallback extends ItemTouchHelper.Callback {
     private final DismissListener dismissListener;
 
     interface DismissListener {
         void onItemDismiss(View view);
     }
 
     public TabsTouchHelperCallback(DismissListener dismissListener) {
         this.dismissListener = dismissListener;
@@ -37,33 +37,40 @@ class TabsTouchHelperCallback extends It
     }
 
     @Override
     public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
                           RecyclerView.ViewHolder target) {
         return false;
     }
 
-    // Alpha on an itemView being swiped should decrease to a min over a distance equal to the
-    // width of the item being swiped.
+    /**
+     * Returns the alpha an itemView should be set to when swiped by an amount {@code dX}, given
+     * that alpha should decrease to its min at distance {@code distanceToAlphaMin}.
+     */
+    abstract protected float alphaForItemSwipeDx(float dX, int distanceToAlphaMin);
+
+    /**
+     * Alpha on an itemView being swiped should decrease to a min over a distance equal to the
+     * width of the item being swiped.
+     */
     @Override
     public void onChildDraw(Canvas c,
                             RecyclerView recyclerView,
                             RecyclerView.ViewHolder viewHolder,
                             float dX,
                             float dY,
                             int actionState,
                             boolean isCurrentlyActive) {
         if (actionState != ItemTouchHelper.ACTION_STATE_SWIPE) {
             return;
         }
 
         super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
 
-        viewHolder.itemView.setAlpha(Math.max(0.1f,
-                Math.min(1f, 1f - 2f * Math.abs(dX) / viewHolder.itemView.getWidth())));
+        viewHolder.itemView.setAlpha(alphaForItemSwipeDx(dX, viewHolder.itemView.getWidth()));
     }
 
     public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
         super.clearView(recyclerView, viewHolder);
         viewHolder.itemView.setAlpha(1);
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/util/UnusedResourcesUtil.java
+++ b/mobile/android/base/java/org/mozilla/gecko/util/UnusedResourcesUtil.java
@@ -89,40 +89,14 @@ final class UnusedResourcesUtil {
             R.xml.preferences_advanced,
             R.xml.preferences_accessibility,
             R.xml.preferences_home,
             R.xml.preferences_privacy,
             R.xml.preferences_privacy_clear_tablet,
             R.xml.preferences_default_browser_tablet
     };
 
-    // We are migrating to Gradle 2.10 and the Android Gradle plugin 2.0. The new plugin does find
-    // more unused resources but we are not ready to remove them yet. Some of the resources are going
-    // to be reused soon. This is a temporary solution so that the gradle migration is not blocked.
-    // See bug 1263390 / bug 1268414.
-    public static final int[] TEMPORARY_UNUSED_WHILE_MIGRATING_GRADLE = {
-            R.color.remote_tabs_setup_button_background_hit,
-
-            R.drawable.remote_tabs_setup_button_background,
-
-            R.style.TabsPanelSectionBase,
-            R.style.TabsPanelSection,
-            R.style.TabsPanelItemBase,
-            R.style.TabsPanelItem,
-            R.style.TabsPanelItem_TextAppearance,
-            R.style.TabsPanelItem_TextAppearance_Header,
-            R.style.TabsPanelItem_TextAppearance_Linkified,
-            R.style.TabWidget,
-            R.style.GeckoDialogTitle,
-            R.style.GeckoDialogTitle_SubTitle,
-            R.style.RemoteTabsPanelItem,
-            R.style.RemoteTabsPanelItem_TextAppearance,
-            R.style.RemoteTabsPanelItem_TextAppearance_Header,
-            R.style.RemoteTabsPanelItem_TextAppearance_Linkified,
-            R.style.RemoteTabsPanelItem_Button,
-    };
-
     // String resources that are used in the full-pane Activity Stream that are temporarily
     // not needed while Activity Stream is part of the HomePager
     public static final int[] TEMPORARY_UNUSED_ACTIVITY_STREAM = {
             R.string.activity_stream_topsites
     };
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/widget/GridSpacingDecoration.java
@@ -0,0 +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.widget;
+
+import android.graphics.Rect;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+/**
+ * An ItemDecoration for a GridLayoutManager that provides fixed spacing (but not fixed padding)
+ * to create fixed sized items, with no spacing on the outer edges of the outer items.
+ * <p>
+ * So, for example, if there are 2 columns and the spacing is s, then the first column gets a right
+ * padding of s/2 and the second column gets a left paddding of s/2.  If there are three columns
+ * then the first column gets a right padding of 2s/3, the second column gets left and right
+ * paddings of s/3, and the third column gets a left padding of 2s/3.
+ * </p>
+ */
+public class GridSpacingDecoration extends RecyclerView.ItemDecoration {
+    private final int horizontalSpacing;
+    private final int verticalPadding;
+
+    public GridSpacingDecoration(int horizontalSpacing, int verticalPadding) {
+        this.horizontalSpacing = horizontalSpacing;
+        this.verticalPadding = verticalPadding;
+    }
+
+    @Override
+    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+        final int position = parent.getChildAdapterPosition(view);
+        if (position == RecyclerView.NO_POSITION) {
+            return;
+        }
+
+        final GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();
+        final int spanCount = layoutManager.getSpanCount();
+        final int column = position % spanCount;
+
+        final int columnLeftOffset = (int) (((float) column / (float) spanCount) * horizontalSpacing);
+        final int columnRightOffset = (int) (((float) (spanCount - (column + 1)) / (float) spanCount) * horizontalSpacing);
+
+        outRect.set(columnLeftOffset, verticalPadding, columnRightOffset, verticalPadding);
+    }
+}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -680,20 +680,20 @@ gbjar.sources += ['java/org/mozilla/geck
     'tabs/PrivateTabsPanel.java',
     'tabs/TabCurve.java',
     'tabs/TabHistoryController.java',
     'tabs/TabHistoryFragment.java',
     'tabs/TabHistoryItemRow.java',
     'tabs/TabHistoryPage.java',
     'tabs/TabPanelBackButton.java',
     'tabs/TabsGridLayout.java',
+    'tabs/TabsGridLayoutAnimator.java',
     'tabs/TabsLayout.java',
     'tabs/TabsLayoutAdapter.java',
     'tabs/TabsLayoutItemView.java',
-    'tabs/TabsLayoutRecyclerAdapter.java',
     'tabs/TabsListLayout.java',
     'tabs/TabsListLayoutAnimator.java',
     'tabs/TabsPanel.java',
     'tabs/TabsPanelThumbnailView.java',
     'tabs/TabsTouchHelperCallback.java',
     'Telemetry.java',
     'telemetry/measurements/CampaignIdMeasurements.java',
     'telemetry/measurements/SearchCountMeasurements.java',
@@ -765,16 +765,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'widget/FadedMultiColorTextView.java',
     'widget/FadedSingleColorTextView.java',
     'widget/FadedTextView.java',
     'widget/FaviconView.java',
     'widget/FilledCardView.java',
     'widget/FlowLayout.java',
     'widget/GeckoActionProvider.java',
     'widget/GeckoPopupMenu.java',
+    'widget/GridSpacingDecoration.java',
     'widget/HistoryDividerItemDecoration.java',
     'widget/IconTabWidget.java',
     'widget/LoginDoorHanger.java',
     'widget/RecyclerViewClickSupport.java',
     'widget/ResizablePathDrawable.java',
     'widget/RoundedCornerLayout.java',
     'widget/SiteLogins.java',
     'widget/SquaredImageView.java',
--- a/mobile/android/base/resources/layout/tabs_layout_item_view.xml
+++ b/mobile/android/base/resources/layout/tabs_layout_item_view.xml
@@ -2,22 +2,22 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <org.mozilla.gecko.tabs.TabsLayoutItemView xmlns:android="http://schemas.android.com/apk/res/android"
                                            xmlns:gecko="http://schemas.android.com/apk/res-auto"
                                            style="@style/TabsItem"
                                            android:id="@+id/info"
-                                           android:layout_width="wrap_content"
+                                           android:layout_width="match_parent"
                                            android:layout_height="wrap_content"
                                            android:gravity="center"
                                            android:orientation="vertical">
 
-    <LinearLayout android:layout_width="fill_parent"
+    <LinearLayout android:layout_width="match_parent"
                   android:layout_height="wrap_content"
                   android:orientation="horizontal"
                   android:duplicateParentState="true"
                   android:paddingLeft="@dimen/tab_highlight_stroke_width"
                   android:paddingRight="@dimen/tab_highlight_stroke_width"
                   android:paddingBottom="@dimen/tab_highlight_stroke_width">
 
        <org.mozilla.gecko.widget.FadedSingleColorTextView
@@ -55,17 +55,17 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:padding="@dimen/tab_highlight_stroke_width"
             android:background="@drawable/tab_thumbnail"
             android:duplicateParentState="true"
             android:clipToPadding="false">
 
         <org.mozilla.gecko.tabs.TabsPanelThumbnailView android:id="@+id/thumbnail"
-                                                       android:layout_width="@dimen/tab_thumbnail_width"
-                                                       android:layout_height="@dimen/tab_thumbnail_height"
+                                                       android:layout_width="match_parent"
+                                                       android:layout_height="wrap_content"
                                                        android:elevation="2dp"
                                                        android:outlineProvider="bounds"
                                                 />
 
     </org.mozilla.gecko.widget.TabThumbnailWrapper>
 
 </org.mozilla.gecko.tabs.TabsLayoutItemView>
--- a/mobile/android/base/resources/values-land/dimens.xml
+++ b/mobile/android/base/resources/values-land/dimens.xml
@@ -2,10 +2,10 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <resources>
 
     <!-- Remote Tabs static view top padding. Less in landscape on phones. -->
     <dimen name="home_remote_tabs_top_padding">16dp</dimen>
-    <dimen name="tab_panel_grid_padding">48dp</dimen>
+    <dimen name="tab_panel_grid_hpadding">48dp</dimen>
 </resources>
--- a/mobile/android/base/resources/values-land/styles.xml
+++ b/mobile/android/base/resources/values-land/styles.xml
@@ -13,20 +13,9 @@
     <style name="TabsItem">
          <item name="android:nextFocusDown">@+id/close</item>
     </style>
 
     <style name="TabsItemClose">
          <item name="android:nextFocusUp">@+id/info</item>
     </style>
 
-    <!-- Tabs panel -->
-    <style name="TabsPanelSection" parent="TabsPanelSectionBase">
-        <item name="android:layout_weight">1</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>
-
 </resources>
--- a/mobile/android/base/resources/values-large-land-v11/styles.xml
+++ b/mobile/android/base/resources/values-large-land-v11/styles.xml
@@ -16,19 +16,9 @@
     <style name="Widget.TopSitesGridView" parent="Widget.GridView">
         <item name="android:paddingLeft">55dp</item>
         <item name="android:paddingRight">55dp</item>
         <item name="android:paddingBottom">30dp</item>
         <item name="android:horizontalSpacing">20dp</item>
         <item name="android:verticalSpacing">20dp</item>
     </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="TabsPanelItem" parent="TabsPanelItemBase">
-        <!-- To override the values-land style. -->
-    </style>
-
 </resources>
--- a/mobile/android/base/resources/values-sw240dp/dimens.xml
+++ b/mobile/android/base/resources/values-sw240dp/dimens.xml
@@ -1,10 +1,10 @@
 <?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/. -->
 
 <resources>
-    <dimen name="tab_panel_column_width">143dip</dimen>
+    <dimen name="tab_panel_item_width">143dip</dimen>
     <dimen name="tab_thumbnail_height">100dip</dimen>
     <dimen name="tab_thumbnail_width">135dip</dimen>
 </resources>
--- a/mobile/android/base/resources/values-sw360dp/dimens.xml
+++ b/mobile/android/base/resources/values-sw360dp/dimens.xml
@@ -1,13 +1,13 @@
 <?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/. -->
 
 <resources>
-    <dimen name="tab_panel_column_width">156dip</dimen>
+    <dimen name="tab_panel_item_width">156dip</dimen>
     <dimen name="tab_thumbnail_height">110dip</dimen>
     <dimen name="tab_thumbnail_width">148dip</dimen>
 
     <dimen name="firstrun_background_height">180dp</dimen>
     <dimen name="firstrun_min_height">180dp</dimen>
 </resources>
--- a/mobile/android/base/resources/values-sw400dp/dimens.xml
+++ b/mobile/android/base/resources/values-sw400dp/dimens.xml
@@ -1,10 +1,10 @@
 <?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/. -->
 
 <resources>
-    <dimen name="tab_panel_column_width">176dip</dimen>
+    <dimen name="tab_panel_item_width">176dip</dimen>
     <dimen name="tab_thumbnail_height">120dip</dimen>
     <dimen name="tab_thumbnail_width">168dip</dimen>
 </resources>
--- a/mobile/android/base/resources/values-v11/themes.xml
+++ b/mobile/android/base/resources/values-v11/themes.xml
@@ -34,12 +34,11 @@
         <item name="android:actionModeCutDrawable">@drawable/ab_cut</item>
         <item name="android:actionModePasteDrawable">@drawable/ab_paste</item>
         <item name="android:listViewStyle">@style/Widget.ListView</item>
         <item name="android:spinnerDropDownItemStyle">@style/Widget.DropDownItem.Spinner</item>
         <item name="android:spinnerItemStyle">@style/Widget.TextView.SpinnerItem</item>
         <item name="menuItemSwitcherLayoutStyle">@style/Widget.MenuItemSwitcherLayout</item>
         <item name="menuItemDefaultStyle">@style/Widget.MenuItemDefault</item>
         <item name="menuItemSecondaryActionBarStyle">@style/Widget.MenuItemSecondaryActionBar</item>
-        <item name="tabGridLayoutViewStyle">@style/Widget.TabsGridLayout</item>
     </style>
 
 </resources>
--- a/mobile/android/base/resources/values-v13/styles.xml
+++ b/mobile/android/base/resources/values-v13/styles.xml
@@ -1,15 +1,10 @@
 <?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/. -->
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <style name="GeckoDialogTitle">
-        <!-- Override this to use a Holo theme on v13+ -->
-        <item name="android:textAppearance">@android:style/TextAppearance.Holo.DialogWindowTitle</item>
-    </style>
-
     <style name="TextAppearance.Widget.ActionBar.Title" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title"/>
 
 </resources>
--- a/mobile/android/base/resources/values-v21/themes.xml
+++ b/mobile/android/base/resources/values-v21/themes.xml
@@ -29,12 +29,11 @@
     <style name="GeckoAppBase" parent="Gecko">
         <item name="android:actionButtonStyle">@style/GeckoActionBar.Button</item>
         <item name="android:listViewStyle">@style/Widget.ListView</item>
         <item name="android:spinnerDropDownItemStyle">@style/Widget.DropDownItem.Spinner</item>
         <item name="android:spinnerItemStyle">@style/Widget.TextView.SpinnerItem</item>
         <item name="menuItemSwitcherLayoutStyle">@style/Widget.MenuItemSwitcherLayout</item>
         <item name="menuItemDefaultStyle">@style/Widget.MenuItemDefault</item>
         <item name="menuItemSecondaryActionBarStyle">@style/Widget.MenuItemSecondaryActionBar</item>
-        <item name="tabGridLayoutViewStyle">@style/Widget.TabsGridLayout</item>
     </style>
 
 </resources>
--- a/mobile/android/base/resources/values-xlarge-land-v11/dimens.xml
+++ b/mobile/android/base/resources/values-xlarge-land-v11/dimens.xml
@@ -1,10 +1,10 @@
 <?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/. -->
 
 <resources>
 
-    <dimen name="tab_panel_grid_padding">64dp</dimen>
+    <dimen name="tab_panel_grid_hpadding">64dp</dimen>
 
 </resources>
--- a/mobile/android/base/resources/values-xlarge-v11/dimens.xml
+++ b/mobile/android/base/resources/values-xlarge-v11/dimens.xml
@@ -1,11 +1,11 @@
 <?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/. -->
 
 <resources>
 
     <dimen name="panel_grid_view_column_width">250dp</dimen>
-    <dimen name="tab_panel_grid_padding">48dp</dimen>
+    <dimen name="tab_panel_grid_hpadding">48dp</dimen>
 
 </resources>
--- a/mobile/android/base/resources/values-xlarge-v11/styles.xml
+++ b/mobile/android/base/resources/values-xlarge-v11/styles.xml
@@ -5,22 +5,16 @@
 
 <resources>
 
     <!--
         Only overriden styles for Honeycomb/Ice cream sandwich XLARGE tablets are specified here.
         Please refer to values/styles.xml for default styles.
     -->
 
-    <!-- TabWidget -->
-    <style name="TabWidget">
-        <item name="android:layout_width">300dip</item>
-        <item name="android:layout_height">48dip</item>
-    </style>
-
     <style name="Widget.TopSitesListView" parent="Widget.BookmarksListView">
         <item name="android:paddingTop">30dp</item>
         <item name="android:paddingLeft">32dp</item>
         <item name="android:paddingRight">32dp</item>
         <item name="android:clipToPadding">false</item>
         <item name="topDivider">false</item>
     </style>
 
--- a/mobile/android/base/resources/values/attrs.xml
+++ b/mobile/android/base/resources/values/attrs.xml
@@ -29,19 +29,16 @@
         <attr name="bookmarksListViewStyle" format="reference" />
 
         <!-- Default style for the TopSitesGridItemView -->
         <attr name="topSitesGridItemViewStyle" format="reference" />
 
         <!-- Styles for dynamic panel grid views -->
         <attr name="panelIconViewStyle" format="reference" />
 
-        <!-- Style for the TabsGridLayout -->
-        <attr name="tabGridLayoutViewStyle" format="reference" />
-
         <!-- Default style for the TopSitesGridView -->
         <attr name="topSitesGridViewStyle" format="reference" />
 
         <!-- Default style for the TopSitesThumbnailView -->
         <attr name="topSitesThumbnailViewStyle" format="reference" />
 
         <!-- Default style for the HomeListView -->
         <attr name="homeListViewStyle" format="reference" />
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -139,20 +139,22 @@
     <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="validation_message_height">50dp</dimen>
     <dimen name="validation_message_margin_top">6dp</dimen>
 
     <dimen name="tab_thumbnail_width">121dp</dimen>
     <dimen name="tab_thumbnail_height">90dp</dimen>
-    <dimen name="tab_panel_column_width">129dp</dimen>
-    <dimen name="tab_panel_grid_padding">20dp</dimen>
-    <dimen name="tab_panel_grid_vspacing">20dp</dimen>
-    <dimen name="tab_panel_grid_padding_top">19dp</dimen>
+    <dimen name="tab_panel_item_width">129dp</dimen>
+    <dimen name="tab_panel_grid_hpadding">20dp</dimen>
+    <dimen name="tab_panel_grid_vpadding">19dp</dimen>
+    <dimen name="tab_panel_grid_ideal_item_hspacing">20dp</dimen>
+    <dimen name="tab_panel_grid_min_item_hspacing">2dp</dimen>
+    <dimen name="tab_panel_grid_item_vpadding">10dp</dimen>
 
     <dimen name="tab_highlight_stroke_width">4dp</dimen>
 
     <!-- PageActionButtons dimensions -->
     <dimen name="page_action_button_width">32dp</dimen>
 
     <!-- Banner -->
     <dimen name="home_banner_height">72dp</dimen>
--- a/mobile/android/base/resources/values/styles.xml
+++ b/mobile/android/base/resources/values/styles.xml
@@ -175,31 +175,16 @@
 
     <style name="Widget.TopSitesGridItemView">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">match_parent</item>
         <item name="android:padding">5dip</item>
         <item name="android:orientation">vertical</item>
     </style>
 
-    <style name="Widget.TabsGridLayout" parent="Widget.GridView">
-        <item name="android:layout_width">match_parent</item>
-        <item name="android:layout_height">match_parent</item>
-        <item name="android:paddingTop">0dp</item>
-        <item name="android:stretchMode">spacingWidth</item>
-        <item name="android:scrollbarStyle">outsideOverlay</item>
-        <item name="android:gravity">center</item>
-        <item name="android:numColumns">auto_fit</item>
-        <item name="android:columnWidth">@dimen/tab_panel_column_width</item>
-        <item name="android:horizontalSpacing">2dp</item>
-        <item name="android:verticalSpacing">@dimen/tab_panel_grid_vspacing</item>
-        <item name="android:drawSelectorOnTop">true</item>
-        <item name="android:clipToPadding">false</item>
-    </style>
-
     <style name="Widget.BookmarkItemView" parent="Widget.TwoLinePageRow"/>
 
     <style name="Widget.BookmarksListView" parent="Widget.HomeListView"/>
 
     <style name="Widget.TopSitesThumbnailView">
       <item name="android:padding">0dip</item>
       <item name="android:scaleType">centerCrop</item>
     </style>
@@ -488,54 +473,16 @@
     <style name="TabsItem">
          <item name="android:nextFocusRight">@+id/close</item>
     </style>
 
     <style name="TabsItemClose">
          <item name="android:nextFocusLeft">@+id/info</item>
     </style>
 
-    <!-- Tabs panel -->
-    <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="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. -->
-    </style>
-
-    <style name="TabsPanelItem.TextAppearance">
-        <item name="android:textColor">#C0C9D0</item>
-        <item name="android:textSize">14sp</item>
-        <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.Linkified">
-        <item name="android:clickable">true</item>
-        <item name="android:focusable">true</item>
-        <item name="android:textColor">#0292D6</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">@color/toolbar_divider_grey</item>
@@ -553,23 +500,16 @@
         <item name="android:ellipsize">middle</item>
     </style>
 
     <!-- TabsLayout RemoteTabs Row Url -->
     <style name="TabLayoutItemTextAppearance.Url">
         <item name="android:textColor">#FFA4A7A9</item>
     </style>
 
-    <!-- TabWidget --> 
-    <style name="TabWidget">
-        <item name="android:layout_width">wrap_content</item>
-        <item name="android:layout_height">40dip</item>
-        <item name="android:layout_weight">1.0</item>
-    </style>
-
     <!-- Find bar -->
     <style name="FindBar">
         <item name="android:background">@color/text_and_tabs_tray_grey</item>
         <item name="android:paddingLeft">3dip</item>
         <item name="android:paddingRight">3dip</item>
         <item name="android:paddingTop">6dip</item>
         <item name="android:paddingBottom">6dip</item>
     </style>
@@ -580,22 +520,16 @@
         <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_marginLeft">5dip</item>
         <item name="android:layout_marginRight">5dip</item>
         <item name="android:scaleType">fitCenter</item>
         <item name="android:layout_centerVertical">true</item>
         <item name="android:background">@drawable/action_bar_button_inverse</item>
     </style>
 
-    <style name="GeckoDialogTitle">
-        <item name="android:textAppearance">@android:style/TextAppearance.DialogWindowTitle</item>
-    </style>
-
-    <style name="GeckoDialogTitle.SubTitle" />
-
     <style name="PopupAnimation">
         <item name="@android:windowEnterAnimation">@anim/popup_show</item>
         <item name="@android:windowExitAnimation">@anim/popup_hide</item>
     </style>
 
     <style name="ToastBase">
         <item name="android:background">@drawable/toast_background</item>
         <item name="android:layout_width">match_parent</item>
--- a/mobile/android/base/resources/values/themes.xml
+++ b/mobile/android/base/resources/values/themes.xml
@@ -90,17 +90,16 @@
     </style>
 
     <!-- All customizations that are NOT specific to a particular API-level can go here. -->
     <style name="Gecko.App" parent="GeckoAppBase">
         <item name="android:gridViewStyle">@style/Widget.GridView</item>
         <item name="android:spinnerStyle">@style/Widget.Spinner</item>
         <item name="android:windowBackground">@android:color/white</item>
         <item name="bookmarksListViewStyle">@style/Widget.BookmarksListView</item>
-        <item name="tabGridLayoutViewStyle">@style/Widget.TabsGridLayout</item>
         <item name="geckoMenuListViewStyle">@style/Widget.GeckoMenuListView</item>
         <item name="homeListViewStyle">@style/Widget.HomeListView</item>
         <item name="menuItemActionBarStyle">@style/Widget.MenuItemActionBar</item>
         <item name="menuItemActionModeStyle">@style/GeckoActionBar.Button</item>
         <item name="topSitesGridItemViewStyle">@style/Widget.TopSitesGridItemView</item>
         <item name="topSitesGridViewStyle">@style/Widget.TopSitesGridView</item>
         <item name="topSitesThumbnailViewStyle">@style/Widget.TopSitesThumbnailView</item>
     </style>
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareCodecCapabilityUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareCodecCapabilityUtils.java
@@ -4,16 +4,17 @@
  *    * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 package org.mozilla.gecko.util;
 
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.AppConstants.Versions;
 
+import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecInfo.CodecCapabilities;
 import android.media.MediaCodecList;
 import android.util.Log;
 
 public final class HardwareCodecCapabilityUtils {
   private static final String LOGTAG = "GeckoHardwareCodecCapabilityUtils";
 
@@ -48,16 +49,35 @@ public final class HardwareCodecCapabili
         if (mimeType.equals(aMimeType)) {
           return true;
         }
       }
     }
     return false;
   }
 
+  @WrapForJNI
+  public static boolean checkSupportsAdaptivePlayback(MediaCodec aCodec, String aMimeType) {
+      // isFeatureSupported supported on API level >= 19.
+      if (!Versions.feature19Plus) {
+          return false;
+      }
+
+      try {
+          MediaCodecInfo info = aCodec.getCodecInfo();
+          MediaCodecInfo.CodecCapabilities capabilities = info.getCapabilitiesForType(aMimeType);
+          return capabilities != null &&
+                 capabilities.isFeatureSupported(
+                     MediaCodecInfo.CodecCapabilities.FEATURE_AdaptivePlayback);
+      } catch (IllegalArgumentException e) {
+            Log.e(LOGTAG, "Retrieve codec information failed", e);
+      }
+      return false;
+  }
+
   public static boolean getHWEncoderCapability() {
     if (Versions.feature20Plus) {
       for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
         MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
         if (!info.isEncoder()) {
           continue;
         }
         String name = null;
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/loader/TestIconDownloader.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/loader/TestIconDownloader.java
@@ -11,21 +11,23 @@ import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.icons.IconDescriptor;
 import org.mozilla.gecko.icons.IconRequest;
 import org.mozilla.gecko.icons.IconResponse;
 import org.mozilla.gecko.icons.Icons;
 import org.mozilla.gecko.icons.storage.FailureCache;
 import org.robolectric.RuntimeEnvironment;
 
+import java.io.IOException;
 import java.net.HttpURLConnection;
 
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
 @RunWith(TestRunner.class)
 public class TestIconDownloader {
     /**
@@ -104,9 +106,37 @@ public class TestIconDownloader {
         final IconDownloader downloader = spy(new IconDownloader());
         doReturn(mockedConnection).when(downloader).connectTo(anyString());
         IconResponse response = downloader.load(request);
 
         Assert.assertNull(response);
 
         Assert.assertTrue(FailureCache.get().isKnownFailure(faviconUrl));
     }
+
+    /**
+     * Scenario: Connected to successfully to server but reading the response code throws an exception.
+     *
+     * Verify that:
+     *  * disconnect() is called on HttpUrlConnection
+     */
+    @Test
+    public void testConnectionIsClosedWhenReadingResponseCodeThrows() throws Exception {
+        final IconRequest request = Icons.with(RuntimeEnvironment.application)
+                .pageUrl("http://www.mozilla.org")
+                .icon(IconDescriptor.createFavicon(
+                        "https://www.mozilla.org/media/img/favicon.52506929be4c.ico",
+                        32,
+                        "image/x-icon"))
+                .build();
+
+        HttpURLConnection mockedConnection = mock(HttpURLConnection.class);
+        doThrow(new IOException()).when(mockedConnection).getResponseCode();
+
+        final IconDownloader downloader = spy(new IconDownloader());
+        doReturn(mockedConnection).when(downloader).connectTo(anyString());
+        IconResponse response = downloader.load(request);
+
+        Assert.assertNull(response);
+
+        verify(mockedConnection).disconnect();
+    }
 }
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/BaseTest.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/BaseTest.java
@@ -607,89 +607,52 @@ abstract class BaseTest extends BaseRobo
     }
 
     public void closeAddedTabs() {
         for(int tabID : mKnownTabIDs) {
             closeTab(tabID);
         }
     }
 
-    // A temporary tabs list/grid holder while the list and grid views are being transitioned to
-    // RecyclerViews (bug 1116415 and bug 1310081).
-    private static class TabsView {
-        private AdapterView<ListAdapter> gridView;
-        private RecyclerView listView;
-
-        public TabsView(View view) {
-            if (view instanceof RecyclerView) {
-                listView = (RecyclerView) view;
-            } else {
-                gridView = (AdapterView<ListAdapter>) view;
-            }
-        }
-
-        public void bringPositionIntoView(int index) {
-            if (gridView != null) {
-                gridView.setSelection(index);
-            } else {
-                listView.scrollToPosition(index);
-            }
-        }
-
-        public View getViewAtIndex(int index) {
-            if (gridView != null) {
-                return gridView.getChildAt(index - gridView.getFirstVisiblePosition());
-            } else {
-                final RecyclerView.ViewHolder itemViewHolder = listView.findViewHolderForLayoutPosition(index);
-                return itemViewHolder == null ? null : itemViewHolder.itemView;
-            }
-        }
-
-        public void post(Runnable runnable) {
-            if (gridView != null) {
-                gridView.post(runnable);
-            } else {
-                listView.post(runnable);
-            }
-        }
-    }
     /**
-     * Gets the AdapterView of the tabs list.
+     * Gets the RecyclerView of the tabs list.
      *
      * @return List view in the tabs panel
      */
-    private final TabsView getTabsLayout() {
+    private final RecyclerView getTabsLayout() {
         Element tabs = mDriver.findElement(getActivity(), R.id.tabs);
         tabs.click();
-        return new TabsView(getActivity().findViewById(R.id.normal_tabs));
+        return (RecyclerView) getActivity().findViewById(R.id.normal_tabs);
     }
 
     /**
      * Gets the view in the tabs panel at the specified index.
      *
      * @return View at index
      */
     private View getTabViewAt(final int index) {
         final View[] childView = { null };
 
-        final TabsView view = getTabsLayout();
+        final RecyclerView view = getTabsLayout();
 
         runOnUiThreadSync(new Runnable() {
             @Override
             public void run() {
-                view.bringPositionIntoView(index);
+                view.scrollToPosition(index);
 
                 // The selection isn't updated synchronously; posting a
                 // runnable to the view's queue guarantees we'll run after the
                 // layout pass.
                 view.post(new Runnable() {
                     @Override
                     public void run() {
                         // Index is relative to all views in the list.
-                        childView[0] = view.getViewAtIndex(index);
+                        final RecyclerView.ViewHolder itemViewHolder =
+                                view.findViewHolderForLayoutPosition(index);
+                        childView[0] = itemViewHolder == null ? null : itemViewHolder.itemView;
                     }
                 });
             }
         });
 
         boolean result = waitForCondition(new Condition() {
             @Override
             public boolean isSatisfied() {
--- a/netwerk/base/nsISocketTransport.idl
+++ b/netwerk/base/nsISocketTransport.idl
@@ -4,24 +4,27 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsITransport.idl"
 
 interface nsIInterfaceRequestor;
 interface nsINetAddr;
 
 %{ C++
+#include "mozilla/BasePrincipal.h"
 namespace mozilla {
 namespace net {
 union NetAddr;
 }
 }
 %}
 native NetAddr(mozilla::net::NetAddr);
 [ptr] native NetAddrPtr(mozilla::net::NetAddr);
+native NeckoOriginAttributes(mozilla::NeckoOriginAttributes);
+[ref] native const_OriginAttributesRef(const mozilla::NeckoOriginAttributes);
 
 /**
  * nsISocketTransport
  *
  * NOTE: Connection setup is triggered by opening an input or output stream,
  * it does not start on its own. Completion of the connection setup is
  * indicated by a STATUS_CONNECTED_TO notification to the event sink (if set).
  *
@@ -40,22 +43,30 @@ interface nsISocketTransport : nsITransp
 
     /**
      * Get the port for the underlying socket connection.
      * For Unix domain sockets, this is zero.
      */
     readonly attribute long port;
 
     /**
-     * This is only non-empty when "privacy.firstparty.isolate" is enabled.
-     * It is used to create sockets, and will eventually be used to isolate
-     * OCSP cache. It's the only way to carry it down to NSPR layers which are
-     * final consumers.  It must be set before the socket transport is built.
+     * The origin attributes are used to create sockets.  The first party domain
+     * will eventually be used to isolate OCSP cache and is only non-empty when
+     * "privacy.firstparty.isolate" is enabled.  Setting this is the only way to
+     * carry origin attributes down to NSPR layers which are final consumers.
+     * It must be set before the socket transport is built.
      */
-    attribute AUTF8String firstPartyDomain;
+    [implicit_jscontext, binaryname(ScriptableOriginAttributes)]
+    attribute jsval originAttributes;
+
+    [noscript, nostdcall, binaryname(GetOriginAttributes)]
+    NeckoOriginAttributes binaryGetOriginAttributes();
+
+    [noscript, nostdcall, binaryname(SetOriginAttributes)]
+    void binarySetOriginAttributes(in const_OriginAttributesRef aOriginAttrs);
 
     /**
      * The platform-specific network interface id that this socket
      * associated with. Note that this attribute can be only accessed
      * in the socket thread.
      */
     attribute ACString networkInterfaceId;
 
--- a/netwerk/base/nsSocketTransport2.cpp
+++ b/netwerk/base/nsSocketTransport2.cpp
@@ -17,16 +17,17 @@
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "plstr.h"
 #include "prerr.h"
 #include "NetworkActivityMonitor.h"
 #include "NSSErrorsService.h"
+#include "mozilla/dom/ToJSValue.h"
 #include "mozilla/net/NeckoChild.h"
 #include "nsThreadUtils.h"
 #include "nsISocketProviderService.h"
 #include "nsISocketProvider.h"
 #include "nsISSLSocketControl.h"
 #include "nsIPipe.h"
 #include "nsIClassInfoImpl.h"
 #include "nsURLHelper.h"
@@ -1164,32 +1165,32 @@ nsSocketTransport::BuildSocket(PRFileDes
                 // service to allocate a new socket
 
                 // when https proxying we want to just connect to the proxy as if
                 // it were the end host (i.e. expect the proxy's cert)
 
                 rv = provider->NewSocket(mNetAddr.raw.family,
                                          mHttpsProxy ? mProxyHost.get() : host,
                                          mHttpsProxy ? mProxyPort : port,
-                                         proxyInfo, mFirstPartyDomain,
+                                         proxyInfo, mOriginAttributes,
                                          controlFlags, &fd,
                                          getter_AddRefs(secinfo));
 
                 if (NS_SUCCEEDED(rv) && !fd) {
                     NS_NOTREACHED("NewSocket succeeded but failed to create a PRFileDesc");
                     rv = NS_ERROR_UNEXPECTED;
                 }
             }
             else {
                 // the socket has already been allocated, 
                 // so we just want the service to add itself
                 // to the stack (such as pushing an io layer)
                 rv = provider->AddToSocket(mNetAddr.raw.family,
                                            host, port, proxyInfo,
-                                           mFirstPartyDomain, controlFlags, fd,
+                                           mOriginAttributes, controlFlags, fd,
                                            getter_AddRefs(secinfo));
             }
 
             // controlFlags = 0; not used below this point...
             if (NS_FAILED(rv))
                 break;
 
             // if the service was ssl or starttls, we want to hold onto the socket info
@@ -2386,29 +2387,56 @@ NS_IMETHODIMP
 nsSocketTransport::SetNetworkInterfaceId(const nsACString_internal &aNetworkInterfaceId)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     mNetworkInterfaceId = aNetworkInterfaceId;
     return NS_OK;
 }
 
 NS_IMETHODIMP
-nsSocketTransport::GetFirstPartyDomain(nsACString &value)
+nsSocketTransport::GetScriptableOriginAttributes(JSContext* aCx,
+    JS::MutableHandle<JS::Value> aOriginAttributes)
 {
-    value = mFirstPartyDomain;
+    if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aOriginAttributes))) {
+        return NS_ERROR_FAILURE;
+    }
     return NS_OK;
 }
 
 NS_IMETHODIMP
-nsSocketTransport::SetFirstPartyDomain(const nsACString &value)
+nsSocketTransport::SetScriptableOriginAttributes(JSContext* aCx,
+    JS::Handle<JS::Value> aOriginAttributes)
 {
     MutexAutoLock lock(mLock);
     NS_ENSURE_FALSE(mFD.IsInitialized(), NS_ERROR_FAILURE);
 
-    mFirstPartyDomain = value;
+    NeckoOriginAttributes attrs;
+    if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+        return NS_ERROR_INVALID_ARG;
+    }
+
+    mOriginAttributes = attrs;
+    return NS_OK;
+}
+
+nsresult
+nsSocketTransport::GetOriginAttributes(NeckoOriginAttributes* aOriginAttributes)
+{
+    NS_ENSURE_ARG(aOriginAttributes);
+    *aOriginAttributes = mOriginAttributes;
+    return NS_OK;
+}
+
+nsresult
+nsSocketTransport::SetOriginAttributes(const NeckoOriginAttributes& aOriginAttributes)
+{
+    MutexAutoLock lock(mLock);
+    NS_ENSURE_FALSE(mFD.IsInitialized(), NS_ERROR_FAILURE);
+
+    mOriginAttributes = aOriginAttributes;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSocketTransport::GetPeerAddr(NetAddr *addr)
 {
     // once we are in the connected state, mNetAddr will not change.
     // so if we can verify that we are in the connected state, then
--- a/netwerk/base/nsSocketTransport2.h
+++ b/netwerk/base/nsSocketTransport2.h
@@ -298,21 +298,22 @@ private:
     nsCOMPtr<nsIProxyInfo> mProxyInfo;
     uint16_t     mProxyPort;
     uint16_t     mOriginPort;
     bool mProxyTransparent;
     bool mProxyTransparentResolvesHost;
     bool mHttpsProxy;
     uint32_t     mConnectionFlags;
 
-    // This is only non-empty when "privacy.firstparty.isolate" is enabled.
-    // It is used to create sockets. It's the only way to carry it down to NSPR
-    // layers which are final consumers.  It must be set before the socket
-    // transport is built.
-    nsCString    mFirstPartyDomain;
+    // The origin attributes are used to create sockets.  The first party domain
+    // will eventually be used to isolate OCSP cache and is only non-empty when
+    // "privacy.firstparty.isolate" is enabled.  Setting this is the only way to
+    // carry origin attributes down to NSPR layers which are final consumers.
+    // It must be set before the socket transport is built.
+    NeckoOriginAttributes mOriginAttributes;
     
     uint16_t         SocketPort() { return (!mProxyHost.IsEmpty() && !mProxyTransparent) ? mProxyPort : mPort; }
     const nsCString &SocketHost() { return (!mProxyHost.IsEmpty() && !mProxyTransparent) ? mProxyHost : mHost; }
 
     //-------------------------------------------------------------------------
     // members accessible only on the socket transport thread:
     //  (the exception being initialization/shutdown time)
     //-------------------------------------------------------------------------
--- a/netwerk/protocol/http/TunnelUtils.cpp
+++ b/netwerk/protocol/http/TunnelUtils.cpp
@@ -72,18 +72,19 @@ TLSFilterTransaction::TLSFilterTransacti
     sLayerMethods.close = FilterClose;
     sLayerMethodsPtr = &sLayerMethods;
   }
 
   mFD = PR_CreateIOLayerStub(sLayerIdentity, &sLayerMethods);
 
   if (provider && mFD) {
     mFD->secret = reinterpret_cast<PRFilePrivate *>(this);
-    provider->AddToSocket(PR_AF_INET, aTLSHost, aTLSPort, nullptr, EmptyCString(),
-                          0, mFD, getter_AddRefs(mSecInfo));
+    provider->AddToSocket(PR_AF_INET, aTLSHost, aTLSPort, nullptr,
+                          NeckoOriginAttributes(), 0, mFD,
+                          getter_AddRefs(mSecInfo));
   }
 
   if (mTransaction) {
     nsCOMPtr<nsIInterfaceRequestor> callbacks;
     mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
     nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(mSecInfo));
     if (secCtrl) {
       secCtrl->SetNotificationCallbacks(callbacks);
@@ -1588,18 +1589,42 @@ FWD_TS_ADDREF(GetScriptablePeerAddr, nsI
 FWD_TS_ADDREF(GetScriptableSelfAddr, nsINetAddr);
 FWD_TS_ADDREF(GetSecurityInfo, nsISupports);
 FWD_TS_ADDREF(GetSecurityCallbacks, nsIInterfaceRequestor);
 FWD_TS_PTR(IsAlive, bool);
 FWD_TS_PTR(GetConnectionFlags, uint32_t);
 FWD_TS(SetConnectionFlags, uint32_t);
 FWD_TS_PTR(GetRecvBufferSize, uint32_t);
 FWD_TS(SetRecvBufferSize, uint32_t);
-FWD_TS(SetFirstPartyDomain, const nsACString&);
-FWD_TS(GetFirstPartyDomain, nsACString&);
+
+nsresult
+SocketTransportShim::GetOriginAttributes(mozilla::NeckoOriginAttributes* aOriginAttributes)
+{
+  return mWrapped->GetOriginAttributes(aOriginAttributes);
+}
+
+nsresult
+SocketTransportShim::SetOriginAttributes(const mozilla::NeckoOriginAttributes& aOriginAttributes)
+{
+  return mWrapped->SetOriginAttributes(aOriginAttributes);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::GetScriptableOriginAttributes(JSContext* aCx,
+  JS::MutableHandle<JS::Value> aOriginAttributes)
+{
+  return mWrapped->GetScriptableOriginAttributes(aCx, aOriginAttributes);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetScriptableOriginAttributes(JSContext* aCx,
+  JS::Handle<JS::Value> aOriginAttributes)
+{
+  return mWrapped->SetScriptableOriginAttributes(aCx, aOriginAttributes);
+}
 
 NS_IMETHODIMP
 SocketTransportShim::GetHost(nsACString & aHost)
 {
   return mWrapped->GetHost(aHost);
 }
 
 NS_IMETHODIMP
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -3076,20 +3076,20 @@ nsHalfOpenSocket::SetupStreams(nsISocket
     }
 
     if (!Allow1918()) {
         tmpFlags |= nsISocketTransport::DISABLE_RFC1918;
     }
 
     socketTransport->SetConnectionFlags(tmpFlags);
 
-    nsAutoCString firstPartyDomain =
-      NS_ConvertUTF16toUTF8(mEnt->mConnInfo->GetOriginAttributes().mFirstPartyDomain);
-    if (!firstPartyDomain.IsEmpty()) {
-        socketTransport->SetFirstPartyDomain(firstPartyDomain);
+    NeckoOriginAttributes originAttributes =
+        mEnt->mConnInfo->GetOriginAttributes();
+    if (originAttributes != NeckoOriginAttributes()) {
+        socketTransport->SetOriginAttributes(originAttributes);
     }
 
     socketTransport->SetQoSBits(gHttpHandler->GetQoSBits());
 
     if (!ci->GetNetworkInterfaceId().IsEmpty()) {
         socketTransport->SetNetworkInterfaceId(ci->GetNetworkInterfaceId());
     }
 
--- a/netwerk/socket/nsISocketProvider.idl
+++ b/netwerk/socket/nsISocketProvider.idl
@@ -2,16 +2,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIProxyInfo;
 [ptr] native PRFileDescStar(struct PRFileDesc);
+native NeckoOriginAttributes(mozilla::NeckoOriginAttributes);
+[ref] native const_OriginAttributesRef(const mozilla::NeckoOriginAttributes);
+
+%{ C++
+#include "mozilla/BasePrincipal.h"
+%}
 
 /**
  * nsISocketProvider
  */
 [scriptable, uuid(508d5469-9e1e-4a08-b5b0-7cfebba1e51a)]
 interface nsISocketProvider : nsISupports
 {
     /**
@@ -31,44 +37,44 @@ interface nsISocketProvider : nsISupport
      *        Control flags that govern this connection (see below.)
      * @param aFileDesc
      *        The resulting PRFileDesc.
      * @param aSecurityInfo
      *        Any security info that should be associated with aFileDesc.  This
      *        object typically implements nsITransportSecurityInfo.
      */
     [noscript]
-    void newSocket(in long            aFamily,
-                   in string          aHost, 
-                   in long            aPort,
-                   in nsIProxyInfo    aProxy,
-                   in ACString        aFirstPartyDomain,
-                   in unsigned long   aFlags,
-                   out PRFileDescStar aFileDesc, 
-                   out nsISupports    aSecurityInfo);
+    void newSocket(in long                      aFamily,
+                   in string                    aHost, 
+                   in long                      aPort,
+                   in nsIProxyInfo              aProxy,
+                   in const_OriginAttributesRef aOriginAttributes,
+                   in unsigned long             aFlags,
+                   out PRFileDescStar           aFileDesc, 
+                   out nsISupports              aSecurityInfo);
 
     /**
      * addToSocket
      *
      * This function is called to allow the socket provider to layer a
      * PRFileDesc on top of another PRFileDesc.  For example, SSL via a SOCKS
      * proxy.
      *
      * Parameters are the same as newSocket with the exception of aFileDesc,
      * which is an in-param instead.
      */
     [noscript]
-    void addToSocket(in long           aFamily,
-                     in string         aHost, 
-                     in long           aPort,
-                     in nsIProxyInfo   aProxy,
-                     in ACString       aFirstPartyDomain,
-                     in unsigned long  aFlags,
-                     in PRFileDescStar aFileDesc, 
-                     out nsISupports   aSecurityInfo);
+    void addToSocket(in long                      aFamily,
+                     in string                    aHost, 
+                     in long                      aPort,
+                     in nsIProxyInfo              aProxy,
+                     in const_OriginAttributesRef aOriginAttributes,
+                     in unsigned long             aFlags,
+                     in PRFileDescStar            aFileDesc, 
+                     out nsISupports              aSecurityInfo);
 
     /**
      * PROXY_RESOLVES_HOST
      *
      * This flag is set if the proxy is to perform hostname resolution instead
      * of the client.  When set, the hostname parameter passed when in this
      * interface will be used instead of the address structure passed for a
      * later connect et al. request.
--- a/netwerk/socket/nsSOCKSSocketProvider.cpp
+++ b/netwerk/socket/nsSOCKSSocketProvider.cpp
@@ -6,16 +6,18 @@
 
 #include "nsIServiceManager.h"
 #include "nsNamedPipeIOLayer.h"
 #include "nsSOCKSSocketProvider.h"
 #include "nsSOCKSIOLayer.h"
 #include "nsCOMPtr.h"
 #include "nsError.h"
 
+using mozilla::NeckoOriginAttributes;
+
 //////////////////////////////////////////////////////////////////////////
 
 NS_IMPL_ISUPPORTS(nsSOCKSSocketProvider, nsISocketProvider)
 
 nsresult
 nsSOCKSSocketProvider::CreateV4(nsISupports *aOuter, REFNSIID aIID, void **aResult)
 {
     nsresult rv;
@@ -41,17 +43,17 @@ nsSOCKSSocketProvider::CreateV5(nsISuppo
     return rv;
 }
 
 NS_IMETHODIMP
 nsSOCKSSocketProvider::NewSocket(int32_t family,
                                  const char *host, 
                                  int32_t port,
                                  nsIProxyInfo *proxy,
-                                 const nsACString &firstPartyDomain,
+                                 const NeckoOriginAttributes &originAttributes,
                                  uint32_t flags,
                                  PRFileDesc **result,
                                  nsISupports **socksInfo)
 {
     PRFileDesc *sock;
 
 #if defined(XP_WIN)
     nsAutoCString proxyHost;
@@ -83,17 +85,17 @@ nsSOCKSSocketProvider::NewSocket(int32_t
     return NS_ERROR_SOCKET_CREATE_FAILED;
 }
 
 NS_IMETHODIMP
 nsSOCKSSocketProvider::AddToSocket(int32_t family,
                                    const char *host,
                                    int32_t port,
                                    nsIProxyInfo *proxy,
-                                   const nsACString &firstPartyDomain,
+                                   const NeckoOriginAttributes &originAttributes,
                                    uint32_t flags,
                                    PRFileDesc *sock,
                                    nsISupports **socksInfo)
 {
     nsresult rv = nsSOCKSIOLayerAddToSocket(family,
                                             host, 
                                             port,
                                             proxy,
--- a/netwerk/socket/nsUDPSocketProvider.cpp
+++ b/netwerk/socket/nsUDPSocketProvider.cpp
@@ -1,28 +1,30 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsUDPSocketProvider.h"
 
 #include "nspr.h"
 
+using mozilla::NeckoOriginAttributes;
+
 NS_IMPL_ISUPPORTS(nsUDPSocketProvider, nsISocketProvider)
 
 nsUDPSocketProvider::~nsUDPSocketProvider()
 {
 }
 
 NS_IMETHODIMP 
 nsUDPSocketProvider::NewSocket(int32_t aFamily,
                                const char *aHost, 
                                int32_t aPort, 
                                nsIProxyInfo *aProxy,
-                               const nsACString &firstPartyDomain,
+                               const NeckoOriginAttributes &originAttributes,
                                uint32_t aFlags,
                                PRFileDesc * *aFileDesc, 
                                nsISupports **aSecurityInfo)
 {
     NS_ENSURE_ARG_POINTER(aFileDesc);
   
     PRFileDesc* udpFD = PR_OpenUDPSocket(aFamily);
     if (!udpFD)
@@ -32,17 +34,17 @@ nsUDPSocketProvider::NewSocket(int32_t a
     return NS_OK;
 }
 
 NS_IMETHODIMP 
 nsUDPSocketProvider::AddToSocket(int32_t aFamily,
                                  const char *aHost,
                                  int32_t aPort,
                                  nsIProxyInfo *aProxy,
-                                 const nsACString &firstPartyDomain,
+                                 const NeckoOriginAttributes &originAttributes,
                                  uint32_t aFlags,
                                  struct PRFileDesc * aFileDesc,
                                  nsISupports **aSecurityInfo)
 {
     // does not make sense to strap a UDP socket onto an existing socket
     NS_NOTREACHED("Cannot layer UDP socket on an existing socket");
     return NS_ERROR_UNEXPECTED;
 }
--- a/python/mozbuild/mozbuild/action/preprocessor.py
+++ b/python/mozbuild/mozbuild/action/preprocessor.py
@@ -4,15 +4,21 @@
 
 from __future__ import absolute_import
 
 import sys
 
 from mozbuild.preprocessor import Preprocessor
 
 
+def generate(output, *args):
+    pp = Preprocessor()
+    pp.out = output
+    pp.handleCommandLine(list(args), True)
+    return set(pp.includes)
+
 def main(args):
-  pp = Preprocessor()
-  pp.handleCommandLine(args, True)
+    pp = Preprocessor()
+    pp.handleCommandLine(args, True)
 
 
 if __name__ == "__main__":
   main(sys.argv[1:])
--- a/python/mozbuild/mozbuild/backend/recursivemake.py
+++ b/python/mozbuild/mozbuild/backend/recursivemake.py
@@ -586,28 +586,28 @@ class RecursiveMakeBackend(CommonBackend
         elif isinstance(obj, StaticLibrary):
             self._process_static_library(obj, backend_file)
             self._process_linked_libraries(obj, backend_file)
 
         elif isinstance(obj, HostLibrary):
             self._process_host_library(obj, backend_file)
             self._process_linked_libraries(obj, backend_file)
 
+        elif isinstance(obj, ObjdirFiles):
+            self._process_objdir_files(obj, obj.files, backend_file)
+
+        elif isinstance(obj, ObjdirPreprocessedFiles):
+            self._process_final_target_pp_files(obj, obj.files, backend_file, 'OBJDIR_PP_FILES')
+
         elif isinstance(obj, FinalTargetFiles):
             self._process_final_target_files(obj, obj.files, backend_file)
 
         elif isinstance(obj, FinalTargetPreprocessedFiles):
             self._process_final_target_pp_files(obj, obj.files, backend_file, 'DIST_FILES')
 
-        elif isinstance(obj, ObjdirFiles):
-            self._process_objdir_files(obj, obj.files, backend_file)
-
-        elif isinstance(obj, ObjdirPreprocessedFiles):
-            self._process_final_target_pp_files(obj, obj.files, backend_file, 'OBJDIR_PP_FILES')
-
         elif isinstance(obj, AndroidResDirs):
             # Order matters.
             for p in obj.paths:
                 backend_file.write('ANDROID_RES_DIRS += %s\n' % p.full_path)
 
         elif isinstance(obj, AndroidAssetsDirs):
             # Order matters.
             for p in obj.paths:
@@ -1040,18 +1040,19 @@ class RecursiveMakeBackend(CommonBackend
         else:
             backend_file.write('SIMPLE_PROGRAMS += %s\n' % obj.program)
 
     def _process_host_simple_program(self, program, backend_file):
         backend_file.write('HOST_SIMPLE_PROGRAMS += %s\n' % program)
 
     def _process_test_manifest(self, obj, backend_file):
         # Much of the logic in this function could be moved to CommonBackend.
-        self.backend_input_files.add(mozpath.join(obj.topsrcdir,
-            obj.manifest_relpath))
+        for source in obj.source_relpaths:
+            self.backend_input_files.add(mozpath.join(obj.topsrcdir,
+                source))
 
         # Don't allow files to be defined multiple times unless it is allowed.
         # We currently allow duplicates for non-test files or test files if
         # the manifest is listed as a duplicate.
         for source, (dest, is_test) in obj.installs.items():
             try:
                 self._install_manifests['_test_files'].add_symlink(source, dest)
             except ValueError:
--- a/python/mozbuild/mozbuild/frontend/context.py
+++ b/python/mozbuild/mozbuild/frontend/context.py
@@ -958,23 +958,25 @@ VARIABLES = {
         This variable contains a list of source code files to compile,
         that can be concatenated all together and built as a single source
         file. This can help make the build faster and reduce the debug info
         size.
         """),
 
     'GENERATED_FILES': (StrictOrderingOnAppendListWithFlagsFactory({
                 'script': unicode,
-                'inputs': list }), list,
+                'inputs': list,
+                'flags': list, }), list,
         """Generic generated files.
 
         This variable contains a list of files for the build system to
         generate at export time. The generation method may be declared
-        with optional ``script`` and ``inputs`` flags on individual entries.
-        If the optional ``script`` flag is not present on an entry, it
+        with optional ``script``, ``inputs`` and ``flags`` attributes on
+        individual entries.
+        If the optional ``script`` attribute is not present on an entry, it
         is assumed that rules for generating the file are present in
         the associated Makefile.in.
 
         Example::
 
            GENERATED_FILES += ['bar.c', 'baz.c', 'foo.c']
            bar = GENERATED_FILES['bar.c']
            bar.script = 'generate.py'
@@ -998,16 +1000,19 @@ VARIABLES = {
         into ``script`` can be specified::
 
           GENERATED_FILES += ['bar.c']
           bar = GENERATED_FILES['bar.c']
           bar.script = 'generate.py:make_bar'
 
         The chosen script entry point may optionally return a set of strings,
         indicating extra files the output depends on.
+
+        When the ``flags`` attribute is present, the given list of flags is
+        passed as extra arguments following the inputs.
         """),
 
     'DEFINES': (InitializedDefines, dict,
         """Dictionary of compiler defines to declare.
 
         These are passed in to the compiler as ``-Dkey='value'`` for string
         values, ``-Dkey=value`` for numeric values, or ``-Dkey`` if the
         value is True. Note that for string values, the outer-level of
--- a/python/mozbuild/mozbuild/frontend/data.py
+++ b/python/mozbuild/mozbuild/frontend/data.py
@@ -636,34 +636,39 @@ class TestManifest(ContextDerived):
         'tests',
 
         # The relative path of the parsed manifest within the srcdir.
         'manifest_relpath',
 
         # The relative path of the parsed manifest within the objdir.
         'manifest_obj_relpath',
 
+        # The relative paths to all source files for this manifest.
+        'source_relpaths',
+
         # If this manifest is a duplicate of another one, this is the
         # manifestparser.TestManifest of the other one.
         'dupe_manifest',
     )
 
     def __init__(self, context, path, manifest, flavor=None,
-            install_prefix=None, relpath=None, dupe_manifest=False):
+            install_prefix=None, relpath=None, sources=(),
+            dupe_manifest=False):
         ContextDerived.__init__(self, context)
 
         assert flavor in all_test_flavors()
 
         self.path = path
         self.directory = mozpath.dirname(path)
         self.manifest = manifest
         self.flavor = flavor
         self.install_prefix = install_prefix
         self.manifest_relpath = relpath
         self.manifest_obj_relpath = relpath
+        self.source_relpaths = sources
         self.dupe_manifest = dupe_manifest
         self.installs = {}
         self.pattern_installs = []
         self.tests = []
         self.external_installs = set()
         self.deferred_installs = set()
 
 
@@ -881,41 +886,29 @@ class FinalTargetPreprocessedFiles(Conte
     """
     __slots__ = ('files')
 
     def __init__(self, sandbox, files):
         ContextDerived.__init__(self, sandbox)
         self.files = files
 
 
-class ObjdirFiles(ContextDerived):
+class ObjdirFiles(FinalTargetFiles):
     """Sandbox container object for OBJDIR_FILES, which is a
     HierarchicalStringList.
     """
-    __slots__ = ('files')
-
-    def __init__(self, sandbox, files):
-        ContextDerived.__init__(self, sandbox)
-        self.files = files
-
     @property
     def install_target(self):
         return ''
 
 
-class ObjdirPreprocessedFiles(ContextDerived):
+class ObjdirPreprocessedFiles(FinalTargetPreprocessedFiles):
     """Sandbox container object for OBJDIR_PP_FILES, which is a
     HierarchicalStringList.
     """
-    __slots__ = ('files')
-
-    def __init__(self, sandbox, files):
-        ContextDerived.__init__(self, sandbox)
-        self.files = files
-
     @property
     def install_target(self):
         return ''
 
 
 class TestHarnessFiles(FinalTargetFiles):
     """Sandbox container object for TEST_HARNESS_FILES,
     which is a HierarchicalStringList.
--- a/python/mozbuild/mozbuild/frontend/emitter.py
+++ b/python/mozbuild/mozbuild/frontend/emitter.py
@@ -123,16 +123,17 @@ ALLOWED_XPCOM_GLUE = {
     ('testcrasher', 'toolkit/crashreporter/test'),
     ('mediaconduit_unittests', 'media/webrtc/signaling/test'),
     ('mediapipeline_unittest', 'media/webrtc/signaling/test'),
     ('sdp_file_parser', 'media/webrtc/signaling/fuzztest'),
     ('signaling_unittests', 'media/webrtc/signaling/test'),
     ('TestMailCookie', 'mailnews/base/test'),
     ('calbasecomps', 'calendar/base/backend/libical/build'),
     ('purplexpcom', 'extensions/purple/purplexpcom/src'),
+    ('ipdlunittest', 'ipc/ipdl/test/cxx/app'),
 }
 
 
 class TreeMetadataEmitter(LoggingMixin):
     """Converts the executed mozbuild files into data structures.
 
     This is a bridge between reader.py and data.py. It takes what was read by
     reader.BuildReader and converts it into the classes defined in the data
@@ -1223,17 +1224,18 @@ class TreeMetadataEmitter(LoggingMixin):
                             not os.path.exists(p.full_path)):
                         raise SandboxValidationError(
                             'Input for generating %s does not exist: %s'
                             % (f, p.full_path), context)
                     inputs.append(p)
             else:
                 script = None
                 method = None
-            yield GeneratedFile(context, script, method, outputs, inputs)
+            yield GeneratedFile(context, script, method, outputs, inputs,
+                                flags.flags)
 
     def _process_test_manifests(self, context):
         for prefix, info in TEST_MANIFESTS.items():
             for path, manifest in context.get('%s_MANIFESTS' % prefix, []):
                 for obj in self._process_test_manifest(context, info, path, manifest):
                     yield obj
 
         for flavor in REFTEST_FLAVORS:
@@ -1248,27 +1250,30 @@ class TreeMetadataEmitter(LoggingMixin):
 
     def _process_test_manifest(self, context, info, manifest_path, mpmanifest):
         flavor, install_root, install_subdir, package_tests = info
 
         path = mozpath.normpath(mozpath.join(context.srcdir, manifest_path))
         manifest_dir = mozpath.dirname(path)
         manifest_reldir = mozpath.dirname(mozpath.relpath(path,
             context.config.topsrcdir))
+        manifest_sources = [mozpath.relpath(pth, context.config.topsrcdir)
+                            for pth in mpmanifest.source_files]
         install_prefix = mozpath.join(install_root, install_subdir)
 
         try:
             if not mpmanifest.tests:
                 raise SandboxValidationError('Empty test manifest: %s'
                     % path, context)
 
             defaults = mpmanifest.manifest_defaults[os.path.normpath(path)]
             obj = TestManifest(context, path, mpmanifest, flavor=flavor,
                 install_prefix=install_prefix,
                 relpath=mozpath.join(manifest_reldir, mozpath.basename(path)),
+                sources=manifest_sources,
                 dupe_manifest='dupe-manifest' in defaults)
 
             filtered = mpmanifest.tests
 
             # Jetpack add-on tests are expected to be generated during the
             # build process so they won't exist here.
             if flavor != 'jetpack-addon':
                 missing = [t['name'] for t in filtered if not os.path.exists(t['path'])]
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -323,17 +323,17 @@ static const unsigned int MIN_RSA_BITS_W
 
 Result
 CertVerifier::VerifyCert(CERTCertificate* cert, SECCertificateUsage usage,
                          Time time, void* pinArg, const char* hostname,
                  /*out*/ UniqueCERTCertList& builtChain,
             /*optional*/ const Flags flags,
             /*optional*/ const SECItem* stapledOCSPResponseSECItem,
             /*optional*/ const SECItem* sctsFromTLSSECItem,
-            /*optional*/ const char* firstPartyDomain,
+            /*optional*/ const NeckoOriginAttributes& originAttributes,
         /*optional out*/ SECOidTag* evOidPolicy,
         /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus,
         /*optional out*/ KeySizeStatus* keySizeStatus,
         /*optional out*/ SHA1ModeResult* sha1ModeResult,
         /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo,
         /*optional out*/ CertificateTransparencyInfo* ctInfo)
 {
   MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("Top of VerifyCert\n"));
@@ -417,17 +417,17 @@ CertVerifier::VerifyCert(CERTCertificate
       // just use trustEmail as it is the closest alternative.
       NSSCertDBTrustDomain trustDomain(trustEmail, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
                                        SHA1Mode::Allowed,
                                        NetscapeStepUpPolicy::NeverMatch,
-                                       firstPartyDomain,
+                                       originAttributes,
                                        builtChain, nullptr, nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::digitalSignature,
                           KeyPurposeId::id_kp_clientAuth,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
@@ -491,17 +491,17 @@ CertVerifier::VerifyCert(CERTCertificate
         }
 
         NSSCertDBTrustDomain
           trustDomain(trustSSL, evOCSPFetching,
                       mOCSPCache, pinArg, ocspGETConfig,
                       mCertShortLifetimeInDays, mPinningMode, MIN_RSA_BITS,
                       ValidityCheckingMode::CheckForEV,
                       sha1ModeConfigurations[i], mNetscapeStepUpPolicy,
-                      firstPartyDomain, builtChain, pinningTelemetryInfo,
+                      originAttributes, builtChain, pinningTelemetryInfo,
                       hostname);
         rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time,
                                           KeyUsage::digitalSignature,// (EC)DHE
                                           KeyUsage::keyEncipherment, // RSA
                                           KeyUsage::keyAgreement,    // (EC)DH
                                           KeyPurposeId::id_kp_serverAuth,
                                           evPolicy, stapledOCSPResponse,
                                           ocspStaplingStatus);
@@ -579,17 +579,17 @@ CertVerifier::VerifyCert(CERTCertificate
 
           NSSCertDBTrustDomain trustDomain(trustSSL, defaultOCSPFetching,
                                            mOCSPCache, pinArg, ocspGETConfig,
                                            mCertShortLifetimeInDays,
                                            mPinningMode, keySizeOptions[i],
                                            ValidityCheckingMode::CheckingOff,
                                            sha1ModeConfigurations[j],
                                            mNetscapeStepUpPolicy,
-                                           firstPartyDomain, builtChain,
+                                           originAttributes, builtChain,
                                            pinningTelemetryInfo, hostname);
           rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time,
                                             KeyUsage::digitalSignature,//(EC)DHE
                                             KeyUsage::keyEncipherment,//RSA
                                             KeyUsage::keyAgreement,//(EC)DH
                                             KeyPurposeId::id_kp_serverAuth,
                                             CertPolicyId::anyPolicy,
                                             stapledOCSPResponse,
@@ -644,34 +644,34 @@ CertVerifier::VerifyCert(CERTCertificate
 
     case certificateUsageSSLCA: {
       NSSCertDBTrustDomain trustDomain(trustSSL, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
                                        SHA1Mode::Allowed, mNetscapeStepUpPolicy,
-                                       firstPartyDomain, builtChain, nullptr,
+                                       originAttributes, builtChain, nullptr,
                                        nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeCA, KeyUsage::keyCertSign,
                           KeyPurposeId::id_kp_serverAuth,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
 
     case certificateUsageEmailSigner: {
       NSSCertDBTrustDomain trustDomain(trustEmail, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
                                        SHA1Mode::Allowed,
                                        NetscapeStepUpPolicy::NeverMatch,
-                                       firstPartyDomain, builtChain, nullptr,
+                                       originAttributes, builtChain, nullptr,
                                        nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::digitalSignature,
                           KeyPurposeId::id_kp_emailProtection,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
         rv = BuildCertChain(trustDomain, certDER, time,
@@ -689,17 +689,17 @@ CertVerifier::VerifyCert(CERTCertificate
       // based on the result of the verification(s).
       NSSCertDBTrustDomain trustDomain(trustEmail, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
                                        SHA1Mode::Allowed,
                                        NetscapeStepUpPolicy::NeverMatch,
-                                       firstPartyDomain, builtChain, nullptr,
+                                       originAttributes, builtChain, nullptr,
                                        nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::keyEncipherment, // RSA
                           KeyPurposeId::id_kp_emailProtection,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
         rv = BuildCertChain(trustDomain, certDER, time,
@@ -714,17 +714,17 @@ CertVerifier::VerifyCert(CERTCertificate
     case certificateUsageObjectSigner: {
       NSSCertDBTrustDomain trustDomain(trustObjectSigning, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
                                        SHA1Mode::Allowed,
                                        NetscapeStepUpPolicy::NeverMatch,
-                                       firstPartyDomain, builtChain, nullptr,
+                                       originAttributes, builtChain, nullptr,
                                        nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::digitalSignature,
                           KeyPurposeId::id_kp_codeSigning,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
@@ -748,45 +748,45 @@ CertVerifier::VerifyCert(CERTCertificate
       }
 
       NSSCertDBTrustDomain sslTrust(trustSSL, defaultOCSPFetching, mOCSPCache,
                                     pinArg, ocspGETConfig, mCertShortLifetimeInDays,
                                     pinningDisabled, MIN_RSA_BITS_WEAK,
                                     ValidityCheckingMode::CheckingOff,
                                     SHA1Mode::Allowed,
                                     NetscapeStepUpPolicy::NeverMatch,
-                                    firstPartyDomain, builtChain, nullptr,
+                                    originAttributes, builtChain, nullptr,
                                     nullptr);
       rv = BuildCertChain(sslTrust, certDER, time, endEntityOrCA,
                           keyUsage, eku, CertPolicyId::anyPolicy,
                           stapledOCSPResponse);
       if (rv == Result::ERROR_UNKNOWN_ISSUER) {
         NSSCertDBTrustDomain emailTrust(trustEmail, defaultOCSPFetching,
                                         mOCSPCache, pinArg, ocspGETConfig,
                                         mCertShortLifetimeInDays,
                                         pinningDisabled, MIN_RSA_BITS_WEAK,
                                         ValidityCheckingMode::CheckingOff,
                                         SHA1Mode::Allowed,
                                         NetscapeStepUpPolicy::NeverMatch,
-                                        firstPartyDomain, builtChain, nullptr,
+                                        originAttributes, builtChain, nullptr,
                                         nullptr);
         rv = BuildCertChain(emailTrust, certDER, time, endEntityOrCA,
                             keyUsage, eku, CertPolicyId::anyPolicy,
                             stapledOCSPResponse);
         if (rv == Result::ERROR_UNKNOWN_ISSUER) {
           NSSCertDBTrustDomain objectSigningTrust(trustObjectSigning,
                                                   defaultOCSPFetching, mOCSPCache,
                                                   pinArg, ocspGETConfig,
                                                   mCertShortLifetimeInDays,
                                                   pinningDisabled,
                                                   MIN_RSA_BITS_WEAK,
                                                   ValidityCheckingMode::CheckingOff,
                                                   SHA1Mode::Allowed,
                                                   NetscapeStepUpPolicy::NeverMatch,
-                                                  firstPartyDomain, builtChain,
+                                                  originAttributes, builtChain,
                                                   nullptr, nullptr);
           rv = BuildCertChain(objectSigningTrust, certDER, time,
                               endEntityOrCA, keyUsage, eku,
                               CertPolicyId::anyPolicy, stapledOCSPResponse);
         }
       }
 
       break;
@@ -808,17 +808,17 @@ CertVerifier::VerifySSLServerCert(const 
                      /*optional*/ const SECItem* stapledOCSPResponse,
                      /*optional*/ const SECItem* sctsFromTLS,
                                   Time time,
                      /*optional*/ void* pinarg,
                                   const char* hostname,
                           /*out*/ UniqueCERTCertList& builtChain,
                      /*optional*/ bool saveIntermediatesInPermanentDatabase,
                      /*optional*/ Flags flags,
-                     /*optional*/ const char* firstPartyDomain,
+                     /*optional*/ const NeckoOriginAttributes& originAttributes,
                  /*optional out*/ SECOidTag* evOidPolicy,
                  /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus,
                  /*optional out*/ KeySizeStatus* keySizeStatus,
                  /*optional out*/ SHA1ModeResult* sha1ModeResult,
                  /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo,
                  /*optional out*/ CertificateTransparencyInfo* ctInfo)
 {
   PR_ASSERT(peerCert);
@@ -833,17 +833,17 @@ CertVerifier::VerifySSLServerCert(const 
   if (!hostname || !hostname[0]) {
     return Result::ERROR_BAD_CERT_DOMAIN;
   }
 
   // CreateCertErrorRunnable assumes that CheckCertHostname is only called
   // if VerifyCert succeeded.
   Result rv = VerifyCert(peerCert.get(), certificateUsageSSLServer, time,
                          pinarg, hostname, builtChain, flags,
-                         stapledOCSPResponse, sctsFromTLS, firstPartyDomain,
+                         stapledOCSPResponse, sctsFromTLS, originAttributes,
                          evOidPolicy, ocspStaplingStatus, keySizeStatus,
                          sha1ModeResult, pinningTelemetryInfo, ctInfo);
   if (rv != Success) {
     return rv;
   }
 
   Input peerCertInput;
   rv = peerCertInput.Init(peerCert->derCert.data, peerCert->derCert.len);
--- a/security/certverifier/CertVerifier.h
+++ b/security/certverifier/CertVerifier.h
@@ -10,16 +10,34 @@
 #include "BRNameMatchingPolicy.h"
 #include "CTVerifyResult.h"
 #include "OCSPCache.h"
 #include "ScopedNSSTypes.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/UniquePtr.h"
 #include "pkix/pkixtypes.h"
 
+#if defined(_MSC_VER)
+#pragma warning(push)
+// Silence "RootingAPI.h(718): warning C4324: 'js::DispatchWrapper<T>':
+// structure was padded due to alignment specifier with [ T=void * ]"
+#pragma warning(disable:4324)
+// Silence "Value.h(448): warning C4365: 'return': conversion from 'const
+// int32_t' to 'JS::Value::PayloadType', signed/unsigned mismatch"
+#pragma warning(disable:4365)
+// Silence "warning C5031: #pragma warning(pop): likely mismatch, popping
+// warning state pushed in different file 
+#pragma warning(disable:5031)
+#endif /* defined(_MSC_VER) */
+#include "mozilla/BasePrincipal.h"
+#if defined(_MSC_VER)
+#pragma warning(pop) /* popping the pragma in Vector.h */
+#pragma warning(pop) /* popping the pragma in this file */
+#endif /* defined(_MSC_VER) */
+
 namespace mozilla { namespace ct {
 
 // Including MultiLogCTVerifier.h would bring along all of its dependent
 // headers and force us to export them in moz.build. Just forward-declare
 // the class here instead.
 class MultiLogCTVerifier;
 
 } } // namespace mozilla::ct
@@ -104,17 +122,18 @@ public:
                     SECCertificateUsage usage,
                     mozilla::pkix::Time time,
                     void* pinArg,
                     const char* hostname,
             /*out*/ UniqueCERTCertList& builtChain,
                     Flags flags = 0,
     /*optional in*/ const SECItem* stapledOCSPResponse = nullptr,
     /*optional in*/ const SECItem* sctsFromTLS = nullptr,
-    /*optional in*/ const char* firstPartyDomain = nullptr,
+    /*optional in*/ const NeckoOriginAttributes& originAttributes =
+                      NeckoOriginAttributes(),
    /*optional out*/ SECOidTag* evOidPolicy = nullptr,
    /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus = nullptr,
    /*optional out*/ KeySizeStatus* keySizeStatus = nullptr,
    /*optional out*/ SHA1ModeResult* sha1ModeResult = nullptr,
    /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr,
    /*optional out*/ CertificateTransparencyInfo* ctInfo = nullptr);
 
   mozilla::pkix::Result VerifySSLServerCert(
@@ -122,17 +141,18 @@ public:
        /*optional*/ const SECItem* stapledOCSPResponse,
        /*optional*/ const SECItem* sctsFromTLS,
                     mozilla::pkix::Time time,
        /*optional*/ void* pinarg,
                     const char* hostname,
             /*out*/ UniqueCERTCertList& builtChain,
        /*optional*/ bool saveIntermediatesInPermanentDatabase = false,
        /*optional*/ Flags flags = 0,
-       /*optional*/ const char* firstPartyDomain = nullptr,
+       /*optional*/ const NeckoOriginAttributes& originAttributes =
+                      NeckoOriginAttributes(),
    /*optional out*/ SECOidTag* evOidPolicy = nullptr,
    /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus = nullptr,
    /*optional out*/ KeySizeStatus* keySizeStatus = nullptr,
    /*optional out*/ SHA1ModeResult* sha1ModeResult = nullptr,
    /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr,
    /*optional out*/ CertificateTransparencyInfo* ctInfo = nullptr);
 
   enum PinningMode {
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -52,32 +52,32 @@ NSSCertDBTrustDomain::NSSCertDBTrustDoma
              /*optional but shouldn't be*/ void* pinArg,
                                            CertVerifier::OcspGetConfig ocspGETConfig,
                                            uint32_t certShortLifetimeInDays,
                                            CertVerifier::PinningMode pinningMode,
                                            unsigned int minRSABits,
                                            ValidityCheckingMode validityCheckingMode,
                                            CertVerifier::SHA1Mode sha1Mode,
                                            NetscapeStepUpPolicy netscapeStepUpPolicy,
-                                           const char* firstPartyDomain,
+                                           const NeckoOriginAttributes& originAttributes,
                                            UniqueCERTCertList& builtChain,
                               /*optional*/ PinningTelemetryInfo* pinningTelemetryInfo,
                               /*optional*/ const char* hostname)
   : mCertDBTrustType(certDBTrustType)
   , mOCSPFetching(ocspFetching)
   , mOCSPCache(ocspCache)
   , mPinArg(pinArg)
   , mOCSPGetConfig(ocspGETConfig)
   , mCertShortLifetimeInDays(certShortLifetimeInDays)
   , mPinningMode(pinningMode)
   , mMinRSABits(minRSABits)
   , mValidityCheckingMode(validityCheckingMode)
   , mSHA1Mode(sha1Mode)
   , mNetscapeStepUpPolicy(netscapeStepUpPolicy)
-  , mFirstPartyDomain(firstPartyDomain)
+  , mOriginAttributes(originAttributes)
   , mBuiltChain(builtChain)
   , mPinningTelemetryInfo(pinningTelemetryInfo)
   , mHostname(hostname)
   , mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID))
   , mOCSPStaplingStatus(CertVerifier::OCSP_STAPLING_NEVER_CHECKED)
   , mSCTListFromCertificate()
   , mSCTListFromOCSPStapling()
 {
@@ -410,17 +410,17 @@ NSSCertDBTrustDomain::CheckRevocation(En
     // no stapled OCSP response
     mOCSPStaplingStatus = CertVerifier::OCSP_STAPLING_NONE;
     MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
            ("NSSCertDBTrustDomain: no stapled OCSP response"));
   }
 
   Result cachedResponseResult = Success;
   Time cachedResponseValidThrough(Time::uninitialized);
-  bool cachedResponsePresent = mOCSPCache.Get(certID, mFirstPartyDomain,
+  bool cachedResponsePresent = mOCSPCache.Get(certID, mOriginAttributes,
                                               cachedResponseResult,
                                               cachedResponseValidThrough);
   if (cachedResponsePresent) {
     if (cachedResponseResult == Success && cachedResponseValidThrough >= time) {
       MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
              ("NSSCertDBTrustDomain: cached OCSP response: good"));
       return Success;
     }
@@ -553,17 +553,17 @@ NSSCertDBTrustDomain::CheckRevocation(En
     SECItem ocspRequestItem = {
       siBuffer,
       ocspRequest,
       static_cast<unsigned int>(ocspRequestLength)
     };
     // Owned by arena
     SECItem* responseSECItem = nullptr;
     Result tempRV =
-      DoOCSPRequest(arena, url, mFirstPartyDomain, &ocspRequestItem,
+      DoOCSPRequest(arena, url, mOriginAttributes, &ocspRequestItem,
                     OCSPFetchingTypeToTimeoutTime(mOCSPFetching),
                     mOCSPGetConfig == CertVerifier::ocspGetEnabled,
                     responseSECItem);
     MOZ_ASSERT((tempRV != Success) || responseSECItem);
     if (tempRV != Success) {
       rv = tempRV;
     } else if (response.Init(responseSECItem->data, responseSECItem->len)
                  != Success) {
@@ -577,17 +577,17 @@ NSSCertDBTrustDomain::CheckRevocation(En
 
   if (response.GetLength() == 0) {
     Result error = rv;
     if (attemptedRequest) {
       Time timeout(time);
       if (timeout.AddSeconds(ServerFailureDelaySeconds) != Success) {
         return Result::FATAL_ERROR_LIBRARY_FAILURE; // integer overflow
       }
-      rv = mOCSPCache.Put(certID, mFirstPartyDomain, error, time, timeout);
+      rv = mOCSPCache.Put(certID, mOriginAttributes, error, time, timeout);
       if (rv != Success) {
         return rv;
       }
     }
     if (mOCSPFetching != FetchOCSPForDVSoftFail) {
       MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
              ("NSSCertDBTrustDomain: returning SECFailure after "
               "OCSP request failure"));
@@ -682,17 +682,17 @@ NSSCertDBTrustDomain::VerifyAndMaybeCach
     }
   }
   if (responseSource == ResponseIsFromNetwork ||
       rv == Success ||
       rv == Result::ERROR_REVOKED_CERTIFICATE ||
       rv == Result::ERROR_OCSP_UNKNOWN_CERT) {
     MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
            ("NSSCertDBTrustDomain: caching OCSP response"));
-    Result putRV = mOCSPCache.Put(certID, mFirstPartyDomain, rv, thisUpdate,
+    Result putRV = mOCSPCache.Put(certID, mOriginAttributes, rv, thisUpdate,
                                   validThrough);
     if (putRV != Success) {
       return putRV;
     }
   }
 
   return rv;
 }
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -4,16 +4,17 @@
  * 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/. */
 
 #ifndef NSSCertDBTrustDomain_h
 #define NSSCertDBTrustDomain_h
 
 #include "CertVerifier.h"
 #include "ScopedNSSTypes.h"
+#include "mozilla/BasePrincipal.h"
 #include "nsICertBlocklist.h"
 #include "nsString.h"
 #include "pkix/pkixtypes.h"
 #include "secmodt.h"
 
 namespace mozilla { namespace psm {
 
 enum class ValidityCheckingMode {
@@ -75,17 +76,17 @@ public:
                        OCSPCache& ocspCache, void* pinArg,
                        CertVerifier::OcspGetConfig ocspGETConfig,
                        uint32_t certShortLifetimeInDays,
                        CertVerifier::PinningMode pinningMode,
                        unsigned int minRSABits,
                        ValidityCheckingMode validityCheckingMode,
                        CertVerifier::SHA1Mode sha1Mode,
                        NetscapeStepUpPolicy netscapeStepUpPolicy,
-                       const char* firstPartyDomain,
+                       const NeckoOriginAttributes& originAttributes,
                        UniqueCERTCertList& builtChain,
           /*optional*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr,
           /*optional*/ const char* hostname = nullptr);
 
   virtual Result FindIssuer(mozilla::pkix::Input encodedIssuerName,
                             IssuerChecker& checker,
                             mozilla::pkix::Time time) override;
 
@@ -179,17 +180,17 @@ private:
   void* mPinArg; // non-owning!
   const CertVerifier::OcspGetConfig mOCSPGetConfig;
   const uint32_t mCertShortLifetimeInDays;
   CertVerifier::PinningMode mPinningMode;
   const unsigned int mMinRSABits;
   ValidityCheckingMode mValidityCheckingMode;
   CertVerifier::SHA1Mode mSHA1Mode;
   NetscapeStepUpPolicy mNetscapeStepUpPolicy;
-  const char* mFirstPartyDomain;
+  const NeckoOriginAttributes& mOriginAttributes;
   UniqueCERTCertList& mBuiltChain; // non-owning
   PinningTelemetryInfo* mPinningTelemetryInfo;
   const char* mHostname; // non-owning - only used for pinning checks
   nsCOMPtr<nsICertBlocklist> mCertBlocklist;
   CertVerifier::OCSPStaplingStatus mOCSPStaplingStatus;
   // Certificate Transparency data extracted during certificate verification
   UniqueSECItem mSCTListFromCertificate;
   UniqueSECItem mSCTListFromOCSPStapling;
--- a/security/certverifier/OCSPCache.cpp
+++ b/security/certverifier/OCSPCache.cpp
@@ -51,38 +51,39 @@ DigestLength(UniquePK11Context& context,
   }
   unsigned char array[2];
   array[0] = length & 255;
   array[1] = (length >> 8) & 255;
 
   return PK11_DigestOp(context.get(), array, MOZ_ARRAY_LENGTH(array));
 }
 
-// Let derIssuer be the DER encoding of the issuer of aCert.
-// Let derPublicKey be the DER encoding of the public key of aIssuerCert.
-// Let serialNumber be the bytes of the serial number of aCert.
+// Let derIssuer be the DER encoding of the issuer of certID.
+// Let derPublicKey be the DER encoding of the public key of certID.
+// Let serialNumber be the bytes of the serial number of certID.
 // Let serialNumberLen be the number of bytes of serialNumber.
-// The first party domain is only non-empty when "privacy.firstParty.isolate"
-// is enabled, in order to isolate OCSP cache by first party.
+// Let firstPartyDomain be the first party domain of originAttributes.
+// It is only non-empty when "privacy.firstParty.isolate" is enabled, in order
+// to isolate OCSP cache by first party.
 // Let firstPartyDomainLen be the number of bytes of firstPartyDomain.
 // The value calculated is SHA384(derIssuer || derPublicKey || serialNumberLen
 // || serialNumber || firstPartyDomainLen || firstPartyDomain).
 // Because the DER encodings include the length of the data encoded, and we also
-// include the length of serialNumber and firstPartyDomain, there do not exist
+// include the length of serialNumber and originAttributes, there do not exist
 // A(derIssuerA, derPublicKeyA, serialNumberLenA, serialNumberA,
-// firstPartyDomainLenA, firstPartyDomainA) and B(derIssuerB, derPublicKeyB,
-// serialNumberLenB, serialNumberB, firstPartyDomainLenB, firstPartyDomainB)
+// originAttributesLenA, originAttributesA) and B(derIssuerB, derPublicKeyB,
+// serialNumberLenB, serialNumberB, originAttributesLenB, originAttributesB)
 // such that the concatenation of each tuple results in the same string of
 // bytes but where each part in A is not equal to its counterpart in B. This is
 // important because as a result it is computationally infeasible to find
 // collisions that would subvert this cache (given that SHA384 is a
 // cryptographically-secure hash function).
 static SECStatus
 CertIDHash(SHA384Buffer& buf, const CertID& certID,
-           const char* firstPartyDomain)
+           const NeckoOriginAttributes& originAttributes)
 {
   UniquePK11Context context(PK11_CreateDigestContext(SEC_OID_SHA384));
   if (!context) {
     return SECFailure;
   }
   SECStatus rv = PK11_DigestBegin(context.get());
   if (rv != SECSuccess) {
     return rv;
@@ -105,41 +106,44 @@ CertIDHash(SHA384Buffer& buf, const Cert
   if (rv != SECSuccess) {
     return rv;
   }
   rv = PK11_DigestOp(context.get(), certIDSerialNumber.data,
                      certIDSerialNumber.len);
   if (rv != SECSuccess) {
     return rv;
   }
-  if (firstPartyDomain) {
-    uint32_t firstPartyDomainLen = strlen(firstPartyDomain);
-    rv = DigestLength(context, firstPartyDomainLen);
+
+  // OCSP should not be isolated by containers.
+  NS_ConvertUTF16toUTF8 firstPartyDomain(originAttributes.mFirstPartyDomain);
+  if (!firstPartyDomain.IsEmpty()) {
+    rv = DigestLength(context, firstPartyDomain.Length());
     if (rv != SECSuccess) {
       return rv;
     }
     rv = PK11_DigestOp(context.get(),
-                       BitwiseCast<const unsigned char*>(firstPartyDomain),
-                       firstPartyDomainLen);
+                       BitwiseCast<const unsigned char*>(firstPartyDomain.get()),
+                       firstPartyDomain.Length());
     if (rv != SECSuccess) {
       return rv;
     }
   }
   uint32_t outLen = 0;
   rv = PK11_DigestFinal(context.get(), buf, &outLen, SHA384_LENGTH);
   if (outLen != SHA384_LENGTH) {
     return SECFailure;
   }
   return rv;
 }
 
 Result
-OCSPCache::Entry::Init(const CertID& aCertID, const char* aFirstPartyDomain)
+OCSPCache::Entry::Init(const CertID& aCertID,
+                       const NeckoOriginAttributes& aOriginAttributes)
 {
-  SECStatus srv = CertIDHash(mIDHash, aCertID, aFirstPartyDomain);
+  SECStatus srv = CertIDHash(mIDHash, aCertID, aOriginAttributes);
   if (srv != SECSuccess) {
     return MapPRErrorCodeToResult(PR_GetError());
   }
   return Success;
 }
 
 OCSPCache::OCSPCache()
   : mMutex("OCSPCache-mutex")
@@ -149,26 +153,27 @@ OCSPCache::OCSPCache()
 OCSPCache::~OCSPCache()
 {
   Clear();
 }
 
 // Returns false with index in an undefined state if no matching entry was
 // found.
 bool
-OCSPCache::FindInternal(const CertID& aCertID, const char* aFirstPartyDomain,
+OCSPCache::FindInternal(const CertID& aCertID,
+                        const NeckoOriginAttributes& aOriginAttributes,
                         /*out*/ size_t& index,
                         const MutexAutoLock& /* aProofOfLock */)
 {
   if (mEntries.length() == 0) {
     return false;
   }
 
   SHA384Buffer idHash;
-  SECStatus rv = CertIDHash(idHash, aCertID, aFirstPartyDomain);
+  SECStatus rv = CertIDHash(idHash, aCertID, aOriginAttributes);
   if (rv != SECSuccess) {
     return false;
   }
 
   // mEntries is sorted with the most-recently-used entry at the end.
   // Thus, searching from the end will often be fastest.
   index = mEntries.length();
   while (index > 0) {
@@ -177,106 +182,109 @@ OCSPCache::FindInternal(const CertID& aC
       return true;
     }
   }
   return false;
 }
 
 static inline void
 LogWithCertID(const char* aMessage, const CertID& aCertID,
-              const char* aFirstPartyDomain)
+              const NeckoOriginAttributes& aOriginAttributes)
 {
+  NS_ConvertUTF16toUTF8 firstPartyDomain(aOriginAttributes.mFirstPartyDomain);
   MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
-          (aMessage, &aCertID, aFirstPartyDomain));
+          (aMessage, &aCertID, firstPartyDomain.get()));
 }
 
 void
 OCSPCache::MakeMostRecentlyUsed(size_t aIndex,
                                 const MutexAutoLock& /* aProofOfLock */)
 {
   Entry* entry = mEntries[aIndex];
   // Since mEntries is sorted with the most-recently-used entry at the end,
   // aIndex is likely to be near the end, so this is likely to be fast.
   mEntries.erase(mEntries.begin() + aIndex);
   // erase() does not shrink or realloc memory, so the append below should
   // always succeed.
   MOZ_RELEASE_ASSERT(mEntries.append(entry));
 }
 
 bool
-OCSPCache::Get(const CertID& aCertID, const char* aFirstPartyDomain,
+OCSPCache::Get(const CertID& aCertID,
+               const NeckoOriginAttributes& aOriginAttributes,
                Result& aResult, Time& aValidThrough)
 {
   MutexAutoLock lock(mMutex);
 
   size_t index;
-  if (!FindInternal(aCertID, aFirstPartyDomain, index, lock)) {
+  if (!FindInternal(aCertID, aOriginAttributes, index, lock)) {
     LogWithCertID("OCSPCache::Get(%p,\"%s\") not in cache", aCertID,
-                  aFirstPartyDomain);
+                  aOriginAttributes);
     return false;
   }
   LogWithCertID("OCSPCache::Get(%p,\"%s\") in cache", aCertID,
-                aFirstPartyDomain);
+                aOriginAttributes);
   aResult = mEntries[index]->mResult;
   aValidThrough = mEntries[index]->mValidThrough;
   MakeMostRecentlyUsed(index, lock);
   return true;
 }
 
 Result
-OCSPCache::Put(const CertID& aCertID, const char* aFirstPartyDomain,
+OCSPCache::Put(const CertID& aCertID,
+               const NeckoOriginAttributes& aOriginAttributes,
                Result aResult, Time aThisUpdate, Time aValidThrough)
 {
   MutexAutoLock lock(mMutex);
 
   size_t index;
-  if (FindInternal(aCertID, aFirstPartyDomain, index, lock)) {
+  if (FindInternal(aCertID, aOriginAttributes, index, lock)) {
     // Never replace an entry indicating a revoked certificate.
     if (mEntries[index]->mResult == Result::ERROR_REVOKED_CERTIFICATE) {
       LogWithCertID("OCSPCache::Put(%p, \"%s\") already in cache as revoked - "
-                    "not replacing", aCertID, aFirstPartyDomain);
+                    "not replacing", aCertID, aOriginAttributes);
       MakeMostRecentlyUsed(index, lock);
       return Success;
     }
 
     // Never replace a newer entry with an older one unless the older entry
     // indicates a revoked certificate, which we want to remember.
     if (mEntries[index]->mThisUpdate > aThisUpdate &&
         aResult != Result::ERROR_REVOKED_CERTIFICATE) {
       LogWithCertID("OCSPCache::Put(%p, \"%s\") already in cache with more "
                     "recent validity - not replacing", aCertID,
-                    aFirstPartyDomain);
+                    aOriginAttributes);
       MakeMostRecentlyUsed(index, lock);
       return Success;
     }
 
     // Only known good responses or responses indicating an unknown
     // or revoked certificate should replace previously known responses.
     if (aResult != Success &&
         aResult != Result::ERROR_OCSP_UNKNOWN_CERT &&
         aResult != Result::ERROR_REVOKED_CERTIFICATE) {
       LogWithCertID("OCSPCache::Put(%p, \"%s\") already in cache - not "
                     "replacing with less important status", aCertID,
-                    aFirstPartyDomain);
+                    aOriginAttributes);
       MakeMostRecentlyUsed(index, lock);
       return Success;
     }
 
     LogWithCertID("OCSPCache::Put(%p, \"%s\") already in cache - replacing",
-                  aCertID, aFirstPartyDomain);
+                  aCertID, aOriginAttributes);
     mEntries[index]->mResult = aResult;
     mEntries[index]->mThisUpdate = aThisUpdate;
     mEntries[index]->mValidThrough = aValidThrough;
     MakeMostRecentlyUsed(index, lock);
     return Success;
   }
 
   if (mEntries.length() == MaxEntries) {
     LogWithCertID("OCSPCache::Put(%p, \"%s\") too full - evicting an entry",
-                  aCertID, aFirstPartyDomain);
+                  aCertID, aOriginAttributes);
     for (Entry** toEvict = mEntries.begin(); toEvict != mEntries.end();
          toEvict++) {
       // Never evict an entry that indicates a revoked or unknokwn certificate,
       // because revoked responses are more security-critical to remember.
       if ((*toEvict)->mResult != Result::ERROR_REVOKED_CERTIFICATE &&
           (*toEvict)->mResult != Result::ERROR_OCSP_UNKNOWN_CERT) {
         delete *toEvict;
         mEntries.erase(toEvict);
@@ -298,27 +306,27 @@ OCSPCache::Put(const CertID& aCertID, co
   Entry* newEntry = new (std::nothrow) Entry(aResult, aThisUpdate,
                                              aValidThrough);
   // Normally we don't have to do this in Gecko, because OOM is fatal.
   // However, if we want to embed this in another project, OOM might not
   // be fatal, so handle this case.
   if (!newEntry) {
     return Result::FATAL_ERROR_NO_MEMORY;
   }
-  Result rv = newEntry->Init(aCertID, aFirstPartyDomain);
+  Result rv = newEntry->Init(aCertID, aOriginAttributes);
   if (rv != Success) {
     delete newEntry;
     return rv;
   }
   if (!mEntries.append(newEntry)) {
     delete newEntry;
     return Result::FATAL_ERROR_NO_MEMORY;
   }
   LogWithCertID("OCSPCache::Put(%p, \"%s\") added to cache", aCertID,
-                aFirstPartyDomain);
+                aOriginAttributes);
   return Success;
 }
 
 void
 OCSPCache::Clear()
 {
   MutexAutoLock lock(mMutex);
   MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("OCSPCache::Clear: clearing cache"));
--- a/security/certverifier/OCSPCache.h
+++ b/security/certverifier/OCSPCache.h
@@ -28,16 +28,20 @@
 #include "hasht.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/Vector.h"
 #include "pkix/Result.h"
 #include "pkix/Time.h"
 #include "prerror.h"
 #include "seccomon.h"
 
+namespace mozilla {
+class NeckoOriginAttributes;
+}
+
 namespace mozilla { namespace pkix {
 struct CertID;
 } } // namespace mozilla::pkix
 
 namespace mozilla { namespace psm {
 
 // make SHA384Buffer be of type "array of uint8_t of length SHA384_LENGTH"
 typedef uint8_t SHA384Buffer[SHA384_LENGTH];
@@ -51,38 +55,40 @@ typedef uint8_t SHA384Buffer[SHA384_LENG
 class OCSPCache
 {
 public:
   OCSPCache();
   ~OCSPCache();
 
   // Returns true if the status of the given certificate (issued by the given
   // issuer) is in the cache, and false otherwise.
-  // The first party domain is only non-empty when "privacy.firstParty.isolate"
-  // is enabled, in order to isolate OCSP cache by first party.
   // If it is in the cache, returns by reference the error code of the cached
   // status and the time through which the status is considered trustworthy.
+  // The passed in origin attributes are used to isolate the OCSP cache.
+  // We currently only use the first party domain portion of the attributes, and
+  // it is non-empty only when "privacy.firstParty.isolate" is enabled.
   bool Get(const mozilla::pkix::CertID& aCertID,
-           const char* aFirstPartyDomain,
+           const NeckoOriginAttributes& aOriginAttributes,
            /*out*/ mozilla::pkix::Result& aResult,
            /*out*/ mozilla::pkix::Time& aValidThrough);
 
   // Caches the status of the given certificate (issued by the given issuer).
-  // The first party domain is only non-empty when "privacy.firstParty.isolate"
-  // is enabled, in order to isolate OCSP cache by first party.
   // The status is considered trustworthy through the given time.
   // A status with an error code of SEC_ERROR_REVOKED_CERTIFICATE will not
   // be replaced or evicted.
   // A status with an error code of SEC_ERROR_OCSP_UNKNOWN_CERT will not
   // be evicted when the cache is full.
   // A status with a more recent thisUpdate will not be replaced with a
   // status with a less recent thisUpdate unless the less recent status
   // indicates the certificate is revoked.
+  // The passed in origin attributes are used to isolate the OCSP cache.
+  // We currently only use the first party domain portion of the attributes, and
+  // it is non-empty only when "privacy.firstParty.isolate" is enabled.
   mozilla::pkix::Result Put(const mozilla::pkix::CertID& aCertID,
-                            const char* aFirstPartyDomain,
+                            const NeckoOriginAttributes& aOriginAttributes,
                             mozilla::pkix::Result aResult,
                             mozilla::pkix::Time aThisUpdate,
                             mozilla::pkix::Time aValidThrough);
 
   // Removes everything from the cache.
   void Clear();
 
 private:
@@ -93,31 +99,31 @@ private:
           mozilla::pkix::Time aThisUpdate,
           mozilla::pkix::Time aValidThrough)
       : mResult(aResult)
       , mThisUpdate(aThisUpdate)
       , mValidThrough(aValidThrough)
     {
     }
     mozilla::pkix::Result Init(const mozilla::pkix::CertID& aCertID,
-                               const char* aFirstPartyDomain);
+                               const NeckoOriginAttributes& aOriginAttributes);
 
     mozilla::pkix::Result mResult;
     mozilla::pkix::Time mThisUpdate;
     mozilla::pkix::Time mValidThrough;
     // The SHA-384 hash of the concatenation of the DER encodings of the
     // issuer name and issuer key, followed by the length of the serial number,
     // the serial number, the length of the first party domain, and the first
     // party domain (if "privacy.firstparty.isolate" is enabled).
     // See the documentation for CertIDHash in OCSPCache.cpp.
     SHA384Buffer mIDHash;
   };
 
   bool FindInternal(const mozilla::pkix::CertID& aCertID,
-                    const char* aFirstPartyDomain,
+                    const NeckoOriginAttributes& aOriginAttributes,
                     /*out*/ size_t& index,
                     const MutexAutoLock& aProofOfLock);
   void MakeMostRecentlyUsed(size_t aIndex, const MutexAutoLock& aProofOfLock);
 
   Mutex mMutex;
   static const size_t MaxEntries = 1024;
   // Sorted with the most-recently-used entry at the end.
   // Using 256 here reserves as much possible inline storage as the vector
--- a/security/certverifier/OCSPRequestor.cpp
+++ b/security/certverifier/OCSPRequestor.cpp
@@ -69,18 +69,19 @@ AppendEscapedBase64Item(const SECItem* e
   base64Request.ReplaceSubstring("/", "%2F");
   base64Request.ReplaceSubstring("=", "%3D");
   path.Append(base64Request);
   return NS_OK;
 }
 
 Result
 DoOCSPRequest(const UniquePLArenaPool& arena, const char* url,
-              const char* firstPartyDomain, const SECItem* encodedRequest,
-              PRIntervalTime timeout, bool useGET,
+              const NeckoOriginAttributes& originAttributes,
+              const SECItem* encodedRequest, PRIntervalTime timeout,
+              bool useGET,
       /*out*/ SECItem*& encodedResponse)
 {
   MOZ_ASSERT(arena.get());
   MOZ_ASSERT(url);
   MOZ_ASSERT(encodedRequest);
   MOZ_ASSERT(encodedRequest->data);
   if (!arena.get() || !url || !encodedRequest || !encodedRequest->data) {
     return Result::FATAL_ERROR_INVALID_ARGS;
@@ -168,17 +169,17 @@ DoOCSPRequest(const UniquePLArenaPool& a
     nsresult nsrv = AppendEscapedBase64Item(encodedRequest, path);
     if (NS_WARN_IF(NS_FAILED(nsrv))) {
       return Result::FATAL_ERROR_LIBRARY_FAILURE;
     }
   }
 
   nsNSSHttpRequestSession* requestSessionPtr;
   rv = nsNSSHttpInterface::createFcn(serverSession.get(), "http", path.get(),
-                                     method.get(), firstPartyDomain, timeout,
+                                     method.get(), originAttributes, timeout,
                                      &requestSessionPtr);
   if (rv != Success) {
     return rv;
   }
 
   UniqueHTTPRequestSession requestSession(requestSessionPtr);
 
   if (!useGET) {
--- a/security/certverifier/OCSPRequestor.h
+++ b/security/certverifier/OCSPRequestor.h
@@ -5,20 +5,24 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef OCSPRequestor_h
 #define OCSPRequestor_h
 
 #include "CertVerifier.h"
 #include "secmodt.h"
 
+namespace mozilla {
+class NeckoOriginAttributes;
+}
+
 namespace mozilla { namespace psm {
 
 // The memory returned via |encodedResponse| is owned by the given arena.
 Result DoOCSPRequest(const UniquePLArenaPool& arena, const char* url,
-                     const char* firstPartyDomain,
+                     const NeckoOriginAttributes& originAttributes,
                      const SECItem* encodedRequest, PRIntervalTime timeout,
                      bool useGET,
              /*out*/ SECItem*& encodedResponse);
 
 } } // namespace mozilla::psm
 
 #endif // OCSPRequestor_h
--- a/security/manager/ssl/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/SSLServerCertVerification.cpp
@@ -1335,17 +1335,17 @@ AuthCertificate(CertVerifier& certVerifi
   }
 
   Result rv = certVerifier.VerifySSLServerCert(cert, stapledOCSPResponse,
                                                sctsFromTLSExtension, time,
                                                infoObject,
                                                infoObject->GetHostNameRaw(),
                                                certList, saveIntermediates,
                                                flags, infoObject->
-                                                      GetFirstPartyDomainRaw(),
+                                                      GetOriginAttributes(),
                                                &evOidPolicy,
                                                &ocspStaplingStatus,
                                                &keySizeStatus, &sha1ModeResult,
                                                &pinningTelemetryInfo,
                                                &certificateTransparencyInfo);
 
   uint32_t evStatus = (rv != Success) ? 0                   // 0 = Failure
                     : (evOidPolicy == SEC_OID_UNKNOWN) ? 1  // 1 = DV
--- a/security/manager/ssl/TransportSecurityInfo.cpp
+++ b/security/manager/ssl/TransportSecurityInfo.cpp
@@ -94,19 +94,20 @@ TransportSecurityInfo::SetPort(int32_t a
 nsresult
 TransportSecurityInfo::GetPort(int32_t *aPort)
 {
   *aPort = mPort;
   return NS_OK;
 }
 
 nsresult
-TransportSecurityInfo::SetFirstPartyDomain(const nsACString& aFirstPartyDomain)
+TransportSecurityInfo::SetOriginAttributes(
+  const NeckoOriginAttributes& aOriginAttributes)
 {
-  mFirstPartyDomain.Assign(aFirstPartyDomain);
+  mOriginAttributes = aOriginAttributes;
   return NS_OK;
 }
 
 PRErrorCode
 TransportSecurityInfo::GetErrorCode() const
 {
   MutexAutoLock lock(mMutex);
 
--- a/security/manager/ssl/TransportSecurityInfo.h
+++ b/security/manager/ssl/TransportSecurityInfo.h
@@ -4,16 +4,17 @@
  * 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/. */
 
 #ifndef TransportSecurityInfo_h
 #define TransportSecurityInfo_h
 
 #include "ScopedNSSTypes.h"
 #include "certt.h"
+#include "mozilla/BasePrincipal.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/RefPtr.h"
 #include "nsDataHashtable.h"
 #include "nsIAssociatedContentSecurity.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsISSLStatusProvider.h"
 #include "nsITransportSecurityInfo.h"
 #include "nsNSSShutDown.h"
@@ -57,18 +58,20 @@ public:
 
   nsresult GetHostName(char **aHostName);
   nsresult SetHostName(const char *aHostName);
 
   int32_t GetPort() const { return mPort; }
   nsresult GetPort(int32_t *aPort);
   nsresult SetPort(int32_t aPort);
 
-  const char* GetFirstPartyDomainRaw() const { return mFirstPartyDomain.get(); }
-  nsresult SetFirstPartyDomain(const nsACString& aFirstPartyDomain);
+  const NeckoOriginAttributes& GetOriginAttributes() const {
+    return mOriginAttributes;
+  }
+  nsresult SetOriginAttributes(const NeckoOriginAttributes& aOriginAttributes);
 
   PRErrorCode GetErrorCode() const;
   
   void GetErrorLogMessage(PRErrorCode errorCode,
                           ::mozilla::psm::SSLErrorMessageType errorMessageType,
                           nsString &result);
   
   void SetCanceled(PRErrorCode errorCode,
@@ -98,17 +101,17 @@ private:
   nsresult formatErrorMessage(::mozilla::MutexAutoLock const & proofOfLock, 
                               PRErrorCode errorCode,
                               ::mozilla::psm::SSLErrorMessageType errorMessageType,
                               bool wantsHtml, bool suppressPort443, 
                               nsString &result);
 
   int32_t mPort;
   nsXPIDLCString mHostName;
-  nsCString mFirstPartyDomain;
+  NeckoOriginAttributes mOriginAttributes;
 
   /* SSL Status */
   RefPtr<nsSSLStatus> mSSLStatus;
 
   /* Peer cert chain for failed connections (for error reporting) */
   nsCOMPtr<nsIX509CertList> mFailedCertChain;
 
   virtual void virtualDestroyNSSReference() override;
--- a/security/manager/ssl/nsNSSCallbacks.cpp
+++ b/security/manager/ssl/nsNSSCallbacks.cpp
@@ -109,20 +109,23 @@ nsHTTPDownloadEvent::Run()
   // high priority to accommodate real time OCSP transactions.
   nsCOMPtr<nsISupportsPriority> priorityChannel = do_QueryInterface(chan);
   if (priorityChannel)
     priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST);
 
   chan->SetLoadFlags(nsIRequest::LOAD_ANONYMOUS |
                      nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
 
-  if (!mRequestSession->mFirstPartyDomain.IsEmpty()) {
+  // For OCSP requests, only the first party domain aspect of origin attributes
+  // is used. This means that OCSP requests are shared across different
+  // containers.
+  if (mRequestSession->mOriginAttributes != NeckoOriginAttributes()) {
     NeckoOriginAttributes attrs;
     attrs.mFirstPartyDomain =
-      NS_ConvertUTF8toUTF16(mRequestSession->mFirstPartyDomain);
+      mRequestSession->mOriginAttributes.mFirstPartyDomain;
 
     nsCOMPtr<nsILoadInfo> loadInfo = chan->GetLoadInfo();
     if (loadInfo) {
       rv = loadInfo->SetOriginAttributes(attrs);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
 
@@ -225,17 +228,17 @@ nsNSSHttpServerSession::createSessionFcn
   return Success;
 }
 
 mozilla::pkix::Result
 nsNSSHttpRequestSession::createFcn(const nsNSSHttpServerSession* session,
                                    const char* http_protocol_variant,
                                    const char* path_and_query_string,
                                    const char* http_request_method,
-                                   const char* first_party_domain,
+                                   const NeckoOriginAttributes& origin_attributes,
                                    const PRIntervalTime timeout,
                            /*out*/ nsNSSHttpRequestSession** pRequest)
 {
   if (!session || !http_protocol_variant || !path_and_query_string ||
       !http_request_method || !pRequest) {
     return Result::FATAL_ERROR_INVALID_ARGS;
   }
 
@@ -255,17 +258,17 @@ nsNSSHttpRequestSession::createFcn(const
 
   rs->mURL.Assign(http_protocol_variant);
   rs->mURL.AppendLiteral("://");
   rs->mURL.Append(session->mHost);
   rs->mURL.Append(':');
   rs->mURL.AppendInt(session->mPort);
   rs->mURL.Append(path_and_query_string);
 
-  rs->mFirstPartyDomain.Assign(first_party_domain);
+  rs->mOriginAttributes = origin_attributes;
 
   rs->mRequestMethod = http_request_method;
 
   *pRequest = rs;
   return Success;
 }
 
 mozilla::pkix::Result
@@ -1164,17 +1167,17 @@ DetermineEVStatusAndSetNewCert(RefPtr<ns
     stapledOCSPResponse,
     sctsFromTLSExtension,
     mozilla::pkix::Now(),
     infoObject,
     infoObject->GetHostNameRaw(),
     unusedBuiltChain,
     saveIntermediates,
     flags,
-    infoObject->GetFirstPartyDomainRaw(),
+    infoObject->GetOriginAttributes(),
     &evOidPolicy);
 
   RefPtr<nsNSSCertificate> nssc(nsNSSCertificate::Create(cert.get()));
   if (rv == Success && evOidPolicy != SEC_OID_UNKNOWN) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
             ("HandshakeCallback using NEW cert %p (is EV)", nssc.get()));
     sslStatus->SetServerCert(nssc, EVStatus::EV);
   } else {
--- a/security/manager/ssl/nsNSSCallbacks.h
+++ b/security/manager/ssl/nsNSSCallbacks.h
@@ -3,28 +3,31 @@
  * 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/. */
 
 #ifndef nsNSSCallbacks_h
 #define nsNSSCallbacks_h
 
 #include "mozilla/Attributes.h"
+#include "mozilla/BasePrincipal.h"
 #include "mozilla/CondVar.h"
 #include "mozilla/Mutex.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsIStreamLoader.h"
 #include "nspr.h"
 #include "nsString.h"
 #include "pk11func.h"
 #include "pkix/pkixtypes.h"
 
 #include "ocspt.h" // Must be included after pk11func.h.
 
+using mozilla::NeckoOriginAttributes;
+
 class nsILoadGroup;
 
 char*
 PK11PasswordPrompt(PK11SlotInfo *slot, PRBool retry, void* arg);
 
 void HandshakeCallback(PRFileDesc *fd, void *client_data);
 SECStatus CanFalseStartCallback(PRFileDesc* fd, void* client_data,
                                 PRBool *canFalseStart);
@@ -94,17 +97,17 @@ protected:
 
 public:
   typedef mozilla::pkix::Result Result;
 
   static Result createFcn(const nsNSSHttpServerSession* session,
                           const char* httpProtocolVariant,
                           const char* pathAndQueryString,
                           const char* httpRequestMethod,
-                          const char* firstPartyDomain,
+                          const NeckoOriginAttributes& originAttributes,
                           const PRIntervalTime timeout,
                   /*out*/ nsNSSHttpRequestSession** pRequest);
 
   Result setPostDataFcn(const char* httpData,
                         const uint32_t httpDataLen,
                         const char* httpContentType);
 
   Result trySendAndReceiveFcn(PRPollDesc** pPollDesc,
@@ -119,17 +122,17 @@ public:
 
   nsCString mURL;
   nsCString mRequestMethod;
 
   bool mHasPostData;
   nsCString mPostData;
   nsCString mPostContentType;
 
-  nsCString mFirstPartyDomain;
+  NeckoOriginAttributes mOriginAttributes;
 
   PRIntervalTime mTimeoutInterval;
 
   RefPtr<nsHTTPListener> mListener;
 
 protected:
   nsNSSHttpRequestSession();
   ~nsNSSHttpRequestSession();
@@ -154,23 +157,23 @@ public:
   {
     return nsNSSHttpServerSession::createSessionFcn(host, portnum, pSession);
   }
 
   static Result createFcn(const nsNSSHttpServerSession* session,
                           const char* httpProtocolVariant,
                           const char* pathAndQueryString,
                           const char* httpRequestMethod,
-                          const char* firstPartyDomain,
+                          const NeckoOriginAttributes& originAttributes,
                           const PRIntervalTime timeout,
                   /*out*/ nsNSSHttpRequestSession** pRequest)
   {
     return nsNSSHttpRequestSession::createFcn(session, httpProtocolVariant,
                                               pathAndQueryString,
-                                              httpRequestMethod, firstPartyDomain,
+                                              httpRequestMethod, originAttributes,
                                               timeout, pRequest);
   }
 
   static Result setPostDataFcn(nsNSSHttpRequestSession* request,
                                const char* httpData,
                                const uint32_t httpDataLen,
                                const char* httpContentType)
   {
--- a/security/manager/ssl/nsNSSCertificateDB.cpp
+++ b/security/manager/ssl/nsNSSCertificateDB.cpp
@@ -1492,27 +1492,27 @@ VerifyCertAtTime(nsIX509Cert* aCert,
                                                nullptr, // stapledOCSPResponse
                                                nullptr, // sctsFromTLSExtension
                                                aTime,
                                                nullptr, // Assume no context
                                                aHostname,
                                                resultChain,
                                                false, // don't save intermediates
                                                aFlags,
-                                               nullptr, // firstPartyDomain
+                                               NeckoOriginAttributes(),
                                                &evOidPolicy);
   } else {
     result = certVerifier->VerifyCert(nssCert.get(), aUsage, aTime,
                                       nullptr, // Assume no context
                                       aHostname,
                                       resultChain,
                                       aFlags,
                                       nullptr, // stapledOCSPResponse
                                       nullptr, // sctsFromTLSExtension
-                                      nullptr, // firstPartyDomain
+                                      NeckoOriginAttributes(),
                                       &evOidPolicy);
   }
 
   nsCOMPtr<nsIX509CertList> nssCertList;
   // This adopts the list
   nssCertList = new nsNSSCertList(Move(resultChain), locker);
   NS_ENSURE_TRUE(nssCertList, NS_ERROR_FAILURE);
 
--- a/security/manager/ssl/nsNSSIOLayer.cpp
+++ b/security/manager/ssl/nsNSSIOLayer.cpp
@@ -1855,28 +1855,28 @@ nsSSLIOLayerHelpers::treatUnsafeNegotiat
   return mTreatUnsafeNegotiationAsBroken;
 }
 
 nsresult
 nsSSLIOLayerNewSocket(int32_t family,
                       const char* host,
                       int32_t port,
                       nsIProxyInfo *proxy,
-                      const nsACString& firstPartyDomain,
+                      const NeckoOriginAttributes& originAttributes,
                       PRFileDesc** fd,
                       nsISupports** info,
                       bool forSTARTTLS,
                       uint32_t flags)
 {
 
   PRFileDesc* sock = PR_OpenTCPSocket(family);
   if (!sock) return NS_ERROR_OUT_OF_MEMORY;
 
   nsresult rv = nsSSLIOLayerAddToSocket(family, host, port, proxy,
-                                        firstPartyDomain, sock, info,
+                                        originAttributes, sock, info,
                                         forSTARTTLS, flags);
   if (NS_FAILED(rv)) {
     PR_Close(sock);
     return rv;
   }
 
   *fd = sock;
   return NS_OK;
@@ -2573,17 +2573,17 @@ nsSSLIOLayerSetOptions(PRFileDesc* fd, b
   return NS_OK;
 }
 
 nsresult
 nsSSLIOLayerAddToSocket(int32_t family,
                         const char* host,
                         int32_t port,
                         nsIProxyInfo* proxy,
-                        const nsACString& firstPartyDomain,
+                        const NeckoOriginAttributes& originAttributes,
                         PRFileDesc* fd,
                         nsISupports** info,
                         bool forSTARTTLS,
                         uint32_t providerFlags)
 {
   nsNSSShutDownPreventionLock locker;
   PRFileDesc* layer = nullptr;
   PRFileDesc* plaintextLayer = nullptr;
@@ -2594,17 +2594,17 @@ nsSSLIOLayerAddToSocket(int32_t family,
     providerFlags & nsISocketProvider::NO_PERMANENT_STORAGE ? PrivateSSLState() : PublicSSLState();
   nsNSSSocketInfo* infoObject = new nsNSSSocketInfo(*sharedState, providerFlags);
   if (!infoObject) return NS_ERROR_FAILURE;
 
   NS_ADDREF(infoObject);
   infoObject->SetForSTARTTLS(forSTARTTLS);
   infoObject->SetHostName(host);
   infoObject->SetPort(port);
-  infoObject->SetFirstPartyDomain(firstPartyDomain);
+  infoObject->SetOriginAttributes(originAttributes);
 
   bool haveProxy = false;
   if (proxy) {
     nsCString proxyHost;
     proxy->GetHost(proxyHost);
     haveProxy = !proxyHost.IsEmpty();
   }
 
--- a/security/manager/ssl/nsNSSIOLayer.h
+++ b/security/manager/ssl/nsNSSIOLayer.h
@@ -14,21 +14,24 @@
 #include "nsIClientAuthDialogs.h"
 #include "nsIProxyInfo.h"
 #include "nsISSLSocketControl.h"
 #include "nsNSSCertificate.h"
 #include "nsTHashtable.h"
 #include "sslt.h"
 
 namespace mozilla {
+class NeckoOriginAttributes;
 namespace psm {
 class SharedSSLState;
 } // namespace psm
 } // namespace mozilla
 
+using mozilla::NeckoOriginAttributes;
+
 class nsIObserver;
 
 class nsNSSSocketInfo final : public mozilla::psm::TransportSecurityInfo,
                               public nsISSLSocketControl,
                               public nsIClientAuthUserDecision
 {
 public:
   nsNSSSocketInfo(mozilla::psm::SharedSSLState& aState, uint32_t providerFlags);
@@ -233,27 +236,27 @@ private:
   mozilla::Mutex mutex;
   nsCOMPtr<nsIObserver> mPrefObserver;
 };
 
 nsresult nsSSLIOLayerNewSocket(int32_t family,
                                const char* host,
                                int32_t port,
                                nsIProxyInfo *proxy,
-                               const nsACString& firstPartyDomain,
+                               const NeckoOriginAttributes& originAttributes,
                                PRFileDesc** fd,
                                nsISupports** securityInfo,
                                bool forSTARTTLS,
                                uint32_t flags);
 
 nsresult nsSSLIOLayerAddToSocket(int32_t family,
                                  const char* host,
                                  int32_t port,
                                  nsIProxyInfo *proxy,
-                                 const nsACString& firstPartyDomain,
+                                 const NeckoOriginAttributes& originAttributes,
                                  PRFileDesc* fd,
                                  nsISupports** securityInfo,
                                  bool forSTARTTLS,
                                  uint32_t flags);
 
 nsresult nsSSLIOLayerFreeTLSIntolerantSites();
 nsresult displayUnknownCertErrorAlert(nsNSSSocketInfo* infoObject, int error);
 
--- a/security/manager/ssl/nsSSLSocketProvider.cpp
+++ b/security/manager/ssl/nsSSLSocketProvider.cpp
@@ -1,65 +1,68 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * 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/. */
 
+#include "mozilla/BasePrincipal.h"
 #include "nsSSLSocketProvider.h"
 #include "nsNSSIOLayer.h"
 #include "nsError.h"
 
+using mozilla::NeckoOriginAttributes;
+
 nsSSLSocketProvider::nsSSLSocketProvider()
 {
 }
 
 nsSSLSocketProvider::~nsSSLSocketProvider()
 {
 }
 
 NS_IMPL_ISUPPORTS(nsSSLSocketProvider, nsISocketProvider)
 
 NS_IMETHODIMP
 nsSSLSocketProvider::NewSocket(int32_t family,
                                const char *host,
                                int32_t port,
                                nsIProxyInfo *proxy,
-                               const nsACString &firstPartyDomain,
+                               const NeckoOriginAttributes &originAttributes,
                                uint32_t flags,
                                PRFileDesc **_result,
                                nsISupports **securityInfo)
 {
   nsresult rv = nsSSLIOLayerNewSocket(family,
                                       host,
                                       port,
                                       proxy,
-                                      firstPartyDomain,
+                                      originAttributes,
                                       _result,
                                       securityInfo,
                                       false,
                                       flags);
   return (NS_FAILED(rv)) ? NS_ERROR_SOCKET_CREATE_FAILED : NS_OK;
 }
 
 // Add the SSL IO layer to an existing socket
 NS_IMETHODIMP
 nsSSLSocketProvider::AddToSocket(int32_t family,
                                  const char *host,
                                  int32_t port,
                                  nsIProxyInfo *proxy,
-                                 const nsACString &firstPartyDomain,
+                                 const NeckoOriginAttributes &originAttributes,
                                  uint32_t flags,
                                  PRFileDesc *aSocket,
                                  nsISupports **securityInfo)
 {
   nsresult rv = nsSSLIOLayerAddToSocket(family,
                                         host,
                                         port,
                                         proxy,
-                                        firstPartyDomain,
+                                        originAttributes,
                                         aSocket,
                                         securityInfo,
                                         false,
                                         flags);
   
   return (NS_FAILED(rv)) ? NS_ERROR_SOCKET_CREATE_FAILED : NS_OK;
 }
--- a/security/manager/ssl/nsTLSSocketProvider.cpp
+++ b/security/manager/ssl/nsTLSSocketProvider.cpp
@@ -1,66 +1,69 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * 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/. */
 
+#include "mozilla/BasePrincipal.h"
 #include "nsTLSSocketProvider.h"
 #include "nsNSSIOLayer.h"
 #include "nsError.h"
 
+using mozilla::NeckoOriginAttributes;
+
 nsTLSSocketProvider::nsTLSSocketProvider()
 {
 }
 
 nsTLSSocketProvider::~nsTLSSocketProvider()
 {
 }
 
 NS_IMPL_ISUPPORTS(nsTLSSocketProvider, nsISocketProvider)
 
 NS_IMETHODIMP
 nsTLSSocketProvider::NewSocket(int32_t family,
                                const char *host,
                                int32_t port,
                                nsIProxyInfo *proxy,
-                               const nsACString &firstPartyDomain,
+                               const NeckoOriginAttributes &originAttributes,
                                uint32_t flags,
                                PRFileDesc **_result,
                                nsISupports **securityInfo)
 {
   nsresult rv = nsSSLIOLayerNewSocket(family,
                                       host,
                                       port,
                                       proxy,
-                                      firstPartyDomain,
+                                      originAttributes,
                                       _result,
                                       securityInfo,
                                       true,
                                       flags);
   
   return (NS_FAILED(rv)) ? NS_ERROR_SOCKET_CREATE_FAILED : NS_OK;
 }
 
 // Add the SSL IO layer to an existing socket
 NS_IMETHODIMP
 nsTLSSocketProvider::AddToSocket(int32_t family,
                                  const char *host,
                                  int32_t port,
                                  nsIProxyInfo *proxy,
-                                 const nsACString &firstPartyDomain,
+                                 const NeckoOriginAttributes &originAttributes,
                                  uint32_t flags,
                                  PRFileDesc *aSocket,
                                  nsISupports **securityInfo)
 {
   nsresult rv = nsSSLIOLayerAddToSocket(family,
                                         host,
                                         port,
                                         proxy,
-                                        firstPartyDomain,
+                                        originAttributes,
                                         aSocket,
                                         securityInfo,
                                         true,
                                         flags);
   
   return (NS_FAILED(rv)) ? NS_ERROR_SOCKET_CREATE_FAILED : NS_OK;
 }
--- a/security/manager/ssl/tests/gtest/OCSPCacheTest.cpp
+++ b/security/manager/ssl/tests/gtest/OCSPCacheTest.cpp
@@ -2,27 +2,30 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #include "CertVerifier.h"
 #include "OCSPCache.h"
 #include "gtest/gtest.h"
+#include "mozilla/BasePrincipal.h"
 #include "mozilla/Casting.h"
 #include "mozilla/Sprintf.h"
 #include "nss.h"
 #include "pkix/pkixtypes.h"
 #include "pkixtestutil.h"
 #include "prerr.h"
 #include "secerr.h"
 
 using namespace mozilla::pkix;
 using namespace mozilla::pkix::test;
 
+using mozilla::NeckoOriginAttributes;
+
 template <size_t N>
 inline Input
 LiteralInput(const char(&valueString)[N])
 {
   // Ideally we would use mozilla::BitwiseCast() here rather than
   // reinterpret_cast for better type checking, but the |N - 1| part trips
   // static asserts.
   return Input(reinterpret_cast<const uint8_t(&)[N - 1]>(valueString));
@@ -41,29 +44,30 @@ protected:
   }
 
   const Time now;
   mozilla::psm::OCSPCache cache;
 };
 
 static void
 PutAndGet(mozilla::psm::OCSPCache& cache, const CertID& certID, Result result,
-          Time time, const char* firstPartyDomain = nullptr)
+          Time time,
+          const NeckoOriginAttributes& originAttributes = NeckoOriginAttributes())
 {
   // The first time is thisUpdate. The second is validUntil.
   // The caller is expecting the validUntil returned with Get
   // to be equal to the passed-in time. Since these values will
   // be different in practice, make thisUpdate less than validUntil.
   Time thisUpdate(time);
   ASSERT_EQ(Success, thisUpdate.SubtractSeconds(10));
-  Result rv = cache.Put(certID, firstPartyDomain, result, thisUpdate, time);
+  Result rv = cache.Put(certID, originAttributes, result, thisUpdate, time);
   ASSERT_TRUE(rv == Success);
   Result resultOut;
   Time timeOut(Time::uninitialized);
-  ASSERT_TRUE(cache.Get(certID, firstPartyDomain, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(certID, originAttributes, resultOut, timeOut));
   ASSERT_EQ(result, resultOut);
   ASSERT_EQ(time, timeOut);
 }
 
 Input fakeIssuer1(LiteralInput("CN=issuer1"));
 Input fakeKey000(LiteralInput("key000"));
 Input fakeKey001(LiteralInput("key001"));
 Input fakeSerial0000(LiteralInput("0000"));
@@ -74,17 +78,17 @@ TEST_F(psm_OCSPCacheTest, TestPutAndGet)
   Input fakeSerial001(LiteralInput("001"));
 
   SCOPED_TRACE("");
   PutAndGet(cache, CertID(fakeIssuer1, fakeKey000, fakeSerial001),
             Success, now);
   Result resultOut;
   Time timeOut(Time::uninitialized);
   ASSERT_FALSE(cache.Get(CertID(fakeIssuer1, fakeKey001, fakeSerial000),
-                         nullptr, resultOut, timeOut));
+                         NeckoOriginAttributes(), resultOut, timeOut));
 }
 
 TEST_F(psm_OCSPCacheTest, TestVariousGets)
 {
   SCOPED_TRACE("");
   for (int i = 0; i < MaxCacheEntries; i++) {
     uint8_t serialBuf[8];
     snprintf(mozilla::BitwiseCast<char*, uint8_t*>(serialBuf), sizeof(serialBuf),
@@ -98,41 +102,41 @@ TEST_F(psm_OCSPCacheTest, TestVariousGet
   }
 
   Time timeIn(now);
   Result resultOut;
   Time timeOut(Time::uninitialized);
 
   // This will be at the end of the list in the cache
   CertID cert0000(fakeIssuer1, fakeKey000, fakeSerial0000);
-  ASSERT_TRUE(cache.Get(cert0000, nullptr, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(cert0000, NeckoOriginAttributes(), resultOut, timeOut));
   ASSERT_EQ(Success, resultOut);
   ASSERT_EQ(timeIn, timeOut);
   // Once we access it, it goes to the front
-  ASSERT_TRUE(cache.Get(cert0000, nullptr, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(cert0000, NeckoOriginAttributes(), resultOut, timeOut));
   ASSERT_EQ(Success, resultOut);
   ASSERT_EQ(timeIn, timeOut);
 
   // This will be in the middle
   Time timeInPlus512(now);
   ASSERT_EQ(Success, timeInPlus512.AddSeconds(512));
 
   static const Input fakeSerial0512(LiteralInput("0512"));
   CertID cert0512(fakeIssuer1, fakeKey000, fakeSerial0512);
-  ASSERT_TRUE(cache.Get(cert0512, nullptr, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(cert0512, NeckoOriginAttributes(), resultOut, timeOut));
   ASSERT_EQ(Success, resultOut);
   ASSERT_EQ(timeInPlus512, timeOut);
-  ASSERT_TRUE(cache.Get(cert0512, nullptr, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(cert0512, NeckoOriginAttributes(), resultOut, timeOut));
   ASSERT_EQ(Success, resultOut);
   ASSERT_EQ(timeInPlus512, timeOut);
 
   // We've never seen this certificate
   static const Input fakeSerial1111(LiteralInput("1111"));
   ASSERT_FALSE(cache.Get(CertID(fakeIssuer1, fakeKey000, fakeSerial1111),
-                         nullptr, resultOut, timeOut));
+                         NeckoOriginAttributes(), resultOut, timeOut));
 }
 
 TEST_F(psm_OCSPCacheTest, TestEviction)
 {
   SCOPED_TRACE("");
   // By putting more distinct entries in the cache than it can hold,
   // we cause the least recently used entry to be evicted.
   for (int i = 0; i < MaxCacheEntries + 1; i++) {
@@ -145,17 +149,17 @@ TEST_F(psm_OCSPCacheTest, TestEviction)
     ASSERT_EQ(Success, timeIn.AddSeconds(i));
     PutAndGet(cache, CertID(fakeIssuer1, fakeKey000, fakeSerial),
               Success, timeIn);
   }
 
   Result resultOut;
   Time timeOut(Time::uninitialized);
   ASSERT_FALSE(cache.Get(CertID(fakeIssuer1, fakeKey001, fakeSerial0000),
-                         nullptr, resultOut, timeOut));
+                         NeckoOriginAttributes(), resultOut, timeOut));
 }
 
 TEST_F(psm_OCSPCacheTest, TestNoEvictionForRevokedResponses)
 {
   SCOPED_TRACE("");
   CertID notEvicted(fakeIssuer1, fakeKey000, fakeSerial0000);
   Time timeIn(now);
   PutAndGet(cache, notEvicted, Result::ERROR_REVOKED_CERTIFICATE, timeIn);
@@ -169,23 +173,23 @@ TEST_F(psm_OCSPCacheTest, TestNoEviction
     ASSERT_EQ(Success, fakeSerial.Init(serialBuf, 4));
     Time timeIn(now);
     ASSERT_EQ(Success, timeIn.AddSeconds(i));
     PutAndGet(cache, CertID(fakeIssuer1, fakeKey000, fakeSerial),
               Success, timeIn);
   }
   Result resultOut;
   Time timeOut(Time::uninitialized);
-  ASSERT_TRUE(cache.Get(notEvicted, nullptr, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(notEvicted, NeckoOriginAttributes(), resultOut, timeOut));
   ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE, resultOut);
   ASSERT_EQ(timeIn, timeOut);
 
   Input fakeSerial0001(LiteralInput("0001"));
   CertID evicted(fakeIssuer1, fakeKey000, fakeSerial0001);
-  ASSERT_FALSE(cache.Get(evicted, nullptr, resultOut, timeOut));
+  ASSERT_FALSE(cache.Get(evicted, NeckoOriginAttributes(), resultOut, timeOut));
 }
 
 TEST_F(psm_OCSPCacheTest, TestEverythingIsRevoked)
 {
   SCOPED_TRACE("");
   Time timeIn(now);
   // Fill up the cache with revoked responses.
   for (int i = 0; i < MaxCacheEntries; i++) {
@@ -202,71 +206,71 @@ TEST_F(psm_OCSPCacheTest, TestEverything
   static const Input fakeSerial1025(LiteralInput("1025"));
   CertID good(fakeIssuer1, fakeKey000, fakeSerial1025);
   // This will "succeed", allowing verification to continue. However,
   // nothing was actually put in the cache.
   Time timeInPlus1025(timeIn);
   ASSERT_EQ(Success, timeInPlus1025.AddSeconds(1025));
   Time timeInPlus1025Minus50(timeInPlus1025);
   ASSERT_EQ(Success, timeInPlus1025Minus50.SubtractSeconds(50));
-  Result result = cache.Put(good, nullptr, Success, timeInPlus1025Minus50,
+  Result result = cache.Put(good, NeckoOriginAttributes(), Success, timeInPlus1025Minus50,
                             timeInPlus1025);
   ASSERT_EQ(Success, result);
   Result resultOut;
   Time timeOut(Time::uninitialized);
-  ASSERT_FALSE(cache.Get(good, nullptr, resultOut, timeOut));
+  ASSERT_FALSE(cache.Get(good, NeckoOriginAttributes(), resultOut, timeOut));
 
   static const Input fakeSerial1026(LiteralInput("1026"));
   CertID revoked(fakeIssuer1, fakeKey000, fakeSerial1026);
   // This will fail, causing verification to fail.
   Time timeInPlus1026(timeIn);
   ASSERT_EQ(Success, timeInPlus1026.AddSeconds(1026));
   Time timeInPlus1026Minus50(timeInPlus1026);
   ASSERT_EQ(Success, timeInPlus1026Minus50.SubtractSeconds(50));
-  result = cache.Put(revoked, nullptr, Result::ERROR_REVOKED_CERTIFICATE,
+  result = cache.Put(revoked, NeckoOriginAttributes(), Result::ERROR_REVOKED_CERTIFICATE,
                      timeInPlus1026Minus50, timeInPlus1026);
   ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE, result);
 }
 
 TEST_F(psm_OCSPCacheTest, VariousIssuers)
 {
   SCOPED_TRACE("");
   Time timeIn(now);
   static const Input fakeIssuer2(LiteralInput("CN=issuer2"));
   static const Input fakeSerial001(LiteralInput("001"));
   CertID subject(fakeIssuer1, fakeKey000, fakeSerial001);
   PutAndGet(cache, subject, Success, now);
   Result resultOut;
   Time timeOut(Time::uninitialized);
-  ASSERT_TRUE(cache.Get(subject, nullptr, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(subject, NeckoOriginAttributes(), resultOut, timeOut));
   ASSERT_EQ(Success, resultOut);
   ASSERT_EQ(timeIn, timeOut);
   // Test that we don't match a different issuer DN
   ASSERT_FALSE(cache.Get(CertID(fakeIssuer2, fakeKey000, fakeSerial001),
-                         nullptr, resultOut, timeOut));
+                         NeckoOriginAttributes(), resultOut, timeOut));
   // Test that we don't match a different issuer key
   ASSERT_FALSE(cache.Get(CertID(fakeIssuer1, fakeKey001, fakeSerial001),
-                         nullptr, resultOut, timeOut));
+                         NeckoOriginAttributes(), resultOut, timeOut));
 }
 
 TEST_F(psm_OCSPCacheTest, Times)
 {
   SCOPED_TRACE("");
   CertID certID(fakeIssuer1, fakeKey000, fakeSerial0000);
   PutAndGet(cache, certID, Result::ERROR_OCSP_UNKNOWN_CERT,
             TimeFromElapsedSecondsAD(100));
   PutAndGet(cache, certID, Success, TimeFromElapsedSecondsAD(200));
   // This should not override the more recent entry.
   ASSERT_EQ(Success,
-            cache.Put(certID, nullptr, Result::ERROR_OCSP_UNKNOWN_CERT,
+            cache.Put(certID, NeckoOriginAttributes(), Result::ERROR_OCSP_UNKNOWN_CERT,
                       TimeFromElapsedSecondsAD(100),
                       TimeFromElapsedSecondsAD(100)));
   Result resultOut;
   Time timeOut(Time::uninitialized);
-  ASSERT_TRUE(cache.Get(certID, nullptr, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(certID, NeckoOriginAttributes(), resultOut, timeOut));
   // Here we see the more recent time.
   ASSERT_EQ(Success, resultOut);
   ASSERT_EQ(TimeFromElapsedSecondsAD(200), timeOut);
 
   // Result::ERROR_REVOKED_CERTIFICATE overrides everything
   PutAndGet(cache, certID, Result::ERROR_REVOKED_CERTIFICATE,
             TimeFromElapsedSecondsAD(50));
 }
@@ -275,51 +279,59 @@ TEST_F(psm_OCSPCacheTest, NetworkFailure
 {
   SCOPED_TRACE("");
   CertID certID(fakeIssuer1, fakeKey000, fakeSerial0000);
   PutAndGet(cache, certID, Result::ERROR_CONNECT_REFUSED,
             TimeFromElapsedSecondsAD(100));
   PutAndGet(cache, certID, Success, TimeFromElapsedSecondsAD(200));
   // This should not override the already present entry.
   ASSERT_EQ(Success,
-            cache.Put(certID, nullptr, Result::ERROR_CONNECT_REFUSED,
+            cache.Put(certID, NeckoOriginAttributes(), Result::ERROR_CONNECT_REFUSED,
                       TimeFromElapsedSecondsAD(300),
                       TimeFromElapsedSecondsAD(350)));
   Result resultOut;
   Time timeOut(Time::uninitialized);
-  ASSERT_TRUE(cache.Get(certID, nullptr, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(certID, NeckoOriginAttributes(), resultOut, timeOut));
   ASSERT_EQ(Success, resultOut);
   ASSERT_EQ(TimeFromElapsedSecondsAD(200), timeOut);
 
   PutAndGet(cache, certID, Result::ERROR_OCSP_UNKNOWN_CERT,
             TimeFromElapsedSecondsAD(400));
   // This should not override the already present entry.
   ASSERT_EQ(Success,
-            cache.Put(certID, nullptr, Result::ERROR_CONNECT_REFUSED,
+            cache.Put(certID, NeckoOriginAttributes(), Result::ERROR_CONNECT_REFUSED,
                       TimeFromElapsedSecondsAD(500),
                       TimeFromElapsedSecondsAD(550)));
-  ASSERT_TRUE(cache.Get(certID, nullptr, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(certID, NeckoOriginAttributes(), resultOut, timeOut));
   ASSERT_EQ(Result::ERROR_OCSP_UNKNOWN_CERT, resultOut);
   ASSERT_EQ(TimeFromElapsedSecondsAD(400), timeOut);
 
   PutAndGet(cache, certID, Result::ERROR_REVOKED_CERTIFICATE,
             TimeFromElapsedSecondsAD(600));
   // This should not override the already present entry.
   ASSERT_EQ(Success,
-            cache.Put(certID, nullptr, Result::ERROR_CONNECT_REFUSED,
+            cache.Put(certID, NeckoOriginAttributes(), Result::ERROR_CONNECT_REFUSED,
                       TimeFromElapsedSecondsAD(700),
                       TimeFromElapsedSecondsAD(750)));
-  ASSERT_TRUE(cache.Get(certID, nullptr, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(certID, NeckoOriginAttributes(), resultOut, timeOut));
   ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE, resultOut);
   ASSERT_EQ(TimeFromElapsedSecondsAD(600), timeOut);
 }
 
-TEST_F(psm_OCSPCacheTest, TestFirstPartyDomain)
+TEST_F(psm_OCSPCacheTest, TestOriginAttributes)
 {
   CertID certID(fakeIssuer1, fakeKey000, fakeSerial0000);
 
   SCOPED_TRACE("");
-  PutAndGet(cache, certID, Success, now, "foo.com");
+  NeckoOriginAttributes attrs;
+  attrs.mFirstPartyDomain.AssignLiteral("foo.com");
+  PutAndGet(cache, certID, Success, now, attrs);
 
   Result resultOut;
   Time timeOut(Time::uninitialized);
-  ASSERT_FALSE(cache.Get(certID, "bar.com", resultOut, timeOut));
+  attrs.mFirstPartyDomain.AssignLiteral("bar.com");
+  ASSERT_FALSE(cache.Get(certID, attrs, resultOut, timeOut));
+
+  // OCSP cache should not be isolated by containers.
+  attrs.mUserContextId = 1;
+  attrs.mFirstPartyDomain.AssignLiteral("foo.com");
+  ASSERT_TRUE(cache.Get(certID, attrs, resultOut, timeOut));
 }
--- a/security/manager/ssl/tests/unit/head_psm.js
+++ b/security/manager/ssl/tests/unit/head_psm.js
@@ -319,39 +319,42 @@ function add_tls_server_setup(serverBinN
  *   A callback function that takes no arguments that will be called before the
  *   connection is attempted.
  * @param {Function} aWithSecurityInfo
  *   A callback function that takes an nsITransportSecurityInfo, which is called
  *   after the TLS handshake succeeds.
  * @param {Function} aAfterStreamOpen
  *   A callback function that is called with the nsISocketTransport once the
  *   output stream is ready.
- * @param {String} aFirstPartyDomain
- *   The first party domain which will be used to double-key the OCSP cache.
+ * @param {OriginAttributes} aOriginAttributes (optional)
+ *   The origin attributes that the socket transport will have. This parameter
+ *   affects OCSP because OCSP cache is double-keyed by origin attributes' first
+ *   party domain.
  */
 function add_connection_test(aHost, aExpectedResult,
                              aBeforeConnect, aWithSecurityInfo,
-                             aAfterStreamOpen, aFirstPartyDomain) {
+                             aAfterStreamOpen,
+                             /*optional*/ aOriginAttributes) {
   const REMOTE_PORT = 8443;
 
   function Connection(host) {
     this.host = host;
     let threadManager = Cc["@mozilla.org/thread-manager;1"]
                           .getService(Ci.nsIThreadManager);
     this.thread = threadManager.currentThread;
     this.defer = Promise.defer();
     let sts = Cc["@mozilla.org/network/socket-transport-service;1"]
                 .getService(Ci.nsISocketTransportService);
     this.transport = sts.createTransport(["ssl"], 1, host, REMOTE_PORT, null);
     // See bug 1129771 - attempting to connect to [::1] when the server is
     // listening on 127.0.0.1 causes frequent failures on OS X 10.10.
     this.transport.connectionFlags |= Ci.nsISocketTransport.DISABLE_IPV6;
     this.transport.setEventSink(this, this.thread);
-    if (aFirstPartyDomain) {
-      this.transport.firstPartyDomain = aFirstPartyDomain;
+    if (aOriginAttributes) {
+      this.transport.originAttributes = aOriginAttributes;
     }
     this.inputStream = null;
     this.outputStream = null;
     this.connected = false;
   }
 
   Connection.prototype = {
     // nsITransportEventSink
--- a/security/manager/ssl/tests/unit/test_ocsp_caching.js
+++ b/security/manager/ssl/tests/unit/test_ocsp_caching.js
@@ -38,30 +38,30 @@ function respondWithError(request, respo
 
 function generateGoodOCSPResponse() {
   let args = [ ["good", "default-ee", "unused" ] ];
   let responses = generateOCSPResponses(args, "ocsp_certs");
   return responses[0];
 }
 
 function add_ocsp_test(aHost, aExpectedResult, aResponses, aMessage,
-                       aFirstPartyDomain) {
+                       aOriginAttributes) {
   add_connection_test(aHost, aExpectedResult,
       function() {
         clearSessionCache();
         gFetchCount = 0;
         gResponsePattern = aResponses;
         gMessage = aMessage;
       },
       function() {
         // check the number of requests matches the size of aResponses
         equal(gFetchCount, aResponses.length,
               "should have made " + aResponses.length +
               " OCSP request" + (aResponses.length == 1 ? "" : "s"));
-      }, null, aFirstPartyDomain);
+      }, null, aOriginAttributes);
 }
 
 function run_test() {
   do_get_profile();
   Services.prefs.setBoolPref("security.ssl.enable_ocsp_stapling", true);
   Services.prefs.setIntPref("security.OCSP.enabled", 1);
   Services.prefs.setIntPref("security.pki.sha1_enforcement_level", 4);
   add_tls_server_setup("OCSPStaplingServer", "ocsp_certs");
@@ -254,22 +254,23 @@ function add_tests() {
     stopObservingChannels = startObservingChannels("foo.com");
     run_next_test();
   });
 
   // A good OCSP response will be cached.
   add_ocsp_test("ocsp-stapling-none.example.com", PRErrorCodeSuccess,
                 [respondWithGoodOCSP],
                 "No stapled response (firstPartyDomain = foo.com) -> a fetch " +
-                "should have been attempted", "foo.com");
+                "should have been attempted", { firstPartyDomain: "foo.com" });
 
   // The cache will prevent a fetch from happening.
   add_ocsp_test("ocsp-stapling-none.example.com", PRErrorCodeSuccess, [],
                 "Noted OCSP server failure (firstPartyDomain = foo.com) -> a " +
-                "fetch should not have been attempted", "foo.com");
+                "fetch should not have been attempted",
+                { firstPartyDomain: "foo.com" });
 
   add_test(function() {
     stopObservingChannels();
     equal(gObservedCnt, 1, "should have observed only 1 OCSP requests");
     gObservedCnt = 0;
     run_next_test();
   });
 
@@ -277,17 +278,17 @@ function add_tests() {
     stopObservingChannels = startObservingChannels("bar.com");
     run_next_test();
   });
 
   // But using a different firstPartyDomain should result in a fetch.
   add_ocsp_test("ocsp-stapling-none.example.com", PRErrorCodeSuccess,
                 [respondWithGoodOCSP],
                 "No stapled response (firstPartyDomain = bar.com) -> a fetch " +
-                "should have been attempted", "bar.com");
+                "should have been attempted", { firstPartyDomain: "bar.com" });
 
   add_test(function() {
     stopObservingChannels();
     equal(gObservedCnt, 1, "should have observed only 1 OCSP requests");
     gObservedCnt = 0;
     run_next_test();
   });
 
--- a/startupcache/StartupCache.cpp
+++ b/startupcache/StartupCache.cpp
@@ -435,18 +435,18 @@ StartupCache::WriteToDisk()
     return;
   }
 
   CacheWriteHolder holder;
   holder.stream = stream;
   holder.writer = zipW;
   holder.time = now;
 
-  for (auto key = mPendingWrites.begin(); key != mPendingWrites.end(); key++) {
-    CacheCloseHelper(*key, mTable.Get(*key), &holder);
+  for (auto& key : mPendingWrites) {
+    CacheCloseHelper(key, mTable.Get(key), &holder);
   }
   mPendingWrites.Clear();
   mTable.Clear();
 
   // Close the archive so Windows doesn't choke.
   mArchive = nullptr;
   zipW->Close();
 
@@ -564,18 +564,17 @@ StartupCacheListener::Observe(nsISupport
 } 
 
 nsresult
 StartupCache::GetDebugObjectOutputStream(nsIObjectOutputStream* aStream,
                                          nsIObjectOutputStream** aOutStream) 
 {
   NS_ENSURE_ARG_POINTER(aStream);
 #ifdef DEBUG
-  StartupCacheDebugOutputStream* stream
-    = new StartupCacheDebugOutputStream(aStream, &mWriteObjectMap);
+  auto* stream = new StartupCacheDebugOutputStream(aStream, &mWriteObjectMap);
   NS_ADDREF(*aOutStream = stream);
 #else
   NS_ADDREF(*aOutStream = aStream);
 #endif
   
   return NS_OK;
 }
 
--- a/testing/docker/desktop1604-test/Dockerfile
+++ b/testing/docker/desktop1604-test/Dockerfile
@@ -48,17 +48,16 @@ ENV           USER          worker
 ENV           LOGNAME       worker
 ENV           HOSTNAME      taskcluster-worker
 ENV           LANG          en_US.UTF-8
 ENV           LC_ALL        en_US.UTF-8
 
 # Add utilities and configuration
 COPY           dot-files/config              /home/worker/.config
 COPY           dot-files/pulse               /home/worker/.pulse
-COPY           bin                           /home/worker/bin
 RUN            chmod +x bin/*
 # TODO: remove this when buildbot is gone
 COPY           buildprops.json               /home/worker/buildprops.json
 COPY           tc-vcs-config.yml /etc/taskcluster-vcs.yml
 
 # TODO: remove
 ADD            https://raw.githubusercontent.com/taskcluster/buildbot-step/master/buildbot_step /home/worker/bin/buildbot_step
 RUN chmod u+x /home/worker/bin/buildbot_step
deleted file mode 100755
--- a/testing/docker/desktop1604-test/bin/run-wizard
+++ /dev/null
@@ -1,108 +0,0 @@
-#!/usr/bin/env python
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this,
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-from __future__ import print_function, unicode_literals
-
-import os
-import subprocess
-import sys
-from textwrap import wrap
-
-
-def call(cmd, **kwargs):
-    print(" ".join(cmd))
-    return subprocess.call(cmd, **kwargs)
-
-
-def resume():
-    call(['run-mozharness'])
-
-
-def setup():
-    call(['run-mozharness', '--no-run-tests'])
-    print("Mozharness has finished downloading the build and "
-          "tests to {}.".format(os.path.join(os.getcwd(), 'build')))
-
-
-def clone():
-    repo = os.environ['GECKO_HEAD_REPOSITORY']
-    rev = os.environ['GECKO_HEAD_REV']
-    clone_path = os.path.expanduser(os.path.join('~', 'gecko'))
-
-    # try is too large to clone, instead clone central and pull
-    # in changes from try
-    if "hg.mozilla.org/try" in repo:
-        central = 'http://hg.mozilla.org/mozilla-central'
-        call(['hg', 'clone', '-U', central, clone_path])
-        call(['hg', 'pull', '-u', '-r', rev, repo], cwd=clone_path)
-    else:
-        call(['hg', 'clone', '-u', rev, repo, clone_path])
-    print("Finished cloning to {} at revision {}.".format(
-                clone_path, rev))
-
-
-def exit():
-    pass
-
-
-OPTIONS = [
-    ('Resume task', resume,
-     "Resume the original task without modification. This can be useful for "
-     "passively monitoring it from another shell."),
-    ('Setup task', setup,
-     "Setup the task (download the application and tests) but don't run the "
-     "tests just yet. The tests can be run with a custom configuration later "
-     "(experimental)."),
-    ('Clone gecko', clone,
-     "Perform a clone of gecko using the task's repo and update it to the "
-     "task's revision."),
-    ('Exit', exit, "Exit this wizard and return to the shell.")
-]
-
-
-def _fmt_options():
-    max_line_len = 60
-    max_name_len = max(len(o[0]) for o in OPTIONS)
-
-    # TODO Pad will be off if there are more than 9 options.
-    pad = ' ' * (max_name_len+6)
-
-    msg = []
-    for i, (name, _, desc) in enumerate(OPTIONS):
-        desc = wrap(desc, width=max_line_len)
-        desc = [desc[0]] + [pad + l for l in desc[1:]]
-
-        optstr = '{}) {} - {}\n'.format(
-            i+1, name.ljust(max_name_len), '\n'.join(desc))
-        msg.append(optstr)
-    msg.append("Select one of the above options: ")
-    return '\n'.join(msg)
-
-
-def wizard():
-    print("This wizard can help you get started with some common debugging "
-          "workflows.\nWhat would you like to do?\n")
-    print(_fmt_options(), end="")
-    choice = None
-    while True:
-        choice = raw_input().decode('utf8')
-        try:
-            choice = int(choice)-1
-            if 0 <= choice < len(OPTIONS):
-                break
-        except ValueError:
-            pass
-
-        print("Must provide an integer from 1-{}:".format(len(OPTIONS)))
-
-    func = OPTIONS[choice][1]
-    ret = func()
-
-    print("Use the 'run-wizard' command to start this wizard again.")
-    return ret
-
-
-if __name__ == '__main__':
-    sys.exit(wizard())
--- a/testing/docker/desktop1604-test/taskcluster-interactive-shell
+++ b/testing/docker/desktop1604-test/taskcluster-interactive-shell
@@ -1,10 +1,22 @@
 #!/usr/bin/env bash
-/home/worker/bin/run-wizard;
+
+download() {
+    name=`basename $1`
+    url=${GECKO_HEAD_REPOSITORY}/raw-file/${GECKO_HEAD_REV}/$1
+    if ! curl --fail --silent -o ./$name --retry 10 $url; then
+        fail "failed downloading $1 from ${GECKO_HEAD_REPOSITORY}"
+    fi
+}
+
+cd $HOME/bin;
+download taskcluster/scripts/tester/run-wizard;
+chmod +x run-wizard;
+./run-wizard;
 
 SPAWN="$SHELL";
-
 if [ "$SHELL" = "bash" ]; then
   SPAWN="bash -li";
 fi;
 
+cd $HOME;
 exec $SPAWN;
--- a/testing/marionette/client/marionette_driver/marionette.py
+++ b/testing/marionette/client/marionette_driver/marionette.py
@@ -13,28 +13,28 @@ import time
 import traceback
 import warnings
 
 from contextlib import contextmanager
 
 from decorators import do_process_check
 from keys import Keys
 
+import errors
 import geckoinstance
-import errors
 import transport
+from timeout import Timeouts
+
 
 WEBELEMENT_KEY = "ELEMENT"
 W3C_WEBELEMENT_KEY = "element-6066-11e4-a52e-4f735466cecf"
 
 
 class HTMLElement(object):
-    """
-    Represents a DOM Element.
-    """
+    """Represents a DOM Element."""
 
     def __init__(self, marionette, id):
         self.marionette = marionette
         assert(id is not None)
         self.id = id
 
     def __str__(self):
         return self.id
@@ -533,58 +533,68 @@ class Alert(object):
         tab modal dialog."""
         body = {"value": Marionette.convert_keys(*string)}
         self.marionette._send_message("sendKeysToDialog", body)
 
 
 class Marionette(object):
     """Represents a Marionette connection to a browser or device."""
 
-    CONTEXT_CHROME = 'chrome'  # non-browser content: windows, dialogs, etc.
-    CONTEXT_CONTENT = 'content'  # browser content: iframes, divs, etc.
+    CONTEXT_CHROME = "chrome"  # non-browser content: windows, dialogs, etc.
+    CONTEXT_CONTENT = "content"  # browser content: iframes, divs, etc.
     DEFAULT_SOCKET_TIMEOUT = 60
     DEFAULT_STARTUP_TIMEOUT = 120
     DEFAULT_SHUTDOWN_TIMEOUT = 65  # Firefox will kill hanging threads after 60s
 
-    def __init__(self, host='localhost', port=2828, app=None, bin=None,
-                 baseurl=None, timeout=None, socket_timeout=DEFAULT_SOCKET_TIMEOUT,
+    def __init__(self, host="localhost", port=2828, app=None, bin=None,
+                 baseurl=None, socket_timeout=DEFAULT_SOCKET_TIMEOUT,
                  startup_timeout=None, **instance_args):
-        """
-        :param host: address for Marionette connection
-        :param port: integer port for Marionette connection
-        :param baseurl: where to look for files served from Marionette's www directory
-        :param startup_timeout: seconds to wait for a connection with binary
-        :param timeout: time to wait for page load, scripts, search
-        :param socket_timeout: timeout for Marionette socket operations
-        :param bin: path to app binary; if any truthy value is given this will
-            attempt to start a gecko instance with the specified `app`
-        :param app: type of instance_class to use for managing app instance.
-            See marionette_driver.geckoinstance
-        :param instance_args: args to pass to instance_class
+        """Construct a holder for the Marionette connection.
+
+        Remember to call ``start_session`` in order to initiate the
+        connection and start a Marionette session.
+
+        :param host: Host where the Marionette server listens.
+            Defaults to localhost.
+        :param port: Port where the Marionette server listens.
+            Defaults to port 2828.
+        :param baseurl: Where to look for files served from Marionette's
+            www directory.
+        :param socket_timeout: Timeout for Marionette socket operations.
+        :param startup_timeout: Seconds to wait for a connection with
+            binary.
+        :param bin: Path to browser binary.  If any truthy value is given
+            this will attempt to start a Gecko instance with the specified
+            `app`.
+        :param app: Type of ``instance_class`` to use for managing app
+            instance. See ``marionette_driver.geckoinstance``.
+        :param instance_args: Arguments to pass to ``instance_class``.
+
         """
         self.host = host
         self.port = self.local_port = int(port)
         self.bin = bin
         self.instance = None
         self.session = None
         self.session_id = None
         self.window = None
         self.chrome_window = None
         self.baseurl = baseurl
         self._test_name = None
-        self.timeout = timeout
         self.socket_timeout = socket_timeout
         self.crashed = 0
 
         startup_timeout = startup_timeout or self.DEFAULT_STARTUP_TIMEOUT
         if self.bin:
             self.instance = self._create_instance(app, instance_args)
             self.instance.start()
             self.raise_for_port(timeout=startup_timeout)
 
+        self.timeout = Timeouts(self)
+
     def _create_instance(self, app, instance_args):
         if not Marionette.is_port_available(self.port, host=self.host):
             ex_msg = "{0}:{1} is unavailable.".format(self.host, self.port)
             raise errors.MarionetteException(message=ex_msg)
         if app:
             # select instance class for the given app
             try:
                 instance_class = geckoinstance.apps[app]
@@ -757,33 +767,16 @@ class Marionette(object):
 
         else:
             error = obj["error"]
             message = obj["message"]
             stacktrace = obj["stacktrace"]
 
         raise errors.lookup(error)(message, stacktrace=stacktrace)
 
-    def reset_timeouts(self):
-        """Resets timeouts to their defaults to the `self.timeout`
-        attribute. If unset, only the page load timeout is reset to
-        30 seconds.
-
-        """
-
-        timeout_types = {"search": self.set_search_timeout,
-                         "script": self.set_script_timeout,
-                         "page load": self.set_page_load_timeout}
-
-        if self.timeout is not None:
-            for typ, ms in self.timeout:
-                timeout_types[typ](ms)
-        else:
-            self.set_page_load_timeout(30000)
-
     def check_for_crash(self):
         """Check if the process crashed.
 
         :returns: True, if a crash happened since the method has been called the last time.
         """
         crash_count = 0
 
         if self.instance:
@@ -1122,17 +1115,17 @@ class Marionette(object):
                     break
 
         if not pref_exists:
             context = self._send_message("getContext", key="value")
             self.delete_session()
             self.instance.restart(prefs)
             self.raise_for_port()
             self.start_session()
-            self.reset_timeouts()
+            self.timeout.reset()
 
             # Restore the context as used before the restart
             self.set_context(context)
 
     def _request_in_app_shutdown(self, shutdown_flags=None):
         """Terminate the currently running instance from inside the application.
 
         :param shutdown_flags: If specified use additional flags for the shutdown
@@ -1171,17 +1164,17 @@ class Marionette(object):
                        by killing the process.
         :param callback: If provided and `in_app` is True, the callback will
                          be used to trigger the shutdown.
         """
         if not self.instance:
             raise errors.MarionetteException("quit() can only be called "
                                              "on Gecko instances launched by Marionette")
 
-        self.reset_timeouts()
+        self.timeout.reset()
 
         if in_app:
             if callable(callback):
                 self._send_message("acceptConnections", {"value": False})
                 callback()
             else:
                 self._request_in_app_shutdown()
 
@@ -1238,17 +1231,17 @@ class Marionette(object):
                     raise exc, "Requested restart of the application was aborted", tb
 
         else:
             self.delete_session()
             self.instance.restart(clean=clean)
             self.raise_for_port()
 
         self.start_session(session_id=session_id)
-        self.reset_timeouts()
+        self.timeout.reset()
 
         # Restore the context as used before the restart
         self.set_context(context)
 
         if in_app and self.session.get("processId"):
             # In some cases Firefox restarts itself by spawning into a new process group.
             # As long as mozprocess cannot track that behavior (bug 1284864) we assist by
             # informing about the new process id.
@@ -1346,67 +1339,64 @@ class Marionette(object):
 
         If a script does not return in the specified amount of time,
         a ScriptTimeoutException is raised.
 
         :param timeout: The maximum number of milliseconds an asynchronous
             script can run without causing an ScriptTimeoutException to
             be raised
 
+        .. note:: `set_script_timeout` is deprecated, please use
+            `timeout.script` setter.
+
         """
-        try:
-            self._send_message("timeouts", {"script": timeout})
-        except errors.MarionetteException as e:
-            # remove when 52.0a is stable
-            if "Not a Number" in e.message:
-                self._send_message("timeouts", {"type": "script", "ms": timeout})
-            else:
-                raise e
+        warnings.warn(
+            "set_script_timeout is deprecated, please use timeout.script setter",
+            DeprecationWarning)
+        self.timeout.script = timeout / 1000
 
     def set_search_timeout(self, timeout):
         """Sets a timeout for the find methods.
 
         When searching for an element using
         either :class:`Marionette.find_element` or
         :class:`Marionette.find_elements`, the method will continue
         trying to locate the element for up to timeout ms. This can be
         useful if, for example, the element you're looking for might
         not exist immediately, because it belongs to a page which is
         currently being loaded.
 
         :param timeout: Timeout in milliseconds.
 
+        .. note:: `set_search_timeout` is deprecated, please use
+            `timeout.implicit` setter.
+
         """
-        try:
-            self._send_message("timeouts", {"implicit": timeout})
-        except errors.MarionetteException as e:
-            # remove when 52.0a is stable
-            if "Not a Number" in e.message:
-                self._send_message("timeouts", {"type": "implicit", "ms": timeout})
-            else:
-                raise e
+        warnings.warn(
+            "set_search_timeout is deprecated, please use timeout.implicit setter",
+            DeprecationWarning)
+        self.timeout.implicit = timeout / 1000
 
     def set_page_load_timeout(self, timeout):
         """Sets a timeout for loading pages.
 
         A page load timeout specifies the amount of time the Marionette
         instance should wait for a page load operation to complete. A
         ``TimeoutException`` is returned if this limit is exceeded.
 
         :param timeout: Timeout in milliseconds.
 
+        .. note:: `set_page_load_timeout` is deprecated, please use
+            `timeout.page_load` setter.
+
         """
-        try:
-            self._send_message("timeouts", {"page load": timeout})
-        except errors.MarionetteException as e:
-            # remove when 52.0a is stable
-            if "Not a Number" in e.message:
-                self._send_message("timeouts", {"type": "page load", "ms": timeout})
-            else:
-                raise e
+        warnings.warn(
+            "set_page_load_timeout is deprecated, please use timeout.page_load setter",
+            DeprecationWarning)
+        self.timeout.page_load = timeout / 1000
 
     @property
     def current_window_handle(self):
         """Get the current window's handle.
 
         Returns an opaque server-assigned identifier to this window
         that uniquely identifies it within this Marionette instance.
         This can be used to switch to this window at a later point.
@@ -1820,17 +1810,17 @@ class Marionette(object):
             in which case no globals are preserved.
         :param debug_script: Capture javascript exceptions when in
             `CONTEXT_CHROME` context.
 
         Usage example:
 
         ::
 
-            marionette.set_script_timeout(10000) # set timeout period of 10 seconds
+            marionette.timeout.script = 10
             result = self.marionette.execute_async_script('''
               // this script waits 5 seconds, and then returns the number 1
               setTimeout(function() {
                 marionetteScriptFinished(1);
               }, 5000);
             ''')
             assert result == 1
         """
@@ -1850,17 +1840,17 @@ class Marionette(object):
 
     def find_element(self, method, target, id=None):
         """Returns an HTMLElement instances that matches the specified
         method and target in the current context.
 
         An HTMLElement instance may be used to call other methods on the
         element, such as click().  If no element is immediately found, the
         attempt to locate an element will be repeated for up to the amount of
-        time set by set_search_timeout(). If multiple elements match the given
+        time set by ``timeout.implicit``. If multiple elements match the given
         criteria, only the first is returned. If no element matches, a
         NoSuchElementException will be raised.
 
         :param method: The method to use to locate the element; one of:
             "id", "name", "class name", "tag name", "css selector",
             "link text", "partial link text", "xpath", "anon" and "anon
             attribute". Note that the "name", "link text" and "partial
             link test" methods are not supported in the chrome DOM.
@@ -1877,17 +1867,17 @@ class Marionette(object):
 
     def find_elements(self, method, target, id=None):
         """Returns a list of all HTMLElement instances that match the
         specified method and target in the current context.
 
         An HTMLElement instance may be used to call other methods on the
         element, such as click().  If no element is immediately found,
         the attempt to locate an element will be repeated for up to the
-        amount of time set by set_search_timeout().
+        amount of time set by ``timeout.implicit``.
 
         :param method: The method to use to locate the elements; one
             of: "id", "name", "class name", "tag name", "css selector",
             "link text", "partial link text", "xpath", "anon" and "anon
             attribute". Note that the "name", "link text" and "partial link
             test" methods are not supported in the chrome DOM.
         :param target: The target of the search.  For example, if method =
             "tag", target might equal "div".  If method = "id", target would be
new file mode 100644
--- /dev/null
+++ b/testing/marionette/client/marionette_driver/timeout.py
@@ -0,0 +1,98 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+import errors
+
+
+DEFAULT_SCRIPT_TIMEOUT = 30
+DEFAULT_PAGE_LOAD_TIMEOUT = 300
+DEFAULT_IMPLICIT_WAIT_TIMEOUT = 0
+
+
+class Timeouts(object):
+    """Manage timeout settings in the Marionette session.
+
+    Usage::
+
+        marionette = Marionette(...)
+        marionette.start_session()
+        marionette.timeout.page_load = 10
+        marionette.timeout.page_load
+        # => 10
+
+    """
+
+    def __init__(self, marionette):
+        self._marionette = marionette
+
+    def _set(self, name, sec):
+        ms = sec * 1000
+        try:
+            self._marionette._send_message("setTimeouts", {name: ms})
+        except errors.UnknownCommandException:
+            # remove when 55 is stable
+            self._marionette._send_message("timeouts", {"type": name, "ms": ms})
+
+    def _get(self, name):
+        ms = self._marionette._send_message("getTimeouts", key=name)
+        return ms / 1000
+
+    @property
+    def script(self):
+        """Get the session's script timeout.  This specifies the time
+        to wait for injected scripts to finished before interrupting
+        them. It is by default 30 seconds.
+
+        """
+        return self._get("script")
+
+    @script.setter
+    def script(self, sec):
+        """Set the session's script timeout.  This specifies the time
+        to wait for injected scripts to finish before interrupting them.
+
+        """
+        self._set("script", sec)
+
+    @property
+    def page_load(self):
+        """Get the session's page load timeout.  This specifies the time
+        to wait for the page loading to complete.  It is by default 5
+        minutes (or 300 seconds).
+
+        """
+        return self._get("page load")
+
+    @page_load.setter
+    def page_load(self, sec):
+        """Set the session's page load timeout.  This specifies the time
+        to wait for the page loading to complete.
+
+        """
+        self._set("page load", sec)
+
+    @property
+    def implicit(self):
+        """Get the session's implicit wait timeout.  This specifies the
+        time to wait for the implicit element location strategy when
+        retrieving elements.  It is by default disabled (0 seconds).
+
+        """
+        return self._get("implicit")
+
+    @implicit.setter
+    def implicit(self, sec):
+        """Set the session's implicit wait timeout.  This specifies the
+        time to wait for the implicit element location strategy when
+        retrieving elements.
+
+        """
+        self._set("implicit", sec)
+
+    def reset(self):
+        """Resets timeouts to their default values."""
+        self.script = DEFAULT_SCRIPT_TIMEOUT
+        self.page_load = DEFAULT_PAGE_LOAD_TIMEOUT
+        self.implicit = DEFAULT_IMPLICIT_WAIT_TIMEOUT
--- a/testing/marionette/client/marionette_driver/wait.py
+++ b/testing/marionette/client/marionette_driver/wait.py
@@ -7,33 +7,34 @@ import errors
 import sys
 import time
 
 DEFAULT_TIMEOUT = 5
 DEFAULT_INTERVAL = 0.1
 
 
 class Wait(object):
+
     """An explicit conditional utility class for waiting until a condition
     evaluates to true or not null.
 
     This will repeatedly evaluate a condition in anticipation for a
     truthy return value, or its timeout to expire, or its waiting
     predicate to become true.
 
     A `Wait` instance defines the maximum amount of time to wait for a
     condition, as well as the frequency with which to check the
     condition.  Furthermore, the user may configure the wait to ignore
     specific types of exceptions whilst waiting, such as
     `errors.NoSuchElementException` when searching for an element on
     the page.
 
     """
 
-    def __init__(self, marionette, timeout=None,
+    def __init__(self, marionette, timeout=DEFAULT_TIMEOUT,
                  interval=DEFAULT_INTERVAL, ignored_exceptions=None,
                  clock=None):
         """Configure the Wait instance to have a custom timeout, interval, and
         list of ignored exceptions.  Optionally a different time
         implementation than the one provided by the standard library
         (time) can also be provided.
 
         Sample usage::
@@ -43,18 +44,17 @@ class Wait(object):
             wait = Wait(marionette, timeout=30, interval=5,
                         ignored_exceptions=errors.NoSuchWindowException)
             window = wait.until(lambda m: m.switch_to_window(42))
 
         :param marionette: The input value to be provided to
             conditions, usually a Marionette instance.
 
         :param timeout: How long to wait for the evaluated condition
-            to become true.  The default timeout is the `timeout`
-            property on the `Marionette` object if set, or
+            to become true.  The default timeout is
             `wait.DEFAULT_TIMEOUT`.
 
         :param interval: How often the condition should be evaluated.
             In reality the interval may be greater as the cost of
             evaluating the condition function. If that is not the case the
             interval for the next condition function call is shortend to keep
             the original interval sequence as best as possible.
             The default polling interval is `wait.DEFAULT_INTERVAL`.
@@ -66,18 +66,17 @@ class Wait(object):
 
         :param clock: Allows overriding the use of the runtime's
             default time library.  See `wait.SystemClock` for
             implementation details.
 
         """
 
         self.marionette = marionette
-        self.timeout = timeout or (self.marionette.timeout and
-                                   self.marionette.timeout / 1000.0) or DEFAULT_TIMEOUT
+        self.timeout = timeout
         self.clock = clock or SystemClock()
         self.end = self.clock.now + self.timeout
         self.interval = interval
 
         exceptions = []
         if ignored_exceptions is not None:
             if isinstance(ignored_exceptions, collections.Iterable):
                 exceptions.extend(iter(ignored_exceptions))
@@ -151,16 +150,17 @@ class Wait(object):
             cause=last_exc)
 
 
 def until_pred(clock, end):
     return clock.now >= end
 
 
 class SystemClock(object):
+
     def __init__(self):
         self._time = time
 
     def sleep(self, duration):
         self._time.sleep(duration)
 
     @property
     def now(self):
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -104,19 +104,21 @@ this.GeckoDriver = function(appName, ser
   this._server = server;
 
   this.sessionId = null;
   this.wins = new browser.Windows();
   this.browsers = {};
   // points to current browser
   this.curBrowser = null;
   this.context = Context.CONTENT;
+
   this.scriptTimeout = 30000;  // 30 seconds
-  this.searchTimeout = null;
+  this.searchTimeout = 0;
   this.pageTimeout = 300000;  // five minutes
+
   this.timer = null;
   this.inactivityTimer = null;
   this.marionetteLog = new logging.ContentLogger();
   // topmost chrome frame
   this.mainFrame = null;
   // chrome iframe that currently has focus
   this.curFrame = null;
   this.mainContentFrameId = null;
@@ -124,17 +126,16 @@ this.GeckoDriver = function(appName, ser
   this.currentFrameElement = null;
   this.testName = null;
   this.mozBrowserClose = null;
   this.sandboxes = new Sandboxes(() => this.getCurrentWindow());
   // frame ID of the current remote frame, used for mozbrowserclose events
   this.oopFrameId = null;
   this.observing = null;
   this._browserIds = new WeakMap();
-  this.actions = new action.Chain();
 
   this.sessionCapabilities = {
     // mandated capabilities
     "browserName": Services.appinfo.name.toLowerCase(),
     "browserVersion": Services.appinfo.version,
     "platformName": Services.sysinfo.getProperty("name").toLowerCase(),
     "platformVersion": Services.sysinfo.getProperty("version"),
     "specificationLevel": 0,
@@ -157,16 +158,18 @@ this.GeckoDriver = function(appName, ser
   let handleDialog = (subject, topic) => {
     let winr;
     if (topic == modal.COMMON_DIALOG_LOADED) {
       winr = Cu.getWeakReference(subject);
     }
     this.dialog = new modal.Dialog(() => this.curBrowser, winr);
   };
   modal.addHandler(handleDialog);
+
+  this.actions = new action.Chain();
 };
 
 GeckoDriver.prototype.QueryInterface = XPCOMUtils.generateQI([
   Ci.nsIMessageListener,
   Ci.nsIObserver,
   Ci.nsISupportsWeakReference
 ]);
 
@@ -1534,28 +1537,36 @@ GeckoDriver.prototype.switchToFrame = fu
           this.curBrowser.frameManager.switchToFrame(winId, frameId);
 
       yield registerBrowsers;
       yield browserListening;
     }
   }
 };
 
+GeckoDriver.prototype.getTimeouts = function(cmd, resp) {
+  return {
+    "implicit": this.searchTimeout,
+    "script": this.scriptTimeout,
+    "page load": this.pageTimeout,
+  };
+};
+
 /**
  * Set timeout for page loading, searching, and scripts.
  *
  * @param {Object.<string, number>}
  *     Dictionary of timeout types and their new value, where all timeout
  *     types are optional.
  *
  * @throws {InvalidArgumentError}
  *     If timeout type key is unknown, or the value provided with it is
  *     not an integer.
  */
-GeckoDriver.prototype.timeouts = function(cmd, resp) {
+GeckoDriver.prototype.setTimeouts = function(cmd, resp) {
   // backwards compatibility with old API
   // that accepted a dictionary {type: <string>, ms: <number>}
   let timeouts = {};
   if (typeof cmd.parameters == "object" &&
       "type" in cmd.parameters &&
       "ms" in cmd.parameters) {
     logger.warn("Using deprecated data structure for setting timeouts");
     timeouts = {[cmd.parameters.type]: parseInt(cmd.parameters.ms)};
@@ -2800,17 +2811,19 @@ GeckoDriver.prototype.commands = {
   "sayHello": GeckoDriver.prototype.sayHello,
   "newSession": GeckoDriver.prototype.newSession,
   "getSessionCapabilities": GeckoDriver.prototype.getSessionCapabilities,
   "log": GeckoDriver.prototype.log,
   "getLogs": GeckoDriver.prototype.getLogs,
   "setContext": GeckoDriver.prototype.setContext,
   "getContext": GeckoDriver.prototype.getContext,
   "executeScript": GeckoDriver.prototype.executeScript,
-  "timeouts": GeckoDriver.prototype.timeouts,
+  "getTimeouts": GeckoDriver.prototype.getTimeouts,
+  "timeouts": GeckoDriver.prototype.setTimeouts,  // deprecated until Firefox 55
+  "setTimeouts": GeckoDriver.prototype.setTimeouts,
   "singleTap": GeckoDriver.prototype.singleTap,
   "actionChain": GeckoDriver.prototype.actionChain,
   "multiAction": GeckoDriver.prototype.multiAction,
   "executeAsyncScript": GeckoDriver.prototype.executeAsyncScript,
   "executeJSScript": GeckoDriver.prototype.executeJSScript,
   "findElement": GeckoDriver.prototype.findElement,
   "findElements": GeckoDriver.prototype.findElements,
   "clickElement": GeckoDriver.prototype.clickElement,
--- a/testing/marionette/harness/docs/basics.rst
+++ b/testing/marionette/harness/docs/basics.rst
@@ -174,10 +174,10 @@ The async method works the same way, exc
        setTimeout(function() {
          marionetteScriptFinished("all done");
        }, arguments[0]);
    """, script_args=[1000])
    assert result == "all done"
 
 Beware that running asynchronous scripts can potentially hang the program
 indefinitely if they are not written properly. It is generally a good idea to
-set a script timeout using :func:`~Marionette.set_script_timeout` and handling
+set a script timeout using :func:`~Marionette.timeout.script` and handling
 `ScriptTimeoutException`.
--- a/testing/marionette/harness/marionette/marionette_test/testcases.py
+++ b/testing/marionette/harness/marionette/marionette_test/testcases.py
@@ -250,17 +250,17 @@ class CommonTestCase(unittest.TestCase):
         # duration of the test; this is deleted in tearDown() to prevent
         # a persistent circular reference which in turn would prevent
         # proper garbage collection.
         self.start_time = time.time()
         self.marionette = self._marionette_weakref()
         self.httpd = self._httpd_weakref()
         if self.marionette.session is None:
             self.marionette.start_session()
-        self.marionette.reset_timeouts()
+        self.marionette.timeout.reset()
 
         super(CommonTestCase, self).setUp()
 
     def cleanTest(self):
         self._deleteSession()
 
     def _deleteSession(self):
         if hasattr(self, 'start_time'):
@@ -349,18 +349,18 @@ if (!testUtils.hasOwnProperty("specialPo
 
         marionette.set_context(context)
 
         if context != 'chrome':
             marionette.navigate('data:text/html,<html>test page</html>')
 
         timeout = JSTest.timeout_re.search(js)
         if timeout:
-            timeout = timeout.group(3)
-            marionette.set_script_timeout(int(timeout))
+            ms = timeout.group(3)
+            marionette.timeout.script = int(ms) / 1000.0
 
         inactivity_timeout = JSTest.inactivity_timeout_re.search(js)
         if inactivity_timeout:
             inactivity_timeout = int(inactivity_timeout.group(3))
 
         try:
             results = marionette.execute_js_script(
                 js,
--- a/testing/marionette/harness/marionette/runner/base.py
+++ b/testing/marionette/harness/marionette/runner/base.py
@@ -302,22 +302,16 @@ class BaseMarionetteArguments(ArgumentPa
                           default=0,
                           help='number of times to repeat the test(s)')
         self.add_argument('--testvars',
                           action='append',
                           help='path to a json file with any test data required')
         self.add_argument('--symbols-path',
                           help='absolute path to directory containing breakpad symbols, or the '
                                'url of a zip file containing symbols')
-        self.add_argument('--timeout',
-                          type=int,
-                          help='if a --timeout value is given, it will set the default page load '
-                               'timeout, search timeout and script timeout to the given value. '
-                               'If not passed in, it will use the default values of 30000ms for '
-                               'page load, 0ms for search timeout and 10000ms for script timeout')
         self.add_argument('--startup-timeout',
                           type=int,
                           default=60,
                           help='the max number of seconds to wait for a Marionette connection '
                                'after launching a binary')
         self.add_argument('--shuffle',
                           action='store_true',
                           default=False,
@@ -499,17 +493,17 @@ class BaseMarionetteTestRunner(object):
 
     textrunnerclass = MarionetteTextTestRunner
     driverclass = Marionette
 
     def __init__(self, address=None,
                  app=None, app_args=None, binary=None, profile=None,
                  logger=None, logdir=None,
                  repeat=0, testvars=None,
-                 symbols_path=None, timeout=None,
+                 symbols_path=None,
                  shuffle=False, shuffle_seed=random.randint(0, sys.maxint), this_chunk=1,
                  total_chunks=1,
                  server_root=None, gecko_log=None, result_callbacks=None,
                  prefs=None, test_tags=None,
                  socket_timeout=BaseMarionetteArguments.socket_timeout_default,
                  startup_timeout=None, addons=None, workspace=None,
                  verbose=0, e10s=True, emulator=False, **kwargs):
         self.extra_kwargs = kwargs
@@ -522,17 +516,16 @@ class BaseMarionetteTestRunner(object):
         self.profile = profile
         self.addons = addons
         self.logger = logger
         self.httpd = None
         self.marionette = None
         self.logdir = logdir
         self.repeat = repeat
         self.symbols_path = symbols_path
-        self.timeout = timeout
         self.socket_timeout = socket_timeout
         self._capabilities = None
         self._appinfo = None
         self._appName = None
         self.shuffle = shuffle
         self.shuffle_seed = shuffle_seed
         self.server_root = server_root
         self.this_chunk = this_chunk
@@ -692,17 +685,16 @@ class BaseMarionetteTestRunner(object):
         self.skipped = 0
         self.failures = []
 
     def _build_kwargs(self):
         if self.logdir and not os.access(self.logdir, os.F_OK):
             os.mkdir(self.logdir)
 
         kwargs = {
-            'timeout': self.timeout,
             'socket_timeout': self.socket_timeout,
             'prefs': self.prefs,
             'startup_timeout': self.startup_timeout,
             'verbose': self.verbose,
             'symbols_path': self.symbols_path,
         }
         if self.bin or self.emulator:
             kwargs.update({
--- a/testing/marionette/harness/marionette/tests/harness_unit/test_marionette_runner.py
+++ b/testing/marionette/harness/marionette/tests/harness_unit/test_marionette_runner.py
@@ -117,17 +117,17 @@ def test_args_passed_to_driverclass(mock
 
 
 def test_build_kwargs_basic_args(build_kwargs_using):
     '''Test the functionality of runner._build_kwargs:
     make sure that basic arguments (those which should
     always be included, irrespective of the runner's settings)
     get passed to the call to runner.driverclass'''
 
-    basic_args = ['timeout', 'socket_timeout', 'prefs',
+    basic_args = ['socket_timeout', 'prefs',
                   'startup_timeout', 'verbose', 'symbols_path']
     built_kwargs = build_kwargs_using([(a, getattr(sentinel, a)) for a in basic_args])
     for arg in basic_args:
         assert built_kwargs[arg] is getattr(sentinel, arg)
 
 
 @pytest.mark.parametrize('workspace', ['path/to/workspace', None])
 def test_build_kwargs_with_workspace(build_kwargs_using, workspace):
--- a/testing/marionette/harness/marionette/tests/unit/single_finger_functions.py
+++ b/testing/marionette/harness/marionette/tests/unit/single_finger_functions.py
@@ -42,17 +42,17 @@ def move_element_offset(marionette, wait
     action.press(ele).move_by_offset(0,150).move_by_offset(0, 150).release()
     action.perform()
     wait_for_condition_else_raise(marionette, wait_for_condition, expected1, "return document.getElementById('button1').innerHTML;")
     wait_for_condition_else_raise(marionette, wait_for_condition, expected2, "return document.getElementById('button2').innerHTML;")
 
 def chain(marionette, wait_for_condition, expected1, expected2):
     testAction = marionette.absolute_url("testAction.html")
     marionette.navigate(testAction)
-    marionette.set_search_timeout(15000)
+    marionette.timeout.implicit = 15
     action = Actions(marionette)
     button1 = marionette.find_element(By.ID, "button1")
     action.press(button1).perform()
     button2 = marionette.find_element(By.ID, "delayed")
     wait_for_condition_else_raise(marionette, wait_for_condition, expected1, "return document.getElementById('button1').innerHTML;")
     action.move(button2).release().perform()
     wait_for_condition_else_raise(marionette, wait_for_condition, expected2, "return document.getElementById('delayed').innerHTML;")
 
--- a/testing/marionette/harness/marionette/tests/unit/test_element_retrieval.py
+++ b/testing/marionette/harness/marionette/tests/unit/test_element_retrieval.py
@@ -42,17 +42,17 @@ name_xhtml = inline('<p name="foo"></p>'
 link_html = inline("<p><a href=#>foo bar</a>", doctype="html")
 link_html_with_trailing_space = inline("<p><a href=#>a link with a trailing space </a>")
 link_xhtml = inline('<p><a href="#">foo bar</a></p>', doctype="xhtml")
 
 
 class TestFindElementHTML(MarionetteTestCase):
     def setUp(self):
         MarionetteTestCase.setUp(self)
-        self.marionette.set_search_timeout(0)
+        self.marionette.timeout.implicit = 0
 
     def test_id(self):
         self.marionette.navigate(id_html)
         expected = self.marionette.execute_script("return document.querySelector('p')")
         found = self.marionette.find_element(By.ID, "foo")
         self.assertIsInstance(found, HTMLElement)
         self.assertEqual(expected, found)
 
@@ -121,52 +121,52 @@ class TestFindElementHTML(MarionetteTest
     def test_xpath(self):
         self.marionette.navigate(id_html)
         el = self.marionette.execute_script("return document.querySelector('#foo')")
         found = self.marionette.find_element(By.XPATH, "id('foo')")
         self.assertIsInstance(found, HTMLElement)
         self.assertEqual(el, found)
 
     def test_not_found(self):
-        self.marionette.set_search_timeout(0)
+        self.marionette.timeout.implicit = 0
         self.assertRaises(NoSuchElementException, self.marionette.find_element, By.CLASS_NAME, "cheese")
         self.assertRaises(NoSuchElementException, self.marionette.find_element, By.CSS_SELECTOR, "cheese")
         self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "cheese")
         self.assertRaises(NoSuchElementException, self.marionette.find_element, By.LINK_TEXT, "cheese")
         self.assertRaises(NoSuchElementException, self.marionette.find_element, By.NAME, "cheese")
         self.assertRaises(NoSuchElementException, self.marionette.find_element, By.PARTIAL_LINK_TEXT, "cheese")
         self.assertRaises(NoSuchElementException, self.marionette.find_element, By.TAG_NAME, "cheese")
         self.assertRaises(NoSuchElementException, self.marionette.find_element, By.XPATH, "cheese")
 
     def test_not_found_implicit_wait(self):
-        self.marionette.set_search_timeout(50)
+        self.marionette.timeout.implicit = 0.5
         self.assertRaises(NoSuchElementException, self.marionette.find_element, By.CLASS_NAME, "cheese")
         self.assertRaises(NoSuchElementException, self.marionette.find_element, By.CSS_SELECTOR, "cheese")
         self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "cheese")
         self.assertRaises(NoSuchElementException, self.marionette.find_element, By.LINK_TEXT, "cheese")
         self.assertRaises(NoSuchElementException, self.marionette.find_element, By.NAME, "cheese")
         self.assertRaises(NoSuchElementException, self.marionette.find_element, By.PARTIAL_LINK_TEXT, "cheese")
         self.assertRaises(NoSuchElementException, self.marionette.find_element, By.TAG_NAME, "cheese")
         self.assertRaises(NoSuchElementException, self.marionette.find_element, By.XPATH, "cheese")
 
     def test_not_found_from_element(self):
-        self.marionette.set_search_timeout(0)
+        self.marionette.timeout.implicit = 0
         self.marionette.navigate(id_html)
         el = self.marionette.find_element(By.ID, "foo")
         self.assertRaises(NoSuchElementException, el.find_element, By.CLASS_NAME, "cheese")
         self.assertRaises(NoSuchElementException, el.find_element, By.CSS_SELECTOR, "cheese")
         self.assertRaises(NoSuchElementException, el.find_element, By.ID, "cheese")
         self.assertRaises(NoSuchElementException, el.find_element, By.LINK_TEXT, "cheese")
         self.assertRaises(NoSuchElementException, el.find_element, By.NAME, "cheese")
         self.assertRaises(NoSuchElementException, el.find_element, By.PARTIAL_LINK_TEXT, "cheese")
         self.assertRaises(NoSuchElementException, el.find_element, By.TAG_NAME, "cheese")
         self.assertRaises(NoSuchElementException, el.find_element, By.XPATH, "cheese")
 
     def test_not_found_implicit_wait_from_element(self):
-        self.marionette.set_search_timeout(50)
+        self.marionette.timeout.implicit = 0.5
         self.marionette.navigate(id_html)
         el = self.marionette.find_element(By.ID, "foo")
         self.assertRaises(NoSuchElementException, el.find_element, By.CLASS_NAME, "cheese")
         self.assertRaises(NoSuchElementException, el.find_element, By.CSS_SELECTOR, "cheese")
         self.assertRaises(NoSuchElementException, el.find_element, By.ID, "cheese")
         self.assertRaises(NoSuchElementException, el.find_element, By.LINK_TEXT, "cheese")
         self.assertRaises(NoSuchElementException, el.find_element, By.NAME, "cheese")
         self.assertRaises(NoSuchElementException, el.find_element, By.PARTIAL_LINK_TEXT, "cheese")
@@ -210,17 +210,17 @@ class TestFindElementHTML(MarionetteTest
         self.marionette.navigate(id_html)
         active = self.marionette.execute_script("return document.activeElement")
         self.assertEqual(active, self.marionette.get_active_element())
 
 
 class TestFindElementXHTML(MarionetteTestCase):
     def setUp(self):
         MarionetteTestCase.setUp(self)
-        self.marionette.set_search_timeout(0)
+        self.marionette.timeout.implicit = 0
 
     def test_id(self):
         self.marionette.navigate(id_xhtml)
         expected = self.marionette.execute_script("return document.querySelector('p')")
         found = self.marionette.find_element(By.ID, "foo")
         self.assertIsInstance(found, HTMLElement)
         self.assertEqual(expected, found)
 
@@ -293,17 +293,17 @@ class TestFindElementXHTML(MarionetteTes
         self.marionette.navigate(id_xhtml)
         active = self.marionette.execute_script("return document.activeElement")
         self.assertEqual(active, self.marionette.get_active_element())
 
 
 class TestFindElementsHTML(MarionetteTestCase):
     def setUp(self):
         MarionetteTestCase.setUp(self)
-        self.marionette.set_search_timeout(0)
+        self.marionette.timeout.implicit = 0
 
     def assertItemsIsInstance(self, items, typ):
         for item in items:
             self.assertIsInstance(item, typ)
 
     def test_child_elements(self):
         self.marionette.navigate(children_html)
         parent = self.marionette.find_element(By.TAG_NAME, "div")
@@ -405,17 +405,17 @@ class TestFindElementsHTML(MarionetteTes
         with self.assertRaises(InvalidSelectorException):
             parent = self.marionette.execute_script("return document.documentElement")
             parent.find_elements(By.CSS_SELECTOR, "")
 
 
 class TestFindElementsXHTML(MarionetteTestCase):
     def setUp(self):
         MarionetteTestCase.setUp(self)
-        self.marionette.set_search_timeout(0)
+        self.marionette.timeout.implicit = 0
 
     def assertItemsIsInstance(self, items, typ):
         for item in items:
             self.assertIsInstance(item, typ)
 
     def test_child_elements(self):
         self.marionette.navigate(children_xhtml)
         parent = self.marionette.find_element(By.TAG_NAME, "div")
--- a/testing/marionette/harness/marionette/tests/unit/test_execute_async_script.py
+++ b/testing/marionette/harness/marionette/tests/unit/test_execute_async_script.py
@@ -6,40 +6,40 @@ from marionette import MarionetteTestCas
 from marionette_driver.errors import ( JavascriptException,
                                        MarionetteException,
                                        ScriptTimeoutException )
 
 
 class TestExecuteAsyncContent(MarionetteTestCase):
     def setUp(self):
         super(TestExecuteAsyncContent, self).setUp()
-        self.marionette.set_script_timeout(1000)
+        self.marionette.timeout.script = 1
 
     def test_execute_async_simple(self):
         self.assertEqual(1, self.marionette.execute_async_script("arguments[arguments.length-1](1);"))
 
     def test_execute_async_ours(self):
         self.assertEqual(1, self.marionette.execute_async_script("marionetteScriptFinished(1);"))
 
     def test_execute_async_timeout(self):
         self.assertRaises(ScriptTimeoutException, self.marionette.execute_async_script, "var x = 1;")
 
     def test_execute_async_unique_timeout(self):
         self.assertEqual(2, self.marionette.execute_async_script("setTimeout(function() {marionetteScriptFinished(2);}, 2000);", script_timeout=5000))
         self.assertRaises(ScriptTimeoutException, self.marionette.execute_async_script, "setTimeout(function() {marionetteScriptFinished(3);}, 2000);")
 
     def test_no_timeout(self):
-        self.marionette.set_script_timeout(10000)
+        self.marionette.timeout.script = 10
         self.assertTrue(self.marionette.execute_async_script("""
             var callback = arguments[arguments.length - 1];
             setTimeout(function() { callback(true); }, 500);
             """))
 
     def test_execute_async_unload(self):
-        self.marionette.set_script_timeout(5000)
+        self.marionette.timeout.script = 5
         unload = """
                 window.location.href = "about:blank";
                  """
         self.assertRaises(JavascriptException, self.marionette.execute_async_script, unload)
 
     def test_check_window(self):
         self.assertTrue(self.marionette.execute_async_script("marionetteScriptFinished(window !=null && window != undefined);"))
 
@@ -136,17 +136,17 @@ class TestExecuteAsyncChrome(TestExecute
     def test_execute_permission(self):
         self.assertEqual(5, self.marionette.execute_async_script("""
 var c = Components.classes;
 marionetteScriptFinished(5);
 """))
 
     def test_execute_async_js_exception(self):
         # Javascript exceptions are not propagated in chrome code
-        self.marionette.set_script_timeout(200)
+        self.marionette.timeout.script = 0.2
         self.assertRaises(ScriptTimeoutException,
             self.marionette.execute_async_script, """
             var callback = arguments[arguments.length - 1];
             setTimeout("callback(foo())", 50);
             """)
         self.assertRaises(JavascriptException,
             self.marionette.execute_async_script, """
             var callback = arguments[arguments.length - 1];
--- a/testing/marionette/harness/marionette/tests/unit/test_execute_isolate.py
+++ b/testing/marionette/harness/marionette/tests/unit/test_execute_isolate.py
@@ -10,23 +10,23 @@ class TestExecuteIsolationContent(Marion
     def setUp(self):
         super(TestExecuteIsolationContent, self).setUp()
         self.content = True
 
     def test_execute_async_isolate(self):
         # Results from one execute call that has timed out should not
         # contaminate a future call.
         multiplier = "*3" if self.content else "*1"
-        self.marionette.set_script_timeout(500)
+        self.marionette.timeout.script = 0.5
         self.assertRaises(ScriptTimeoutException,
                           self.marionette.execute_async_script,
                           ("setTimeout(function() {{ marionetteScriptFinished(5{}); }}, 3000);"
                               .format(multiplier)))
 
-        self.marionette.set_script_timeout(6000)
+        self.marionette.timeout.script = 6
         result = self.marionette.execute_async_script("""
         setTimeout(function() {{ marionetteScriptFinished(10{}); }}, 5000);
         """.format(multiplier))
         self.assertEqual(result, 30 if self.content else 10)
 
 class TestExecuteIsolationChrome(TestExecuteIsolationContent):
     def setUp(self):
         super(TestExecuteIsolationChrome, self).setUp()
--- a/testing/marionette/harness/marionette/tests/unit/test_findelement_chrome.py
+++ b/testing/marionette/harness/marionette/tests/unit/test_findelement_chrome.py
@@ -63,19 +63,19 @@ class TestElementsChrome(MarionetteTestC
 
     def test_xpath(self):
         el = self.marionette.execute_script("return window.document.getElementById('testBox');")
         found_el = self.marionette.find_element(By.XPATH, "id('testBox')")
         self.assertEqual(HTMLElement, type(found_el))
         self.assertEqual(el, found_el)
 
     def test_not_found(self):
-        self.marionette.set_search_timeout(1000)
+        self.marionette.timeout.implicit = 1
         self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "I'm not on the page")
-        self.marionette.set_search_timeout(0)
+        self.marionette.timeout.implicit = 0
         self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "I'm not on the page")
 
     def test_timeout(self):
         self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, "myid")
-        self.assertTrue(True, self.marionette.set_search_timeout(4000))
+        self.marionette.timeout.implicit = 4
         self.marionette.execute_script("window.setTimeout(function() {var b = window.document.createElement('button'); b.id = 'myid'; document.getElementById('things').appendChild(b);}, 1000)")
         self.assertEqual(HTMLElement, type(self.marionette.find_element(By.ID, "myid")))
         self.marionette.execute_script("window.document.getElementById('things').removeChild(window.document.getElementById('myid'));")
--- a/testing/marionette/harness/marionette/tests/unit/test_implicit_waits.py
+++ b/testing/marionette/harness/marionette/tests/unit/test_implicit_waits.py
@@ -3,28 +3,23 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from marionette import MarionetteTestCase
 from marionette_driver.errors import NoSuchElementException
 from marionette_driver.by import By
 
 
 class TestImplicitWaits(MarionetteTestCase):
-    def testShouldImplicitlyWaitForASingleElement(self):
+    def test_implicitly_wait_for_single_element(self):
         test_html = self.marionette.absolute_url("test_dynamic.html")
         self.marionette.navigate(test_html)
         add = self.marionette.find_element(By.ID, "adder")
-        self.marionette.set_search_timeout(30000)
+        self.marionette.timeout.implicit = 30
         add.click()
-        # All is well if this doesnt throw
+        # all is well if this does not throw
         self.marionette.find_element(By.ID, "box0")
 
-    def testShouldStillFailToFindAnElementWhenImplicitWaitsAreEnabled(self):
+    def test_implicit_wait_reaches_timeout(self):
         test_html = self.marionette.absolute_url("test_dynamic.html")
         self.marionette.navigate(test_html)
-        self.marionette.set_search_timeout(3000)
-        try:
+        self.marionette.timeout.implicit = 3
+        with self.assertRaises(NoSuchElementException):
             self.marionette.find_element(By.ID, "box0")
-            self.fail("Should have thrown a a NoSuchElementException")
-        except NoSuchElementException:
-            pass
-        except Exception:
-            self.fail("Should have thrown a NoSuchElementException")
--- a/testing/marionette/harness/marionette/tests/unit/test_navigation.py
+++ b/testing/marionette/harness/marionette/tests/unit/test_navigation.py
@@ -119,17 +119,17 @@ class TestNavigate(MarionetteTestCase):
     def test_find_element_state_complete(self):
         self.marionette.navigate(self.test_doc)
         state = self.marionette.execute_script("return window.document.readyState")
         self.assertEqual("complete", state)
         self.assertTrue(self.marionette.find_element(By.ID, "mozLink"))
 
     def test_error_when_exceeding_page_load_timeout(self):
         with self.assertRaises(TimeoutException):
-            self.marionette.set_page_load_timeout(0)
+            self.marionette.timeout.page_load = 0
             self.marionette.navigate(self.marionette.absolute_url("slow"))
             self.marionette.find_element(By.TAG_NAME, "p")
 
     def test_navigate_iframe(self):
         self.marionette.navigate(self.iframe_doc)
         self.assertTrue('test_iframe.html' in self.marionette.get_url())
         self.assertTrue(self.marionette.find_element(By.ID, "test_iframe"))
 
--- a/testing/marionette/harness/marionette/tests/unit/test_simpletest_sanity.py
+++ b/testing/marionette/harness/marionette/tests/unit/test_simpletest_sanity.py
@@ -20,17 +20,17 @@ class SimpletestSanityTest(MarionetteTes
             sentPass1 = "is(true, true, 'isTest3');" + self.callFinish
             sentPass2 = "is(true, true, 'isTest4');" + s