Merge autoland to m-c, a=merge
authorPhil Ringnalda <philringnalda@gmail.com>
Tue, 29 Nov 2016 19:16:09 -0800
changeset 324690 5d49c9792a3417455bc5683ee2264b3a36d68682
parent 324668 adcc39e3cad0f32aba0efb478cc4a023a5dfc43f (current diff)
parent 324689 c283c3fbcdcb949c84fbd03e7c53dbfd562f5052 (diff)
child 324737 a69583d2dbc6fdc18f63761a89cf539c356668be
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
reviewersmerge
milestone53.0a1
Merge autoland to m-c, a=merge MozReview-Commit-ID: 3unzaMHXBGa
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5626,17 +5626,22 @@ function middleMousePaste(event) {
   });
 
   event.stopPropagation();
 }
 
 function stripUnsafeProtocolOnPaste(pasteData) {
   // Don't allow pasting javascript URIs since we don't support
   // LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL for those.
-  return pasteData.replace(/\r?\n/g, "").replace(/^(?:\s*javascript:)+/i, "");
+  let changed = false;
+  let pasteDataNoJS = pasteData.replace(/\r?\n/g, "")
+                               .replace(/^(?:\s*javascript:)+/i,
+                                        () => { changed = true;
+                                                return ""; });
+  return changed ? pasteDataNoJS : pasteData;
 }
 
 // handleDroppedLink has the following 2 overloads:
 //   handleDroppedLink(event, url, name)
 //   handleDroppedLink(event, links)
 function handleDroppedLink(event, urlOrLinks, name)
 {
   let links;
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -4607,17 +4607,21 @@
                   "tabs.muteAudio.background.tooltip";
               }
 
               label = this.mStringBundle.getString(stringID);
             }
           } else {
             label = tab.getAttribute("label") +
                       (this.AppConstants.E10S_TESTING_ONLY && tab.linkedBrowser && tab.linkedBrowser.isRemoteBrowser ? " - e10s" : "");
+            if (tab.userContextId) {
+              label = this.mStringBundle.getFormattedString("tabs.containers.tooltip", [label, ContextualIdentityService.getUserContextLabel(tab.userContextId)]);
+            }
           }
+
           event.target.setAttribute("label", label);
         ]]></body>
       </method>
 
       <method name="handleEvent">
         <parameter name="aEvent"/>
         <body><![CDATA[
           switch (aEvent.type) {
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -33,16 +33,18 @@ subsuite = clipboard
 support-files =
   redirect_bug623155.sjs
 [browser_bug783614.js]
 [browser_canonizeURL.js]
 [browser_locationBarCommand.js]
 skip-if = true # bug 917535, bug 1289765
 [browser_locationBarExternalLoad.js]
 [browser_moz_action_link.js]
+[browser_pasteAndGo.js]
+subsuite = clipboard
 [browser_removeUnsafeProtocolsFromURLBarPaste.js]
 subsuite = clipboard
 [browser_search_favicon.js]
 [browser_tabMatchesInAwesomebar.js]
 support-files =
   moz.png
 [browser_tabMatchesInAwesomebar_perwindowpb.js]
 skip-if = os == 'linux' # Bug 1104755
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_pasteAndGo.js
@@ -0,0 +1,38 @@
+"use strict";
+
+let clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
+
+add_task(function*() {
+  const kURLs = [
+    "http://example.com/1",
+    "http://example.org/2\n",
+    "http://\nexample.com/3\n",
+  ];
+  for (let url of kURLs) {
+    yield BrowserTestUtils.withNewTab("about:blank", function* (browser) {
+      gURLBar.focus();
+      yield new Promise((resolve, reject) => {
+        waitForClipboard(url, function() {
+          clipboardHelper.copyString(url);
+        }, resolve,
+          () => reject(new Error(`Failed to copy string '${url}' to clipboard`))
+        );
+      });
+      let textBox = document.getAnonymousElementByAttribute(gURLBar,
+        "anonid", "textbox-input-box");
+      let cxmenu = document.getAnonymousElementByAttribute(textBox,
+        "anonid", "input-box-contextmenu");
+      let cxmenuPromise = BrowserTestUtils.waitForEvent(cxmenu, "popupshown");
+      EventUtils.synthesizeMouseAtCenter(gURLBar, {type: "contextmenu", button: 2});
+      yield cxmenuPromise;
+      let menuitem = document.getAnonymousElementByAttribute(textBox,
+        "anonid", "paste-and-go");
+      let browserLoadedPromise = BrowserTestUtils.browserLoaded(browser, url.replace(/\n/g, ""));
+      EventUtils.synthesizeMouseAtCenter(menuitem, {});
+      // Using toSource in order to get the newlines escaped:
+      info("Paste and go, loading " + url.toSource());
+      yield browserLoadedPromise;
+      ok(true, "Successfully loaded " + url);
+    });
+  }
+});
--- a/browser/base/content/test/urlbar/browser_removeUnsafeProtocolsFromURLBarPaste.js
+++ b/browser/base/content/test/urlbar/browser_removeUnsafeProtocolsFromURLBarPaste.js
@@ -2,16 +2,19 @@ function test() {
   waitForExplicitFinish();
   testNext();
 }
 
 var pairs = [
   ["javascript:", ""],
   ["javascript:1+1", "1+1"],
   ["javascript:document.domain", "document.domain"],
+  ["java\nscript:foo", "foo"],
+  ["http://\nexample.com", "http://example.com"],
+  ["http://\nexample.com\n", "http://example.com"],
   ["data:text/html,<body>hi</body>", "data:text/html,<body>hi</body>"],
   // Nested things get confusing because some things don't parse as URIs:
   ["javascript:javascript:alert('hi!')", "alert('hi!')"],
   ["data:data:text/html,<body>hi</body>", "data:data:text/html,<body>hi</body>"],
   ["javascript:data:javascript:alert('hi!')", "data:javascript:alert('hi!')"],
   ["javascript:data:text/html,javascript:alert('hi!')", "data:text/html,javascript:alert('hi!')"],
   ["data:data:text/html,javascript:alert('hi!')", "data:data:text/html,javascript:alert('hi!')"],
 ];
--- a/browser/locales/en-US/chrome/browser/tabbrowser.properties
+++ b/browser/locales/en-US/chrome/browser/tabbrowser.properties
@@ -44,8 +44,14 @@ tabs.unmuteAudio.tooltip=Unmute tab (%S)
 tabs.muteAudio.background.tooltip=Mute tab
 tabs.unmuteAudio.background.tooltip=Unmute tab
 
 tabs.unblockAudio.tooltip=Play tab
 
 # LOCALIZATION NOTE (tabs.allowTabFocusByPromptForSite):
 # %S is the hostname of the site where dialogs are allowed to switch tabs
 tabs.allowTabFocusByPromptForSite=Allow dialogs from %S to take you to their tab
+
+# LOCALIZATION NOTE (tabs.containers.tooltip):
+# Displayed as a tooltip on container tabs
+# %1$S is the title of the current tab
+# %2$S is the name of the current container
+tabs.containers.tooltip=%1$S - %2$S
--- a/devtools/client/locales/en-US/storage.properties
+++ b/devtools/client/locales/en-US/storage.properties
@@ -55,17 +55,17 @@ table.headers.Cache.status=Status
 
 table.headers.indexedDB.name=Key
 table.headers.indexedDB.db=Database Name
 table.headers.indexedDB.objectStore=Object Store Name
 table.headers.indexedDB.value=Value
 table.headers.indexedDB.origin=Origin
 table.headers.indexedDB.version=Version
 table.headers.indexedDB.objectStores=Object Stores
-table.headers.indexedDB.keyPath=Key
+table.headers.indexedDB.keyPath2=Key Path
 table.headers.indexedDB.autoIncrement=Auto Increment
 table.headers.indexedDB.indexes=Indexes
 
 # LOCALIZATION NOTE (label.expires.session):
 # This string is displayed in the expires column when the cookie is Session
 # Cookie
 label.expires.session=Session
 
--- a/devtools/client/storage/test/browser.ini
+++ b/devtools/client/storage/test/browser.ini
@@ -2,16 +2,17 @@
 tags = devtools
 subsuite = devtools
 support-files =
   storage-cache-error.html
   storage-complex-values.html
   storage-cookies.html
   storage-empty-objectstores.html
   storage-idb-delete-blocked.html
+  storage-indexeddb-duplicate-names.html
   storage-listings.html
   storage-localstorage.html
   storage-overflow.html
   storage-search.html
   storage-secured-iframe.html
   storage-sessionstorage.html
   storage-unsecured-iframe.html
   storage-updates.html
@@ -30,16 +31,17 @@ support-files =
 [browser_storage_delete_all.js]
 [browser_storage_delete_tree.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_indexeddb_duplicate_names.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]
 [browser_storage_sessionstorage_edit.js]
 [browser_storage_sidebar.js]
 [browser_storage_sidebar_update.js]
--- a/devtools/client/storage/test/browser_storage_basic.js
+++ b/devtools/client/storage/test/browser_storage_basic.js
@@ -48,38 +48,38 @@ const testCases = [
    ["iframe-s-ls1"]],
   [["sessionStorage", "http://test1.example.org"],
    ["ss1"]],
   [["sessionStorage", "http://sectest1.example.org"],
    ["iframe-u-ss1", "iframe-u-ss2"]],
   [["sessionStorage", "https://sectest1.example.org"],
    ["iframe-s-ss1"]],
   [["indexedDB", "http://test1.example.org"],
-   ["idb1", "idb2"]],
-  [["indexedDB", "http://test1.example.org", "idb1"],
+   ["idb1 (default)", "idb2 (default)"]],
+  [["indexedDB", "http://test1.example.org", "idb1 (default)"],
    ["obj1", "obj2"]],
-  [["indexedDB", "http://test1.example.org", "idb2"],
+  [["indexedDB", "http://test1.example.org", "idb2 (default)"],
    ["obj3"]],
-  [["indexedDB", "http://test1.example.org", "idb1", "obj1"],
+  [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
    [1, 2, 3]],
-  [["indexedDB", "http://test1.example.org", "idb1", "obj2"],
+  [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj2"],
    [1]],
-  [["indexedDB", "http://test1.example.org", "idb2", "obj3"],
+  [["indexedDB", "http://test1.example.org", "idb2 (default)", "obj3"],
    []],
   [["indexedDB", "http://sectest1.example.org"],
    []],
   [["indexedDB", "https://sectest1.example.org"],
-   ["idb-s1", "idb-s2"]],
-  [["indexedDB", "https://sectest1.example.org", "idb-s1"],
+   ["idb-s1 (default)", "idb-s2 (default)"]],
+  [["indexedDB", "https://sectest1.example.org", "idb-s1 (default)"],
    ["obj-s1"]],
-  [["indexedDB", "https://sectest1.example.org", "idb-s2"],
+  [["indexedDB", "https://sectest1.example.org", "idb-s2 (default)"],
    ["obj-s2"]],
-  [["indexedDB", "https://sectest1.example.org", "idb-s1", "obj-s1"],
+  [["indexedDB", "https://sectest1.example.org", "idb-s1 (default)", "obj-s1"],
    [6, 7]],
-  [["indexedDB", "https://sectest1.example.org", "idb-s2", "obj-s2"],
+  [["indexedDB", "https://sectest1.example.org", "idb-s2 (default)", "obj-s2"],
    [16]],
   [["Cache", "http://test1.example.org", "plop"],
    [MAIN_DOMAIN + "404_cached_file.js",
     MAIN_DOMAIN + "browser_storage_basic.js"]],
 ];
 
 /**
  * Test that the desired number of tree items are present
--- a/devtools/client/storage/test/browser_storage_delete.js
+++ b/devtools/client/storage/test/browser_storage_delete.js
@@ -12,17 +12,17 @@ const TEST_CASES = [
   [["localStorage", "http://test1.example.org"],
     "ls1", "name"],
   [["sessionStorage", "http://test1.example.org"],
     "ss1", "name"],
   [
     ["cookies", "test1.example.org"],
     getCookieId("c1", "test1.example.org", "/browser"), "name"
   ],
-  [["indexedDB", "http://test1.example.org", "idb1", "obj1"],
+  [["indexedDB", "http://test1.example.org", "idb1 (default)", "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");
 
--- a/devtools/client/storage/test/browser_storage_delete_all.js
+++ b/devtools/client/storage/test/browser_storage_delete_all.js
@@ -24,29 +24,29 @@ add_task(function* () {
     [["localStorage", "https://sectest1.example.org"],
       ["iframe-s-ls1"]],
     [["sessionStorage", "http://test1.example.org"],
       ["ss1"]],
     [["sessionStorage", "http://sectest1.example.org"],
       ["iframe-u-ss1", "iframe-u-ss2"]],
     [["sessionStorage", "https://sectest1.example.org"],
       ["iframe-s-ss1"]],
-    [["indexedDB", "http://test1.example.org", "idb1", "obj1"],
+    [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
       [1, 2, 3]],
     [["Cache", "http://test1.example.org", "plop"],
       [MAIN_DOMAIN + "404_cached_file.js", MAIN_DOMAIN + "browser_storage_basic.js"]],
   ];
 
   yield checkState(beforeState);
 
   info("do the delete");
   const deleteHosts = [
     [["localStorage", "https://sectest1.example.org"], "iframe-s-ls1", "name"],
     [["sessionStorage", "https://sectest1.example.org"], "iframe-s-ss1", "name"],
-    [["indexedDB", "http://test1.example.org", "idb1", "obj1"], 1, "name"],
+    [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"], 1, "name"],
     [["Cache", "http://test1.example.org", "plop"],
       MAIN_DOMAIN + "404_cached_file.js", "url"],
   ];
 
   for (let [store, rowName, cellToClick] of deleteHosts) {
     let storeName = store.join(" > ");
 
     yield selectTreeItem(store);
@@ -73,17 +73,17 @@ add_task(function* () {
     [["localStorage", "https://sectest1.example.org"],
       []],
     [["sessionStorage", "http://test1.example.org"],
       ["ss1"]],
     [["sessionStorage", "http://sectest1.example.org"],
       ["iframe-u-ss1", "iframe-u-ss2"]],
     [["sessionStorage", "https://sectest1.example.org"],
       []],
-    [["indexedDB", "http://test1.example.org", "idb1", "obj1"],
+    [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
       []],
     [["Cache", "http://test1.example.org", "plop"],
       []],
   ];
 
   yield checkState(afterState);
 
   yield finishTests();
--- a/devtools/client/storage/test/browser_storage_delete_tree.js
+++ b/devtools/client/storage/test/browser_storage_delete_tree.js
@@ -23,27 +23,27 @@ add_task(function* () {
         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]],
+    [["indexedDB", "http://test1.example.org", "idb1 (default)", "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");
   const deleteHosts = [
     ["cookies", "test1.example.org"],
     ["localStorage", "http://test1.example.org"],
     ["sessionStorage", "http://test1.example.org"],
-    ["indexedDB", "http://test1.example.org", "idb1", "obj1"],
+    ["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
     ["Cache", "http://test1.example.org", "plop"],
   ];
 
   for (let store of deleteHosts) {
     let storeName = store.join(" > ");
 
     yield selectTreeItem(store);
 
@@ -62,14 +62,14 @@ add_task(function* () {
     yield eventWait;
   }
 
   info("test state after delete");
   yield checkState([
     [["cookies", "test1.example.org"], []],
     [["localStorage", "http://test1.example.org"], []],
     [["sessionStorage", "http://test1.example.org"], []],
-    [["indexedDB", "http://test1.example.org", "idb1", "obj1"], []],
+    [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"], []],
     [["Cache", "http://test1.example.org", "plop"], []],
   ]);
 
   yield finishTests();
 });
--- a/devtools/client/storage/test/browser_storage_empty_objectstores.js
+++ b/devtools/client/storage/test/browser_storage_empty_objectstores.js
@@ -16,24 +16,24 @@
 //   - The value of the first (unique) column for each row in the table
 //     corresponding to the tree item selected.
 // ]
 // 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
 const storeItems = [
   [["indexedDB", "http://test1.example.org"],
-   ["idb1", "idb2"]],
-  [["indexedDB", "http://test1.example.org", "idb1"],
+   ["idb1 (default)", "idb2 (default)"]],
+  [["indexedDB", "http://test1.example.org", "idb1 (default)"],
    ["obj1", "obj2"]],
-  [["indexedDB", "http://test1.example.org", "idb2"],
+  [["indexedDB", "http://test1.example.org", "idb2 (default)"],
    []],
-  [["indexedDB", "http://test1.example.org", "idb1", "obj1"],
+  [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
    [1, 2, 3]],
-  [["indexedDB", "http://test1.example.org", "idb1", "obj2"],
+  [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj2"],
    [1]]
 ];
 
 /**
  * Test that the desired number of tree items are present
  */
 function testTree() {
   let doc = gPanelWindow.document;
--- a/devtools/client/storage/test/browser_storage_indexeddb_delete.js
+++ b/devtools/client/storage/test/browser_storage_indexeddb_delete.js
@@ -11,21 +11,21 @@
 add_task(function* () {
   yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-empty-objectstores.html");
 
   let contextMenu = gPanelWindow.document.getElementById("storage-tree-popup");
   let menuDeleteDb = contextMenu.querySelector("#storage-tree-popup-delete");
 
   info("test state before delete");
   yield checkState([
-    [["indexedDB", "http://test1.example.org"], ["idb1", "idb2"]],
+    [["indexedDB", "http://test1.example.org"], ["idb1 (default)", "idb2 (default)"]],
   ]);
 
   info("do the delete");
-  const deletedDb = ["indexedDB", "http://test1.example.org", "idb1"];
+  const deletedDb = ["indexedDB", "http://test1.example.org", "idb1 (default)"];
 
   yield selectTreeItem(deletedDb);
 
   // Wait once for update and another time for value fetching
   let eventWait = gUI.once("store-objects-updated").then(
     () => gUI.once("store-objects-updated"));
 
   let selector = `[data-id='${JSON.stringify(deletedDb)}'] > .tree-widget-item`;
@@ -35,13 +35,13 @@ add_task(function* () {
     info(`Opened tree context menu in ${deletedDb.join(" > ")}`);
     menuDeleteDb.click();
   });
 
   yield eventWait;
 
   info("test state after delete");
   yield checkState([
-    [["indexedDB", "http://test1.example.org"], ["idb2"]],
+    [["indexedDB", "http://test1.example.org"], ["idb2 (default)"]],
   ]);
 
   yield finishTests();
 });
--- a/devtools/client/storage/test/browser_storage_indexeddb_delete_blocked.js
+++ b/devtools/client/storage/test/browser_storage_indexeddb_delete_blocked.js
@@ -8,29 +8,29 @@
 
 // Test what happens when deleting indexedDB database is blocked
 
 add_task(function* () {
   yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-idb-delete-blocked.html");
 
   info("test state before delete");
   yield checkState([
-    [["indexedDB", "http://test1.example.org"], ["idb"]]
+    [["indexedDB", "http://test1.example.org"], ["idb (default)"]]
   ]);
 
   info("do the delete");
   yield selectTreeItem(["indexedDB", "http://test1.example.org"]);
   let actor = gUI.getCurrentActor();
-  let result = yield actor.removeDatabase("http://test1.example.org", "idb");
+  let result = yield actor.removeDatabase("http://test1.example.org", "idb (default)");
 
   ok(result.blocked, "removeDatabase attempt is blocked");
 
   info("test state after blocked delete");
   yield checkState([
-    [["indexedDB", "http://test1.example.org"], ["idb"]]
+    [["indexedDB", "http://test1.example.org"], ["idb (default)"]]
   ]);
 
   let eventWait = gUI.once("store-objects-updated");
 
   info("telling content to close the db");
   yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
     let win = content.wrappedJSObject;
     yield win.closeDb();
@@ -42,17 +42,17 @@ add_task(function* () {
   info("test state after real delete");
   yield checkState([
     [["indexedDB", "http://test1.example.org"], []]
   ]);
 
   info("try to delete database from nonexistent host");
   let errorThrown = false;
   try {
-    result = yield actor.removeDatabase("http://test2.example.org", "idb");
+    result = yield actor.removeDatabase("http://test2.example.org", "idb (default)");
   } catch (ex) {
     errorThrown = true;
   }
 
   ok(errorThrown, "error was reported when trying to delete");
 
   yield finishTests();
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_indexeddb_duplicate_names.js
@@ -0,0 +1,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/. */
+
+// Test to verify that indexedDBs with duplicate names (different types / paths)
+// work as expected.
+
+"use strict";
+
+add_task(function* () {
+  const TESTPAGE = MAIN_DOMAIN + "storage-indexeddb-duplicate-names.html";
+
+  setPermission(TESTPAGE, "indexedDB");
+
+  yield openTabAndSetupStorage(TESTPAGE);
+
+  yield checkState([
+    [
+      ["indexedDB", "http://test1.example.org"], [
+        "idb1 (default)",
+        "idb1 (temporary)",
+        "idb1 (persistent)",
+        "idb2 (default)",
+        "idb2 (temporary)",
+        "idb2 (persistent)"
+      ]
+    ]
+  ]);
+
+  yield finishTests();
+});
--- a/devtools/client/storage/test/browser_storage_sidebar.js
+++ b/devtools/client/storage/test/browser_storage_sidebar.js
@@ -67,27 +67,27 @@ const testCases = [
     sendEscape: true
   },
 
   {
     location: ["indexedDB", "http://test1.example.org"],
     sidebarHidden: true
   },
   {
-    location: "idb2",
+    location: "idb2 (default)",
     sidebarHidden: false
   },
 
   {
-    location: ["indexedDB", "http://test1.example.org", "idb2", "obj3"],
+    location: ["indexedDB", "http://test1.example.org", "idb2 (default)", "obj3"],
     sidebarHidden: true
   },
 
   {
-    location: ["indexedDB", "https://sectest1.example.org", "idb-s2"],
+    location: ["indexedDB", "https://sectest1.example.org", "idb-s2 (default)"],
     sidebarHidden: true
   },
   {
     location: "obj-s2",
     sidebarHidden: false
   },
   {
     sendEscape: true
--- a/devtools/client/storage/test/browser_storage_values.js
+++ b/devtools/client/storage/test/browser_storage_values.js
@@ -119,26 +119,26 @@ const testCases = [
   ["ss5", [
     {name: "ss5", value: "Array"},
     {name: "ss5.0", value: LONG_WORD},
     {name: "ss5.1", value: LONG_WORD},
     {name: "ss5.2", value: LONG_WORD},
     {name: "ss5.3", value: `${LONG_WORD}&${LONG_WORD}`},
     {name: "ss5.4", value: `${LONG_WORD}&${LONG_WORD}`},
   ], true],
-  [["indexedDB", "http://test1.example.org", "idb1", "obj1"]],
+  [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"]],
   [1, [
     {name: 1, value: JSON.stringify({id: 1, name: "foo", email: "foo@bar.com"})}
   ]],
   [null, [
     {name: "1.id", value: "1"},
     {name: "1.name", value: "foo"},
     {name: "1.email", value: "foo@bar.com"},
   ], true],
-  [["indexedDB", "http://test1.example.org", "idb1", "obj2"]],
+  [["indexedDB", "http://test1.example.org", "idb1 (default)", "obj2"]],
   [1, [
     {name: 1, value: JSON.stringify({
       id2: 1, name: "foo", email: "foo@bar.com", extra: "baz"
     })}
   ]],
   [null, [
     {name: "1.id2", value: "1"},
     {name: "1.name", value: "foo"},
--- a/devtools/client/storage/test/head.js
+++ b/devtools/client/storage/test/head.js
@@ -875,8 +875,24 @@ var focusSearchBoxUsingShortcut = Task.a
   if (callback) {
     callback();
   }
 });
 
 function getCookieId(name, domain, path) {
   return `${name}${SEPARATOR_GUID}${domain}${SEPARATOR_GUID}${path}`;
 }
+
+function setPermission(url, permission) {
+  const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
+
+  let uri = Components.classes["@mozilla.org/network/io-service;1"]
+                      .getService(Components.interfaces.nsIIOService)
+                      .newURI(url, null, null);
+  let ssm = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
+                      .getService(Ci.nsIScriptSecurityManager);
+  let principal = ssm.createCodebasePrincipal(uri, {});
+
+  Components.classes["@mozilla.org/permissionmanager;1"]
+            .getService(nsIPermissionManager)
+            .addFromPrincipal(principal, permission,
+                              nsIPermissionManager.ALLOW_ACTION);
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/storage/test/storage-indexeddb-duplicate-names.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+  <meta charset="utf-8">
+  <title>Storage inspector IndexedDBs with duplicate names</title>
+
+  <script type="application/javascript;version=1.7">
+    "use strict";
+
+    function createIndexedDBs() {
+      createIndexedDB("idb1", "temporary");
+      createIndexedDB("idb1", "default");
+      createIndexedDB("idb1", "persistent");
+      createIndexedDB("idb2", "temporary");
+      createIndexedDB("idb2", "default");
+      createIndexedDB("idb2", "persistent");
+    }
+
+    function createIndexedDB(name, storage) {
+      let open = indexedDB.open(name, {storage: storage});
+
+      open.onsuccess = function () {
+        let db = open.result;
+        db.close();
+      };
+    }
+
+    function deleteDB(dbName, storage) {
+      return new Promise(resolve => {
+        dump(`removing database ${dbName} (${storage}) from ${document.location}\n`);
+        indexedDB.deleteDatabase(dbName, { storage: storage }).onsuccess = resolve;
+      });
+    }
+
+    window.clear = function* () {
+      yield deleteDB("idb1", "temporary");
+      yield deleteDB("idb1", "default");
+      yield deleteDB("idb1", "persistent");
+      yield deleteDB("idb2", "temporary");
+      yield deleteDB("idb2", "default");
+      yield deleteDB("idb2", "persistent");
+
+      dump(`removed indexedDB data from ${document.location}\n`);
+    };
+  </script>
+</head>
+<body onload="createIndexedDBs()">
+  <h1>storage-indexeddb-duplicate-names.html</h1>
+</body>
+</html>
--- a/devtools/client/storage/ui.js
+++ b/devtools/client/storage/ui.js
@@ -804,17 +804,20 @@ StorageUI.prototype = {
 
       if (f.private) {
         privateFields.push(f.name);
       }
 
       columns[f.name] = f.name;
       let columnName;
       try {
-        columnName = L10N.getStr("table.headers." + type + "." + f.name);
+        // Path key names for l10n in the case of a string change.
+        let name = f.name === "keyPath" ? "keyPath2" : f.name;
+
+        columnName = L10N.getStr("table.headers." + type + "." + 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;
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_batching.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_batching.js
@@ -7,34 +7,16 @@
 
 // Check adding console calls as batch keep the order of the message.
 
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/mochitest/test-batching.html";
 const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
 
 add_task(function* () {
   let hud = yield openNewTabAndConsole(TEST_URI);
-
-  const store = hud.ui.newConsoleOutput.getStore();
-  // Adding loggin each time the store is modified in order to check
-  // the store state in case of failure.
-  store.subscribe(() => {
-    const messages = store.getState().messages.messagesById.toJS()
-      .map(message => {
-        return {
-          id: message.id,
-          type: message.type,
-          parameters: message.parameters,
-          messageText: message.messageText
-        };
-      }
-    );
-    info("messages : " + JSON.stringify(messages));
-  });
-
   const messageNumber = 100;
   yield testSimpleBatchLogging(hud, messageNumber);
   yield testBatchLoggingAndClear(hud, messageNumber);
 });
 
 function* testSimpleBatchLogging(hud, messageNumber) {
   yield ContentTask.spawn(gBrowser.selectedBrowser, messageNumber,
     function (numMessages) {
--- a/devtools/client/webconsole/new-console-output/test/mochitest/head.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/head.js
@@ -90,17 +90,17 @@ function waitForMessages({ hud, messages
  *        true in order to return the value.
  * @param string message [optional]
  *        A message to output if the condition failes.
  * @param number interval [optional]
  *        How often the predicate is invoked, in milliseconds.
  * @return object
  *         A promise that is resolved with the result of the condition.
  */
-function* waitFor(condition, message = "waitFor", interval = 100, maxTries = 50) {
+function* waitFor(condition, message = "waitFor", interval = 10, maxTries = 500) {
   return new Promise(resolve => {
     BrowserTestUtils.waitForCondition(condition, message, interval, maxTries)
       .then(() => resolve(condition()));
   });
 }
 
 /**
  * Find a message in the output.
--- a/devtools/server/actors/storage.js
+++ b/devtools/server/actors/storage.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/. */
 
+/* globals StopIteration */
+
 "use strict";
 
 const {Cc, Ci, Cu, CC} = require("chrome");
 const events = require("sdk/event/core");
 const protocol = require("devtools/shared/protocol");
 const {LongStringActor} = require("devtools/server/actors/string");
 const {DebuggerServer} = require("devtools/server/main");
 const Services = require("Services");
@@ -88,17 +90,17 @@ var StorageActors = {};
  * Creates a default object with the common methods required by all storage
  * actors.
  *
  * This default object is missing a couple of required methods that should be
  * implemented seperately for each actor. They are namely:
  *   - observe : Method which gets triggered on the notificaiton of the watched
  *               topic.
  *   - getNamesForHost : Given a host, get list of all known store names.
- *   - getValuesForHost : Given a host (and optianally a name) get all known
+ *   - getValuesForHost : Given a host (and optionally a name) get all known
  *                        store objects.
  *   - toStoreObject : Given a store object, convert it to the required format
  *                     so that it can be transferred over wire.
  *   - populateStoresForHost : Given a host, populate the map of all store
  *                             objects for it
  *   - getFields: Given a subType(optional), get an array of objects containing
  *                column field info. The info includes,
  *                "name" is name of colume key.
@@ -136,16 +138,19 @@ StorageActors.defaults = function (typeN
     get windows() {
       return this.storageActor.windows;
     },
 
     /**
      * Converts the window.location object into host.
      */
     getHostName(location) {
+      if (location.protocol === "chrome:") {
+        return location.href;
+      }
       return location.hostname || location.href;
     },
 
     initialize(storageActor) {
       protocol.Actor.prototype.initialize.call(this, null);
 
       this.storageActor = storageActor;
 
@@ -750,16 +755,17 @@ var cookieHelpers = {
     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 || {});
+
     while (enumerator.hasMoreElements()) {
       let nsiCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
       if (nsiCookie.name === origName &&
           nsiCookie.host === origHost &&
           nsiCookie.path === origPath) {
         cookie = {
           host: nsiCookie.host,
           path: nsiCookie.path,
@@ -1047,16 +1053,19 @@ function getObjectForLocalOrSessionStora
         value: storage.getItem(key)
       }));
     },
 
     getHostName(location) {
       if (!location.host) {
         return location.href;
       }
+      if (location.protocol === "chrome:") {
+        return location.href;
+      }
       return location.protocol + "//" + location.host;
     },
 
     populateStoresForHost(host, window) {
       try {
         this.hostVsStores.set(host, window[type]);
       } catch (ex) {
         console.warn(`Failed to enumerate ${type} for host ${host}: ${ex}`);
@@ -1259,16 +1268,19 @@ StorageActors.createActor({
       { name: "status", editable: false }
     ];
   }),
 
   getHostName(location) {
     if (!location.host) {
       return location.href;
     }
+    if (location.protocol === "chrome:") {
+      return location.href;
+    }
     return location.protocol + "//" + location.host;
   },
 
   populateStoresForHost: Task.async(function* (host) {
     let storeMap = new Map();
     let caches = yield this.getCachesForHost(host);
     try {
       for (let name of (yield caches.keys())) {
@@ -1431,22 +1443,25 @@ ObjectStoreMetadata.prototype = {
 
 /**
  * Meta data object for a particular indexed db in a host.
  *
  * @param {string} origin
  *        The host associated with this indexed db.
  * @param {IDBDatabase} db
  *        The particular indexed db.
+ * @param {String} storage
+ *        Storage type, either "temporary", "default" or "persistent".
  */
-function DatabaseMetadata(origin, db) {
+function DatabaseMetadata(origin, db, storage) {
   this._origin = origin;
   this._name = db.name;
   this._version = db.version;
   this._objectStores = [];
+  this.storage = storage;
 
   if (db.objectStoreNames.length) {
     let transaction = db.transaction(db.objectStoreNames, "readonly");
 
     for (let i = 0; i < transaction.objectStoreNames.length; i++) {
       let objectStore =
         transaction.objectStore(transaction.objectStoreNames[i]);
       this._objectStores.push([transaction.objectStoreNames[i],
@@ -1456,17 +1471,17 @@ function DatabaseMetadata(origin, db) {
 }
 DatabaseMetadata.prototype = {
   get objectStores() {
     return this._objectStores;
   },
 
   toObject() {
     return {
-      name: this._name,
+      name: `${this._name} (${this.storage})`,
       origin: this._origin,
       version: this._version,
       objectStores: this._objectStores.size
     };
   }
 };
 
 StorageActors.createActor({
@@ -1536,16 +1551,19 @@ StorageActors.createActor({
     let principal = win.document.nodePrincipal;
     this.removeDBRecord(host, principal, db, store, id);
   }),
 
   getHostName(location) {
     if (!location.host) {
       return location.href;
     }
+    if (location.protocol === "chrome:") {
+      return location.href;
+    }
     return location.protocol + "//" + location.host;
   },
 
   /**
    * This method is overriden and left blank as for indexedDB, this operation
    * cannot be performed synchronously. Thus, the preListStores method exists to
    * do the same task asynchronously.
    */
@@ -1628,25 +1646,27 @@ StorageActors.createActor({
     for (let host of this.hosts) {
       yield this.populateStoresForHost(host);
     }
   }),
 
   populateStoresForHost: Task.async(function* (host) {
     let storeMap = new Map();
     let {names} = yield this.getDBNamesForHost(host);
+
     let win = this.storageActor.getWindowFromHost(host);
     if (win) {
       let principal = win.document.nodePrincipal;
 
-      for (let name of names) {
-        let metadata = yield this.getDBMetaData(host, principal, name);
+      for (let {name, storage} of names) {
+        let metadata = yield this.getDBMetaData(host, principal, name, storage);
 
         metadata = indexedDBHelpers.patchMetadataMapsAndProtos(metadata);
-        storeMap.set(name, metadata);
+
+        storeMap.set(`${name} (${storage})`, metadata);
       }
     }
 
     this.hostVsStores.set(host, storeMap);
   }),
 
   /**
    * Returns the over-the-wire implementation of the indexed db entity.
@@ -1669,20 +1689,32 @@ StorageActors.createActor({
       // DB meta data
       return {
         db: item.name,
         origin: item.origin,
         version: item.version,
         objectStores: item.objectStores
       };
     }
+
+    let value = JSON.stringify(item.value);
+
+    // FIXME: Bug 1318029 - Due to a bug that is thrown whenever a
+    // LongStringActor string reaches DebuggerServer.LONG_STRING_LENGTH we need
+    // to trim the value. When the bug is fixed we should stop trimming the
+    // string here.
+    let maxLength = DebuggerServer.LONG_STRING_LENGTH - 1;
+    if (value.length > maxLength) {
+      value = value.substr(0, maxLength);
+    }
+
     // Indexed db entry
     return {
       name: item.name,
-      value: new LongStringActor(this.conn, JSON.stringify(item.value))
+      value: new LongStringActor(this.conn, value)
     };
   },
 
   form(form, detail) {
     if (detail === "actorid") {
       return this.actorID;
     }
 
@@ -1708,38 +1740,41 @@ StorageActors.createActor({
     this.storageActor.update(action, "indexedDB", {
       [host]: [ JSON.stringify(path) ]
     });
   },
 
   maybeSetupChildProcess() {
     if (!DebuggerServer.isInChildProcess) {
       this.backToChild = (func, rv) => rv;
+      this.clearDBStore = indexedDBHelpers.clearDBStore;
+      this.gatherFilesOrFolders = indexedDBHelpers.gatherFilesOrFolders;
       this.getDBMetaData = indexedDBHelpers.getDBMetaData;
-      this.openWithPrincipal = indexedDBHelpers.openWithPrincipal;
       this.getDBNamesForHost = indexedDBHelpers.getDBNamesForHost;
+      this.getNameFromDatabaseFile = indexedDBHelpers.getNameFromDatabaseFile;
+      this.getObjectStoreData = indexedDBHelpers.getObjectStoreData;
       this.getSanitizedHost = indexedDBHelpers.getSanitizedHost;
-      this.getNameFromDatabaseFile = indexedDBHelpers.getNameFromDatabaseFile;
       this.getValuesForHost = indexedDBHelpers.getValuesForHost;
-      this.getObjectStoreData = indexedDBHelpers.getObjectStoreData;
+      this.openWithPrincipal = indexedDBHelpers.openWithPrincipal;
       this.removeDB = indexedDBHelpers.removeDB;
       this.removeDBRecord = indexedDBHelpers.removeDBRecord;
-      this.clearDBStore = indexedDBHelpers.clearDBStore;
+      this.splitNameAndStorage = indexedDBHelpers.splitNameAndStorage;
       return;
     }
 
     const { sendAsyncMessage, addMessageListener } =
       this.conn.parentMessageManager;
 
     this.conn.setupInParent({
       module: "devtools/server/actors/storage",
       setupParent: "setupParentProcessForIndexedDB"
     });
 
     this.getDBMetaData = callParentProcessAsync.bind(null, "getDBMetaData");
+    this.splitNameAndStorage = callParentProcessAsync.bind(null, "splitNameAndStorage");
     this.getDBNamesForHost = callParentProcessAsync.bind(null, "getDBNamesForHost");
     this.getValuesForHost = callParentProcessAsync.bind(null, "getValuesForHost");
     this.removeDB = callParentProcessAsync.bind(null, "removeDB");
     this.removeDBRecord = callParentProcessAsync.bind(null, "removeDBRecord");
     this.clearDBStore = callParentProcessAsync.bind(null, "clearDBStore");
 
     addMessageListener("debug:storage-indexedDB-request-child", msg => {
       switch (msg.json.method) {
@@ -1825,51 +1860,66 @@ var indexedDBHelpers = {
     });
   },
 
   /**
    * Fetches and stores all the metadata information for the given database
    * `name` for the given `host` with its `principal`. The stored metadata
    * information is of `DatabaseMetadata` type.
    */
-  getDBMetaData: Task.async(function* (host, principal, name) {
-    let request = this.openWithPrincipal(principal, name);
+  getDBMetaData: Task.async(function* (host, principal, name, storage) {
+    let request = this.openWithPrincipal(principal, name, storage);
     let success = promise.defer();
 
     request.onsuccess = event => {
       let db = event.target.result;
-
-      let dbData = new DatabaseMetadata(host, db);
+      let dbData = new DatabaseMetadata(host, db, storage);
       db.close();
 
       success.resolve(this.backToChild("getDBMetaData", dbData));
     };
     request.onerror = ({target}) => {
       console.error(
         `Error opening indexeddb database ${name} for host ${host}`, target.error);
       success.resolve(this.backToChild("getDBMetaData", null));
     };
     return success.promise;
   }),
 
+  splitNameAndStorage: function (name) {
+    let lastOpenBracketIndex = name.lastIndexOf("(");
+    let lastCloseBracketIndex = name.lastIndexOf(")");
+    let delta = lastCloseBracketIndex - lastOpenBracketIndex - 1;
+
+    let storage = name.substr(lastOpenBracketIndex + 1, delta);
+
+    name = name.substr(0, lastOpenBracketIndex - 1);
+
+    return { storage, name };
+  },
+
   /**
    * Opens an indexed db connection for the given `principal` and
    * database `name`.
    */
-  openWithPrincipal(principal, name) {
-    return indexedDBForStorage.openForPrincipal(principal, name);
+  openWithPrincipal: function (principal, name, storage) {
+    return indexedDBForStorage.openForPrincipal(principal, name,
+                                                { storage: storage });
   },
 
-  removeDB: Task.async(function* (host, principal, name) {
+  removeDB: Task.async(function* (host, principal, dbName) {
     let result = new promise(resolve => {
-      let request = indexedDBForStorage.deleteForPrincipal(principal, name);
+      let {name, storage} = this.splitNameAndStorage(dbName);
+      let request =
+        indexedDBForStorage.deleteForPrincipal(principal, name,
+                                               { storage: storage });
 
       request.onsuccess = () => {
         resolve({});
-        this.onItemUpdated("deleted", host, [name]);
+        this.onItemUpdated("deleted", host, [dbName]);
       };
 
       request.onblocked = () => {
         console.warn(`Deleting indexedDB database ${name} for host ${host} is blocked`);
         resolve({ blocked: true });
       };
 
       request.onerror = () => {
@@ -1885,20 +1935,21 @@ var indexedDBHelpers = {
       setTimeout(() => resolve({ blocked: true }), 3000);
     });
 
     return this.backToChild("removeDB", yield result);
   }),
 
   removeDBRecord: Task.async(function* (host, principal, dbName, storeName, id) {
     let db;
+    let {name, storage} = this.splitNameAndStorage(dbName);
 
     try {
       db = yield new promise((resolve, reject) => {
-        let request = this.openWithPrincipal(principal, dbName);
+        let request = this.openWithPrincipal(principal, name, storage);
         request.onsuccess = ev => resolve(ev.target.result);
         request.onerror = ev => reject(ev.target.error);
       });
 
       let transaction = db.transaction(storeName, "readwrite");
       let store = transaction.objectStore(storeName);
 
       yield new promise((resolve, reject) => {
@@ -1917,20 +1968,21 @@ var indexedDBHelpers = {
       db.close();
     }
 
     return this.backToChild("removeDBRecord", null);
   }),
 
   clearDBStore: Task.async(function* (host, principal, dbName, storeName) {
     let db;
+    let {name, storage} = this.splitNameAndStorage(dbName);
 
     try {
       db = yield new promise((resolve, reject) => {
-        let request = this.openWithPrincipal(principal, dbName);
+        let request = this.openWithPrincipal(principal, name, storage);
         request.onsuccess = ev => resolve(ev.target.result);
         request.onerror = ev => reject(ev.target.error);
       });
 
       let transaction = db.transaction(storeName, "readwrite");
       let store = transaction.objectStore(storeName);
 
       yield new promise((resolve, reject) => {
@@ -1952,77 +2004,141 @@ var indexedDBHelpers = {
     return this.backToChild("clearDBStore", null);
   }),
 
   /**
    * Fetches all the databases and their metadata for the given `host`.
    */
   getDBNamesForHost: Task.async(function* (host) {
     let sanitizedHost = this.getSanitizedHost(host);
-    let directory = OS.Path.join(OS.Constants.Path.profileDir, "storage",
-                                 "default", sanitizedHost, "idb");
+    let profileDir = OS.Constants.Path.profileDir;
+    let files = [];
+    let names = [];
+    let storagePath = OS.Path.join(profileDir, "storage");
 
-    let exists = yield OS.File.exists(directory);
-    if (!exists && host.startsWith("about:")) {
-      // try for moz-safe-about directory
-      sanitizedHost = this.getSanitizedHost("moz-safe-" + host);
-      directory = OS.Path.join(OS.Constants.Path.profileDir, "storage",
-                               "permanent", sanitizedHost, "idb");
-      exists = yield OS.File.exists(directory);
-    }
-    if (!exists) {
-      return this.backToChild("getDBNamesForHost", {names: []});
+    // We expect sqlite DB paths to look something like this:
+    // - PathToProfileDir/storage/default/http+++www.example.com/
+    //   idb/1556056096MeysDaabta.sqlite
+    // - PathToProfileDir/storage/permanent/http+++www.example.com/
+    //   idb/1556056096MeysDaabta.sqlite
+    // - PathToProfileDir/storage/temporary/http+++www.example.com/
+    //   idb/1556056096MeysDaabta.sqlite
+    //
+    // The subdirectory inside the storage folder is determined by the storage
+    // type:
+    // - default:   { storage: "default" } or not specified.
+    // - permanent: { storage: "persistent" }.
+    // - temporary: { storage: "temporary" }.
+    let sqliteFiles = yield this.gatherFilesOrFolders(storagePath, path => {
+      if (path.endsWith(".sqlite")) {
+        let { components } = OS.Path.split(path);
+        let isIDB = components[components.length - 2] === "idb";
+
+        return isIDB;
+      }
+      return false;
+    });
+
+    for (let file of sqliteFiles) {
+      let splitPath = OS.Path.split(file).components;
+      let idbIndex = splitPath.indexOf("idb");
+      let name = splitPath[idbIndex - 1];
+      let storage = splitPath[idbIndex - 2];
+      let relative = file.substr(profileDir.length + 1);
+
+      if (name.startsWith(sanitizedHost)) {
+        files.push({
+          file: relative,
+          storage: storage === "permanent" ? "persistent" : storage
+        });
+      }
     }
 
-    let names = [];
-    let dirIterator = new OS.File.DirectoryIterator(directory);
-    try {
-      yield dirIterator.forEach(file => {
-        // Skip directories.
-        if (file.isDir) {
-          return null;
-        }
-
-        // Skip any non-sqlite files.
-        if (!file.name.endsWith(".sqlite")) {
-          return null;
+    if (files.length > 0) {
+      for (let {file, storage} of files) {
+        let name = yield this.getNameFromDatabaseFile(file);
+        if (name) {
+          names.push({
+            name,
+            storage
+          });
         }
+      }
+    }
 
-        return this.getNameFromDatabaseFile(file.path).then(name => {
-          if (name) {
-            names.push(name);
+    return this.backToChild("getDBNamesForHost", {names});
+  }),
+
+  /**
+   * Gather together all of the files in path and pass each path through a
+   * validation function.
+   *
+   * @param {String}
+   *        Path in which to begin searching.
+   * @param {Function}
+   *        Validation function, which checks each file path. If this function
+   *        Returns true the file path is kept.
+   *
+   * @returns {Array}
+   *          An array of file paths.
+   */
+  gatherFilesOrFolders: Task.async(function* (path, validationFunc) {
+    let files = [];
+    let iterator;
+    let paths = [path];
+
+    while (paths.length > 0) {
+      try {
+        iterator = new OS.File.DirectoryIterator(paths.pop());
+
+        for (let child in iterator) {
+          child = yield child;
+
+          path = child.path;
+
+          if (child.isDir) {
+            paths.push(path);
+          } else if (validationFunc(path)) {
+            files.push(path);
           }
-          return null;
-        });
-      });
-    } finally {
-      dirIterator.close();
+        }
+      } catch (ex) {
+        // Ignore StopIteration to prevent exiting the loop.
+        if (ex != StopIteration) {
+          throw ex;
+        }
+      }
     }
-    return this.backToChild("getDBNamesForHost", {names: names});
+    iterator.close();
+
+    return files;
   }),
 
   /**
    * Removes any illegal characters from the host name to make it a valid file
    * name.
    */
   getSanitizedHost(host) {
+    if (host.startsWith("about:")) {
+      host = "moz-safe-" + host;
+    }
     return host.replace(ILLEGAL_CHAR_REGEX, "+");
   },
 
   /**
    * Retrieves the proper indexed db database name from the provided .sqlite
    * file location.
    */
   getNameFromDatabaseFile: Task.async(function* (path) {
     let connection = null;
     let retryCount = 0;
 
     // Content pages might be having an open transaction for the same indexed db
     // which this sqlite file belongs to. In that case, sqlite.openConnection
-    // will throw. Thus we retey for some time to see if lock is removed.
+    // will throw. Thus we retry for some time to see if lock is removed.
     while (!connection && retryCount++ < 25) {
       try {
         connection = yield Sqlite.openConnection({ path: path });
       } catch (ex) {
         // Continuously retrying is overkill. Waiting for 100ms before next try
         yield sleep(100);
       }
     }
@@ -2073,48 +2189,58 @@ var indexedDBHelpers = {
 
         for (let objectStore2 of objectStores2) {
           objectStores.push(objectStore2[1].toObject());
         }
       }
       return this.backToChild("getValuesForHost", {objectStores: objectStores});
     }
     // Get either all entries from the object store, or a particular id
-    let result = yield this.getObjectStoreData(host, principal, db2,
-      objectStore, id, options.index, options.size);
+    let storage = hostVsStores.get(host).get(db2).storage;
+    let result = yield this.getObjectStoreData(host, principal, db2, storage, {
+      objectStore: objectStore,
+      id: id,
+      index: options.index,
+      offset: 0,
+      size: options.size
+    });
     return this.backToChild("getValuesForHost", {result: result});
   }),
 
   /**
    * Returns all or requested entries from a particular objectStore from the db
    * in the given host.
    *
    * @param {string} host
    *        The given host.
    * @param {nsIPrincipal} principal
    *        The principal of the given document.
    * @param {string} dbName
    *        The name of the indexed db from the above host.
-   * @param {string} objectStore
-   *        The name of the object store from the above db.
-   * @param {string} id
-   *        id of the requested entry from the above object store.
-   *        null if all entries from the above object store are requested.
-   * @param {string} index
-   *        name of the IDBIndex to be iterated on while fetching entries.
-   *        null or "name" if no index is to be iterated.
-   * @param {number} offset
-   *        ofsset of the entries to be fetched.
-   * @param {number} size
-   *        The intended size of the entries to be fetched.
+   * @param {String} storage
+   *        Storage type, either "temporary", "default" or "persistent".
+   * @param {Object} requestOptions
+   *        An object in the following format:
+   *        {
+   *          objectStore: The name of the object store from the above db,
+   *          id:          Id of the requested entry from the above object
+   *                       store. null if all entries from the above object
+   *                       store are requested,
+   *          index:       Name of the IDBIndex to be iterated on while fetching
+   *                       entries. null or "name" if no index is to be
+   *                       iterated,
+   *          offset:      offset of the entries to be fetched,
+   *          size:        The intended size of the entries to be fetched
+   *        }
    */
-  getObjectStoreData(host, principal, dbName, objectStore, id, index,
-                     offset, size) {
-    let request = this.openWithPrincipal(principal, dbName);
+  getObjectStoreData(host, principal, dbName, storage, requestOptions) {
+    let {name} = this.splitNameAndStorage(dbName);
+    let request = this.openWithPrincipal(principal, name, storage);
     let success = promise.defer();
+    let {objectStore, id, index, offset, size} = requestOptions;
     let data = [];
     let db;
 
     if (!size || size > MAX_STORE_OBJECT_COUNT) {
       size = MAX_STORE_OBJECT_COUNT;
     }
 
     request.onsuccess = event => {
@@ -2206,31 +2332,35 @@ var indexedDBHelpers = {
     return md;
   },
 
   handleChildRequest(msg) {
     let args = msg.data.args;
 
     switch (msg.json.method) {
       case "getDBMetaData": {
-        let [host, principal, name] = args;
-        return indexedDBHelpers.getDBMetaData(host, principal, name);
+        let [host, principal, name, storage] = args;
+        return indexedDBHelpers.getDBMetaData(host, principal, name, storage);
+      }
+      case "splitNameAndStorage": {
+        let [name] = args;
+        return indexedDBHelpers.splitNameAndStorage(name);
       }
       case "getDBNamesForHost": {
         let [host] = args;
         return indexedDBHelpers.getDBNamesForHost(host);
       }
       case "getValuesForHost": {
         let [host, name, options, hostVsStores, principal] = args;
         return indexedDBHelpers.getValuesForHost(host, name, options,
                                                  hostVsStores, principal);
       }
       case "removeDB": {
-        let [host, principal, name] = args;
-        return indexedDBHelpers.removeDB(host, principal, name);
+        let [host, principal, dbName] = args;
+        return indexedDBHelpers.removeDB(host, principal, dbName);
       }
       case "removeDBRecord": {
         let [host, principal, db, store, id] = args;
         return indexedDBHelpers.removeDBRecord(host, principal, db, store, id);
       }
       case "clearDBStore": {
         let [host, principal, db, store] = args;
         return indexedDBHelpers.clearDBStore(host, principal, db, store);
--- a/devtools/server/tests/browser/browser_storage_listings.js
+++ b/devtools/server/tests/browser/browser_storage_listings.js
@@ -116,59 +116,59 @@ const storeMap = {
       }
     ]
   }
 };
 
 const IDBValues = {
   listStoresResponse: {
     "http://test1.example.org": [
-      ["idb1", "obj1"], ["idb1", "obj2"], ["idb2", "obj3"]
+      ["idb1 (default)", "obj1"], ["idb1 (default)", "obj2"], ["idb2 (default)", "obj3"]
     ],
     "http://sectest1.example.org": [
     ],
     "https://sectest1.example.org": [
-      ["idb-s1", "obj-s1"], ["idb-s2", "obj-s2"]
+      ["idb-s1 (default)", "obj-s1"], ["idb-s2 (default)", "obj-s2"]
     ]
   },
-  dbDetails : {
+  dbDetails: {
     "http://test1.example.org": [
       {
-        db: "idb1",
+        db: "idb1 (default)",
         origin: "http://test1.example.org",
         version: 1,
         objectStores: 2
       },
       {
-        db: "idb2",
+        db: "idb2 (default)",
         origin: "http://test1.example.org",
         version: 1,
         objectStores: 1
       },
     ],
     "http://sectest1.example.org": [
     ],
     "https://sectest1.example.org": [
       {
-        db: "idb-s1",
+        db: "idb-s1 (default)",
         origin: "https://sectest1.example.org",
         version: 1,
         objectStores: 1
       },
       {
-        db: "idb-s2",
+        db: "idb-s2 (default)",
         origin: "https://sectest1.example.org",
         version: 1,
         objectStores: 1
       },
     ]
   },
   objectStoreDetails: {
     "http://test1.example.org": {
-      idb1: [
+      "idb1 (default)": [
         {
           objectStore: "obj1",
           keyPath: "id",
           autoIncrement: false,
           indexes: [
             {
               name: "name",
               keyPath: "name",
@@ -185,17 +185,17 @@ const IDBValues = {
         },
         {
           objectStore: "obj2",
           keyPath: "id2",
           autoIncrement: false,
           indexes: []
         }
       ],
-      idb2: [
+      "idb2 (default)": [
         {
           objectStore: "obj3",
           keyPath: "id3",
           autoIncrement: false,
           indexes: [
             {
               name: "name2",
               keyPath: "name2",
@@ -203,25 +203,25 @@ const IDBValues = {
               multiEntry: false,
             }
           ]
         },
       ]
     },
     "http://sectest1.example.org" : {},
     "https://sectest1.example.org": {
-      "idb-s1": [
+      "idb-s1 (default)": [
         {
           objectStore: "obj-s1",
           keyPath: "id",
           autoIncrement: false,
           indexes: []
         },
       ],
-      "idb-s2": [
+      "idb-s2 (default)": [
         {
           objectStore: "obj-s2",
           keyPath: "id3",
           autoIncrement: true,
           indexes: [
             {
               name: "name2",
               keyPath: "name2",
@@ -231,17 +231,17 @@ const IDBValues = {
           ]
         },
       ]
     }
 
   },
   entries: {
     "http://test1.example.org": {
-      "idb1#obj1": [
+      "idb1 (default)#obj1": [
         {
           name: 1,
           value: {
             id: 1,
             name: "foo",
             email: "foo@bar.com",
           }
         },
@@ -257,32 +257,32 @@ const IDBValues = {
           name: 3,
           value: {
             id: 3,
             name: "foo2",
             email: "foo3@bar.com",
           }
         }
       ],
-      "idb1#obj2": [
+      "idb1 (default)#obj2": [
         {
           name: 1,
           value: {
             id2: 1,
             name: "foo",
             email: "foo@bar.com",
             extra: "baz"
           }
         }
       ],
-      "idb2#obj3": []
+      "idb2 (default)#obj3": []
     },
     "http://sectest1.example.org" : {},
     "https://sectest1.example.org": {
-      "idb-s1#obj-s1": [
+      "idb-s1 (default)#obj-s1": [
         {
           name: 6,
           value: {
             id: 6,
             name: "foo",
             email: "foo@bar.com",
           }
         },
@@ -290,17 +290,17 @@ const IDBValues = {
           name: 7,
           value: {
             id: 7,
             name: "foo2",
             email: "foo2@bar.com",
           }
         }
       ],
-      "idb-s2#obj-s2": [
+      "idb-s2 (default)#obj-s2": [
         {
           name: 13,
           value: {
             id2: 13,
             name2: "foo",
             email: "foo@bar.com",
           }
         }
--- a/devtools/shared/specs/storage.js
+++ b/devtools/shared/specs/storage.js
@@ -56,17 +56,17 @@ types.addDictType("cookieobject", {
 types.addDictType("cookiestoreobject", {
   total: "number",
   offset: "number",
   data: "array:nullable:cookieobject"
 });
 
 // Common methods for edit/remove
 const editRemoveMethods = {
-  getEditableFields: {
+  getFields: {
     request: {},
     response: {
       value: RetVal("json")
     }
   },
   editItem: {
     request: {
       data: Arg(0, "json"),
--- a/dom/media/MediaShutdownManager.cpp
+++ b/dom/media/MediaShutdownManager.cpp
@@ -69,32 +69,41 @@ MediaShutdownManager::EnsureCorrectShutd
 {
   bool needShutdownObserver = mDecoders.Count() > 0;
   if (needShutdownObserver != mIsObservingShutdown) {
     mIsObservingShutdown = needShutdownObserver;
     if (mIsObservingShutdown) {
       nsresult rv = GetShutdownBarrier()->AddBlocker(
         this, NS_LITERAL_STRING(__FILE__), __LINE__,
         NS_LITERAL_STRING("MediaShutdownManager shutdown"));
-      MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+      if (NS_FAILED(rv)) {
+        // Leak the buffer on the heap to make sure that it lives long enough,
+        // as MOZ_CRASH_ANNOTATE expects the pointer passed to it to live to
+        // the end of the program.
+        const size_t CAPACITY = 256;
+        auto buf = new char[CAPACITY];
+        snprintf(buf, CAPACITY, "Failed to add shutdown blocker! rv=%x", uint32_t(rv));
+        MOZ_CRASH_ANNOTATE(buf);
+        MOZ_REALLY_CRASH();
+      }
     } else {
       GetShutdownBarrier()->RemoveBlocker(this);
       // Clear our singleton reference. This will probably delete
       // this instance, so don't deref |this| clearing sInstance.
       sInstance = nullptr;
       DECODER_LOG(LogLevel::Debug, ("MediaShutdownManager::BlockShutdown() end."));
     }
   }
 }
 
 void
 MediaShutdownManager::Register(MediaDecoder* aDecoder)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_DIAGNOSTIC_ASSERT(!mIsDoingXPCOMShutDown);
+  MOZ_RELEASE_ASSERT(!mIsDoingXPCOMShutDown);
   // Don't call Register() after you've Unregistered() all the decoders,
   // that's not going to work.
   MOZ_ASSERT(!mDecoders.Contains(aDecoder));
   mDecoders.PutEntry(aDecoder);
   MOZ_ASSERT(mDecoders.Contains(aDecoder));
   MOZ_ASSERT(mDecoders.Count() > 0);
   EnsureCorrectShutdownObserverState();
 }
--- a/ipc/ipdl/ipdl/lower.py
+++ b/ipc/ipdl/ipdl/lower.py
@@ -1543,17 +1543,18 @@ class _GenerateProtocolCode(ipdl.ast.Vis
 
         # state information
         stateenum = TypeEnum('State')
         # NB: __Dead is the first state on purpose, so that it has
         # value '0'
         stateenum.addId(_deadState().name)
         stateenum.addId(_nullState().name)
         stateenum.addId(_errorState().name)
-        stateenum.addId(_dyingState().name)
+        if self.protocol.decl.type.hasReentrantDelete:
+            stateenum.addId(_dyingState().name)
         for ts in p.transitionStmts:
             stateenum.addId(ts.state.decl.cxxname)
         if len(p.transitionStmts):
             startstate = p.transitionStmts[0].state.decl.cxxname
         else:
             startstate = _nullState().name
         stateenum.addId(_startState().name, startstate)
 
@@ -1764,29 +1765,25 @@ class _GenerateProtocolCode(ipdl.ast.Vis
         # special case for Dead
         deadblock = Block()
         deadblock.addstmts([
             _logicError('__delete__()d actor'),
             StmtReturn(ExprLiteral.FALSE) ])
         fromswitch.addcase(CaseLabel(_deadState().name), deadblock)
 
         # special case for Dying
-        dyingblock = Block()
         if ptype.hasReentrantDelete:
+            dyingblock = Block()
             ifdelete = StmtIf(ExprBinary(_deleteReplyId(), '==', msgexpr))
             ifdelete.addifstmt(
                 StmtExpr(ExprAssn(ExprDeref(nextvar), _deadState())))
             dyingblock.addstmt(ifdelete)
             dyingblock.addstmt(
                 StmtReturn(ExprLiteral.TRUE))
-        else:
-            dyingblock.addstmts([
-                _logicError('__delete__()d (and unexpectedly dying) actor'),
-                StmtReturn(ExprLiteral.FALSE) ])
-        fromswitch.addcase(CaseLabel(_dyingState().name), dyingblock)
+            fromswitch.addcase(CaseLabel(_dyingState().name), dyingblock)
 
         unreachedblock = Block()
         unreachedblock.addstmts([
             _logicError('corrupted actor state'),
             StmtReturn(ExprLiteral.FALSE) ])
         fromswitch.addcase(DefaultLabel(), unreachedblock)
 
         if usesend:
--- a/ipc/ipdl/ipdl/parser.py
+++ b/ipc/ipdl/ipdl/parser.py
@@ -2,21 +2,16 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import os, sys
 from ply import lex, yacc
 
 from ipdl.ast import *
 
-def _getcallerpath():
-    '''Return the absolute path of the file containing the code that
-**CALLED** this function.'''
-    return os.path.abspath(sys._getframe(1).f_code.co_filename)
-
 ##-----------------------------------------------------------------------------
 
 class ParseError(Exception):
     def __init__(self, loc, fmt, *args):
         self.loc = loc
         self.error = ('%s%s: error: %s'% (
             Parser.includeStackString(), loc, fmt)) % args
     def __str__(self):
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -99,16 +99,28 @@ Char16ToUChar(const char16_t* chars)
 }
 
 inline UChar*
 Char16ToUChar(char16_t* chars)
 {
     MOZ_CRASH("Char16ToUChar: Intl API disabled");
 }
 
+inline char16_t*
+UCharToChar16(UChar* chars)
+{
+    MOZ_CRASH("UCharToChar16: Intl API disabled");
+}
+
+inline const char16_t*
+UCharToChar16(const UChar* chars)
+{
+    MOZ_CRASH("UCharToChar16: Intl API disabled");
+}
+
 struct UEnumeration;
 
 int32_t
 uenum_count(UEnumeration* en, UErrorCode* status)
 {
     MOZ_CRASH("uenum_count: Intl API disabled");
 }
 
@@ -347,16 +359,37 @@ enum UCalendarDateFields {
     UCAL_EXTENDED_YEAR,
     UCAL_JULIAN_DAY,
     UCAL_MILLISECONDS_IN_DAY,
     UCAL_IS_LEAP_MONTH,
     UCAL_FIELD_COUNT,
     UCAL_DAY_OF_MONTH = UCAL_DATE
 };
 
+enum UCalendarMonths {
+  UCAL_JANUARY,
+  UCAL_FEBRUARY,
+  UCAL_MARCH,
+  UCAL_APRIL,
+  UCAL_MAY,
+  UCAL_JUNE,
+  UCAL_JULY,
+  UCAL_AUGUST,
+  UCAL_SEPTEMBER,
+  UCAL_OCTOBER,
+  UCAL_NOVEMBER,
+  UCAL_DECEMBER,
+  UCAL_UNDECIMBER
+};
+
+enum UCalendarAMPMs {
+  UCAL_AM,
+  UCAL_PM
+};
+
 UCalendar*
 ucal_open(const UChar* zoneID, int32_t len, const char* locale,
           UCalendarType type, UErrorCode* status)
 {
     MOZ_CRASH("ucal_open: Intl API disabled");
 }
 
 const char*
@@ -411,32 +444,47 @@ ucal_getCanonicalTimeZoneID(const UChar*
 }
 
 int32_t
 ucal_getDefaultTimeZone(UChar* result, int32_t resultCapacity, UErrorCode* status)
 {
     MOZ_CRASH("ucal_getDefaultTimeZone: Intl API disabled");
 }
 
+enum UDateTimePatternField {
+    UDATPG_YEAR_FIELD,
+    UDATPG_MONTH_FIELD,
+    UDATPG_WEEK_OF_YEAR_FIELD,
+    UDATPG_DAY_FIELD,
+};
+
 typedef void* UDateTimePatternGenerator;
 
 UDateTimePatternGenerator*
 udatpg_open(const char* locale, UErrorCode* pErrorCode)
 {
     MOZ_CRASH("udatpg_open: Intl API disabled");
 }
 
 int32_t
 udatpg_getBestPattern(UDateTimePatternGenerator* dtpg, const UChar* skeleton,
                       int32_t length, UChar* bestPattern, int32_t capacity,
                       UErrorCode* pErrorCode)
 {
     MOZ_CRASH("udatpg_getBestPattern: Intl API disabled");
 }
 
+static const UChar *
+udatpg_getAppendItemName(const UDateTimePatternGenerator *dtpg,
+                         UDateTimePatternField field,
+                         int32_t *pLength)
+{
+    MOZ_CRASH("udatpg_getAppendItemName: Intl API disabled");
+}
+
 void
 udatpg_close(UDateTimePatternGenerator* dtpg)
 {
     MOZ_CRASH("udatpg_close: Intl API disabled");
 }
 
 typedef void* UCalendar;
 typedef void* UDateFormat;
@@ -479,20 +527,56 @@ enum UDateFormatField {
     UDAT_RELATED_YEAR_FIELD = 34,
     UDAT_AM_PM_MIDNIGHT_NOON_FIELD = 35,
     UDAT_FLEXIBLE_DAY_PERIOD_FIELD = 36,
     UDAT_TIME_SEPARATOR_FIELD = 37,
     UDAT_FIELD_COUNT = 38
 };
 
 enum UDateFormatStyle {
+    UDAT_FULL,
+    UDAT_LONG,
+    UDAT_MEDIUM,
+    UDAT_SHORT,
+    UDAT_DEFAULT = UDAT_MEDIUM,
     UDAT_PATTERN = -2,
     UDAT_IGNORE = UDAT_PATTERN
 };
 
+enum UDateFormatSymbolType {
+    UDAT_ERAS,
+    UDAT_MONTHS,
+    UDAT_SHORT_MONTHS,
+    UDAT_WEEKDAYS,
+    UDAT_SHORT_WEEKDAYS,
+    UDAT_AM_PMS,
+    UDAT_LOCALIZED_CHARS,
+    UDAT_ERA_NAMES,
+    UDAT_NARROW_MONTHS,
+    UDAT_NARROW_WEEKDAYS,
+    UDAT_STANDALONE_MONTHS,
+    UDAT_STANDALONE_SHORT_MONTHS,
+    UDAT_STANDALONE_NARROW_MONTHS,
+    UDAT_STANDALONE_WEEKDAYS,
+    UDAT_STANDALONE_SHORT_WEEKDAYS,
+    UDAT_STANDALONE_NARROW_WEEKDAYS,
+    UDAT_QUARTERS,
+    UDAT_SHORT_QUARTERS,
+    UDAT_STANDALONE_QUARTERS,
+    UDAT_STANDALONE_SHORT_QUARTERS,
+    UDAT_SHORTER_WEEKDAYS,
+    UDAT_STANDALONE_SHORTER_WEEKDAYS,
+    UDAT_CYCLIC_YEARS_WIDE,
+    UDAT_CYCLIC_YEARS_ABBREVIATED,
+    UDAT_CYCLIC_YEARS_NARROW,
+    UDAT_ZODIAC_NAMES_WIDE,
+    UDAT_ZODIAC_NAMES_ABBREVIATED,
+    UDAT_ZODIAC_NAMES_NARROW
+};
+
 int32_t
 udat_countAvailable()
 {
     MOZ_CRASH("udat_countAvailable: Intl API disabled");
 }
 
 const char*
 udat_getAvailable(int32_t localeIndex)
@@ -556,16 +640,23 @@ ufieldpositer_next(UFieldPositionIterato
 void
 udat_close(UDateFormat* format)
 {
     MOZ_CRASH("udat_close: Intl API disabled");
 }
 
 } // anonymous namespace
 
+static int32_t
+udat_getSymbols(const UDateFormat *fmt, UDateFormatSymbolType type, int32_t symbolIndex,
+                UChar *result, int32_t resultLength, UErrorCode *status)
+{
+    MOZ_CRASH("udat_getSymbols: Intl API disabled");
+}
+
 #endif
 
 
 /******************** Common to Intl constructors ********************/
 
 static bool
 IntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
                HandleValue locales, HandleValue options)
@@ -2917,16 +3008,306 @@ js::intl_GetCalendarInfo(JSContext* cx, 
 
     if (!DefineProperty(cx, info, cx->names().weekendEnd, weekendEnd))
         return false;
 
     args.rval().setObject(*info);
     return true;
 }
 
+template<size_t N>
+inline bool
+MatchPart(const char** pattern, const char (&part)[N])
+{
+    if (strncmp(*pattern, part, N - 1))
+        return false;
+
+    *pattern += N - 1;
+    return true;
+}
+
+bool
+js::intl_ComputeDisplayNames(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 3);
+    // 1. Assert: locale is a string.
+    MOZ_ASSERT(args[0].isString());
+    // 2. Assert: style is a string.
+    MOZ_ASSERT(args[1].isString());
+    // 3. Assert: keys is an Array.
+    MOZ_ASSERT(args[2].isObject());
+
+    JSAutoByteString locale(cx, args[0].toString());
+    if (!locale)
+        return false;
+
+    JSAutoByteString style(cx, args[1].toString());
+    if (!style)
+        return false;
+
+    RootedArrayObject keys(cx, &args[2].toObject().as<ArrayObject>());
+    if (!keys)
+        return false;
+
+    // 4. Let result be ArrayCreate(0).
+    RootedArrayObject result(cx, NewDenseUnallocatedArray(cx, keys->length()));
+    if (!result)
+        return false;
+
+    UErrorCode status = U_ZERO_ERROR;
+
+    UDateFormat* fmt =
+        udat_open(UDAT_DEFAULT, UDAT_DEFAULT, icuLocale(locale.ptr()),
+        nullptr, 0, nullptr, 0, &status);
+    if (U_FAILURE(status)) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        return false;
+    }
+    ScopedICUObject<UDateFormat, udat_close> datToClose(fmt);
+
+    // UDateTimePatternGenerator will be needed for translations of date and
+    // time fields like "month", "week", "day" etc.
+    UDateTimePatternGenerator* dtpg = udatpg_open(icuLocale(locale.ptr()), &status);
+    if (U_FAILURE(status)) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        return false;
+    }
+    ScopedICUObject<UDateTimePatternGenerator, udatpg_close> datPgToClose(dtpg);
+
+    RootedValue keyValue(cx);
+    RootedString keyValStr(cx);
+    RootedValue wordVal(cx);
+    Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
+    if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
+        return false;
+
+    // 5. For each element of keys,
+    for (uint32_t i = 0; i < keys->length(); i++) {
+        /**
+         * We iterate over keys array looking for paths that we have code
+         * branches for.
+         *
+         * For any unknown path branch, the wordVal will keep NullValue and
+         * we'll throw at the end.
+         */
+
+        if (!GetElement(cx, keys, keys, i, &keyValue))
+            return false;
+
+        JSAutoByteString pattern;
+        keyValStr = keyValue.toString();
+        if (!pattern.encodeUtf8(cx, keyValStr))
+            return false;
+
+        wordVal.setNull();
+
+        // 5.a. Perform an implementation dependent algorithm to map a key to a
+        //      corresponding display name.
+        const char* pat = pattern.ptr();
+
+        if (!MatchPart(&pat, "dates")) {
+            JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+            return false;
+        }
+
+        if (!MatchPart(&pat, "/")) {
+            JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+            return false;
+        }
+
+        if (MatchPart(&pat, "fields")) {
+            if (!MatchPart(&pat, "/")) {
+                JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+                return false;
+            }
+
+            UDateTimePatternField fieldType;
+
+            if (MatchPart(&pat, "year")) {
+                fieldType = UDATPG_YEAR_FIELD;
+            } else if (MatchPart(&pat, "month")) {
+                fieldType = UDATPG_MONTH_FIELD;
+            } else if (MatchPart(&pat, "week")) {
+                fieldType = UDATPG_WEEK_OF_YEAR_FIELD;
+            } else if (MatchPart(&pat, "day")) {
+                fieldType = UDATPG_DAY_FIELD;
+            } else {
+                JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+                return false;
+            }
+
+            // This part must be the final part with no trailing data.
+            if (*pat != '\0') {
+                JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+                return false;
+            }
+
+            int32_t resultSize;
+
+            const UChar* value = udatpg_getAppendItemName(dtpg, fieldType, &resultSize);
+            if (U_FAILURE(status)) {
+                JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+                return false;
+            }
+
+            JSString* word = NewStringCopyN<CanGC>(cx, UCharToChar16(value), resultSize);
+            if (!word)
+                return false;
+
+            wordVal.setString(word);
+        } else if (MatchPart(&pat, "gregorian")) {
+            if (!MatchPart(&pat, "/")) {
+                JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+                return false;
+            }
+
+            UDateFormatSymbolType symbolType;
+            int32_t index;
+
+            if (MatchPart(&pat, "months")) {
+                if (!MatchPart(&pat, "/")) {
+                    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+                    return false;
+                }
+
+                if (equal(style, "narrow")) {
+                    symbolType = UDAT_STANDALONE_NARROW_MONTHS;
+                } else if (equal(style, "short")) {
+                    symbolType = UDAT_STANDALONE_SHORT_MONTHS;
+                } else {
+                    MOZ_ASSERT(equal(style, "long"));
+                    symbolType = UDAT_STANDALONE_MONTHS;
+                }
+
+                if (MatchPart(&pat, "january")) {
+                    index = UCAL_JANUARY;
+                } else if (MatchPart(&pat, "february")) {
+                    index = UCAL_FEBRUARY;
+                } else if (MatchPart(&pat, "march")) {
+                    index = UCAL_MARCH;
+                } else if (MatchPart(&pat, "april")) {
+                    index = UCAL_APRIL;
+                } else if (MatchPart(&pat, "may")) {
+                    index = UCAL_MAY;
+                } else if (MatchPart(&pat, "june")) {
+                    index = UCAL_JUNE;
+                } else if (MatchPart(&pat, "july")) {
+                    index = UCAL_JULY;
+                } else if (MatchPart(&pat, "august")) {
+                    index = UCAL_AUGUST;
+                } else if (MatchPart(&pat, "september")) {
+                    index = UCAL_SEPTEMBER;
+                } else if (MatchPart(&pat, "october")) {
+                    index = UCAL_OCTOBER;
+                } else if (MatchPart(&pat, "november")) {
+                    index = UCAL_NOVEMBER;
+                } else if (MatchPart(&pat, "december")) {
+                    index = UCAL_DECEMBER;
+                } else {
+                    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+                    return false;
+                }
+            } else if (MatchPart(&pat, "weekdays")) {
+                if (!MatchPart(&pat, "/")) {
+                    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+                    return false;
+                }
+
+                if (equal(style, "narrow")) {
+                    symbolType = UDAT_STANDALONE_NARROW_WEEKDAYS;
+                } else if (equal(style, "short")) {
+                    symbolType = UDAT_STANDALONE_SHORT_WEEKDAYS;
+                } else {
+                    MOZ_ASSERT(equal(style, "long"));
+                    symbolType = UDAT_STANDALONE_WEEKDAYS;
+                }
+
+                if (MatchPart(&pat, "monday")) {
+                    index = UCAL_MONDAY;
+                } else if (MatchPart(&pat, "tuesday")) {
+                    index = UCAL_TUESDAY;
+                } else if (MatchPart(&pat, "wednesday")) {
+                    index = UCAL_WEDNESDAY;
+                } else if (MatchPart(&pat, "thursday")) {
+                    index = UCAL_THURSDAY;
+                } else if (MatchPart(&pat, "friday")) {
+                    index = UCAL_FRIDAY;
+                } else if (MatchPart(&pat, "saturday")) {
+                    index = UCAL_SATURDAY;
+                } else if (MatchPart(&pat, "sunday")) {
+                    index = UCAL_SUNDAY;
+                } else {
+                    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+                    return false;
+                }
+            } else if (MatchPart(&pat, "dayperiods")) {
+                if (!MatchPart(&pat, "/")) {
+                    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+                    return false;
+                }
+
+                symbolType = UDAT_AM_PMS;
+
+                if (MatchPart(&pat, "am")) {
+                    index = UCAL_AM;
+                } else if (MatchPart(&pat, "pm")) {
+                    index = UCAL_PM;
+                } else {
+                    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+                    return false;
+                }
+            } else {
+                JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+                return false;
+            }
+
+            // This part must be the final part with no trailing data.
+            if (*pat != '\0') {
+                JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+                return false;
+            }
+
+            int32_t resultSize =
+                udat_getSymbols(fmt, symbolType, index, Char16ToUChar(chars.begin()),
+                                INITIAL_CHAR_BUFFER_SIZE, &status);
+            if (status == U_BUFFER_OVERFLOW_ERROR) {
+                if (!chars.resize(resultSize))
+                    return false;
+                status = U_ZERO_ERROR;
+                udat_getSymbols(fmt, symbolType, index, Char16ToUChar(chars.begin()),
+                                resultSize, &status);
+            }
+            if (U_FAILURE(status)) {
+                JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+                return false;
+            }
+
+            JSString* word = NewStringCopyN<CanGC>(cx, chars.begin(), resultSize);
+            if (!word)
+                return false;
+
+            wordVal.setString(word);
+        } else {
+            JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+            return false;
+        }
+
+        MOZ_ASSERT(wordVal.isString());
+
+        // 5.b. Append the result string to result.
+        if (!DefineElement(cx, result, i, wordVal))
+            return false;
+    }
+
+    // 6. Return result.
+    args.rval().setObject(*result);
+    return true;
+}
+
 /******************** Intl ********************/
 
 const Class js::IntlClass = {
     js_Object_str,
     JSCLASS_HAS_CACHED_PROTO(JSProto_Intl)
 };
 
 #if JS_HAS_TOSOURCE
--- a/js/src/builtin/Intl.h
+++ b/js/src/builtin/Intl.h
@@ -384,28 +384,82 @@ intl_FormatDateTime(JSContext* cx, unsig
  *     considered the end of a weekend, e.g. 1 for en-US, 1 for en-GB,
  *     1 for bn-IN (note that "weekend" is *not* necessarily two days)
  *
  * NOTE: "calendar" and "locale" properties are *not* added to the object.
  */
 extern MOZ_MUST_USE bool
 intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp);
 
+/**
+ * Returns an Array with CLDR-based fields display names.
+ * The function takes three arguments:
+ *
+ *   locale
+ *     BCP47 compliant locale string
+ *   style
+ *     A string with values: long or short or narrow
+ *   keys
+ *     An array or path-like strings that identify keys to be returned
+ *     At the moment the following types of keys are supported:
+ *
+ *       'dates/fields/{year|month|week|day}'
+ *       'dates/gregorian/months/{january|...|december}'
+ *       'dates/gregorian/weekdays/{sunday|...|saturday}'
+ *       'dates/gregorian/dayperiods/{am|pm}'
+ *
+ * Example:
+ *
+ * let info = intl_ComputeDisplayNames(
+ *   'en-US',
+ *   'long',
+ *   [
+ *     'dates/fields/year',
+ *     'dates/gregorian/months/january',
+ *     'dates/gregorian/weekdays/monday',
+ *     'dates/gregorian/dayperiods/am',
+ *   ]
+ * );
+ *
+ * Returned value:
+ *
+ * [
+ *   'year',
+ *   'January',
+ *   'Monday',
+ *   'AM'
+ * ]
+ */
+extern MOZ_MUST_USE bool
+intl_ComputeDisplayNames(JSContext* cx, unsigned argc, Value* vp);
+
 #if ENABLE_INTL_API
 /**
  * Cast char16_t* strings to UChar* strings used by ICU.
  */
 inline const UChar*
 Char16ToUChar(const char16_t* chars)
 {
   return reinterpret_cast<const UChar*>(chars);
 }
 
 inline UChar*
 Char16ToUChar(char16_t* chars)
 {
   return reinterpret_cast<UChar*>(chars);
 }
+
+inline char16_t*
+UCharToChar16(UChar* chars)
+{
+  return reinterpret_cast<char16_t*>(chars);
+}
+
+inline const char16_t*
+UCharToChar16(const UChar* chars)
+{
+  return reinterpret_cast<const char16_t*>(chars);
+}
 #endif // ENABLE_INTL_API
 
 } // namespace js
 
 #endif /* builtin_Intl_h */
--- a/js/src/builtin/Intl.js
+++ b/js/src/builtin/Intl.js
@@ -4,17 +4,18 @@
 
 /* Portions Copyright Norbert Lindenberg 2011-2012. */
 
 /*global JSMSG_INTL_OBJECT_NOT_INITED: false, JSMSG_INVALID_LOCALES_ELEMENT: false,
          JSMSG_INVALID_LANGUAGE_TAG: false, JSMSG_INVALID_LOCALE_MATCHER: false,
          JSMSG_INVALID_OPTION_VALUE: false, JSMSG_INVALID_DIGITS_VALUE: false,
          JSMSG_INTL_OBJECT_REINITED: false, JSMSG_INVALID_CURRENCY_CODE: false,
          JSMSG_UNDEFINED_CURRENCY: false, JSMSG_INVALID_TIME_ZONE: false,
-         JSMSG_DATE_NOT_FINITE: false,
+         JSMSG_DATE_NOT_FINITE: false, JSMSG_INVALID_KEYS_TYPE: false,
+         JSMSG_INVALID_KEY: false,
          intl_Collator_availableLocales: false,
          intl_availableCollations: false,
          intl_CompareStrings: false,
          intl_NumberFormat_availableLocales: false,
          intl_numberingSystem: false,
          intl_FormatNumber: false,
          intl_DateTimeFormat_availableLocales: false,
          intl_availableCalendars: false,
@@ -2999,8 +3000,131 @@ function Intl_getCalendarInfo(locales) {
                           localeData);
 
   const result = intl_GetCalendarInfo(r.locale);
   result.calendar = r.ca;
   result.locale = r.locale;
 
   return result;
 }
+
+/**
+ * This function is a custom method designed after Intl API, but currently
+ * not part of the spec or spec proposal.
+ * We want to use it internally to retrieve translated values from CLDR in
+ * order to ensure they're aligned with what Intl API returns.
+ *
+ * This API may one day be a foundation for an ECMA402 API spec proposal.
+ *
+ * The function takes two arguments - locales which is a list of locale strings
+ * and options which is an object with two optional properties:
+ *
+ *   keys:
+ *     an Array of string values that are paths to individual terms
+ *
+ *   style:
+ *     a String with a value "long", "short" or "narrow"
+ *
+ * It returns an object with properties:
+ *
+ *   locale:
+ *     a negotiated locale string
+ *
+ *   style:
+ *     negotiated style
+ *
+ *   values:
+ *     A key-value pair list of requested keys and corresponding
+ *     translated values
+ *
+ */
+function Intl_getDisplayNames(locales, options) {
+    // 1. Let requestLocales be ? CanonicalizeLocaleList(locales).
+    const requestedLocales = CanonicalizeLocaleList(locales);
+
+    // 2. If options is undefined, then
+    if (options === undefined)
+        // a. Let options be ObjectCreate(%ObjectPrototype%).
+        options = {};
+    // 3. Else,
+    else
+        // a. Let options be ? ToObject(options).
+        options = ToObject(options);
+
+    const DateTimeFormat = dateTimeFormatInternalProperties;
+
+    // 4. Let localeData be %DateTimeFormat%.[[localeData]].
+    const localeData = DateTimeFormat.localeData;
+
+    // 5. Let opt be a new Record.
+    const localeOpt = new Record();
+    // 6. Set localeOpt.[[localeMatcher]] to "best fit".
+    localeOpt.localeMatcher = "best fit";
+
+    // 7. Let r be ResolveLocale(%DateTimeFormat%.[[availableLocales]], requestedLocales, localeOpt,
+    //    %DateTimeFormat%.[[relevantExtensionKeys]], localeData).
+    const r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat),
+                          requestedLocales,
+                          localeOpt,
+                          DateTimeFormat.relevantExtensionKeys,
+                          localeData);
+
+    // 8. Let style be ? GetOption(options, "style", "string", « "long", "short", "narrow" », "long").
+    const style = GetOption(options, "style", "string", ["long", "short", "narrow"], "long");
+    // 9. Let keys be ? Get(options, "keys").
+    let keys = options.keys;
+
+    // 10. If keys is undefined,
+    if (keys === undefined) {
+        // a. Let keys be ArrayCreate(0).
+        keys = [];
+    } else if (!IsObject(keys)) {
+        // 11. Else,
+        //   a. If Type(keys) is not Object, throw a TypeError exception.
+        ThrowTypeError(JSMSG_INVALID_KEYS_TYPE);
+    }
+
+    // 12. Let processedKeys be ArrayCreate(0).
+    // (This really should be a List, but we use an Array here in order that
+    // |intl_ComputeDisplayNames| may infallibly access the list's length via
+    // |ArrayObject::length|.)
+    let processedKeys = [];
+    // 13. Let len be ? ToLength(? Get(keys, "length")).
+    let len = ToLength(keys.length);
+    // 14. Let i be 0.
+    // 15. Repeat, while i < len
+    for (let i = 0; i < len; i++) {
+        // a. Let processedKey be ? ToString(? Get(keys, i)).
+        // b. Perform ? CreateDataPropertyOrThrow(processedKeys, i, processedKey).
+        callFunction(std_Array_push, processedKeys, ToString(keys[i]));
+    }
+
+    // 16. Let names be ? ComputeDisplayNames(r.[[locale]], style, processedKeys).
+    const names = intl_ComputeDisplayNames(r.locale, style, processedKeys);
+
+    // 17. Let values be ObjectCreate(%ObjectPrototype%).
+    const values = {};
+
+    // 18. Set i to 0.
+    // 19. Repeat, while i < len
+    for (let i = 0; i < len; i++) {
+        // a. Let key be ? Get(processedKeys, i).
+        const key = processedKeys[i];
+        // b. Let name be ? Get(names, i).
+        const name = names[i];
+        // c. Assert: Type(name) is string.
+        assert(typeof name === "string", "unexpected non-string value");
+        // d. Assert: the length of name is greater than zero.
+        assert(name.length > 0, "empty string value");
+        // e. Perform ? DefinePropertyOrThrow(values, key, name).
+        _DefineDataProperty(values, key, name);
+    }
+
+    // 20. Let options be ObjectCreate(%ObjectPrototype%).
+    // 21. Perform ! DefinePropertyOrThrow(result, "locale", r.[[locale]]).
+    // 22. Perform ! DefinePropertyOrThrow(result, "style", style).
+    // 23. Perform ! DefinePropertyOrThrow(result, "values", values).
+    const result = { locale: r.locale, style, values };
+
+    // 24. Return result.
+    return result;
+}
+
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -472,16 +472,18 @@ MSG_DEF(JSMSG_TRACELOGGER_ENABLE_FAIL, 1
 
 // Intl
 MSG_DEF(JSMSG_DATE_NOT_FINITE,         0, JSEXN_RANGEERR, "date value is not finite in DateTimeFormat.format()")
 MSG_DEF(JSMSG_INTERNAL_INTL_ERROR,     0, JSEXN_ERR, "internal error while computing Intl data")
 MSG_DEF(JSMSG_INTL_OBJECT_NOT_INITED,  3, JSEXN_TYPEERR, "Intl.{0}.prototype.{1} called on value that's not an object initialized as a {2}")
 MSG_DEF(JSMSG_INTL_OBJECT_REINITED,    0, JSEXN_TYPEERR, "can't initialize object twice as an object of an Intl constructor")
 MSG_DEF(JSMSG_INVALID_CURRENCY_CODE,   1, JSEXN_RANGEERR, "invalid currency code in NumberFormat(): {0}")
 MSG_DEF(JSMSG_INVALID_DIGITS_VALUE,    1, JSEXN_RANGEERR, "invalid digits value: {0}")
+MSG_DEF(JSMSG_INVALID_KEYS_TYPE,       0, JSEXN_TYPEERR, "calendar info keys must be an object or undefined")
+MSG_DEF(JSMSG_INVALID_KEY,             1, JSEXN_RANGEERR, "invalid key: {0}")
 MSG_DEF(JSMSG_INVALID_LANGUAGE_TAG,    1, JSEXN_RANGEERR, "invalid language tag: {0}")
 MSG_DEF(JSMSG_INVALID_LOCALES_ELEMENT, 0, JSEXN_TYPEERR, "invalid element in locales argument")
 MSG_DEF(JSMSG_INVALID_LOCALE_MATCHER,  1, JSEXN_RANGEERR, "invalid locale matcher in supportedLocalesOf(): {0}")
 MSG_DEF(JSMSG_INVALID_OPTION_VALUE,    2, JSEXN_RANGEERR, "invalid value {1} for option {0}")
 MSG_DEF(JSMSG_INVALID_TIME_ZONE,       1, JSEXN_RANGEERR, "invalid time zone in DateTimeFormat(): {0}")
 MSG_DEF(JSMSG_UNDEFINED_CURRENCY,      0, JSEXN_TYPEERR, "undefined currency in NumberFormat() with currency style")
 
 // RegExp
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -902,16 +902,17 @@ AddIntlExtras(JSContext* cx, unsigned ar
     if (!args.get(0).isObject()) {
         JS_ReportErrorASCII(cx, "addIntlExtras must be passed an object");
         return false;
     }
     JS::RootedObject intl(cx, &args[0].toObject());
 
     static const JSFunctionSpec funcs[] = {
         JS_SELF_HOSTED_FN("getCalendarInfo", "Intl_getCalendarInfo", 1, 0),
+        JS_SELF_HOSTED_FN("getDisplayNames", "Intl_getDisplayNames", 2, 0),
         JS_FS_END
     };
 
     if (!JS_DefineFunctions(cx, intl, funcs))
         return false;
 
     args.rval().setUndefined();
     return true;
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/getDisplayNames.js
@@ -0,0 +1,238 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl')||!this.hasOwnProperty('addIntlExtras'))
+/* 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/. */
+
+// Tests the getCalendarInfo function with a diverse set of arguments.
+
+/*
+ * Test if getDisplayNames return value matches expected values.
+ */
+function checkDisplayNames(names, expected)
+{
+  assertEq(Object.getPrototypeOf(names), Object.prototype);
+
+  assertEq(names.locale, expected.locale);
+  assertEq(names.style, expected.style);
+
+  const nameValues = names.values;
+  const expectedValues = expected.values;
+
+  const nameValuesKeys = Object.getOwnPropertyNames(nameValues).sort();
+  const expectedValuesKeys = Object.getOwnPropertyNames(expectedValues).sort();
+
+  assertEqArray(nameValuesKeys, expectedValuesKeys);
+
+  for (let key of expectedValuesKeys)
+    assertEq(nameValues[key], expectedValues[key]);
+}
+
+addIntlExtras(Intl);
+
+let gDN = Intl.getDisplayNames;
+
+assertEq(gDN.length, 2);
+
+checkDisplayNames(gDN('en-US', {
+}), {
+  locale: 'en-US',
+  style: 'long',
+  values: {}
+});
+
+checkDisplayNames(gDN('en-US', {
+  keys: [
+    'dates/gregorian/weekdays/wednesday'
+  ],
+  style: 'narrow'
+}), {
+  locale: 'en-US',
+  style: 'narrow',
+  values: {
+    'dates/gregorian/weekdays/wednesday': 'W'
+  }
+});
+
+checkDisplayNames(gDN('en-US', {
+  keys: [
+    'dates/fields/year',
+    'dates/fields/month',
+    'dates/fields/week',
+    'dates/fields/day',
+    'dates/gregorian/months/january',
+    'dates/gregorian/months/february',
+    'dates/gregorian/months/march',
+    'dates/gregorian/weekdays/tuesday'
+  ]
+}), {
+  locale: 'en-US',
+  style: 'long',
+  values: {
+    'dates/fields/year': 'year',
+    'dates/fields/month': 'month',
+    'dates/fields/week': 'week',
+    'dates/fields/day': 'day',
+    'dates/gregorian/months/january': 'January',
+    'dates/gregorian/months/february': 'February',
+    'dates/gregorian/months/march': 'March',
+    'dates/gregorian/weekdays/tuesday': 'Tuesday',
+  }
+});
+
+checkDisplayNames(gDN('fr', {
+  keys: [
+    'dates/fields/year',
+    'dates/fields/day',
+    'dates/gregorian/months/october',
+    'dates/gregorian/weekdays/saturday',
+    'dates/gregorian/dayperiods/pm'
+  ]
+}), {
+  locale: 'fr',
+  style: 'long',
+  values: {
+    'dates/fields/year': 'année',
+    'dates/fields/day': 'jour',
+    'dates/gregorian/months/october': 'octobre',
+    'dates/gregorian/weekdays/saturday': 'samedi',
+    'dates/gregorian/dayperiods/pm': 'PM'
+  }
+});
+
+checkDisplayNames(gDN('it', {
+  style: 'short',
+  keys: [
+    'dates/gregorian/weekdays/thursday',
+    'dates/gregorian/months/august',
+    'dates/gregorian/dayperiods/am',
+    'dates/fields/month',
+  ]
+}), {
+  locale: 'it',
+  style: 'short',
+  values: {
+    'dates/gregorian/weekdays/thursday': 'gio',
+    'dates/gregorian/months/august': 'ago',
+    'dates/gregorian/dayperiods/am': 'AM',
+    'dates/fields/month': 'mese'
+  }
+});
+
+checkDisplayNames(gDN('ar', {
+  style: 'long',
+  keys: [
+    'dates/gregorian/weekdays/thursday',
+    'dates/gregorian/months/august',
+    'dates/gregorian/dayperiods/am',
+    'dates/fields/month',
+  ]
+}), {
+  locale: 'ar',
+  style: 'long',
+  values: {
+    'dates/gregorian/weekdays/thursday': 'الخميس',
+    'dates/gregorian/months/august': 'أغسطس',
+    'dates/gregorian/dayperiods/am': 'ص',
+    'dates/fields/month': 'الشهر'
+  }
+});
+
+/* Invalid input */
+
+assertThrowsInstanceOf(() => {
+  gDN('en-US', {
+    style: '',
+    keys: [
+      'dates/gregorian/weekdays/thursday',
+    ]
+  });
+}, RangeError);
+
+assertThrowsInstanceOf(() => {
+  gDN('en-US', {
+    style: 'bogus',
+    keys: [
+      'dates/gregorian/weekdays/thursday',
+    ]
+  });
+}, RangeError);
+
+assertThrowsInstanceOf(() => {
+  gDN('foo-X', {
+    keys: [
+      'dates/gregorian/weekdays/thursday',
+    ]
+  });
+}, RangeError);
+
+const typeErrorKeys = [
+  null,
+  'string',
+  Symbol.iterator,
+  15,
+  1,
+  3.7,
+  NaN,
+  Infinity
+];
+
+for (let keys of typeErrorKeys) {
+  assertThrowsInstanceOf(() => {
+    gDN('en-US', {
+      keys
+    });
+  }, TypeError);
+}
+
+const rangeErrorKeys = [
+  [''],
+  ['foo'],
+  ['dates/foo'],
+  ['/dates/foo'],
+  ['dates/foo/foo'],
+  ['dates/fields'],
+  ['dates/fields/'],
+  ['dates/fields/foo'],
+  ['dates/fields/foo/month'],
+  ['/dates/foo/faa/bar/baz'],
+  ['dates///bar/baz'],
+  ['dates/gregorian'],
+  ['dates/gregorian/'],
+  ['dates/gregorian/foo'],
+  ['dates/gregorian/months'],
+  ['dates/gregorian/months/foo'],
+  ['dates/gregorian/weekdays'],
+  ['dates/gregorian/weekdays/foo'],
+  ['dates/gregorian/dayperiods'],
+  ['dates/gregorian/dayperiods/foo'],
+  ['dates/gregorian/months/الشهر'],
+  [3],
+  [null],
+  ['d', 'a', 't', 'e', 's'],
+  ['datesEXTRA'],
+  ['dates/fieldsEXTRA'],
+  ['dates/gregorianEXTRA'],
+  ['dates/gregorian/monthsEXTRA'],
+  ['dates/gregorian/weekdaysEXTRA'],
+  ['dates/fields/dayperiods/amEXTRA'],
+  ['dates/gregori\u1161n/months/january'],
+  ["dates/fields/year/"],
+  ["dates/fields/month/"],
+  ["dates/fields/week/"],
+  ["dates/fields/day/"],
+  ["dates/gregorian/months/january/"],
+  ["dates/gregorian/weekdays/saturday/"],
+  ["dates/gregorian/dayperiods/am/"],
+  ["dates/fields/months/january/"],
+];
+
+for (let keys of rangeErrorKeys) {
+  assertThrowsInstanceOf(() => {
+    gDN('en-US', {
+      keys
+    });
+  }, RangeError);
+}
+
+if (typeof reportCompare === 'function')
+    reportCompare(0, 0);
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2472,16 +2472,17 @@ static const JSFunctionSpec intrinsic_fu
     JS_FN("intl_CompareStrings", intl_CompareStrings, 3,0),
     JS_FN("intl_DateTimeFormat", intl_DateTimeFormat, 2,0),
     JS_FN("intl_DateTimeFormat_availableLocales", intl_DateTimeFormat_availableLocales, 0,0),
     JS_FN("intl_defaultTimeZone", intl_defaultTimeZone, 0,0),
     JS_FN("intl_defaultTimeZoneOffset", intl_defaultTimeZoneOffset, 0,0),
     JS_FN("intl_FormatDateTime", intl_FormatDateTime, 2,0),
     JS_FN("intl_FormatNumber", intl_FormatNumber, 2,0),
     JS_FN("intl_GetCalendarInfo", intl_GetCalendarInfo, 1,0),
+    JS_FN("intl_ComputeDisplayNames", intl_ComputeDisplayNames, 3,0),
     JS_FN("intl_IsValidTimeZoneName", intl_IsValidTimeZoneName, 1,0),
     JS_FN("intl_NumberFormat", intl_NumberFormat, 2,0),
     JS_FN("intl_NumberFormat_availableLocales", intl_NumberFormat_availableLocales, 0,0),
     JS_FN("intl_numberingSystem", intl_numberingSystem, 1,0),
     JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 2,0),
 
     JS_INLINABLE_FN("IsRegExpObject",
                     intrinsic_IsInstanceOfBuiltin<RegExpObject>, 1,0,
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -339,17 +339,17 @@ public:
     }
     return mFrame->StyleColor()->
       CalcComplexColor(mFrame->StyleText()->mWebkitTextStrokeColor);
   }
   float GetWebkitTextStrokeWidth() {
     if (mFrame->IsSVGText()) {
       return 0.0f;
     }
-    nscoord coord = mFrame->StyleText()->mWebkitTextStrokeWidth.GetCoordValue();
+    nscoord coord = mFrame->StyleText()->mWebkitTextStrokeWidth;
     return mFrame->PresContext()->AppUnitsToFloatDevPixels(coord);
   }
 
   /**
    * Compute the colors for normally-selected text. Returns false if
    * the normal selection is not being displayed.
    */
   bool GetSelectionColors(nscolor* aForeColor,
@@ -5696,17 +5696,17 @@ nsTextFrame::UnionAdditionalOverflow(nsP
                     : nsRect(0, topOrLeft, measure, bottomOrRight - topOrLeft));
     }
 
     aVisualOverflowRect->UnionRect(*aVisualOverflowRect,
                                    UpdateTextEmphasis(parentWM, aProvider));
   }
 
   // text-stroke overflows
-  nscoord textStrokeWidth = StyleText()->mWebkitTextStrokeWidth.GetCoordValue();
+  nscoord textStrokeWidth = StyleText()->mWebkitTextStrokeWidth;
   if (textStrokeWidth > 0) {
     nsRect strokeRect = *aVisualOverflowRect;
     strokeRect.x -= textStrokeWidth;
     strokeRect.y -= textStrokeWidth;
     strokeRect.width += textStrokeWidth;
     strokeRect.height += textStrokeWidth;
     aVisualOverflowRect->UnionRect(*aVisualOverflowRect, strokeRect);
   }
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -3189,17 +3189,17 @@ CSS_PROP_OUTLINE(
     outline_width,
     OutlineWidth,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_VALUE_NONNEGATIVE,
     "",
     VARIANT_HKL | VARIANT_CALC,
     kBorderWidthKTable,
     offsetof(nsStyleOutline, mOutlineWidth),
-    eStyleAnimType_Coord)
+    eStyleAnimType_nscoord)
 CSS_PROP_SHORTHAND(
     overflow,
     overflow,
     Overflow,
     CSS_PROPERTY_PARSE_FUNCTION,
     "")
 CSS_PROP_DISPLAY(
     overflow-clip-box,
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -4059,17 +4059,17 @@ nsComputedDOMStyle::DoGetWebkitTextStrok
   SetValueFromComplexColor(val, StyleText()->mWebkitTextStrokeColor);
   return val.forget();
 }
 
 already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetWebkitTextStrokeWidth()
 {
   RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
-  val->SetAppUnits(StyleText()->mWebkitTextStrokeWidth.GetCoordValue());
+  val->SetAppUnits(StyleText()->mWebkitTextStrokeWidth);
   return val.forget();
 }
 
 already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetPointerEvents()
 {
   RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
   val->SetIdent(
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -13,16 +13,17 @@
 #include <functional>
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // for PlaybackDirection
 #include "mozilla/Likely.h"
 #include "mozilla/LookAndFeel.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/OperatorNewExtensions.h"
 #include "mozilla/Unused.h"
 
 #include "mozilla/css/Declaration.h"
 
 #include "nsAlgorithm.h" // for clamped()
 #include "nsRuleNode.h"
 #include "nscore.h"
@@ -1151,16 +1152,56 @@ SetComplexColor(const nsCSSValue& aValue
                   nullptr, aResult.mColor, aConditions)) {
       MOZ_ASSERT_UNREACHABLE("Unknown color value");
       return;
     }
     aResult.mForegroundRatio = 0;
   }
 }
 
+template<UnsetAction UnsetTo>
+static Maybe<nscoord>
+ComputeLineWidthValue(const nsCSSValue& aValue,
+                      const nscoord aParentCoord,
+                      const nscoord aInitialCoord,
+                      nsStyleContext* aStyleContext,
+                      nsPresContext* aPresContext,
+                      RuleNodeCacheConditions& aConditions)
+{
+  nsCSSUnit unit = aValue.GetUnit();
+  if (unit == eCSSUnit_Initial ||
+      (UnsetTo == eUnsetInitial && unit == eCSSUnit_Unset)) {
+    return Some(aInitialCoord);
+  } else if (unit == eCSSUnit_Inherit ||
+             (UnsetTo == eUnsetInherit && unit == eCSSUnit_Unset)) {
+    aConditions.SetUncacheable();
+    return Some(aParentCoord);
+  } else if (unit == eCSSUnit_Enumerated) {
+    NS_ASSERTION(aValue.GetIntValue() == NS_STYLE_BORDER_WIDTH_THIN ||
+                 aValue.GetIntValue() == NS_STYLE_BORDER_WIDTH_MEDIUM ||
+                 aValue.GetIntValue() == NS_STYLE_BORDER_WIDTH_THICK,
+                 "Unexpected line-width keyword!");
+    return Some(nsPresContext::GetBorderWidthForKeyword(aValue.GetIntValue()));
+  } else if (aValue.IsLengthUnit() ||
+             aValue.IsCalcUnit()) {
+    nscoord len =
+      CalcLength(aValue, aStyleContext, aPresContext, aConditions);
+    if (len < 0) {
+      NS_ASSERTION(aValue.IsCalcUnit(),
+                   "Parser should have rejected negative length!");
+      len = 0;
+    }
+    return Some(len);
+  } else {
+    NS_ASSERTION(unit == eCSSUnit_Null,
+                 "Missing case handling for line-width computing!");
+    return Maybe<nscoord>(Nothing());
+  }
+}
+
 static void SetGradientCoord(const nsCSSValue& aValue, nsPresContext* aPresContext,
                              nsStyleContext* aContext, nsStyleCoord& aResult,
                              RuleNodeCacheConditions& aConditions)
 {
   // OK to pass bad aParentCoord since we're not passing SETCOORD_INHERIT
   if (!SetCoord(aValue, aResult, nsStyleCoord(),
                 SETCOORD_LPO | SETCOORD_BOX_POSITION | SETCOORD_STORE_CALC,
                 aContext, aPresContext, aConditions)) {
@@ -4919,32 +4960,23 @@ nsRuleNode::ComputeTextData(void* aStart
   setComplexColor(aRuleData->ValueForWebkitTextFillColor(),
                   &nsStyleText::mWebkitTextFillColor);
 
   // -webkit-text-stroke-color: color, string, inherit, initial
   setComplexColor(aRuleData->ValueForWebkitTextStrokeColor(),
                   &nsStyleText::mWebkitTextStrokeColor);
 
   // -webkit-text-stroke-width: length, inherit, initial, enum
-  const nsCSSValue*
-    webkitTextStrokeWidthValue = aRuleData->ValueForWebkitTextStrokeWidth();
-  if (webkitTextStrokeWidthValue->GetUnit() == eCSSUnit_Enumerated) {
-    NS_ASSERTION(webkitTextStrokeWidthValue->GetIntValue() == NS_STYLE_BORDER_WIDTH_THIN ||
-                 webkitTextStrokeWidthValue->GetIntValue() == NS_STYLE_BORDER_WIDTH_MEDIUM ||
-                 webkitTextStrokeWidthValue->GetIntValue() == NS_STYLE_BORDER_WIDTH_THICK,
-                 "Unexpected enum value");
-    text->mWebkitTextStrokeWidth.SetCoordValue(
-      nsPresContext::GetBorderWidthForKeyword(webkitTextStrokeWidthValue->GetIntValue()));
-  } else {
-    SetCoord(*webkitTextStrokeWidthValue, text->mWebkitTextStrokeWidth,
-             parentText->mWebkitTextStrokeWidth,
-             SETCOORD_LH | SETCOORD_CALC_LENGTH_ONLY |
-               SETCOORD_CALC_CLAMP_NONNEGATIVE |
-               SETCOORD_INITIAL_ZERO | SETCOORD_UNSET_INHERIT,
-             aContext, mPresContext, conditions);
+  Maybe<nscoord> coord =
+    ComputeLineWidthValue<eUnsetInherit>(
+      *aRuleData->ValueForWebkitTextStrokeWidth(),
+      parentText->mWebkitTextStrokeWidth, 0,
+      aContext, mPresContext, conditions);
+  if (coord.isSome()) {
+    text->mWebkitTextStrokeWidth = *coord;
   }
 
   // -moz-control-character-visibility: enum, inherit, initial
   SetValue(*aRuleData->ValueForControlCharacterVisibility(),
            text->mControlCharacterVisibility,
            conditions,
            SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
            parentText->mControlCharacterVisibility,
@@ -7526,45 +7558,23 @@ nsRuleNode::ComputeBorderData(void* aSta
       NS_ASSERTION(eCSSUnit_Percent != value.GetUnit(),
                    "Percentage borders not implemented yet "
                    "If implementing, make sure to fix all consumers of "
                    "nsStyleBorder, the IsPercentageAwareChild method, "
                    "the nsAbsoluteContainingBlock::FrameDependsOnContainer "
                    "method, the "
                    "nsLineLayout::IsPercentageAwareReplacedElement method "
                    "and probably some other places");
-      if (eCSSUnit_Enumerated == value.GetUnit()) {
-        NS_ASSERTION(value.GetIntValue() == NS_STYLE_BORDER_WIDTH_THIN ||
-                     value.GetIntValue() == NS_STYLE_BORDER_WIDTH_MEDIUM ||
-                     value.GetIntValue() == NS_STYLE_BORDER_WIDTH_THICK,
-                     "Unexpected enum value");
-        border->SetBorderWidth(side,
-          nsPresContext::GetBorderWidthForKeyword(value.GetIntValue()));
-      }
-      // OK to pass bad aParentCoord since we're not passing SETCOORD_INHERIT
-      else if (SetCoord(value, coord, nsStyleCoord(),
-                        SETCOORD_LENGTH | SETCOORD_CALC_LENGTH_ONLY,
-                        aContext, mPresContext, conditions)) {
-        NS_ASSERTION(coord.GetUnit() == eStyleUnit_Coord, "unexpected unit");
-        // clamp negative calc() to 0.
-        border->SetBorderWidth(side, std::max(coord.GetCoordValue(), 0));
-      }
-      else if (eCSSUnit_Inherit == value.GetUnit()) {
-        conditions.SetUncacheable();
-        border->SetBorderWidth(side,
-                               parentBorder->GetComputedBorder().Side(side));
-      }
-      else if (eCSSUnit_Initial == value.GetUnit() ||
-               eCSSUnit_Unset == value.GetUnit()) {
-        border->SetBorderWidth(side,
-          nsPresContext::GetBorderWidthForKeyword(NS_STYLE_BORDER_WIDTH_MEDIUM));
-      }
-      else {
-        NS_ASSERTION(eCSSUnit_Null == value.GetUnit(),
-                     "missing case handling border width");
+      Maybe<nscoord> coord =
+        ComputeLineWidthValue<eUnsetInitial>(
+          value, parentBorder->GetComputedBorder().Side(side),
+          nsPresContext::GetBorderWidthForKeyword(NS_STYLE_BORDER_WIDTH_MEDIUM),
+          aContext, mPresContext, conditions);
+      if (coord.isSome()) {
+        border->SetBorderWidth(side, *coord);
       }
     }
   }
 
   // border-style, border-*-style: enum, inherit
   {
     const nsCSSPropertyID* subprops =
       nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_style);
@@ -7814,27 +7824,23 @@ nsRuleNode::ComputeOutlineData(void* aSt
                                nsStyleContext* aContext,
                                nsRuleNode* aHighestNode,
                                const RuleDetail aRuleDetail,
                                const RuleNodeCacheConditions aConditions)
 {
   COMPUTE_START_RESET(Outline, outline, parentOutline)
 
   // outline-width: length, enum, inherit
-  const nsCSSValue* outlineWidthValue = aRuleData->ValueForOutlineWidth();
-  if (eCSSUnit_Initial == outlineWidthValue->GetUnit() ||
-      eCSSUnit_Unset == outlineWidthValue->GetUnit()) {
-    outline->mOutlineWidth =
-      nsStyleCoord(NS_STYLE_BORDER_WIDTH_MEDIUM, eStyleUnit_Enumerated);
-  }
-  else {
-    SetCoord(*outlineWidthValue, outline->mOutlineWidth,
-             parentOutline->mOutlineWidth,
-             SETCOORD_LEH | SETCOORD_CALC_LENGTH_ONLY, aContext,
-             mPresContext, conditions);
+  Maybe<nscoord> coord =
+    ComputeLineWidthValue<eUnsetInitial>(
+      *aRuleData->ValueForOutlineWidth(), parentOutline->mOutlineWidth,
+      nsPresContext::GetBorderWidthForKeyword(NS_STYLE_BORDER_WIDTH_MEDIUM),
+      aContext, mPresContext, conditions);
+  if (coord.isSome()) {
+    outline->mOutlineWidth = *coord;
   }
 
   // outline-offset: length, inherit
   nsStyleCoord tempCoord;
   const nsCSSValue* outlineOffsetValue = aRuleData->ValueForOutlineOffset();
   if (SetCoord(*outlineOffsetValue, tempCoord,
                nsStyleCoord(parentOutline->mOutlineOffset,
                             nsStyleCoord::CoordConstructor),
@@ -9152,46 +9158,24 @@ nsRuleNode::ComputeColumnData(void* aSta
     column->mColumnCount = std::min(column->mColumnCount,
                                     nsStyleColumn::kMaxColumnCount);
   } else if (eCSSUnit_Inherit == columnCountValue->GetUnit()) {
     conditions.SetUncacheable();
     column->mColumnCount = parent->mColumnCount;
   }
 
   // column-rule-width: length, enum, inherit
-  const nsCSSValue& widthValue = *aRuleData->ValueForColumnRuleWidth();
-  if (eCSSUnit_Initial == widthValue.GetUnit() ||
-      eCSSUnit_Unset == widthValue.GetUnit()) {
-    column->SetColumnRuleWidth(
-        nsPresContext::GetBorderWidthForKeyword(NS_STYLE_BORDER_WIDTH_MEDIUM));
-  }
-  else if (eCSSUnit_Enumerated == widthValue.GetUnit()) {
-    NS_ASSERTION(widthValue.GetIntValue() == NS_STYLE_BORDER_WIDTH_THIN ||
-                 widthValue.GetIntValue() == NS_STYLE_BORDER_WIDTH_MEDIUM ||
-                 widthValue.GetIntValue() == NS_STYLE_BORDER_WIDTH_THICK,
-                 "Unexpected enum value");
-    column->SetColumnRuleWidth(
-        nsPresContext::GetBorderWidthForKeyword(widthValue.GetIntValue()));
-  }
-  else if (eCSSUnit_Inherit == widthValue.GetUnit()) {
-    column->SetColumnRuleWidth(parent->GetComputedColumnRuleWidth());
-    conditions.SetUncacheable();
-  }
-  else if (widthValue.IsLengthUnit() || widthValue.IsCalcUnit()) {
-    nscoord len =
-      CalcLength(widthValue, aContext, mPresContext, conditions);
-    if (len < 0) {
-      // FIXME: This is untested (by test_value_storage.html) for
-      // column-rule-width since it gets covered up by the border
-      // rounding code.
-      NS_ASSERTION(widthValue.IsCalcUnit(),
-                   "parser should have rejected negative length");
-      len = 0;
-    }
-    column->SetColumnRuleWidth(len);
+  Maybe<nscoord> coord =
+    ComputeLineWidthValue<eUnsetInitial>(
+      *aRuleData->ValueForColumnRuleWidth(),
+      parent->GetComputedColumnRuleWidth(),
+      nsPresContext::GetBorderWidthForKeyword(NS_STYLE_BORDER_WIDTH_MEDIUM),
+      aContext, mPresContext, conditions);
+  if (coord.isSome()) {
+    column->SetColumnRuleWidth(*coord);
   }
 
   // column-rule-style: enum, inherit
   const nsCSSValue& styleValue = *aRuleData->ValueForColumnRuleStyle();
   MOZ_ASSERT(eCSSUnit_None != styleValue.GetUnit(),
              "'none' should be handled as enumerated value");
   if (eCSSUnit_Enumerated == styleValue.GetUnit()) {
     column->mColumnRuleStyle = styleValue.GetIntValue();
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -234,32 +234,16 @@ nsStyleFont::GetLanguage(StyleStructCont
     // NOTE this should not be used elsewhere, because we want websites
     // to use UTF-8 with proper language tag, instead of relying on
     // deriving language from charset. See bug 1040668 comment 67.
     language = aContext.GetLanguageFromCharset();
   }
   return language.forget();
 }
 
-static nscoord
-CalcCoord(const nsStyleCoord& aCoord, const nscoord* aEnumTable, int32_t aNumEnums)
-{
-  if (aCoord.GetUnit() == eStyleUnit_Enumerated) {
-    MOZ_ASSERT(aEnumTable, "must have enum table");
-    int32_t value = aCoord.GetIntValue();
-    if (0 <= value && value < aNumEnums) {
-      return aEnumTable[aCoord.GetIntValue()];
-    }
-    NS_NOTREACHED("unexpected enum value");
-    return 0;
-  }
-  MOZ_ASSERT(aCoord.ConvertsToLength(), "unexpected unit");
-  return nsRuleNode::ComputeCoordPercentCalc(aCoord, 0);
-}
-
 nsStyleMargin::nsStyleMargin(StyleStructContext aContext)
 {
   MOZ_COUNT_CTOR(nsStyleMargin);
   nsStyleCoord zero(0, nsStyleCoord::CoordConstructor);
   NS_FOR_CSS_SIDES(side) {
     mMargin.Set(side, zero);
   }
 }
@@ -542,17 +526,18 @@ nsStyleBorder::CalcDifference(const nsSt
   if (mBorder != aNewData.mBorder) {
     return nsChangeHint_NeutralChange;
   }
 
   return nsChangeHint(0);
 }
 
 nsStyleOutline::nsStyleOutline(StyleStructContext aContext)
-  : mOutlineWidth(NS_STYLE_BORDER_WIDTH_MEDIUM, eStyleUnit_Enumerated)
+  : mOutlineWidth((StaticPresData::Get()
+                     ->GetBorderWidthTable())[NS_STYLE_BORDER_WIDTH_MEDIUM])
   , mOutlineOffset(0)
   , mOutlineColor(StyleComplexColor::CurrentColor())
   , mOutlineStyle(NS_STYLE_BORDER_STYLE_NONE)
   , mActualOutlineWidth(0)
   , mTwipsPerPixel(aContext.DevPixelsToAppUnits(1))
 {
   MOZ_COUNT_CTOR(nsStyleOutline);
   // spacing values not inherited
@@ -575,24 +560,18 @@ nsStyleOutline::nsStyleOutline(const nsS
 }
 
 void
 nsStyleOutline::RecalcData()
 {
   if (NS_STYLE_BORDER_STYLE_NONE == mOutlineStyle) {
     mActualOutlineWidth = 0;
   } else {
-    MOZ_ASSERT(mOutlineWidth.ConvertsToLength() ||
-               mOutlineWidth.GetUnit() == eStyleUnit_Enumerated);
-    // Clamp negative calc() to 0.
     mActualOutlineWidth =
-      std::max(CalcCoord(mOutlineWidth,
-                         StaticPresData::Get()->GetBorderWidthTable(), 3), 0);
-    mActualOutlineWidth =
-      NS_ROUND_BORDER_TO_PIXELS(mActualOutlineWidth, mTwipsPerPixel);
+      NS_ROUND_BORDER_TO_PIXELS(mOutlineWidth, mTwipsPerPixel);
   }
 }
 
 nsChangeHint
 nsStyleOutline::CalcDifference(const nsStyleOutline& aNewData) const
 {
   if (mActualOutlineWidth != aNewData.mActualOutlineWidth ||
       (mActualOutlineWidth > 0 &&
@@ -3786,17 +3765,17 @@ nsStyleText::nsStyleText(StyleStructCont
   , mTextEmphasisColor(StyleComplexColor::CurrentColor())
   , mWebkitTextFillColor(StyleComplexColor::CurrentColor())
   , mWebkitTextStrokeColor(StyleComplexColor::CurrentColor())
   , mTabSize(float(NS_STYLE_TABSIZE_INITIAL), eStyleUnit_Factor)
   , mWordSpacing(0, nsStyleCoord::CoordConstructor)
   , mLetterSpacing(eStyleUnit_Normal)
   , mLineHeight(eStyleUnit_Normal)
   , mTextIndent(0, nsStyleCoord::CoordConstructor)
-  , mWebkitTextStrokeWidth(0, nsStyleCoord::CoordConstructor)
+  , mWebkitTextStrokeWidth(0)
   , mTextShadow(nullptr)
 {
   MOZ_COUNT_CTOR(nsStyleText);
   nsCOMPtr<nsIAtom> language = aContext.GetContentLanguage();
   mTextEmphasisPosition = language &&
     nsStyleUtil::MatchesLanguagePrefix(language, u"zh") ?
     NS_STYLE_TEXT_EMPHASIS_POSITION_DEFAULT_ZH :
     NS_STYLE_TEXT_EMPHASIS_POSITION_DEFAULT;
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -1481,17 +1481,17 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
 
   nsStyleCorners  mOutlineRadius; // [reset] coord, percent, calc
 
   // This is the specified value of outline-width, but with length values
   // computed to absolute.  mActualOutlineWidth stores the outline-width
   // value used by layout.  (We must store mOutlineWidth for the same
   // style struct resolution reasons that we do nsStyleBorder::mBorder;
   // see that field's comment.)
-  nsStyleCoord  mOutlineWidth;    // [reset] coord, enum (see nsStyleConsts.h)
+  nscoord       mOutlineWidth;    // [reset] coord, enum (see nsStyleConsts.h)
   nscoord       mOutlineOffset;   // [reset]
   mozilla::StyleComplexColor mOutlineColor; // [reset]
   uint8_t       mOutlineStyle;    // [reset] See nsStyleConsts.h
 
   nscoord GetOutlineWidth() const
   {
     return mActualOutlineWidth;
   }
@@ -2087,17 +2087,17 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
   mozilla::StyleComplexColor mWebkitTextFillColor;    // [inherited]
   mozilla::StyleComplexColor mWebkitTextStrokeColor;  // [inherited]
 
   nsStyleCoord mTabSize;                // [inherited] coord, factor, calc
   nsStyleCoord mWordSpacing;            // [inherited] coord, percent, calc
   nsStyleCoord mLetterSpacing;          // [inherited] coord, normal
   nsStyleCoord mLineHeight;             // [inherited] coord, factor, normal
   nsStyleCoord mTextIndent;             // [inherited] coord, percent, calc
-  nsStyleCoord mWebkitTextStrokeWidth;  // [inherited] coord
+  nscoord mWebkitTextStrokeWidth;       // [inherited] coord
 
   RefPtr<nsCSSShadowArray> mTextShadow; // [inherited] nullptr in case of a zero-length
 
   nsString mTextEmphasisStyleString;    // [inherited]
 
   bool WhiteSpaceIsSignificant() const {
     return mWhiteSpace == NS_STYLE_WHITESPACE_PRE ||
            mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP ||
@@ -2133,17 +2133,17 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
            mOverflowWrap == NS_STYLE_OVERFLOWWRAP_BREAK_WORD;
   }
 
   bool HasTextEmphasis() const {
     return !mTextEmphasisStyleString.IsEmpty();
   }
 
   bool HasWebkitTextStroke() const {
-    return mWebkitTextStrokeWidth.GetCoordValue() > 0;
+    return mWebkitTextStrokeWidth > 0;
   }
 
   // These are defined in nsStyleStructInlines.h.
   inline bool HasTextShadow() const;
   inline nsCSSShadowArray* GetTextShadow() const;
 
   // The aContextFrame argument on each of these is the frame this
   // style struct is for.  If the frame is for SVG text or inside ruby,
--- a/media/webrtc/signaling/test/mediaconduit_unittests.cpp
+++ b/media/webrtc/signaling/test/mediaconduit_unittests.cpp
@@ -545,24 +545,26 @@ class TransportConduitTest : public ::te
   //1. Dump audio samples to dummy external transport
   void TestDummyAudioAndTransport()
   {
     //get pointer to AudioSessionConduit
     int err=0;
     mozilla::SyncRunnable::DispatchToThread(gMainThread,
                                             WrapRunnableNMRet(&mAudioSession,
                                                 &mozilla::AudioSessionConduit::Create));
-    if( !mAudioSession )
+    if( !mAudioSession ) {
       ASSERT_NE(mAudioSession, (void*)nullptr);
+    }
 
     mozilla::SyncRunnable::DispatchToThread(gMainThread,
                                             WrapRunnableNMRet(&mAudioSession2,
                                                 &mozilla::AudioSessionConduit::Create));
-    if( !mAudioSession2 )
+    if( !mAudioSession2 ) {
       ASSERT_NE(mAudioSession2, (void*)nullptr);
+    }
 
     WebrtcMediaTransport* xport = new WebrtcMediaTransport();
     ASSERT_NE(xport, (void*)nullptr);
     xport->SetAudioSession(mAudioSession, mAudioSession2);
     mAudioTransport = xport;
 
     // attach the transport to audio-conduit
     err = mAudioSession->SetTransmitterTransport(mAudioTransport);
@@ -610,25 +612,27 @@ class TransportConduitTest : public ::te
   //2. Dump audio samples to dummy external transport
   void TestDummyVideoAndTransport(bool send_vp8 = true, const char *source_file = nullptr)
   {
     int err = 0;
     //get pointer to VideoSessionConduit
     mozilla::SyncRunnable::DispatchToThread(gMainThread,
                                             WrapRunnableNMRet(&mVideoSession,
                                                 &mozilla::VideoSessionConduit::Create));
-    if( !mVideoSession )
+    if( !mVideoSession ) {
       ASSERT_NE(mVideoSession, (void*)nullptr);
+   }
 
    // This session is for other one
     mozilla::SyncRunnable::DispatchToThread(gMainThread,
                                             WrapRunnableNMRet(&mVideoSession2,
                                                 &mozilla::VideoSessionConduit::Create));
-    if( !mVideoSession2 )
+    if( !mVideoSession2 ) {
       ASSERT_NE(mVideoSession2,(void*)nullptr);
+    }
 
     if (!send_vp8) {
       SetGmpCodecs();
     }
 
     mVideoRenderer = new DummyVideoTarget();
     ASSERT_NE(mVideoRenderer, (void*)nullptr);
 
@@ -711,18 +715,19 @@ class TransportConduitTest : public ::te
  void TestVideoConduitCodecAPI()
   {
     int err = 0;
     RefPtr<mozilla::VideoSessionConduit> videoSession;
     //get pointer to VideoSessionConduit
     mozilla::SyncRunnable::DispatchToThread(gMainThread,
                                             WrapRunnableNMRet(&videoSession,
                                                 &mozilla::VideoSessionConduit::Create));
-    if( !videoSession )
+    if( !videoSession ) {
       ASSERT_NE(videoSession, (void*)nullptr);
+    }
 
     //Test Configure Recv Codec APIS
     cerr << "   *************************************************" << endl;
     cerr << "    Test Receive Codec Configuration API Now " << endl;
     cerr << "   *************************************************" << endl;
 
     std::vector<mozilla::VideoCodecConfig* > rcvCodecList;
 
@@ -826,18 +831,19 @@ class TransportConduitTest : public ::te
                                    int *new_width, int *new_height)
   {
     int err = 0;
 
     // Get pointer to VideoSessionConduit.
     mozilla::SyncRunnable::DispatchToThread(gMainThread,
                                             WrapRunnableNMRet(&mVideoSession,
                                                 &mozilla::VideoSessionConduit::Create));
-    if( !mVideoSession )
+    if( !mVideoSession ) {
       ASSERT_NE(mVideoSession, (void*)nullptr);
+    }
 
     mozilla::EncodingConstraints constraints;
     constraints.maxFs = max_fs;
     // Configure send codecs on the conduit.
     mozilla::VideoCodecConfig cinst1(120, "VP8", constraints);
 
     err = mVideoSession->ConfigureSendMediaCodec(&cinst1);
     ASSERT_EQ(mozilla::kMediaConduitNoError, err);
--- a/media/webrtc/signaling/test/signaling_unittests.cpp
+++ b/media/webrtc/signaling/test/signaling_unittests.cpp
@@ -1470,18 +1470,18 @@ class SignalingAgent {
   }
 
   void AddIceCandidate(const std::string& candidate, const std::string& mid, unsigned short level,
                        bool expectSuccess) {
     PCImplSignalingState endState = signaling_state();
     pObserver->addIceCandidateState = TestObserver::stateNoResponse;
     pc->AddIceCandidate(candidate.c_str(), mid.c_str(), level);
     ASSERT_TRUE(pObserver->addIceCandidateState ==
-                expectSuccess ? TestObserver::stateSuccess :
-                                TestObserver::stateError
+                (expectSuccess ? TestObserver::stateSuccess :
+                                TestObserver::stateError)
                );
 
     // Verify that adding ICE candidates does not change the signaling state
     ASSERT_EQ(signaling_state(), endState);
     ASSERT_NE("", mid);
   }
 
   int GetPacketsReceived(const std::string& streamId) const
--- a/mobile/android/base/java/org/mozilla/gecko/DataReportingNotification.java
+++ b/mobile/android/base/java/org/mozilla/gecko/DataReportingNotification.java
@@ -56,21 +56,16 @@ public class DataReportingNotification {
             } else {
                 // Silently update the version.
                 SharedPreferences.Editor editor = dataPrefs.edit();
                 editor.putInt(PREFS_POLICY_VERSION, DATA_REPORTING_VERSION);
                 editor.apply();
             }
             return;
         }
-
-        if (currentVersion >= DATA_REPORTING_VERSION) {
-            // Do nothing, we're at a current (or future) version.
-            return;
-        }
     }
 
     /**
      * Launch a notification of the data policy, and record notification time and version.
      */
     public static void notifyDataPolicy(Context context, SharedPreferences sharedPrefs) {
         boolean result = false;
         try {
--- a/mobile/android/base/java/org/mozilla/gecko/push/Fetched.java
+++ b/mobile/android/base/java/org/mozilla/gecko/push/Fetched.java
@@ -28,17 +28,17 @@ public class Fetched {
 
     public static Fetched now(String value) {
         return new Fetched(value, System.currentTimeMillis());
     }
 
     public static @NonNull Fetched fromJSONObject(@NonNull JSONObject json) {
         final String value = json.optString("value", null);
         final String timestampString = json.optString("timestamp", null);
-        final long timestamp = timestampString != null ? Long.valueOf(timestampString) : 0L;
+        final long timestamp = timestampString != null ? Long.parseLong(timestampString) : 0L;
         return new Fetched(value, timestamp);
     }
 
     public JSONObject toJSONObject() throws JSONException {
         final JSONObject jsonObject = new JSONObject();
         if (value != null) {
             jsonObject.put("value", value);
         } else {
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -144,17 +144,19 @@
     <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_item_width">129dp</dimen>
     <dimen name="tab_panel_grid_hpadding">20dp</dimen>
-    <dimen name="tab_panel_grid_vpadding">19dp</dimen>
+    <!-- Top and bottom tab panel grid padding is split between tab_panel_grid_vpadding (the
+         RecyclerView padding) and tab_panel_grid_item_vpadding (individual item padding). -->
+    <dimen name="tab_panel_grid_vpadding">9dp</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>
--- a/mobile/android/config/tooltool-manifests/android-x86/releng.manifest
+++ b/mobile/android/config/tooltool-manifests/android-x86/releng.manifest
@@ -43,16 +43,24 @@
 "version": "gcc 4.8.5 + PR64905",
 "size": 80160264,
 "digest": "c1a9dc9da289b8528874d16300b9d13a997cec99195bb0bc46ff665216d8535d6d6cb5af6b4b1f2749af6815dab12e703fdb3849014e5c23a70eff351a0baf4e",
 "algorithm": "sha512",
 "filename": "gcc.tar.xz",
 "unpack": true
 },
 {
+"version": "rustc 1.14.0-beta.2 (e627a2e6e 2016-11-16) repack",
+"size": 75964276,
+"digest": "3a83a42330cdc42fbffcd91aa80f8e33749c716068047699fbd0ee5b5c51ec350f60285b34fd45d9bb038a5af9118e16ab66cbf0a7167eed5a6d239f50e32462",
+"algorithm": "sha512",
+"filename": "rustc.tar.xz",
+"unpack": true
+},
+{
 "algorithm": "sha512",
 "visibility": "public",
 "filename": "jcentral.tar.xz",
 "unpack": true,
 "digest": "28b36cc7a74babc9075aad2a11d3489242f76a90c3701b86fca91a215e3d31c2fcd89bd23a574af7b3f374c0ca3f247fd073ab2e68a2b26ea45e2c9a7494df0f",
 "size": 52463192
 },
 {
--- a/mobile/android/config/tooltool-manifests/android/releng.manifest
+++ b/mobile/android/config/tooltool-manifests/android/releng.manifest
@@ -69,18 +69,18 @@
 "visibility": "public",
 "filename": "gradle-dist.tar.xz",
 "unpack": true,
 "digest": "e3cfe7f8259ad97722243d4e873d5a05c014bfc24d637427f89d804bf5073290229c778ea303142cf06c2dc79e0492f23521f57d3a73825f55b8db587317646f",
 "size": 51753660
 },
 {
 "version": "rustc 1.14.0-beta.2 (e627a2e6e 2016-11-16) repack",
-"size": 96360828,
-"digest": "6f9f6c5fde5366fe7c67f9a7699b8c64c2e14940d57e9cf87026b61a9cbf4aabe0d35933c1365034980031164817fd51c3bc02d6449bd9e8f2225981b053e47e",
+"size": 75964276,
+"digest": "3a83a42330cdc42fbffcd91aa80f8e33749c716068047699fbd0ee5b5c51ec350f60285b34fd45d9bb038a5af9118e16ab66cbf0a7167eed5a6d239f50e32462",
 "algorithm": "sha512",
 "filename": "rustc.tar.xz",
 "unpack": true
 },
 {
 "algorithm": "sha512",
 "visibility": "public",
 "filename": "dotgradle.tar.xz",
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FormHistoryRepositorySession.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FormHistoryRepositorySession.java
@@ -612,20 +612,16 @@ public class FormHistoryRepositorySessio
               Logger.trace(LOG_TAG, "Remote is newer, and deleted. Purging local.");
               deleteExistingRecord(existingRecord);
               trackRecord(record);
               delegate.onRecordStoreSucceeded(record.guid);
               return;
             }
 
             Logger.trace(LOG_TAG, "Remote is older, local is not deleted. Ignoring.");
-            if (!locallyModified) {
-              Logger.warn(LOG_TAG, "Inconsistency: old remote record is deleted, but local record not modified!");
-              // Ensure that this is tracked for upload.
-            }
             return;
           }
           // End deletion logic.
 
           // Now we're processing a non-deleted incoming record.
           if (existingRecord == null) {
             Logger.trace(LOG_TAG, "Looking up match for record " + record.guid);
             existingRecord = findExistingRecordByPayload(record);
@@ -667,20 +663,16 @@ public class FormHistoryRepositorySessio
             Logger.trace(LOG_TAG, "Remote is newer, and not deleted. Storing.");
             replaceExistingRecordWithRegularRecord(record, existingRecord);
             trackRecord(record);
             delegate.onRecordStoreSucceeded(record.guid);
             return;
           }
 
           Logger.trace(LOG_TAG, "Remote is older, local is not deleted. Ignoring.");
-          if (!locallyModified) {
-            Logger.warn(LOG_TAG, "Inconsistency: old remote record is not deleted, but local record not modified!");
-          }
-          return;
         } catch (Exception e) {
           Logger.error(LOG_TAG, "Store failed for " + record.guid, e);
           delegate.onRecordStoreFailed(e, record.guid);
           return;
         }
       }
     };
 
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/PasswordsRepositorySession.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/PasswordsRepositorySession.java
@@ -311,20 +311,17 @@ public class PasswordsRepositorySession 
           trace("Both local and remote records have been modified.");
           if (remoteRecord.lastModified > existingRecord.lastModified) {
             trace("Remote is newer, and deleted. Deleting local.");
             storeRecordDeletion(remoteRecord);
             return;
           }
 
           trace("Remote is older, local is not deleted. Ignoring.");
-          if (!locallyModified) {
-            Logger.warn(LOG_TAG, "Inconsistency: old remote record is deleted, but local record not modified!");
-            // Ensure that this is tracked for upload.
-          }
+
           return;
         }
         // End deletion logic.
 
         // Validate the incoming record.
         if (!remoteRecord.isValid()) {
             Logger.warn(LOG_TAG, "Incoming record is invalid. Reporting store failed.");
             delegate.onRecordStoreFailed(new RuntimeException("Can't store invalid password record."), record.guid);
--- a/toolkit/components/mozintl/MozIntl.cpp
+++ b/toolkit/components/mozintl/MozIntl.cpp
@@ -39,16 +39,42 @@ MozIntl::AddGetCalendarInfo(JS::Handle<J
 
   if (!JS_DefineFunctions(cx, realIntlObj, funcs)) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+MozIntl::AddGetDisplayNames(JS::Handle<JS::Value> val, JSContext* cx)
+{
+  if (!val.isObject()) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  JS::Rooted<JSObject*> realIntlObj(cx, js::CheckedUnwrap(&val.toObject()));
+  if (!realIntlObj) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  JSAutoCompartment ac(cx, realIntlObj);
+
+  static const JSFunctionSpec funcs[] = {
+    JS_SELF_HOSTED_FN("getDisplayNames", "Intl_getDisplayNames", 2, 0),
+    JS_FS_END
+  };
+
+  if (!JS_DefineFunctions(cx, realIntlObj, funcs)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
 NS_GENERIC_FACTORY_CONSTRUCTOR(MozIntl)
 NS_DEFINE_NAMED_CID(MOZ_MOZINTL_CID);
 
 static const Module::CIDEntry kMozIntlCIDs[] = {
   { &kMOZ_MOZINTL_CID, false, nullptr, MozIntlConstructor },
   { nullptr }
 };
 
--- a/toolkit/components/mozintl/mozIMozIntl.idl
+++ b/toolkit/components/mozintl/mozIMozIntl.idl
@@ -4,9 +4,10 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 [scriptable, uuid(9f9bc42e-54f4-11e6-9aed-4b1429ac0ba0)]
 interface mozIMozIntl : nsISupports
 {
   [implicit_jscontext] void addGetCalendarInfo(in jsval intlObject);
+  [implicit_jscontext] void addGetDisplayNames(in jsval intlObject);
 };
--- a/toolkit/components/mozintl/test/test_mozintl.js
+++ b/toolkit/components/mozintl/test/test_mozintl.js
@@ -2,16 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function run_test() {
   const mozIntl = Components.classes["@mozilla.org/mozintl;1"]
                             .getService(Components.interfaces.mozIMozIntl);
 
   test_this_global(mozIntl);
   test_cross_global(mozIntl);
+  test_methods_presence(mozIntl);
 
   ok(true);
 }
 
 function test_this_global(mozIntl) {
   let x = {};
 
   mozIntl.addGetCalendarInfo(x);
@@ -25,8 +26,21 @@ function test_cross_global(mozIntl) {
 
   mozIntl.addGetCalendarInfo(x);
   var waivedX = Components.utils.waiveXrays(x);
   equal(waivedX.getCalendarInfo instanceof Function, false);
   equal(waivedX.getCalendarInfo instanceof global.Function, true);
   equal(waivedX.getCalendarInfo() instanceof Object, false);
   equal(waivedX.getCalendarInfo() instanceof global.Object, true);
 }
+
+function test_methods_presence(mozIntl) {
+  equal(mozIntl.addGetCalendarInfo instanceof Function, true);
+  equal(mozIntl.addGetDisplayNames instanceof Function, true);
+
+  let x = {};
+
+  mozIntl.addGetCalendarInfo(x);
+  equal(x.getCalendarInfo instanceof Function, true);
+
+  mozIntl.addGetDisplayNames(x);
+  equal(x.getDisplayNames instanceof Function, true);
+}