Merge m-c to f-t
authorPhil Ringnalda <philringnalda@gmail.com>
Thu, 27 Oct 2016 20:39:29 -0700
changeset 319840 71536044069576fc877bf7ef0ba13a4ada793706
parent 319708 9272c247ecf1fc24a72b4ef3181d95c12b2063d2 (current diff)
parent 319839 944cb0fd05526894fcd90fbe7d1e625ee53cd73d (diff)
child 320148 1b170b39ed6bdbde366233ab84594bdaaa960a5a
push id20748
push userphilringnalda@gmail.com
push dateFri, 28 Oct 2016 03:39:55 +0000
treeherderfx-team@715360440695 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone52.0a1
Merge m-c to f-t
browser/locales/searchjson.py
browser/locales/searchplugins.py
devtools/client/locales/en-US/promisedebugger.dtd
devtools/client/promisedebugger/moz.build
devtools/client/promisedebugger/promise-controller.js
devtools/client/promisedebugger/promise-debugger.xhtml
devtools/client/promisedebugger/promise-panel.js
devtools/client/promisedebugger/test/.eslintrc.js
devtools/client/promisedebugger/test/head.js
dom/ipc/nsIBrowser.idl
gfx/angle/src/compiler/translator/RenameFunction.h
gfx/angle/src/compiler/translator/depgraph/DependencyGraph.cpp
gfx/angle/src/compiler/translator/depgraph/DependencyGraph.h
gfx/angle/src/compiler/translator/depgraph/DependencyGraphBuilder.cpp
gfx/angle/src/compiler/translator/depgraph/DependencyGraphBuilder.h
gfx/angle/src/compiler/translator/depgraph/DependencyGraphOutput.cpp
gfx/angle/src/compiler/translator/depgraph/DependencyGraphOutput.h
gfx/angle/src/compiler/translator/depgraph/DependencyGraphTraverse.cpp
gfx/angle/src/compiler/translator/timing/RestrictFragmentShaderTiming.cpp
gfx/angle/src/compiler/translator/timing/RestrictFragmentShaderTiming.h
gfx/angle/src/compiler/translator/timing/RestrictVertexShaderTiming.cpp
gfx/angle/src/compiler/translator/timing/RestrictVertexShaderTiming.h
gfx/angle/src/tests/compiler_tests/BuiltInFunctionEmulator_test.cpp
gfx/harfbuzz/harfbuzz.pc.in
--- a/.eslintignore
+++ b/.eslintignore
@@ -60,21 +60,19 @@ browser/app/**
 browser/branding/**/firefox-branding.js
 browser/base/content/browser-social.js
 browser/base/content/nsContextMenu.js
 browser/base/content/sanitizeDialog.js
 browser/base/content/test/general/file_csp_block_all_mixedcontent.html
 browser/base/content/test/urlbar/file_blank_but_not_blank.html
 browser/base/content/newtab/**
 browser/components/downloads/**
-browser/components/feeds/**
 browser/components/privatebrowsing/**
 browser/components/sessionstore/**
 browser/components/tabview/**
-browser/components/translation/**
 # generated files in cld2
 browser/components/translation/cld2/cld-worker.js
 browser/extensions/pdfjs/**
 # generated or library files in pocket
 browser/extensions/pocket/content/panels/js/tmpl.js
 browser/extensions/pocket/content/panels/js/vendor/**
 browser/locales/**
 
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -394,44 +394,46 @@ SocialShare = {
     // define at least url. If it is undefined, we're sharing the current url in
     // the browser tab.
     let pageData = graphData ? graphData : this.currentShare;
     let sharedURI = pageData ? Services.io.newURI(pageData.url, null, null) :
                                 gBrowser.currentURI;
     if (!SocialUI.canSharePage(sharedURI))
       return;
 
+    let browserMM = gBrowser.selectedBrowser.messageManager;
+
     // the point of this action type is that we can use existing share
     // endpoints (e.g. oexchange) that do not support additional
     // socialapi functionality.  One tweak is that we shoot an event
     // containing the open graph data.
     let _dataFn;
     if (!pageData || sharedURI == gBrowser.currentURI) {
-      messageManager.addMessageListener("PageMetadata:PageDataResult", _dataFn = (msg) => {
-        messageManager.removeMessageListener("PageMetadata:PageDataResult", _dataFn);
+      browserMM.addMessageListener("PageMetadata:PageDataResult", _dataFn = (msg) => {
+        browserMM.removeMessageListener("PageMetadata:PageDataResult", _dataFn);
         let pageData = msg.json;
         if (graphData) {
           // overwrite data retreived from page with data given to us as a param
           for (let p in graphData) {
             pageData[p] = graphData[p];
           }
         }
         this.sharePage(providerOrigin, pageData, target, anchor);
       });
-      gBrowser.selectedBrowser.messageManager.sendAsyncMessage("PageMetadata:GetPageData", null, { target });
+      browserMM.sendAsyncMessage("PageMetadata:GetPageData", null, { target });
       return;
     }
     // if this is a share of a selected item, get any microformats
     if (!pageData.microformats && target) {
-      messageManager.addMessageListener("PageMetadata:MicroformatsResult", _dataFn = (msg) => {
-        messageManager.removeMessageListener("PageMetadata:MicroformatsResult", _dataFn);
+      browserMM.addMessageListener("PageMetadata:MicroformatsResult", _dataFn = (msg) => {
+        browserMM.removeMessageListener("PageMetadata:MicroformatsResult", _dataFn);
         pageData.microformats = msg.data;
         this.sharePage(providerOrigin, pageData, target, anchor);
       });
-      gBrowser.selectedBrowser.messageManager.sendAsyncMessage("PageMetadata:GetMicroformats", null, { target });
+      browserMM.sendAsyncMessage("PageMetadata:GetMicroformats", null, { target });
       return;
     }
     this.currentShare = pageData;
 
     let provider;
     if (providerOrigin)
       provider = Social._getProviderFromOrigin(providerOrigin);
     else
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js
@@ -44,20 +44,22 @@ var gTests = [
     ok(gIdentityHandler._identityPopup.hidden,
        "control center should be hidden in the first window");
     win.gIdentityHandler._identityPopup.hidden = true;
 
     // Closing the new window should remove all sharing indicators.
     // We need to load the content script in the first window so that we can
     // catch the notifications fired globally when closing the second window.
     gBrowser.selectedBrowser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
-    yield BrowserTestUtils.closeWindow(win);
 
-    yield expectObserverCalled("recording-window-ended");
-    yield expectObserverCalled("recording-device-events");
+    let promises = [promiseObserverCalled("recording-device-events"),
+                    promiseObserverCalled("recording-window-ended")];
+    yield BrowserTestUtils.closeWindow(win);
+    yield Promise.all(promises);
+
     yield expectNoObserverCalled();
     yield checkNotSharing();
   }
 }
 
 ];
 
 function test() {
--- a/browser/components/feeds/FeedConverter.js
+++ b/browser/components/feeds/FeedConverter.js
@@ -1,9 +1,9 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ 
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/debug.js");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
@@ -222,17 +222,17 @@ FeedConverter.prototype = {
             case "client":
             case "default":
               try {
                 let title = feed.title ? feed.title.plainText() : "";
                 let desc = feed.subtitle ? feed.subtitle.plainText() : "";
                 let feedReader = safeGetCharPref(getPrefActionForType(feedType), "bookmarks");
                 feedService.addToClientReader(result.uri.spec, title, desc, feed.type, feedReader);
                 return;
-              } catch(ex) { /* fallback to preview mode */ }
+              } catch (ex) { /* fallback to preview mode */ }
           }
         }
       }
 
       let ios =
           Cc["@mozilla.org/network/io-service;1"].
           getService(Ci.nsIIOService);
       let chromeChannel;
--- a/browser/components/feeds/FeedWriter.js
+++ b/browser/components/feeds/FeedWriter.js
@@ -606,17 +606,17 @@ FeedWriter.prototype = {
   _setAlwaysUseCheckedState(feedType) {
     let checkbox = this._document.getElementById("alwaysUse");
     if (checkbox) {
       let alwaysUse = false;
       try {
         if (Services.prefs.getCharPref(getPrefActionForType(feedType)) != "ask")
           alwaysUse = true;
       }
-      catch(ex) { }
+      catch (ex) { }
       this._setCheckboxCheckedState(checkbox, alwaysUse);
     }
   },
 
   _setSubscribeUsingLabel() {
     let stringLabel = "subscribeFeedUsing";
     switch (this._getFeedType()) {
       case Ci.nsIFeed.TYPE_VIDEO:
@@ -944,17 +944,17 @@ FeedWriter.prototype = {
     prefs.addObserver(PREF_AUDIO_SELECTED_READER, this, false);
     prefs.addObserver(PREF_AUDIO_SELECTED_WEB, this, false);
     prefs.addObserver(PREF_AUDIO_SELECTED_APP, this, false);
 
     this._mm.addMessageListener("FeedWriter:SetApplicationLauncherMenuItem", this);
   },
 
   receiveMessage(msg) {
-    switch(msg.name) {
+    switch (msg.name) {
       case "FeedWriter:SetApplicationLauncherMenuItem":
         let menuItem = null;
 
         if (msg.data.type == "DefaultAppMenuItem") {
           menuItem = this._defaultHandlerMenuItem;
         } else {
           // Most likely SelectedAppMenuItem
           menuItem = this._selectedAppMenuItem;
--- a/browser/components/feeds/WebContentConverter.js
+++ b/browser/components/feeds/WebContentConverter.js
@@ -915,17 +915,17 @@ WebContentConverterRegistrarContent.prot
     }).filter(child => !!child)
       .sort();
 
     // now register them
     for (num of nums) {
       let branch = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + num + ".");
       try {
         this._registerContentHandlerHavingBranch(branch);
-      } catch(ex) {
+      } catch (ex) {
         // do nothing, the next branch might have values
       }
     }
   },
 
   _typeIsRegistered(contentType, uri) {
     return this._contentTypes[contentType]
                .some(e => e.uri == uri);
--- a/browser/components/feeds/content/subscribe.js
+++ b/browser/components/feeds/content/subscribe.js
@@ -3,17 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 var SubscribeHandler = {
   /**
    * The nsIFeedWriter object that produces the UI
    */
   _feedWriter: null,
-  
+
   init: function SH_init() {
     this._feedWriter = new BrowserFeedWriter();
   },
 
   writeContent: function SH_writeContent() {
     this._feedWriter.writeContent();
   },
 
--- a/browser/components/feeds/test/test_bug436801.html
+++ b/browser/components/feeds/test/test_bug436801.html
@@ -81,29 +81,29 @@ function checkNode(node, schema) {
     var text = schema.shift();
     is(node.data, text, "Text should match");
     return;
   }
   // type == Node.ELEMENT_NODE
   var tag = schema.shift();
   is(node.localName, tag, "Element should have expected tag");
   while (schema.length) {
-    var val = schema.shift();
+    let val = schema.shift();
     if (Array.isArray(val))
       var childSchema = val;
     else
       var attrSchema = val;
   }
   if (attrSchema) {
     var nsTable = {
       xml: "http://www.w3.org/XML/1998/namespace",
     };
     for (var name in attrSchema) {
       var [ns, nsName] = name.split(":");
-      var val = nsName ? node.getAttributeNS(nsTable[ns], nsName) :
+      let val = nsName ? node.getAttributeNS(nsTable[ns], nsName) :
                 node.getAttribute(name);
       is(val, attrSchema[name], "Attribute " + name + " should match");
     }
   }
   if (childSchema) {
     var numChildren = node.childNodes.length;
     is(childSchema.length, numChildren,
        "Element should have expected number of children");
--- a/browser/components/feeds/test/test_registerHandler.html
+++ b/browser/components/feeds/test/test_registerHandler.html
@@ -23,17 +23,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   function testRegisterHandler(aIsProtocol, aTxt, aUri, aTitle)
   {
     try {
       if (aIsProtocol)
         navigator.registerProtocolHandler(aTxt, aUri, aTitle);
       else
         navigator.registerContentHandler(aTxt, aUri, aTitle);
     }
-    catch(e) {
+    catch (e) {
       return false;
     }
 
     return true;
   }
 
   ok(navigator.registerProtocolHandler, "navigator.registerProtocolHandler should be defined");
   ok(navigator.registerContentHandler, "navigator.registerContentHandler should be defined");
@@ -56,20 +56,20 @@ https://bugzilla.mozilla.org/show_bug.cg
 
   // restriction to http(s) for the uri of the handler (bug 401343)
   // https should work (http already tested in the generic case)
   is(testRegisterHandler(true, "foo", "https://mochi.test:8888/%s", "Foo handler"), true, "registering a foo protocol handler with https scheme should work");
   is(testRegisterHandler(false, "application/rss+xml", "https://mochi.test:8888/%s", "Foo handler"), true, "registering a foo content handler with https scheme should work");
   // ftp should not work
   is(testRegisterHandler(true, "foo", "ftp://mochi.test:8888/%s", "Foo handler"), false, "registering a foo protocol handler with ftp scheme should not work");
   is(testRegisterHandler(false, "application/rss+xml", "ftp://mochi.test:8888/%s", "Foo handler"), false, "registering a foo content handler with ftp scheme should not work");
-  // chrome should not work 
+  // chrome should not work
   is(testRegisterHandler(true, "foo", "chrome://mochi.test:8888/%s", "Foo handler"), false, "registering a foo protocol handler with chrome scheme should not work");
   is(testRegisterHandler(false, "application/rss+xml", "chrome://mochi.test:8888/%s", "Foo handler"), false, "registering a foo content handler with chrome scheme should not work");
-  // foo should not work 
+  // foo should not work
   is(testRegisterHandler(true, "foo", "foo://mochi.test:8888/%s", "Foo handler"), false, "registering a foo protocol handler with foo scheme should not work");
   is(testRegisterHandler(false, "application/rss+xml", "foo://mochi.test:8888/%s", "Foo handler"), false, "registering a foo content handler with foo scheme should not work");
 
   // for security reasons, protocol handlers should never be registered for some schemes (chrome, vbscript, ...) (bug 402788)
   is(testRegisterHandler(true, "chrome", "http://mochi.test:8888/%s", "chrome handler"), false, "registering a chrome protocol handler should not work");
   is(testRegisterHandler(true, "vbscript", "http://mochi.test:8888/%s", "vbscript handler"), false, "registering a vbscript protocol handler should not work");
   is(testRegisterHandler(true, "javascript", "http://mochi.test:8888/%s", "javascript handler"), false, "registering a javascript protocol handler should not work");
   is(testRegisterHandler(true, "moz-icon", "http://mochi.test:8888/%s", "moz-icon handler"), false, "registering a moz-icon protocol handler should not work");
--- a/browser/components/translation/BingTranslator.jsm
+++ b/browser/components/translation/BingTranslator.jsm
@@ -281,17 +281,17 @@ function BingRequest(translationData, so
   this.characterCount = 0;
 }
 
 BingRequest.prototype = {
   /**
    * Initiates the request
    */
   fireRequest: function() {
-    return Task.spawn(function *(){
+    return Task.spawn(function *() {
       // Prepare authentication.
       let token = yield BingTokenManager.getToken();
       let auth = "Bearer " + token;
 
       // Prepare URL.
       let url = getUrlParam("https://api.microsofttranslator.com/v2/Http.svc/TranslateArray",
                             "browser.translation.bing.translateArrayURL");
 
--- a/browser/components/translation/TranslationDocument.jsm
+++ b/browser/components/translation/TranslationDocument.jsm
@@ -161,29 +161,27 @@ this.TranslationDocument.prototype = {
         // it's a translation node: it has useful content for translation.
         // In this case, we need to stringify this node.
         // However, if this item is a root, we should skip it here in this
         // object's child list (and just add a placeholder for it), because
         // it will be stringfied separately for being a root.
         item.original.push(objInMap);
         str += this.generateTextForItem(objInMap);
         wasLastItemPlaceholder = false;
-      } else {
+      } else if (!wasLastItemPlaceholder) {
         // Otherwise, if this node doesn't contain any useful content,
         // or if it is a root itself, we can replace it with a placeholder node.
         // We can't simply eliminate this node from our string representation
         // because that could change the HTML structure (e.g., it would
         // probably merge two separate text nodes).
         // It's not necessary to add more than one placeholder in sequence;
         // we can optimize them away.
-        if (!wasLastItemPlaceholder) {
-          item.original.push(TranslationItem_NodePlaceholder);
-          str += '<br>';
-          wasLastItemPlaceholder = true;
-        }
+        item.original.push(TranslationItem_NodePlaceholder);
+        str += '<br>';
+        wasLastItemPlaceholder = true;
       }
     }
 
     return generateTranslationHtmlForItem(item, str);
   },
 
   /**
    * Changes the document to display its translated
@@ -272,19 +270,25 @@ function TranslationItem(node, id, isRoo
   this.children = [];
 }
 
 TranslationItem.prototype = {
   isRoot: false,
   isSimpleRoot: false,
 
   toString: function() {
-    let rootType = this.isRoot
-                   ? (this.isSimpleRoot ? ' (simple root)' : ' (non simple root)')
-                   : '';
+    let rootType = "";
+    if (this.isRoot) {
+      if (this.isSimpleRoot) {
+        rootType = " (simple root)";
+      }
+      else {
+        rootType = " (non simple root)";
+      }
+    }
     return "[object TranslationItem: <" + this.nodeRef.localName + ">"
            + rootType + "]";
   },
 
   /**
    * This function will parse the result of the translation of one translation
    * item. If this item was a simple root, all we sent was a plain-text version
    * of it, so the result is also straightforward text.
--- a/browser/components/translation/YandexTranslator.jsm
+++ b/browser/components/translation/YandexTranslator.jsm
@@ -288,17 +288,17 @@ function YandexRequest(translationData, 
   this.characterCount = 0;
 }
 
 YandexRequest.prototype = {
   /**
    * Initiates the request
    */
   fireRequest: function() {
-    return Task.spawn(function *(){
+    return Task.spawn(function *() {
       // Prepare URL.
       let url = getUrlParam("https://translate.yandex.net/api/v1.5/tr.json/translate",
                             "browser.translation.yandex.translateURLOverride");
 
       // Prepare the request body.
       let apiKey = getUrlParam("%YANDEX_API_KEY%", "browser.translation.yandex.apiKeyOverride");
       let params = [
         ["key", apiKey],
--- a/browser/components/translation/cld2/post.js
+++ b/browser/components/translation/cld2/post.js
@@ -120,17 +120,17 @@ var Encodings = {
 // Accept forms both with and without underscores/hypens.
 for (let code of Object.keys(Encodings)) {
   if (code['includes']("_"))
     Encodings[code.replace(/_/g, "")] = Encodings[code];
 }
 
 addOnPreMain(function() {
 
-  onmessage = function(aMsg){
+  onmessage = function(aMsg) {
     let data = aMsg['data'];
 
     let langInfo;
     if (data['tld'] == undefined && data['encoding'] == undefined && data['language'] == undefined) {
       langInfo = LanguageInfo.detectLanguage(data['text'], !data['isHTML']);
     } else {
       // Do our best to find the given encoding in the encodings table.
       // Otherwise, just fall back to unknown.
--- a/browser/components/translation/test/browser_translation_bing.js
+++ b/browser/components/translation/test/browser_translation_bing.js
@@ -98,17 +98,17 @@ add_task(function* test_handling_out_of_
 });
 
 /**
  * A helper function for constructing a URL to a page stored in the
  * local fixture folder.
  *
  * @param filename  Name of a fixture file.
  */
-function constructFixtureURL(filename){
+function constructFixtureURL(filename) {
   // Deduce the Mochitest server address in use from a pref that was pre-processed.
   let server = Services.prefs.getCharPref("browser.translation.bing.authURL")
                              .replace("http://", "");
   server = server.substr(0, server.indexOf("/"));
   let url = "http://" + server +
     "/browser/browser/components/translation/test/fixtures/" + filename;
   return url;
 }
--- a/browser/components/translation/test/browser_translation_telemetry.js
+++ b/browser/components/translation/test/browser_translation_telemetry.js
@@ -19,17 +19,17 @@ var MetricsChecker = {
     AUTO_REJECTED         : Services.telemetry.getHistogramById("AUTO_REJECTED_TRANSLATION_OFFERS"),
     SHOW_ORIGINAL         : Services.telemetry.getHistogramById("REQUESTS_OF_ORIGINAL_CONTENT"),
     TARGET_CHANGES        : Services.telemetry.getHistogramById("CHANGES_OF_TARGET_LANGUAGE"),
     DETECTION_CHANGES     : Services.telemetry.getHistogramById("CHANGES_OF_DETECTED_LANGUAGE"),
     SHOW_UI               : Services.telemetry.getHistogramById("SHOULD_TRANSLATION_UI_APPEAR"),
     DETECT_LANG           : Services.telemetry.getHistogramById("SHOULD_AUTO_DETECT_LANGUAGE"),
   },
 
-  reset: function(){
+  reset: function() {
     for (let i of Object.keys(this.HISTOGRAMS)) {
       this.HISTOGRAMS[i].clear();
     }
     this.updateMetrics();
   },
 
   updateMetrics: function () {
     this._metrics = {
@@ -60,17 +60,17 @@ var MetricsChecker = {
         this._metrics.pageCountByLang[key] = pages[key] ? pages[key].sum : 0;
       }
     }
   },
 
   /**
    * A recurrent loop for making assertions about collected metrics.
    */
-  _assertionLoop: function (prevMetrics, metrics, additions){
+  _assertionLoop: function (prevMetrics, metrics, additions) {
     for (let metric of Object.keys(additions)) {
       let addition = additions[metric];
       // Allows nesting metrics. Useful for keyed histograms.
       if (typeof addition === 'object') {
         this._assertionLoop(prevMetrics[metric], metrics[metric], addition);
         continue;
       }
       Assert.equal(prevMetrics[metric] + addition, metrics[metric]);
@@ -112,19 +112,19 @@ var acceptTranslationOffer = Task.async(
   yield waitForMessage(browser, "Translation:Finished");
 });
 
 var translate = Task.async(function*(text, from, closeTab = true) {
   let tab = yield offerTranslationFor(text, from);
   yield acceptTranslationOffer(tab);
   if (closeTab) {
     gBrowser.removeTab(tab);
-  } else {
-    return tab;
+    return null;
   }
+  return tab;
 });
 
 function waitForMessage({messageManager}, name) {
   return new Promise(resolve => {
     messageManager.addMessageListener(name, function onMessage() {
       messageManager.removeMessageListener(name, onMessage);
       resolve();
     });
--- a/browser/components/translation/test/browser_translation_yandex.js
+++ b/browser/components/translation/test/browser_translation_yandex.js
@@ -86,17 +86,17 @@ add_task(function* test_preference_attri
 });
 
 /**
  * A helper function for constructing a URL to a page stored in the
  * local fixture folder.
  *
  * @param filename  Name of a fixture file.
  */
-function constructFixtureURL(filename){
+function constructFixtureURL(filename) {
   // Deduce the Mochitest server address in use from a pref that was pre-processed.
   let server = Services.prefs.getCharPref("browser.translation.yandex.translateURLOverride")
                              .replace("http://", "");
   server = server.substr(0, server.indexOf("/"));
   let url = "http://" + server +
     "/browser/browser/components/translation/test/fixtures/" + filename;
   return url;
 }
--- a/browser/locales/Makefile.in
+++ b/browser/locales/Makefile.in
@@ -56,17 +56,17 @@ UNINSTALLER_PACKAGE_HOOK = $(RM) -r $(ST
 
 STUB_HOOK = $(NSINSTALL) -D '$(ABS_DIST)/$(PKG_INST_PATH)'; \
     $(RM) '$(ABS_DIST)/$(PKG_INST_PATH)$(PKG_STUB_BASENAME).exe'; \
     cp ../installer/windows/l10ngen/stub.exe '$(ABS_DIST)/$(PKG_INST_PATH)$(PKG_STUB_BASENAME).exe'; \
     chmod 0755 '$(ABS_DIST)/$(PKG_INST_PATH)$(PKG_STUB_BASENAME).exe'; \
     $(NULL)
 endif
 
-SEARCHPLUGINS_FILENAMES := $(shell $(PYTHON) $(srcdir)/searchplugins.py $(srcdir)/search/list.json $(AB_CD))
+SEARCHPLUGINS_FILENAMES := $(shell $(call py_action,output_searchplugins_list,$(srcdir)/search/list.json $(AB_CD)))
 SEARCHPLUGINS_PATH := .deps/generated_$(AB_CD)
 SEARCHPLUGINS_TARGET := libs searchplugins
 SEARCHPLUGINS := $(foreach plugin,$(addsuffix .xml,$(SEARCHPLUGINS_FILENAMES)),$(or $(wildcard $(call EN_US_OR_L10N_FILE,searchplugins/$(plugin))),$(warning Missing searchplugin: $(plugin))))
 # Some locale-specific search plugins may have preprocessor directives, but the
 # default en-US ones do not.
 SEARCHPLUGINS_FLAGS := --silence-missing-directive-warnings
 PP_TARGETS += SEARCHPLUGINS
 
@@ -79,17 +79,17 @@ libs:: searchplugins
 # be included in langpack xpis.
 DIST_SUBDIRS = $(DIST_SUBDIR)
 
 include $(topsrcdir)/config/rules.mk
 
 include $(topsrcdir)/toolkit/locales/l10n.mk
 
 $(list-json): $(call mkdir_deps,$(SEARCHPLUGINS_PATH)) $(if $(IS_LANGUAGE_REPACK),FORCE)
-	$(shell $(PYTHON) $(srcdir)/searchjson.py $(srcdir)/search/list.json $(AB_CD) $(list-json))
+	$(call py_action,generate_searchjson,$(srcdir)/search/list.json $(AB_CD) $(list-json))
 searchplugins:: $(list-json)
 
 $(STAGEDIST): $(DIST)/branding
 
 $(DIST)/branding:
 	$(NSINSTALL) -D $@
 
 DEFINES += -DBOOKMARKS_INCLUDE_DIR=$(dir $(call MERGE_FILE,profile/bookmarks.inc))
--- a/browser/modules/BrowserUsageTelemetry.jsm
+++ b/browser/modules/BrowserUsageTelemetry.jsm
@@ -26,16 +26,17 @@ const DOMWINDOW_OPENED_TOPIC = "domwindo
 
 // Probe names.
 const MAX_TAB_COUNT_SCALAR_NAME = "browser.engagement.max_concurrent_tab_count";
 const MAX_WINDOW_COUNT_SCALAR_NAME = "browser.engagement.max_concurrent_window_count";
 const TAB_OPEN_EVENT_COUNT_SCALAR_NAME = "browser.engagement.tab_open_event_count";
 const WINDOW_OPEN_EVENT_COUNT_SCALAR_NAME = "browser.engagement.window_open_event_count";
 const UNIQUE_DOMAINS_COUNT_SCALAR_NAME = "browser.engagement.unique_domains_count";
 const TOTAL_URI_COUNT_SCALAR_NAME = "browser.engagement.total_uri_count";
+const UNFILTERED_URI_COUNT_SCALAR_NAME = "browser.engagement.unfiltered_uri_count";
 
 // A list of known search origins.
 const KNOWN_SEARCH_SOURCES = [
   "abouthome",
   "contextmenu",
   "newtab",
   "searchbar",
   "urlbar",
@@ -73,23 +74,23 @@ function getSearchEngineId(engine) {
 }
 
 let URICountListener = {
   // A set containing the visited domains, see bug 1271310.
   _domainSet: new Set(),
   // A map to keep track of the URIs loaded from the restored tabs.
   _restoredURIsMap: new WeakMap(),
 
-  isValidURI(uri) {
+  isHttpURI(uri) {
     // Only consider http(s) schemas.
     return uri.schemeIs("http") || uri.schemeIs("https");
   },
 
   addRestoredURI(browser, uri) {
-    if (!this.isValidURI(uri)) {
+    if (!this.isHttpURI(uri)) {
       return;
     }
 
     this._restoredURIsMap.set(browser, uri.spec);
   },
 
   onLocationChange(browser, webProgress, request, uri, flags) {
     // Don't count this URI if it's an error page.
@@ -97,38 +98,61 @@ let URICountListener = {
       return;
     }
 
     // We only care about top level loads.
     if (!webProgress.isTopLevel) {
       return;
     }
 
-    if (!this.isValidURI(uri)) {
-      return;
-    }
-
     // The SessionStore sets the URI of a tab first, firing onLocationChange the
     // first time, then manages content loading using its scheduler. Once content
     // loads, we will hit onLocationChange again.
     // We can catch the first case by checking for null requests: be advised that
     // this can also happen when navigating page fragments, so account for it.
     if (!request &&
         !(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
       return;
     }
 
+    // Track URI loads, even if they're not http(s).
+    let uriSpec = null;
+    try {
+      uriSpec = uri.spec;
+    } catch (e) {
+      // If we have troubles parsing the spec, still count this as
+      // an unfiltered URI.
+      Services.telemetry.scalarAdd(UNFILTERED_URI_COUNT_SCALAR_NAME, 1);
+      return;
+    }
+
+
+    // Don't count about:blank and similar pages, as they would artificially
+    // inflate the counts.
+    if (browser.ownerDocument.defaultView.gInitialPages.includes(uriSpec)) {
+      return;
+    }
+
     // If the URI we're loading is in the _restoredURIsMap, then it comes from a
     // restored tab. If so, let's skip it and remove it from the map as we want to
     // count page refreshes.
-    if (this._restoredURIsMap.get(browser) === uri.spec) {
+    if (this._restoredURIsMap.get(browser) === uriSpec) {
       this._restoredURIsMap.delete(browser);
       return;
     }
 
+    // The URI wasn't from a restored tab. Count it among the unfiltered URIs.
+    // If this is an http(s) URI, this also gets counted by the "total_uri_count"
+    // probe.
+    Services.telemetry.scalarAdd(UNFILTERED_URI_COUNT_SCALAR_NAME, 1);
+
+    if (!this.isHttpURI(uri)) {
+      return;
+    }
+
     // Update the URI counts.
     Services.telemetry.scalarAdd(TOTAL_URI_COUNT_SCALAR_NAME, 1);
 
     // We only want to count the unique domains up to MAX_UNIQUE_VISITED_DOMAINS.
     if (this._domainSet.size == MAX_UNIQUE_VISITED_DOMAINS) {
       return;
     }
 
--- a/browser/modules/test/browser_UsageTelemetry.js
+++ b/browser/modules/test/browser_UsageTelemetry.js
@@ -1,16 +1,17 @@
 "use strict";
 
 const MAX_CONCURRENT_TABS = "browser.engagement.max_concurrent_tab_count";
 const TAB_EVENT_COUNT = "browser.engagement.tab_open_event_count";
 const MAX_CONCURRENT_WINDOWS = "browser.engagement.max_concurrent_window_count";
 const WINDOW_OPEN_COUNT = "browser.engagement.window_open_event_count";
 const TOTAL_URI_COUNT = "browser.engagement.total_uri_count";
 const UNIQUE_DOMAINS_COUNT = "browser.engagement.unique_domains_count";
+const UNFILTERED_URI_COUNT = "browser.engagement.unfiltered_uri_count";
 
 const TELEMETRY_SUBSESSION_TOPIC = "internal-telemetry-after-subsession-split";
 
 /**
  * Waits for the web progress listener associated with this tab to fire an
  * onLocationChange for a non-error page.
  *
  * @param {xul:browser} browser
@@ -54,191 +55,214 @@ let checkScalar = (scalars, scalarName, 
     return;
   }
   ok(!(scalarName in scalars), scalarName + " must not be reported.");
 };
 
 /**
  * Get a snapshot of the scalars and check them against the provided values.
  */
-let checkScalars = (maxTabs, tabOpenCount, maxWindows, windowsOpenCount, totalURIs, domainCount) => {
+let checkScalars = (countsObject) => {
   const scalars =
     Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
 
   // Check the expected values. Scalars that are never set must not be reported.
-  checkScalar(scalars, MAX_CONCURRENT_TABS, maxTabs,
+  checkScalar(scalars, MAX_CONCURRENT_TABS, countsObject.maxTabs,
               "The maximum tab count must match the expected value.");
-  checkScalar(scalars, TAB_EVENT_COUNT, tabOpenCount,
+  checkScalar(scalars, TAB_EVENT_COUNT, countsObject.tabOpenCount,
               "The number of open tab event count must match the expected value.");
-  checkScalar(scalars, MAX_CONCURRENT_WINDOWS, maxWindows,
+  checkScalar(scalars, MAX_CONCURRENT_WINDOWS, countsObject.maxWindows,
               "The maximum window count must match the expected value.");
-  checkScalar(scalars, WINDOW_OPEN_COUNT, windowsOpenCount,
+  checkScalar(scalars, WINDOW_OPEN_COUNT, countsObject.windowsOpenCount,
               "The number of window open event count must match the expected value.");
-  checkScalar(scalars, TOTAL_URI_COUNT, totalURIs,
+  checkScalar(scalars, TOTAL_URI_COUNT, countsObject.totalURIs,
               "The total URI count must match the expected value.");
-  checkScalar(scalars, UNIQUE_DOMAINS_COUNT, domainCount,
+  checkScalar(scalars, UNIQUE_DOMAINS_COUNT, countsObject.domainCount,
               "The unique domains count must match the expected value.");
+  checkScalar(scalars, UNFILTERED_URI_COUNT, countsObject.totalUnfilteredURIs,
+              "The unfiltered URI count must match the expected value.");
 };
 
 add_task(function* test_tabsAndWindows() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
 
   let openedTabs = [];
   let expectedTabOpenCount = 0;
   let expectedWinOpenCount = 0;
   let expectedMaxTabs = 0;
   let expectedMaxWins = 0;
 
   // Add a new tab and check that the count is right.
   openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"));
   expectedTabOpenCount = 1;
   expectedMaxTabs = 2;
-  checkScalars(expectedMaxTabs, expectedTabOpenCount, expectedMaxWins, expectedWinOpenCount, 0, 0);
+  // This, and all the checks below, also check that initial pages (about:newtab, about:blank, ..)
+  // are not counted by the total_uri_count and the unfiltered_uri_count probes.
+  checkScalars({maxTabs: expectedMaxTabs, tabOpenCount: expectedTabOpenCount, maxWindows: expectedMaxWins,
+                windowsOpenCount: expectedWinOpenCount, totalURIs: 0, domainCount: 0,
+                totalUnfilteredURIs: 0});
 
   // Add two new tabs in the same window.
   openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"));
   openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"));
   expectedTabOpenCount += 2;
   expectedMaxTabs += 2;
-  checkScalars(expectedMaxTabs, expectedTabOpenCount, expectedMaxWins, expectedWinOpenCount, 0, 0);
+  checkScalars({maxTabs: expectedMaxTabs, tabOpenCount: expectedTabOpenCount, maxWindows: expectedMaxWins,
+                windowsOpenCount: expectedWinOpenCount, totalURIs: 0, domainCount: 0,
+                totalUnfilteredURIs: 0});
 
   // Add a new window and then some tabs in it. An empty new windows counts as a tab.
   let win = yield BrowserTestUtils.openNewBrowserWindow();
   openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:blank"));
   openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:blank"));
   openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"));
   // The new window started with a new tab, so account for it.
   expectedTabOpenCount += 4;
   expectedWinOpenCount += 1;
   expectedMaxWins = 2;
   expectedMaxTabs += 4;
 
   // Remove a tab from the first window, the max shouldn't change.
   yield BrowserTestUtils.removeTab(openedTabs.pop());
-  checkScalars(expectedMaxTabs, expectedTabOpenCount, expectedMaxWins, expectedWinOpenCount, 0, 0);
+  checkScalars({maxTabs: expectedMaxTabs, tabOpenCount: expectedTabOpenCount, maxWindows: expectedMaxWins,
+                windowsOpenCount: expectedWinOpenCount, totalURIs: 0, domainCount: 0,
+                totalUnfilteredURIs: 0});
 
   // Remove all the extra windows and tabs.
   for (let tab of openedTabs) {
     yield BrowserTestUtils.removeTab(tab);
   }
   yield BrowserTestUtils.closeWindow(win);
 
   // Make sure all the scalars still have the expected values.
-  checkScalars(expectedMaxTabs, expectedTabOpenCount, expectedMaxWins, expectedWinOpenCount, 0, 0);
+  checkScalars({maxTabs: expectedMaxTabs, tabOpenCount: expectedTabOpenCount, maxWindows: expectedMaxWins,
+                windowsOpenCount: expectedWinOpenCount, totalURIs: 0, domainCount: 0,
+                totalUnfilteredURIs: 0});
 });
 
 add_task(function* test_subsessionSplit() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
 
   // Add a new window (that will have 4 tabs).
   let win = yield BrowserTestUtils.openNewBrowserWindow();
   let openedTabs = [];
   openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:blank"));
-  openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:blank"));
+  openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:mozilla"));
   openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "http://www.example.com"));
 
-  // Check that the scalars have the right values.
-  checkScalars(5 /*maxTabs*/, 4 /*tabOpen*/, 2 /*maxWins*/, 1 /*winOpen*/,
-               1 /* toalURIs */, 1 /* uniqueDomains */);
+  // Check that the scalars have the right values. We expect 2 unfiltered URI loads
+  // (about:mozilla and www.example.com, but no about:blank) and 1 URI totalURIs
+  // (only www.example.com).
+  checkScalars({maxTabs: 5, tabOpenCount: 4, maxWindows: 2, windowsOpenCount: 1,
+                totalURIs: 1, domainCount: 1, totalUnfilteredURIs: 2});
 
   // Remove a tab.
   yield BrowserTestUtils.removeTab(openedTabs.pop());
 
   // Simulate a subsession split by clearing the scalars (via |snapshotScalars|) and
   // notifying the subsession split topic.
   Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN,
                                      true /* clearScalars*/);
   Services.obs.notifyObservers(null, TELEMETRY_SUBSESSION_TOPIC, "");
 
   // After a subsession split, only the MAX_CONCURRENT_* scalars must be available
-  // and have the correct value. No tabs or windows were opened so other scalars
+  // and have the correct value. No tabs, windows or URIs were opened so other scalars
   // must not be reported.
-  checkScalars(4 /*maxTabs*/, 0 /*tabOpen*/, 2 /*maxWins*/, 0 /*winOpen*/,
-               0 /* toalURIs */, 0 /* uniqueDomains */);
+  checkScalars({maxTabs: 4, tabOpenCount: 0, maxWindows: 2, windowsOpenCount: 0,
+                totalURIs: 0, domainCount: 0, totalUnfilteredURIs: 0});
 
   // Remove all the extra windows and tabs.
   for (let tab of openedTabs) {
     yield BrowserTestUtils.removeTab(tab);
   }
   yield BrowserTestUtils.closeWindow(win);
 });
 
 add_task(function* test_URIAndDomainCounts() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
 
-  let checkCounts = (URICount, domainCount) => {
+  let checkCounts = (countsObject) => {
     // Get a snapshot of the scalars and then clear them.
     const scalars =
       Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
-    checkScalar(scalars, TOTAL_URI_COUNT, URICount,
+    checkScalar(scalars, TOTAL_URI_COUNT, countsObject.totalURIs,
                 "The URI scalar must contain the expected value.");
-    checkScalar(scalars, UNIQUE_DOMAINS_COUNT, domainCount,
+    checkScalar(scalars, UNIQUE_DOMAINS_COUNT, countsObject.domainCount,
                 "The unique domains scalar must contain the expected value.");
+    checkScalar(scalars, UNFILTERED_URI_COUNT, countsObject.totalUnfilteredURIs,
+                "The unfiltered URI scalar must contain the expected value.");
   };
 
   // Check that about:blank doesn't get counted in the URI total.
   let firstTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
-  checkCounts(0, 0);
+  checkCounts({totalURIs: 0, domainCount: 0, totalUnfilteredURIs: 0});
 
   // Open a different page and check the counts.
   yield BrowserTestUtils.loadURI(firstTab.linkedBrowser, "http://example.com/");
   yield BrowserTestUtils.browserLoaded(firstTab.linkedBrowser);
-  checkCounts(1, 1);
+  checkCounts({totalURIs: 1, domainCount: 1, totalUnfilteredURIs: 1});
 
   // Activating a different tab must not increase the URI count.
   let secondTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
   yield BrowserTestUtils.switchTab(gBrowser, firstTab);
-  checkCounts(1, 1);
+  checkCounts({totalURIs: 1, domainCount: 1, totalUnfilteredURIs: 1});
   yield BrowserTestUtils.removeTab(secondTab);
 
   // Open a new window and set the tab to a new address.
   let newWin = yield BrowserTestUtils.openNewBrowserWindow();
   yield BrowserTestUtils.loadURI(newWin.gBrowser.selectedBrowser, "http://example.com/");
   yield BrowserTestUtils.browserLoaded(newWin.gBrowser.selectedBrowser);
-  checkCounts(2, 1);
+  checkCounts({totalURIs: 2, domainCount: 1, totalUnfilteredURIs: 2});
 
   // We should not count AJAX requests.
   const XHR_URL = "http://example.com/r";
   yield ContentTask.spawn(newWin.gBrowser.selectedBrowser, XHR_URL, function(url) {
     return new Promise(resolve => {
       var xhr = new content.window.XMLHttpRequest();
       xhr.open("GET", url);
       xhr.onload = () => resolve();
       xhr.send();
     });
   });
-  checkCounts(2, 1);
+  checkCounts({totalURIs: 2, domainCount: 1, totalUnfilteredURIs: 2});
 
   // Check that we're counting page fragments.
   let loadingStopped = browserLocationChanged(newWin.gBrowser.selectedBrowser);
   yield BrowserTestUtils.loadURI(newWin.gBrowser.selectedBrowser, "http://example.com/#2");
   yield loadingStopped;
-  checkCounts(3, 1);
+  checkCounts({totalURIs: 3, domainCount: 1, totalUnfilteredURIs: 3});
 
-  // Check test.domain.com and some.domain.com are only counted once unique.
+  // Check that a different URI from the example.com domain doesn't increment the unique count.
   yield BrowserTestUtils.loadURI(newWin.gBrowser.selectedBrowser, "http://test1.example.com/");
   yield BrowserTestUtils.browserLoaded(newWin.gBrowser.selectedBrowser);
-  checkCounts(4, 1);
+  checkCounts({totalURIs: 4, domainCount: 1, totalUnfilteredURIs: 4});
 
   // Make sure that the unique domains counter is incrementing for a different domain.
   yield BrowserTestUtils.loadURI(newWin.gBrowser.selectedBrowser, "https://example.org/");
   yield BrowserTestUtils.browserLoaded(newWin.gBrowser.selectedBrowser);
-  checkCounts(5, 2);
+  checkCounts({totalURIs: 5, domainCount: 2, totalUnfilteredURIs: 5});
 
   // Check that we only account for top level loads (e.g. we don't count URIs from
   // embedded iframes).
   yield ContentTask.spawn(newWin.gBrowser.selectedBrowser, null, function* () {
     let doc = content.document;
     let iframe = doc.createElement("iframe");
     let promiseIframeLoaded = ContentTaskUtils.waitForEvent(iframe, "load", false);
     iframe.src = "https://example.org/test";
     doc.body.insertBefore(iframe, doc.body.firstChild);
     yield promiseIframeLoaded;
   });
-  checkCounts(5, 2);
+  checkCounts({totalURIs: 5, domainCount: 2, totalUnfilteredURIs: 5});
+
+  // Check that uncommon protocols get counted in the unfiltered URI probe.
+  const TEST_PAGE =
+    "data:text/html,<a id='target' href='%23par1'>Click me</a><a name='par1'>The paragraph.</a>";
+  yield BrowserTestUtils.loadURI(newWin.gBrowser.selectedBrowser, TEST_PAGE);
+  yield BrowserTestUtils.browserLoaded(newWin.gBrowser.selectedBrowser);
+  checkCounts({totalURIs: 5, domainCount: 2, totalUnfilteredURIs: 6});
 
   // Clean up.
   yield BrowserTestUtils.removeTab(firstTab);
   yield BrowserTestUtils.closeWindow(newWin);
 });
--- a/browser/modules/test/browser_UsageTelemetry_private_and_restore.js
+++ b/browser/modules/test/browser_UsageTelemetry_private_and_restore.js
@@ -1,15 +1,16 @@
 "use strict";
 
 const MAX_CONCURRENT_TABS = "browser.engagement.max_concurrent_tab_count";
 const TAB_EVENT_COUNT = "browser.engagement.tab_open_event_count";
 const MAX_CONCURRENT_WINDOWS = "browser.engagement.max_concurrent_window_count";
 const WINDOW_OPEN_COUNT = "browser.engagement.window_open_event_count";
 const TOTAL_URI_COUNT = "browser.engagement.total_uri_count";
+const UNFILTERED_URI_COUNT = "browser.engagement.unfiltered_uri_count";
 const UNIQUE_DOMAINS_COUNT = "browser.engagement.unique_domains_count";
 
 function promiseBrowserStateRestored() {
   return new Promise(resolve => {
      Services.obs.addObserver(function observer(aSubject, aTopic) {
        Services.obs.removeObserver(observer, "sessionstore-browser-state-restored");
        resolve();
      }, "sessionstore-browser-state-restored", false);
@@ -25,16 +26,17 @@ add_task(function* test_privateMode() {
   yield BrowserTestUtils.loadURI(privateWin.gBrowser.selectedBrowser, "http://example.com/");
   yield BrowserTestUtils.browserLoaded(privateWin.gBrowser.selectedBrowser);
 
   // Check that tab and window count is recorded.
   const scalars =
     Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
 
   ok(!(TOTAL_URI_COUNT in scalars), "We should not track URIs in private mode.");
+  ok(!(UNFILTERED_URI_COUNT in scalars), "We should not track URIs in private mode.");
   ok(!(UNIQUE_DOMAINS_COUNT in scalars), "We should not track unique domains in private mode.");
   is(scalars[TAB_EVENT_COUNT], 1, "The number of open tab event count must match the expected value.");
   is(scalars[MAX_CONCURRENT_TABS], 2, "The maximum tab count must match the expected value.");
   is(scalars[WINDOW_OPEN_COUNT], 1, "The number of window open event count must match the expected value.");
   is(scalars[MAX_CONCURRENT_WINDOWS], 2, "The maximum window count must match the expected value.");
 
   // Clean up.
   yield BrowserTestUtils.closeWindow(privateWin);
@@ -74,15 +76,16 @@ add_task(function* test_sessionRestore()
   SessionStore.setBrowserState(JSON.stringify(state));
   yield tabRestored;
 
   // Check that the URI is not recorded.
   const scalars =
     Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
 
   ok(!(TOTAL_URI_COUNT in scalars), "We should not track URIs from restored sessions.");
+  ok(!(UNFILTERED_URI_COUNT in scalars), "We should not track URIs from restored sessions.");
   ok(!(UNIQUE_DOMAINS_COUNT in scalars), "We should not track unique domains from restored sessions.");
 
   // Restore the original session and cleanup.
   let sessionRestored = promiseBrowserStateRestored();
   SessionStore.setBrowserState(JSON.stringify(state));
   yield sessionRestored;
 });
--- a/devtools/client/animationinspector/components/animation-time-block.js
+++ b/devtools/client/animationinspector/components/animation-time-block.js
@@ -7,16 +7,33 @@
 "use strict";
 
 const EventEmitter = require("devtools/shared/event-emitter");
 const {createNode, TimeScale} = require("devtools/client/animationinspector/utils");
 
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper("devtools/locale/animationinspector.properties");
 
+// In the createPathSegments function, an animation duration is divided by
+// DURATION_RESOLUTION in order to draw the way the animation progresses.
+// But depending on the timing-function, we may be not able to make the graph
+// smoothly progress if this resolution is not high enough.
+// So, if the difference of animation progress between 2 divisions is more than
+// MIN_PROGRESS_THRESHOLD, then createPathSegments re-divides
+// by DURATION_RESOLUTION.
+// DURATION_RESOLUTION shoud be integer and more than 2.
+const DURATION_RESOLUTION = 4;
+// MIN_PROGRESS_THRESHOLD shoud be between more than 0 to 1.
+const MIN_PROGRESS_THRESHOLD = 0.1;
+// Show max 10 iterations for infinite animations
+// to give users a clue that the animation does repeat.
+const MAX_INFINITE_ANIMATIONS_ITERATIONS = 10;
+// SVG namespace
+const SVG_NS = "http://www.w3.org/2000/svg";
+
 /**
  * UI component responsible for displaying a single animation timeline, which
  * basically looks like a rectangle that shows the delay and iterations.
  */
 function AnimationTimeBlock() {
   EventEmitter.decorate(this);
   this.onClick = this.onClick.bind(this);
 }
@@ -46,85 +63,200 @@ AnimationTimeBlock.prototype = {
     this.unrender();
 
     this.animation = animation;
     let {state} = this.animation;
 
     // Create a container element to hold the delay and iterations.
     // It is positioned according to its delay (divided by the playbackrate),
     // and its width is according to its duration (divided by the playbackrate).
-    let {x, iterationW, delayX, delayW, negativeDelayW, endDelayX, endDelayW} =
+    const {x, delayX, delayW, endDelayX, endDelayW} =
       TimeScale.getAnimationDimensions(animation);
 
-    // background properties for .iterations element
-    let backgroundIterations = TimeScale.getIterationsBackgroundData(animation);
-
-    createNode({
+    // Animation summary graph element.
+    const summaryEl = createNode({
       parent: this.containerEl,
+      namespace: "http://www.w3.org/2000/svg",
+      nodeType: "svg",
       attributes: {
-        "class": "iterations" + (state.iterationCount ? "" : " infinite"),
-        // Individual iterations are represented by setting the size of the
-        // repeating linear-gradient.
-        // The background-size, background-position, background-repeat represent
-        // iterationCount and iterationStart.
-        "style": `left:${x}%;
-                  width:${iterationW}%;
-                  background-size:${backgroundIterations.size}% 100%;
-                  background-position:${backgroundIterations.position}% 0;
-                  background-repeat:${backgroundIterations.repeat};`
+        "class": "summary",
+        "preserveAspectRatio": "none",
+        "style": `left: ${ x - (state.delay > 0 ? delayW : 0) }%`
       }
     });
 
-    // The animation name is displayed over the iterations.
-    // Note that in case of negative delay, it is pushed towards the right so
-    // the delay element does not overlap.
+    // Total displayed duration
+    const totalDisplayedDuration = state.playbackRate * TimeScale.getDuration();
+
+    // Calculate stroke height in viewBox to display stroke of path.
+    const strokeHeightForViewBox = 0.5 / this.containerEl.clientHeight;
+
+    // Set viewBox
+    summaryEl.setAttribute("viewBox",
+                           `${ state.delay < 0 ? state.delay : 0 }
+                            -${ 1 + strokeHeightForViewBox }
+                            ${ totalDisplayedDuration }
+                            ${ 1 + strokeHeightForViewBox * 2 }`);
+
+    // Get a helper function that returns the path segment of timing-function.
+    const segmentHelper = getSegmentHelper(state, this.win);
+
+    // Minimum segment duration is the duration of one pixel.
+    const minSegmentDuration =
+      totalDisplayedDuration / this.containerEl.clientWidth;
+    // Minimum progress threshold.
+    let minProgressThreshold = MIN_PROGRESS_THRESHOLD;
+    // If the easing is step function,
+    // minProgressThreshold should be changed by the steps.
+    const stepFunction = state.easing.match(/steps\((\d+)/);
+    if (stepFunction) {
+      minProgressThreshold = 1 / (parseInt(stepFunction[1], 10) + 1);
+    }
+
+    // Starting time of main iteration.
+    let mainIterationStartTime = 0;
+    let iterationStart = state.iterationStart;
+    let iterationCount = state.iterationCount ? state.iterationCount : Infinity;
+
+    // Append delay.
+    if (state.delay > 0) {
+      renderDelay(summaryEl, state, segmentHelper);
+      mainIterationStartTime = state.delay;
+    } else {
+      const negativeDelayCount = -state.delay / state.duration;
+      // Move to forward the starting point for negative delay.
+      iterationStart += negativeDelayCount;
+      // Consume iteration count by negative delay.
+      if (iterationCount !== Infinity) {
+        iterationCount -= negativeDelayCount;
+      }
+    }
+
+    // Append 1st section of iterations,
+    // This section is only useful in cases where iterationStart has decimals.
+    // e.g.
+    // if { iterationStart: 0.25, iterations: 3 }, firstSectionCount is 0.75.
+    const firstSectionCount =
+      iterationStart % 1 === 0
+      ? 0 : Math.min(iterationCount, 1) - iterationStart % 1;
+    if (firstSectionCount) {
+      renderFirstIteration(summaryEl, state, mainIterationStartTime,
+                           firstSectionCount, minSegmentDuration,
+                           minProgressThreshold, segmentHelper);
+    }
+
+    if (iterationCount === Infinity) {
+      // If the animation repeats infinitely,
+      // we fill the remaining area with iteration paths.
+      renderInfinity(summaryEl, state, mainIterationStartTime,
+                     firstSectionCount, totalDisplayedDuration,
+                     minSegmentDuration, minProgressThreshold, segmentHelper);
+    } else {
+      // Otherwise, we show remaining iterations, endDelay and fill.
+
+      // Append forwards fill-mode.
+      if (state.fill === "both" || state.fill === "forwards") {
+        renderForwardsFill(summaryEl, state, mainIterationStartTime,
+                           iterationCount, totalDisplayedDuration,
+                           segmentHelper);
+      }
+
+      // Append middle section of iterations.
+      // e.g.
+      // if { iterationStart: 0.25, iterations: 3 }, middleSectionCount is 2.
+      const middleSectionCount =
+        Math.floor(iterationCount - firstSectionCount);
+      renderMiddleIterations(summaryEl, state, mainIterationStartTime,
+                             firstSectionCount, middleSectionCount,
+                             minSegmentDuration, minProgressThreshold,
+                             segmentHelper);
+
+      // Append last section of iterations, if there is remaining iteration.
+      // e.g.
+      // if { iterationStart: 0.25, iterations: 3 }, lastSectionCount is 0.25.
+      const lastSectionCount =
+        iterationCount - middleSectionCount - firstSectionCount;
+      if (lastSectionCount) {
+        renderLastIteration(summaryEl, state, mainIterationStartTime,
+                            firstSectionCount, middleSectionCount,
+                            lastSectionCount, minSegmentDuration,
+                            minProgressThreshold, segmentHelper);
+      }
+
+      // Append endDelay.
+      if (state.endDelay > 0) {
+        renderEndDelay(summaryEl, state,
+                       mainIterationStartTime, iterationCount, segmentHelper);
+      }
+    }
+
+    // Append negative delay (which overlap the animation).
+    if (state.delay < 0) {
+      segmentHelper.animation.effect.timing.fill = "both";
+      segmentHelper.asOriginalBehavior = false;
+      renderNegativeDelayHiddenProgress(summaryEl, state, minSegmentDuration,
+                                        minProgressThreshold, segmentHelper);
+    }
+    // Append negative endDelay (which overlap the animation).
+    if (state.iterationCount && state.endDelay < 0) {
+      if (segmentHelper.asOriginalBehavior) {
+        segmentHelper.animation.effect.timing.fill = "both";
+        segmentHelper.asOriginalBehavior = false;
+      }
+      renderNegativeEndDelayHiddenProgress(summaryEl, state,
+                                           minSegmentDuration,
+                                           minProgressThreshold,
+                                           segmentHelper);
+    }
+
+    // The animation name is displayed over the animation.
     createNode({
       parent: createNode({
         parent: this.containerEl,
         attributes: {
           "class": "name",
-          "title": this.getTooltipText(state),
-          // Place the name at the same position as the iterations, but make
-          // space for the negative delay if any.
-          "style": `left:${x + negativeDelayW}%;
-                    width:${iterationW - negativeDelayW}%;`
+          "title": this.getTooltipText(state)
         },
       }),
       textContent: state.name
     });
 
     // Delay.
     if (state.delay) {
       // Negative delays need to start at 0.
       createNode({
         parent: this.containerEl,
         attributes: {
-          "class": "delay" + (state.delay < 0 ? " negative" : ""),
-          "style": `left:${delayX}%;
-                    width:${delayW}%;`
+          "class": "delay"
+                   + (state.delay < 0 ? " negative" : " positive")
+                   + (state.fill === "both" ||
+                      state.fill === "backwards" ? " fill" : ""),
+          "style": `left:${ delayX }%; width:${ delayW }%;`
         }
       });
     }
 
     // endDelay
-    if (state.endDelay) {
+    if (state.iterationCount && state.endDelay) {
       createNode({
         parent: this.containerEl,
         attributes: {
-          "class": "end-delay" + (state.endDelay < 0 ? " negative" : ""),
-          "style": `left:${endDelayX}%;
-                    width:${endDelayW}%;`
+          "class": "end-delay"
+                   + (state.endDelay < 0 ? " negative" : " positive")
+                   + (state.fill === "both" ||
+                      state.fill === "forwards" ? " fill" : ""),
+          "style": `left:${ endDelayX }%; width:${ endDelayW }%;`
         }
       });
     }
   },
 
   getTooltipText: function (state) {
     let getTime = time => L10N.getFormatStr("player.timeLabel",
-                            L10N.numberWithDecimals(time / 1000, 2));
+                                            L10N.numberWithDecimals(time / 1000, 2));
 
     let text = "";
 
     // Adding the name.
     text += getFormattedAnimationTitle({state});
     text += "\n";
 
     // Adding the delay.
@@ -158,43 +290,68 @@ AnimationTimeBlock.prototype = {
     if (state.iterationStart !== 0) {
       let iterationStartTime = state.iterationStart * state.duration / 1000;
       text += L10N.getFormatStr("player.animationIterationStartLabel",
                                 state.iterationStart,
                                 L10N.numberWithDecimals(iterationStartTime, 2));
       text += "\n";
     }
 
+    // Adding the easing.
+    if (state.easing) {
+      text += L10N.getStr("player.animationEasingLabel") + " ";
+      text += state.easing;
+      text += "\n";
+    }
+
+    // Adding the fill mode.
+    if (state.fill) {
+      text += L10N.getStr("player.animationFillLabel") + " ";
+      text += state.fill;
+      text += "\n";
+    }
+
+    // Adding the direction mode.
+    if (state.direction) {
+      text += L10N.getStr("player.animationDirectionLabel") + " ";
+      text += state.direction;
+      text += "\n";
+    }
+
     // Adding the playback rate if it's different than 1.
     if (state.playbackRate !== 1) {
       text += L10N.getStr("player.animationRateLabel") + " ";
       text += state.playbackRate;
       text += "\n";
     }
 
     // Adding a note that the animation is running on the compositor thread if
     // needed.
     if (state.propertyState) {
       if (state.propertyState
-          .every(propState => propState.runningOnCompositor)) {
+               .every(propState => propState.runningOnCompositor)) {
         text += L10N.getStr("player.allPropertiesOnCompositorTooltip");
       } else if (state.propertyState
-                 .some(propState => propState.runningOnCompositor)) {
+                      .some(propState => propState.runningOnCompositor)) {
         text += L10N.getStr("player.somePropertiesOnCompositorTooltip");
       }
     } else if (state.isRunningOnCompositor) {
       text += L10N.getStr("player.runningOnCompositorTooltip");
     }
 
     return text;
   },
 
   onClick: function (e) {
     e.stopPropagation();
     this.emit("selected", this.animation);
+  },
+
+  get win() {
+    return this.containerEl.ownerDocument.defaultView;
   }
 };
 
 /**
  * Get a formatted title for this animation. This will be either:
  * "some-name", "some-name : CSS Transition", "some-name : CSS Animation",
  * "some-name : Script Animation", or "Script Animation", depending
  * if the server provides the type, what type it is and if the animation
@@ -211,8 +368,351 @@ function getFormattedAnimationTitle({sta
 
   // Script-generated animations may not have a name.
   if (state.type === "scriptanimation" && !state.name) {
     return L10N.getStr("timeline.scriptanimation.unnamedLabel");
   }
 
   return L10N.getFormatStr(`timeline.${state.type}.nameLabel`, state.name);
 }
+
+/**
+ * Render delay section.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Object} state - State of animation.
+ * @param {Object} segmentHelper - The object returned by getSegmentHelper.
+ */
+function renderDelay(parentEl, state, segmentHelper) {
+  const startSegment = segmentHelper.getSegment(0);
+  const endSegment = { x: state.delay, y: startSegment.y };
+  appendPathElement(parentEl, [startSegment, endSegment], "delay-path");
+}
+
+/**
+ * Render first iteration section.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Object} state - State of animation.
+ * @param {Number} mainIterationStartTime - Starting time of main iteration.
+ * @param {Number} firstSectionCount - Iteration count of first section.
+ * @param {Number} minSegmentDuration - Minimum segment duration.
+ * @param {Number} minProgressThreshold - Minimum progress threshold.
+ * @param {Object} segmentHelper - The object returned by getSegmentHelper.
+ */
+function renderFirstIteration(parentEl, state, mainIterationStartTime,
+                              firstSectionCount, minSegmentDuration,
+                              minProgressThreshold, segmentHelper) {
+  const startTime = mainIterationStartTime;
+  const endTime = startTime + firstSectionCount * state.duration;
+  const segments =
+    createPathSegments(startTime, endTime, minSegmentDuration,
+                       minProgressThreshold, segmentHelper);
+  appendPathElement(parentEl, segments, "iteration-path");
+}
+
+/**
+ * Render middle iterations section.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Object} state - State of animation.
+ * @param {Number} mainIterationStartTime - Starting time of main iteration.
+ * @param {Number} firstSectionCount - Iteration count of first section.
+ * @param {Number} middleSectionCount - Iteration count of middle section.
+ * @param {Number} minSegmentDuration - Minimum segment duration.
+ * @param {Number} minProgressThreshold - Minimum progress threshold.
+ * @param {Object} segmentHelper - The object returned by getSegmentHelper.
+ */
+function renderMiddleIterations(parentEl, state, mainIterationStartTime,
+                                firstSectionCount, middleSectionCount,
+                                minSegmentDuration, minProgressThreshold,
+                                segmentHelper) {
+  const offset = mainIterationStartTime + firstSectionCount * state.duration;
+  for (let i = 0; i < middleSectionCount; i++) {
+    // Get the path segments of each iteration.
+    const startTime = offset + i * state.duration;
+    const endTime = startTime + state.duration;
+    const segments =
+      createPathSegments(startTime, endTime, minSegmentDuration,
+                         minProgressThreshold, segmentHelper);
+    appendPathElement(parentEl, segments, "iteration-path");
+  }
+}
+
+/**
+ * Render last iteration section.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Object} state - State of animation.
+ * @param {Number} mainIterationStartTime - Starting time of main iteration.
+ * @param {Number} firstSectionCount - Iteration count of first section.
+ * @param {Number} middleSectionCount - Iteration count of middle section.
+ * @param {Number} lastSectionCount - Iteration count of last section.
+ * @param {Number} minSegmentDuration - Minimum segment duration.
+ * @param {Number} minProgressThreshold - Minimum progress threshold.
+ * @param {Object} segmentHelper - The object returned by getSegmentHelper.
+ */
+function renderLastIteration(parentEl, state, mainIterationStartTime,
+                             firstSectionCount, middleSectionCount,
+                             lastSectionCount, minSegmentDuration,
+                             minProgressThreshold, segmentHelper) {
+  const startTime = mainIterationStartTime +
+                      (firstSectionCount + middleSectionCount) * state.duration;
+  const endTime = startTime + lastSectionCount * state.duration;
+  const segments =
+    createPathSegments(startTime, endTime, minSegmentDuration,
+                       minProgressThreshold, segmentHelper);
+  appendPathElement(parentEl, segments, "iteration-path");
+}
+
+/**
+ * Render Infinity iterations.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Object} state - State of animation.
+ * @param {Number} mainIterationStartTime - Starting time of main iteration.
+ * @param {Number} firstSectionCount - Iteration count of first section.
+ * @param {Number} totalDuration - Displayed max duration.
+ * @param {Number} minSegmentDuration - Minimum segment duration.
+ * @param {Number} minProgressThreshold - Minimum progress threshold.
+ * @param {Object} segmentHelper - The object returned by getSegmentHelper.
+ */
+function renderInfinity(parentEl, state, mainIterationStartTime,
+                        firstSectionCount, totalDuration, minSegmentDuration,
+                        minProgressThreshold, segmentHelper) {
+  // Calculate the number of iterations to display,
+  // with a maximum of MAX_INFINITE_ANIMATIONS_ITERATIONS
+  let uncappedInfinityIterationCount =
+    (totalDuration - firstSectionCount * state.duration) / state.duration;
+  // If there is a small floating point error resulting in, e.g. 1.0000001
+  // ceil will give us 2 so round first.
+  uncappedInfinityIterationCount =
+    parseFloat(uncappedInfinityIterationCount.toPrecision(6));
+  const infinityIterationCount =
+    Math.min(MAX_INFINITE_ANIMATIONS_ITERATIONS,
+             Math.ceil(uncappedInfinityIterationCount));
+
+  // Append first full iteration path.
+  const firstStartTime =
+    mainIterationStartTime + firstSectionCount * state.duration;
+  const firstEndTime = firstStartTime + state.duration;
+  const firstSegments =
+    createPathSegments(firstStartTime, firstEndTime, minSegmentDuration,
+                       minProgressThreshold, segmentHelper);
+  appendPathElement(parentEl, firstSegments, "iteration-path infinity");
+
+  // Append other iterations. We can copy first segments.
+  const isAlternate = state.direction.match(/alternate/);
+  for (let i = 1; i < infinityIterationCount; i++) {
+    const startTime = firstStartTime + i * state.duration;
+    let segments;
+    if (isAlternate && i % 2) {
+      // Copy as reverse.
+      segments = firstSegments.map(segment => {
+        return { x: firstEndTime - segment.x + startTime, y: segment.y };
+      });
+    } else {
+      // Copy as is.
+      segments = firstSegments.map(segment => {
+        return { x: segment.x - firstStartTime + startTime, y: segment.y };
+      });
+    }
+    appendPathElement(parentEl, segments, "iteration-path infinity copied");
+  }
+}
+
+/**
+ * Render endDelay section.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Object} state - State of animation.
+ * @param {Number} mainIterationStartTime - Starting time of main iteration.
+ * @param {Number} iterationCount - Whole iteration count.
+ * @param {Object} segmentHelper - The object returned by getSegmentHelper.
+ */
+function renderEndDelay(parentEl, state,
+                        mainIterationStartTime, iterationCount, segmentHelper) {
+  const startTime = mainIterationStartTime + iterationCount * state.duration;
+  const startSegment = segmentHelper.getSegment(startTime);
+  const endSegment = { x: startTime + state.endDelay, y: startSegment.y };
+  appendPathElement(parentEl, [startSegment, endSegment], "enddelay-path");
+}
+
+/**
+ * Render forwards fill section.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Object} state - State of animation.
+ * @param {Number} mainIterationStartTime - Starting time of main iteration.
+ * @param {Number} iterationCount - Whole iteration count.
+ * @param {Number} totalDuration - Displayed max duration.
+ * @param {Object} segmentHelper - The object returned by getSegmentHelper.
+ */
+function renderForwardsFill(parentEl, state, mainIterationStartTime,
+                            iterationCount, totalDuration, segmentHelper) {
+  const startTime = mainIterationStartTime + iterationCount * state.duration +
+                      (state.endDelay > 0 ? state.endDelay : 0);
+  const startSegment = segmentHelper.getSegment(startTime);
+  const endSegment = { x: totalDuration, y: startSegment.y };
+  appendPathElement(parentEl, [startSegment, endSegment], "fill-forwards-path");
+}
+
+/**
+ * Render hidden progress of negative delay.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Object} state - State of animation.
+ * @param {Number} minSegmentDuration - Minimum segment duration.
+ * @param {Number} minProgressThreshold - Minimum progress threshold.
+ * @param {Object} segmentHelper - The object returned by getSegmentHelper.
+ */
+function renderNegativeDelayHiddenProgress(parentEl, state, minSegmentDuration,
+                                           minProgressThreshold,
+                                           segmentHelper) {
+  const startTime = state.delay;
+  const endTime = 0;
+  const segments =
+    createPathSegments(startTime, endTime, minSegmentDuration,
+                       minProgressThreshold, segmentHelper);
+  appendPathElement(parentEl, segments, "delay-path negative");
+}
+
+/**
+ * Render hidden progress of negative endDelay.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Object} state - State of animation.
+ * @param {Number} minSegmentDuration - Minimum segment duration.
+ * @param {Number} minProgressThreshold - Minimum progress threshold.
+ * @param {Object} segmentHelper - The object returned by getSegmentHelper.
+ */
+function renderNegativeEndDelayHiddenProgress(parentEl, state,
+                                              minSegmentDuration,
+                                              minProgressThreshold,
+                                              segmentHelper) {
+  const endTime = state.delay + state.iterationCount * state.duration;
+  const startTime = endTime + state.endDelay;
+  const segments =
+    createPathSegments(startTime, endTime, minSegmentDuration,
+                       minProgressThreshold, segmentHelper);
+  appendPathElement(parentEl, segments, "enddelay-path negative");
+}
+
+/**
+ * Get a helper function which returns the segment coord from given time.
+ * @param {Object} state - animation state
+ * @param {Object} win - window object
+ * @return {Object} A segmentHelper object that has the following properties:
+ * - animation: The script animation used to get the progress
+ * - endTime: The end time of the animation
+ * - asOriginalBehavior: The spec is that the progress of animation is changed
+ *                       if the time of setCurrentTime is during the endDelay.
+ *                       Likewise, in case the time is less than 0.
+ *                       If this flag is true, we prevent the time
+ *                       to make the same animation behavior as the original.
+ * - getSegment: Helper function that, given a time,
+ *               will calculate the progress through the dummy animation.
+ */
+function getSegmentHelper(state, win) {
+  // Create a dummy Animation timing data as the
+  // state object we're being passed in.
+  const timing = Object.assign({}, state, {
+    iterations: state.iterationCount ? state.iterationCount : Infinity
+  });
+
+  // Create a dummy Animation with the given timing.
+  const dummyAnimation =
+    new win.Animation(new win.KeyframeEffect(null, null, timing), null);
+
+  // Returns segment helper object.
+  return {
+    animation: dummyAnimation,
+    endTime: dummyAnimation.effect.getComputedTiming().endTime,
+    asOriginalBehavior: true,
+    getSegment: function (time) {
+      if (this.asOriginalBehavior) {
+        // If the given time is less than 0, returned progress is 0.
+        if (time < 0) {
+          return { x: time, y: 0 };
+        }
+        // Avoid to apply over endTime.
+        this.animation.currentTime = time < this.endTime ? time : this.endTime;
+      } else {
+        this.animation.currentTime = time;
+      }
+      const progress = this.animation.effect.getComputedTiming().progress;
+      return { x: time, y: Math.max(progress, 0) };
+    }
+  };
+}
+
+/**
+ * Create the path segments from given parameters.
+ * @param {Number} startTime - Starting time of animation.
+ * @param {Number} endTime - Ending time of animation.
+ * @param {Number} minSegmentDuration - Minimum segment duration.
+ * @param {Number} minProgressThreshold - Minimum progress threshold.
+ * @param {Object} segmentHelper - The object of getSegmentHelper.
+ * @return {Array} path segments -
+ *                 [{x: {Number} time, y: {Number} progress}, ...]
+ */
+function createPathSegments(startTime, endTime, minSegmentDuration,
+                            minProgressThreshold, segmentHelper) {
+  // If the duration is too short, early return.
+  if (endTime - startTime < minSegmentDuration) {
+    return [segmentHelper.getSegment(startTime),
+            segmentHelper.getSegment(endTime)];
+  }
+
+  // Otherwise, start creating segments.
+  let pathSegments = [];
+
+  // Append the segment for the startTime position.
+  const startTimeSegment = segmentHelper.getSegment(startTime);
+  pathSegments.push(startTimeSegment);
+  let previousSegment = startTimeSegment;
+
+  // Split the duration in equal intervals, and iterate over them.
+  // See the definition of DURATION_RESOLUTION for more information about this.
+  const interval = (endTime - startTime) / DURATION_RESOLUTION;
+  for (let index = 1; index <= DURATION_RESOLUTION; index++) {
+    // Create a segment for this interval.
+    const currentSegment =
+      segmentHelper.getSegment(startTime + index * interval);
+
+    // If the distance between the Y coordinate (the animation's progress) of
+    // the previous segment and the Y coordinate of the current segment is too
+    // large, then recurse with a smaller duration to get more details
+    // in the graph.
+    if (Math.abs(currentSegment.y - previousSegment.y) > minProgressThreshold) {
+      // Divide the current interval (excluding start and end bounds
+      // by adding/subtracting 1ms).
+      pathSegments = pathSegments.concat(
+        createPathSegments(previousSegment.x + 1, currentSegment.x - 1,
+                           minSegmentDuration, minProgressThreshold,
+                           segmentHelper));
+    }
+
+    pathSegments.push(currentSegment);
+    previousSegment = currentSegment;
+  }
+
+  return pathSegments;
+}
+
+/**
+ * Append path element.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Array} pathSegments - Path segments. Please see createPathSegments.
+ * @param {String} cls - Class name.
+ * @return {Element} path element.
+ */
+function appendPathElement(parentEl, pathSegments, cls) {
+  // Create path string.
+  let path = `M${ pathSegments[0].x },0`;
+  pathSegments.forEach(pathSegment => {
+    path += ` L${ pathSegment.x },${ pathSegment.y }`;
+  });
+  path += ` L${ pathSegments[pathSegments.length - 1].x },0 Z`;
+  // Append and return the path element.
+  return createNode({
+    parent: parentEl,
+    namespace: SVG_NS,
+    nodeType: "path",
+    attributes: {
+      "d": path,
+      "class": cls,
+      "vector-effect": "non-scaling-stroke",
+      "transform": "scale(1, -1)"
+    }
+  });
+}
--- a/devtools/client/animationinspector/test/browser.ini
+++ b/devtools/client/animationinspector/test/browser.ini
@@ -7,16 +7,17 @@ support-files =
   doc_frame_script.js
   doc_keyframes.html
   doc_modify_playbackRate.html
   doc_negative_animation.html
   doc_pseudo_elements.html
   doc_script_animation.html
   doc_simple_animation.html
   doc_multiple_animation_types.html
+  doc_timing_combination_animation.html
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/framework/test/shared-head.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/test-actor-registry.js
   !/devtools/client/shared/test/test-actor.js
 
@@ -51,16 +52,17 @@ skip-if = os == "linux" && !debug # Bug 
 [browser_animation_timeline_pause_button_01.js]
 [browser_animation_timeline_pause_button_02.js]
 [browser_animation_timeline_pause_button_03.js]
 [browser_animation_timeline_rate_selector.js]
 [browser_animation_timeline_rewind_button.js]
 [browser_animation_timeline_scrubber_exists.js]
 [browser_animation_timeline_scrubber_movable.js]
 [browser_animation_timeline_scrubber_moves.js]
+[browser_animation_timeline_setCurrentTime.js]
 [browser_animation_timeline_shows_delay.js]
 [browser_animation_timeline_shows_endDelay.js]
 [browser_animation_timeline_shows_iterations.js]
 [browser_animation_timeline_shows_time_info.js]
 [browser_animation_timeline_takes_rate_into_account.js]
 [browser_animation_timeline_ui.js]
 [browser_animation_toggle_button_resets_on_navigate.js]
 [browser_animation_toggle_button_toggles_animations.js]
--- a/devtools/client/animationinspector/test/browser_animation_timeline_iterationStart.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_iterationStart.js
@@ -16,17 +16,17 @@ add_task(function* () {
   for (let i = 0; i < timeBlockComponents.length; i++) {
     info(`Expand time block ${i} so its keyframes are visible`);
     yield clickOnAnimation(panel, i);
 
     info(`Check the state of time block ${i}`);
     let {containerEl, animation: {state}} = timeBlockComponents[i];
 
     checkAnimationTooltip(containerEl, state);
-    checkIterationBackground(containerEl, state);
+    checkProgressAtStartingTime(containerEl, state);
 
     // Get the first set of keyframes (there's only one animated property
     // anyway), and the first frame element from there, we're only interested in
     // its offset.
     let keyframeComponent = detailsComponents[i].keyframeComponents[0];
     let frameEl = keyframeComponent.keyframesEl.querySelector(".frame");
     checkKeyframeOffset(containerEl, frameEl, state);
   }
@@ -43,37 +43,24 @@ function checkAnimationTooltip(el, {iter
   }).replace(".", "\\.");
   let iterationStartString = iterationStart.toString().replace(".", "\\.");
 
   let regex = new RegExp("Iteration start: " + iterationStartString +
                          " \\(" + iterationStartTimeString + "s\\)");
   ok(title.match(regex), "The tooltip shows the expected iteration start");
 }
 
-function checkIterationBackground(el, {iterationCount, iterationStart}) {
-  info("Check the background-image used to display iterations is offset " +
-       "correctly to represent the iterationStart");
-
-  let iterationsEl = el.querySelector(".iterations");
-  let start = getIterationStartFromBackground(iterationsEl, iterationCount);
-  is(start, iterationStart % 1,
-     "The right background-position for iteration start");
-}
-
-function getIterationStartFromBackground(el, iterationCount) {
-  if (iterationCount == 1) {
-    let size = parseFloat(/([.\d]+)%/.exec(el.style.backgroundSize)[1]);
-    return 1 - size / 100;
-  }
-
-  let size = parseFloat(/([.\d]+)%/.exec(el.style.backgroundSize)[1]);
-  let position = parseFloat(/([-\d]+)%/.exec(el.style.backgroundPosition)[1]);
-  let iterationStartW = -position / size * (100 - size);
-  let rounded = Math.round(iterationStartW * 100);
-  return rounded / 10000;
+function checkProgressAtStartingTime(el, { iterationStart }) {
+  info("Check the progress of starting time");
+  const pathEl = el.querySelector(".iteration-path");
+  const pathSegList = pathEl.pathSegList;
+  const pathSeg = pathSegList.getItem(1);
+  const progress = pathSeg.y;
+  is(progress, iterationStart % 1,
+     `The progress at starting point should be ${ iterationStart % 1 }`);
 }
 
 function checkKeyframeOffset(timeBlockEl, frameEl, {iterationStart}) {
   info("Check that the first keyframe is offset correctly");
 
   let start = getIterationStartFromLeft(frameEl);
   is(start, iterationStart % 1, "The frame offset for iteration start");
 }
new file mode 100644
--- /dev/null
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_setCurrentTime.js
@@ -0,0 +1,88 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Animation.currentTime ignores neagtive delay and positive/negative endDelay
+// during fill-mode, even if they are set.
+// For example, when the animation timing is
+// { duration: 1000, iterations: 1, endDelay: -500, easing: linear },
+// the animation progress is 0.5 at 700ms because the progress stops as 0.5 at
+// 500ms in original animation. However, if you set as
+// animation.currentTime = 700 manually, the progress will be 0.7.
+// So we modify setCurrentTime method since
+// AnimationInspector should re-produce same as original animation.
+// In these tests,
+// we confirm the behavior of setCurrentTime by delay and endDelay.
+
+add_task(function* () {
+  yield addTab(URL_ROOT + "doc_timing_combination_animation.html");
+  const { panel, controller } = yield openAnimationInspector();
+
+  yield clickTimelinePlayPauseButton(panel);
+
+  const timelineComponent = panel.animationsTimelineComponent;
+  const timeBlockComponents = timelineComponent.timeBlocks;
+
+  // Test -5000ms.
+  let time = -5000;
+  yield controller.setCurrentTimeAll(time, true);
+  for (let i = 0; i < timeBlockComponents.length; i++) {
+    yield timeBlockComponents[i].animation.refreshState();
+    const state = yield timeBlockComponents[i].animation.state;
+    info(`Check the state at ${ time }ms with `
+         + `delay:${ state.delay } and endDelay:${ state.endDelay }`);
+    is(state.currentTime, 0,
+       `The currentTime should be 0 at setCurrentTime(${ time })`);
+  }
+
+  // Test 10000ms.
+  time = 10000;
+  yield controller.setCurrentTimeAll(time, true);
+  for (let i = 0; i < timeBlockComponents.length; i++) {
+    yield timeBlockComponents[i].animation.refreshState();
+    const state = yield timeBlockComponents[i].animation.state;
+    info(`Check the state at ${ time }ms with `
+         + `delay:${ state.delay } and endDelay:${ state.endDelay }`);
+    const expected = state.delay < 0 ? 0 : time;
+    is(state.currentTime, expected,
+       `The currentTime should be ${ expected } at setCurrentTime(${ time }).`
+       + ` delay: ${ state.delay } and endDelay: ${ state.endDelay }`);
+  }
+
+  // Test 60000ms.
+  time = 60000;
+  yield controller.setCurrentTimeAll(time, true);
+  for (let i = 0; i < timeBlockComponents.length; i++) {
+    yield timeBlockComponents[i].animation.refreshState();
+    const state = yield timeBlockComponents[i].animation.state;
+    info(`Check the state at ${ time }ms with `
+         + `delay:${ state.delay } and endDelay:${ state.endDelay }`);
+    const expected = state.delay < 0 ? time + state.delay : time;
+    is(state.currentTime, expected,
+       `The currentTime should be ${ expected } at setCurrentTime(${ time }).`
+       + ` delay: ${ state.delay } and endDelay: ${ state.endDelay }`);
+  }
+
+  // Test 150000ms.
+  time = 150000;
+  yield controller.setCurrentTimeAll(time, true);
+  for (let i = 0; i < timeBlockComponents.length; i++) {
+    yield timeBlockComponents[i].animation.refreshState();
+    const state = yield timeBlockComponents[i].animation.state;
+    info(`Check the state at ${ time }ms with `
+         + `delay:${ state.delay } and endDelay:${ state.endDelay }`);
+    const currentTime = state.delay < 0 ? time + state.delay : time;
+    const endTime =
+      state.delay + state.iterationCount * state.duration + state.endDelay;
+    const expected =
+      state.endDelay < 0 && state.fill === "both" && currentTime > endTime
+      ? endTime : currentTime;
+    is(state.currentTime, expected,
+       `The currentTime should be ${ expected } at setCurrentTime(${ time }).`
+       + ` delay: ${ state.delay } and endDelay: ${ state.endDelay }`);
+  }
+});
--- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_delay.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_delay.js
@@ -14,42 +14,83 @@ requestLongerTimeout(2);
 add_task(function* () {
   yield addTab(URL_ROOT + "doc_simple_animation.html");
   let {inspector, panel} = yield openAnimationInspector();
 
   info("Selecting a delayed animated node");
   yield selectNodeAndWaitForAnimations(".delayed", inspector);
   let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
   checkDelayAndName(timelineEl, true);
+  let animationEl = timelineEl.querySelector(".animation");
+  let state = panel.animationsTimelineComponent.timeBlocks[0].animation.state;
+  checkPath(animationEl, state);
 
   info("Selecting a no-delay animated node");
   yield selectNodeAndWaitForAnimations(".animated", inspector);
   checkDelayAndName(timelineEl, false);
+  animationEl = timelineEl.querySelector(".animation");
+  state = panel.animationsTimelineComponent.timeBlocks[0].animation.state;
+  checkPath(animationEl, state);
 
   info("Selecting a negative-delay animated node");
   yield selectNodeAndWaitForAnimations(".negative-delay", inspector);
   checkDelayAndName(timelineEl, true);
+  animationEl = timelineEl.querySelector(".animation");
+  state = panel.animationsTimelineComponent.timeBlocks[0].animation.state;
+  checkPath(animationEl, state);
 });
 
 function checkDelayAndName(timelineEl, hasDelay) {
   let delay = timelineEl.querySelector(".delay");
 
   is(!!delay, hasDelay, "The timeline " +
                         (hasDelay ? "contains" : "does not contain") +
                         " a delay element, as expected");
 
   if (hasDelay) {
-    let name = timelineEl.querySelector(".name");
     let targetNode = timelineEl.querySelector(".target");
 
     // Check that the delay element does not cause the timeline to overflow.
     let delayLeft = Math.round(delay.getBoundingClientRect().x);
     let sidebarWidth = Math.round(targetNode.getBoundingClientRect().width);
     ok(delayLeft >= sidebarWidth,
        "The delay element isn't displayed over the sidebar");
-
-    // Check that the delay is not displayed on top of the name.
-    let delayRight = Math.round(delay.getBoundingClientRect().right);
-    let nameLeft = Math.round(name.getBoundingClientRect().left);
-    ok(delayRight <= nameLeft,
-       "The delay element does not span over the name element");
   }
 }
+
+function checkPath(animationEl, state) {
+  // Check existance of delay path.
+  const delayPathEl = animationEl.querySelector(".delay-path");
+  if (!state.iterationCount && state.delay < 0) {
+    // Infinity
+    ok(!delayPathEl, "The delay path for Infinity should not exist");
+    return;
+  }
+  if (state.delay === 0) {
+    ok(!delayPathEl, "The delay path for zero delay should not exist");
+    return;
+  }
+  ok(delayPathEl, "The delay path should exist");
+
+  // Check delay path coordinates.
+  const pathSegList = delayPathEl.pathSegList;
+  const startingPathSeg = pathSegList.getItem(0);
+  const endingPathSeg = pathSegList.getItem(pathSegList.numberOfItems - 2);
+  if (state.delay < 0) {
+    ok(delayPathEl.classList.contains("negative"),
+       "The delay path should have 'negative' class");
+    const startingX = state.delay;
+    const endingX = 0;
+    is(startingPathSeg.x, startingX,
+       `The x of starting point should be ${ startingX }`);
+    is(endingPathSeg.x, endingX,
+       `The x of ending point should be ${ endingX }`);
+  } else {
+    ok(!delayPathEl.classList.contains("negative"),
+       "The delay path should not have 'negative' class");
+    const startingX = 0;
+    const endingX = state.delay;
+    is(startingPathSeg.x, startingX,
+       `The x of starting point should be ${ startingX }`);
+    is(endingPathSeg.x, endingX,
+       `The x of ending point should be ${ endingX }`);
+  }
+}
--- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_endDelay.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_endDelay.js
@@ -15,18 +15,21 @@ add_task(function* () {
   yield addTab(URL_ROOT + "doc_end_delay.html");
   let {inspector, panel} = yield openAnimationInspector();
 
   let selectors = ["#target1", "#target2", "#target3", "#target4"];
   for (let i = 0; i < selectors.length; i++) {
     let selector = selectors[i];
     yield selectNode(selector, inspector);
     let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
-    let animationEl = timelineEl.querySelectorAll(".animation")[0];
+    let animationEl = timelineEl.querySelector(".animation");
     checkEndDelayAndName(animationEl);
+    const state =
+      panel.animationsTimelineComponent.timeBlocks[0].animation.state;
+    checkPath(animationEl, state);
   }
 });
 
 function checkEndDelayAndName(animationEl) {
   let endDelay = animationEl.querySelector(".end-delay");
   let name = animationEl.querySelector(".name");
   let targetNode = animationEl.querySelector(".target");
 
@@ -37,8 +40,39 @@ function checkEndDelayAndName(animationE
      "The endDelay element isn't displayed over the sidebar");
 
   // Check that the endDelay is not displayed on top of the name.
   let endDelayRight = Math.round(endDelay.getBoundingClientRect().right);
   let nameLeft = Math.round(name.getBoundingClientRect().left);
   ok(endDelayRight >= nameLeft,
      "The endDelay element does not span over the name element");
 }
+
+function checkPath(animationEl, state) {
+  // Check existance of enddelay path.
+  const endDelayPathEl = animationEl.querySelector(".enddelay-path");
+  ok(endDelayPathEl, "The endDelay path should exist");
+
+  // Check enddelay path coordinates.
+  const pathSegList = endDelayPathEl.pathSegList;
+  const startingPathSeg = pathSegList.getItem(0);
+  const endingPathSeg = pathSegList.getItem(pathSegList.numberOfItems - 2);
+  if (state.endDelay < 0) {
+    ok(endDelayPathEl.classList.contains("negative"),
+       "The endDelay path should have 'negative' class");
+    const endingX = state.delay + state.iterationCount * state.duration;
+    const startingX = endingX + state.endDelay;
+    is(startingPathSeg.x, startingX,
+       `The x of starting point should be ${ startingX }`);
+    is(endingPathSeg.x, endingX,
+       `The x of ending point should be ${ endingX }`);
+  } else {
+    ok(!endDelayPathEl.classList.contains("negative"),
+       "The endDelay path should not have 'negative' class");
+    const startingX =
+      state.delay + state.iterationCount * state.duration;
+    const endingX = startingX + state.endDelay;
+    is(startingPathSeg.x, startingX,
+       `The x of starting point should be ${ startingX }`);
+    is(endingPathSeg.x, endingX,
+       `The x of ending point should be ${ endingX }`);
+  }
+}
--- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_iterations.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_iterations.js
@@ -12,41 +12,36 @@ requestLongerTimeout(2);
 add_task(function* () {
   yield addTab(URL_ROOT + "doc_simple_animation.html");
   let {inspector, panel} = yield openAnimationInspector();
 
   info("Selecting the test node");
   yield selectNodeAndWaitForAnimations(".delayed", inspector);
 
   info("Getting the animation element from the panel");
-  let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
+  const timelineComponent = panel.animationsTimelineComponent;
+  const timelineEl = timelineComponent.rootWrapperEl;
   let animation = timelineEl.querySelector(".time-block");
-  let iterations = animation.querySelector(".iterations");
-
-  // Iterations are rendered with a repeating linear-gradient, so we need to
-  // calculate how many iterations are represented by looking at the background
-  // size.
-  let iterationCount = getIterationCountFromBackground(iterations);
+  // Get iteration count from summary graph path.
+  let iterationCount = getIterationCount(animation);
 
   is(iterationCount, 10,
      "The animation timeline contains the right number of iterations");
-  ok(!iterations.classList.contains("infinite"),
-     "The iteration element doesn't have the infinite class");
+  ok(!animation.querySelector(".infinity"),
+     "The summary graph does not have any elements "
+     + " that have infinity class");
 
   info("Selecting another test node with an infinite animation");
   yield selectNodeAndWaitForAnimations(".animated", inspector);
 
   info("Getting the animation element from the panel again");
   animation = timelineEl.querySelector(".time-block");
-  iterations = animation.querySelector(".iterations");
-
-  iterationCount = getIterationCountFromBackground(iterations);
+  iterationCount = getIterationCount(animation);
 
   is(iterationCount, 1,
-     "The animation timeline contains just one iteration");
-  ok(iterations.classList.contains("infinite"),
-     "The iteration element has the infinite class");
+     "The animation timeline contains one iteration");
+  ok(animation.querySelector(".infinity"),
+     "The summary graph has an element that has infinity class");
 });
 
-function getIterationCountFromBackground(el) {
-  let backgroundSize = parseFloat(el.style.backgroundSize.split(" ")[0]);
-  return Math.round(100 / backgroundSize);
+function getIterationCount(timeblockEl) {
+  return timeblockEl.querySelectorAll(".iteration-path").length;
 }
--- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_time_info.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_time_info.js
@@ -30,12 +30,21 @@ add_task(function* () {
     if (controller.animationPlayers[i].state.endDelay) {
       ok(title.match(/End delay: [\d.-]+s/), "The tooltip shows the endDelay");
     }
     if (controller.animationPlayers[i].state.iterationCount !== 1) {
       ok(title.match(/Repeats: /), "The tooltip shows the iterations");
     } else {
       ok(!title.match(/Repeats: /), "The tooltip doesn't show the iterations");
     }
+    if (controller.animationPlayers[i].state.easing) {
+      ok(title.match(/Easing: /), "The tooltip shows the easing");
+    }
+    if (controller.animationPlayers[i].state.fill) {
+      ok(title.match(/Fill: /), "The tooltip shows the fill");
+    }
+    if (controller.animationPlayers[i].state.direction) {
+      ok(title.match(/Direction: /), "The tooltip shows the direction");
+    }
     ok(!title.match(/Iteration start:/),
       "The tooltip doesn't show the iteration start");
   });
 });
--- a/devtools/client/animationinspector/test/browser_animation_timeline_takes_rate_into_account.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_takes_rate_into_account.js
@@ -19,25 +19,63 @@ add_task(function* () {
   let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
 
   let timeBlocks = timelineEl.querySelectorAll(".time-block");
   is(timeBlocks.length, 2, "2 animations are displayed");
 
   info("The first animation has its rate set to 1, let's measure it");
 
   let el = timeBlocks[0];
-  let duration = parseInt(el.querySelector(".iterations").style.width, 10);
+  let duration = getDuration(el.querySelector("path"));
   let delay = parseInt(el.querySelector(".delay").style.width, 10);
 
   info("The second animation has its rate set to 2, so should be shorter");
 
   let el2 = timeBlocks[1];
-  let duration2 = parseInt(el2.querySelector(".iterations").style.width, 10);
+  let duration2 = getDuration(el2.querySelector("path"));
   let delay2 = parseInt(el2.querySelector(".delay").style.width, 10);
 
   // The width are calculated by the animation-inspector dynamically depending
   // on the size of the panel, and therefore depends on the test machine/OS.
   // Let's not try to be too precise here and compare numbers.
   let durationDelta = (2 * duration2) - duration;
   ok(durationDelta <= 1, "The duration width is correct");
   let delayDelta = (2 * delay2) - delay;
   ok(delayDelta <= 1, "The delay width is correct");
 });
+
+function getDuration(pathEl) {
+  const pathSegList = pathEl.pathSegList;
+  // Find the index of starting iterations.
+  let startingIterationIndex = 0;
+  const firstPathSeg = pathSegList.getItem(1);
+  for (let i = 2, n = pathSegList.numberOfItems - 2; i < n; i++) {
+    // Changing point of the progress acceleration is the time.
+    const pathSeg = pathSegList.getItem(i);
+    if (firstPathSeg.y != pathSeg.y) {
+      startingIterationIndex = i;
+      break;
+    }
+  }
+  // Find the index of ending iterations.
+  let endingIterationIndex = 0;
+  let previousPathSegment = pathSegList.getItem(startingIterationIndex);
+  for (let i = startingIterationIndex + 1, n = pathSegList.numberOfItems - 2;
+       i < n; i++) {
+    // Find forwards fill-mode.
+    const pathSeg = pathSegList.getItem(i);
+    if (previousPathSegment.y == pathSeg.y) {
+      endingIterationIndex = i;
+      break;
+    }
+    previousPathSegment = pathSeg;
+  }
+  if (endingIterationIndex) {
+    // Not forwards fill-mode
+    endingIterationIndex = pathSegList.numberOfItems - 2;
+  }
+  // Return the distance of starting and ending
+  const startingIterationPathSegment =
+    pathSegList.getItem(startingIterationIndex);
+  const endingIterationPathSegment =
+    pathSegList.getItem(startingIterationIndex);
+  return endingIterationPathSegment.x - startingIterationPathSegment.x;
+}
--- a/devtools/client/animationinspector/test/browser_animation_timeline_ui.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_ui.js
@@ -32,12 +32,12 @@ add_task(function* () {
 
     ok(animationEl.querySelector(".target"),
        "The animated node target element is in the DOM");
     ok(animationEl.querySelector(".time-block"),
        "The timeline element is in the DOM");
     is(animationEl.querySelector(".name").textContent,
        animation.state.name,
        "The name on the timeline is correct");
-    ok(animationEl.querySelector(".iterations"),
-       "The timeline has iterations displayed");
+    ok(animationEl.querySelector("svg path"),
+       "The timeline has svg and path element as summary graph");
   }
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/animationinspector/test/doc_timing_combination_animation.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8">
+    <style>
+    div {
+      display: inline-block;
+      width: 100px;
+      height: 100px;
+      background-color: lime;
+    }
+    </style>
+  </head>
+  <body>
+    <script>
+    "use strict";
+
+    const delayList = [0, 50000, -50000];
+    const endDelayList = [0, 50000, -50000];
+
+    delayList.forEach(delay => {
+      endDelayList.forEach(endDelay => {
+        const el = document.createElement("div");
+        document.body.appendChild(el);
+        el.animate({ opacity: [0, 1] },
+                   { duration: 200000,
+                     iterations: 1,
+                     fill: "both",
+                     delay: delay,
+                     endDelay: endDelay });
+      });
+    });
+    </script>
+  </body>
+</html>
--- a/devtools/client/animationinspector/utils.js
+++ b/devtools/client/animationinspector/utils.js
@@ -22,25 +22,29 @@ const MILLIS_TIME_FORMAT_MAX_DURATION = 
 /**
  * DOM node creation helper function.
  * @param {Object} Options to customize the node to be created.
  * - nodeType {String} Optional, defaults to "div",
  * - attributes {Object} Optional attributes object like
  *   {attrName1:value1, attrName2: value2, ...}
  * - parent {DOMNode} Mandatory node to append the newly created node to.
  * - textContent {String} Optional text for the node.
+ * - namespace {String} Optional namespace
  * @return {DOMNode} The newly created node.
  */
 function createNode(options) {
   if (!options.parent) {
     throw new Error("Missing parent DOMNode to create new node");
   }
 
   let type = options.nodeType || "div";
-  let node = options.parent.ownerDocument.createElement(type);
+  let node =
+    options.namespace
+    ? options.parent.ownerDocument.createElementNS(options.namespace, type)
+    : options.parent.ownerDocument.createElement(type);
 
   for (let name in options.attributes || {}) {
     let value = options.attributes[name];
     node.setAttribute(name, value);
   }
 
   if (options.textContent) {
     node.textContent = options.textContent;
@@ -259,37 +263,12 @@ var TimeScale = {
     // The width of the endDelay.
     let endDelayW = this.durationToDistance(Math.abs(endDelay) / rate);
     // The start position of the endDelay.
     let endDelayX = endDelay < 0 ? x + iterationW - endDelayW
                                  : x + iterationW;
 
     return {x, w, iterationW, delayX, delayW, negativeDelayW,
             endDelayX, endDelayW};
-  },
-
-  /**
-   * Given an animation, get the background data for .iterations element.
-   * This background represents iterationCount and iterationStart.
-   * Returns three properties.
-   * 1. size: x of background-size (%)
-   * 2. position: x of background-position (%)
-   * 3. repeat: background-repeat (string)
-   */
-  getIterationsBackgroundData: function ({state}) {
-    let iterationCount = state.iterationCount || 1;
-    let iterationStartW = state.iterationStart % 1 * 100;
-    let background = {};
-    if (iterationCount == 1) {
-      background.size = 100 - iterationStartW;
-      background.position = 0;
-      background.repeat = "no-repeat";
-    } else {
-      background.size = 1 / iterationCount * 100;
-      background.position = -iterationStartW * background.size /
-                              (100 - background.size);
-      background.repeat = "repeat-x";
-    }
-    return background;
   }
 };
 
 exports.TimeScale = TimeScale;
--- a/devtools/client/debugger/content/views/sources-view.js
+++ b/devtools/client/debugger/content/views/sources-view.js
@@ -186,17 +186,16 @@ SourcesView.prototype = Heritage.extend(
   _addCommands: function () {
     XULUtils.addCommands(this._commandset, {
       addBreakpointCommand: e => this._onCmdAddBreakpoint(e),
       addConditionalBreakpointCommand: e => this._onCmdAddConditionalBreakpoint(e),
       blackBoxCommand: () => this.toggleBlackBoxing(),
       unBlackBoxButton: () => this._onStopBlackBoxing(),
       prettyPrintCommand: () => this.togglePrettyPrint(),
       toggleBreakpointsCommand: () =>this.toggleBreakpoints(),
-      togglePromiseDebuggerCommand: () => this.togglePromiseDebugger(),
       nextSourceCommand: () => this.selectNextItem(),
       prevSourceCommand: () => this.selectPrevItem()
     });
   },
 
   /**
    * Sets the preferred location to be selected in this sources container.
    * @param string aUrl
@@ -609,27 +608,16 @@ SourcesView.prototype = Heritage.extend(
       this._toggleBreakpointsButton.setAttribute("checked", true);
       this._onDisableAll();
     } else {
       this._toggleBreakpointsButton.removeAttribute("checked");
       this._onEnableAll();
     }
   },
 
-  togglePromiseDebugger: function () {
-    if (Prefs.promiseDebuggerEnabled) {
-      let promisePane = this.DebuggerView._promisePane;
-      promisePane.hidden = !promisePane.hidden;
-
-      if (!this.DebuggerView._promiseDebuggerIframe) {
-        this.DebuggerView._initializePromiseDebugger();
-      }
-    }
-  },
-
   hidePrettyPrinting: function () {
     this._prettyPrintButton.style.display = "none";
 
     if (this._blackBoxButton.style.display === "none") {
       let sep = document.querySelector("#sources-toolbar .devtools-separator");
       sep.style.display = "none";
     }
   },
--- a/devtools/client/debugger/debugger-controller.js
+++ b/devtools/client/debugger/debugger-controller.js
@@ -1206,17 +1206,16 @@ var Prefs = new PrefsHelper("devtools", 
   pauseOnExceptions: ["Bool", "debugger.pause-on-exceptions"],
   ignoreCaughtExceptions: ["Bool", "debugger.ignore-caught-exceptions"],
   sourceMapsEnabled: ["Bool", "debugger.source-maps-enabled"],
   prettyPrintEnabled: ["Bool", "debugger.pretty-print-enabled"],
   autoPrettyPrint: ["Bool", "debugger.auto-pretty-print"],
   workersEnabled: ["Bool", "debugger.workers"],
   editorTabSize: ["Int", "editor.tabsize"],
   autoBlackBox: ["Bool", "debugger.auto-black-box"],
-  promiseDebuggerEnabled: ["Bool", "debugger.promise"]
 });
 
 /**
  * Convenient way of emitting events from the panel window.
  */
 EventEmitter.decorate(this);
 
 /**
--- a/devtools/client/debugger/debugger-view.js
+++ b/devtools/client/debugger/debugger-view.js
@@ -18,18 +18,16 @@ const FUNCTION_SEARCH_ACTION_MAX_DELAY =
 const SEARCH_GLOBAL_FLAG = "!";
 const SEARCH_FUNCTION_FLAG = "@";
 const SEARCH_TOKEN_FLAG = "#";
 const SEARCH_LINE_FLAG = ":";
 const SEARCH_VARIABLE_FLAG = "*";
 const SEARCH_AUTOFILL = [SEARCH_GLOBAL_FLAG, SEARCH_FUNCTION_FLAG, SEARCH_TOKEN_FLAG];
 const TOOLBAR_ORDER_POPUP_POSITION = "topcenter bottomleft";
 const RESIZE_REFRESH_RATE = 50; // ms
-const PROMISE_DEBUGGER_URL =
-  "chrome://devtools/content/promisedebugger/promise-debugger.xhtml";
 
 const EventListenersView = require("./content/views/event-listeners-view");
 const SourcesView = require("./content/views/sources-view");
 var actions = Object.assign(
   {},
   require("./content/globalActions"),
   require("./content/actions/breakpoints"),
   require("./content/actions/sources"),
@@ -130,17 +128,16 @@ var DebuggerView = {
     this.Filtering.destroy();
     this.StackFrames.destroy();
     this.StackFramesClassicList.destroy();
     this.Sources.destroy();
     this.VariableBubble.destroy();
     this.WatchExpressions.destroy();
     this.EventListeners.destroy();
     this.GlobalSearch.destroy();
-    this._destroyPromiseDebugger();
     this._destroyPanes();
 
     this.editor.destroy();
     this.editor = null;
 
     this.controller.dispatch(actions.removeAllBreakpoints());
   },
 
@@ -150,17 +147,16 @@ var DebuggerView = {
   _initializePanes: function () {
     dumpn("Initializing the DebuggerView panes");
 
     this._body = document.getElementById("body");
     this._editorDeck = document.getElementById("editor-deck");
     this._workersAndSourcesPane = document.getElementById("workers-and-sources-pane");
     this._instrumentsPane = document.getElementById("instruments-pane");
     this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle");
-    this._promisePane = document.getElementById("promise-debugger-pane");
 
     this.showEditor = this.showEditor.bind(this);
     this.showBlackBoxMessage = this.showBlackBoxMessage.bind(this);
     this.showProgressBar = this.showProgressBar.bind(this);
 
     this._onTabSelect = this._onInstrumentsPaneTabSelect.bind(this);
     this._instrumentsPane.tabpanels.addEventListener("select", this._onTabSelect);
 
@@ -186,17 +182,16 @@ var DebuggerView = {
     if (gHostType != "side") {
       Prefs.workersAndSourcesWidth = this._workersAndSourcesPane.getAttribute("width");
       Prefs.instrumentsWidth = this._instrumentsPane.getAttribute("width");
     }
 
     this._workersAndSourcesPane = null;
     this._instrumentsPane = null;
     this._instrumentsPaneToggleButton = null;
-    this._promisePane = null;
   },
 
   /**
    * Initializes the VariablesView instance and attaches a controller.
    */
   _initializeVariablesView: function () {
     this.Variables = new VariablesView(document.getElementById("variables"), {
       searchPlaceholder: L10N.getStr("emptyVariablesFilterText"),
@@ -234,51 +229,16 @@ var DebuggerView = {
         case "properties":
           window.emit(EVENTS.FETCHED_PROPERTIES);
           break;
       }
     });
   },
 
   /**
-   * Initialie the Promise Debugger instance.
-   */
-  _initializePromiseDebugger: function () {
-    let iframe = this._promiseDebuggerIframe = document.createElement("iframe");
-    iframe.setAttribute("flex", 1);
-
-    let onLoad = (event) => {
-      iframe.removeEventListener("load", onLoad, true);
-
-      let doc = event.target;
-      let win = doc.defaultView;
-
-      win.setPanel(DebuggerController._toolbox);
-    };
-
-    iframe.addEventListener("load", onLoad, true);
-    iframe.setAttribute("src", PROMISE_DEBUGGER_URL);
-    this._promisePane.appendChild(iframe);
-  },
-
-  /**
-   * Destroy the Promise Debugger instance.
-   */
-  _destroyPromiseDebugger: function () {
-    if (this._promiseDebuggerIframe) {
-      this._promiseDebuggerIframe.contentWindow.destroy();
-
-      this._promiseDebuggerIframe.parentNode.removeChild(
-        this._promiseDebuggerIframe);
-
-      this._promiseDebuggerIframe = null;
-    }
-  },
-
-  /**
    * Initializes the Editor instance.
    *
    * @param function aCallback
    *        Called after the editor finishes initializing.
    */
   _initializeEditor: function (callback) {
     dumpn("Initializing the DebuggerView editor");
 
--- a/devtools/client/debugger/debugger.xul
+++ b/devtools/client/debugger/debugger.xul
@@ -336,21 +336,16 @@
                                  command="prettyPrintCommand"
                                  hidden="true"/>
                 </hbox>
                 <vbox class="devtools-separator"/>
                 <toolbarbutton id="toggle-breakpoints"
                                class="devtools-toolbarbutton"
                                tooltiptext="&debuggerUI.sources.toggleBreakpoints;"
                                command="toggleBreakpointsCommand"/>
-                <toolbarbutton id="toggle-promise-debugger"
-                               class="devtools-toolbarbutton"
-                               tooltiptext="&debuggerUI.sources.togglePromiseDebugger;"
-                               command="togglePromiseDebuggerCommand"
-                               hidden="true"/>
               </toolbar>
             </tabpanel>
             <tabpanel id="callstack-tabpanel">
               <vbox id="callstack-list" flex="1"/>
             </tabpanel>
           </tabpanels>
         </tabbox>
       </vbox>
@@ -398,22 +393,16 @@
                 <vbox id="variables" flex="1"/>
               </tabpanel>
               <tabpanel id="events-tabpanel">
                 <vbox id="event-listeners" flex="1"/>
               </tabpanel>
             </tabpanels>
           </tabbox>
         </hbox>
-        <splitter id="editor-and-promise-splitter"
-                class="devtools-horizontal-splitter"/>
-        <vbox id="promise-debugger-pane"
-              flex="1"
-              hidden="true">
-        </vbox>
       </vbox>
       <splitter id="vertical-layout-splitter"
                 class="devtools-horizontal-splitter"/>
       <hbox id="vertical-layout-panes-container">
         <splitter id="sources-and-instruments-splitter"
                   class="devtools-side-splitter"/>
         <!-- The sources-pane and instruments-pane will be moved in this
              container if the toolbox's host requires it. -->
--- a/devtools/client/debugger/test/mochitest/browser_dbg_host-layout.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_host-layout.js
@@ -120,49 +120,43 @@ function testHost(aPanel, aHostType, aLa
   is(gView._body.getAttribute("layout"), aLayoutType,
     "The default host type is present as an attribute on the panel's body.");
 
   if (aLayoutType == "horizontal") {
     is(gView._workersAndSourcesPane.parentNode.id, "debugger-widgets",
       "The workers and sources pane's parent is correct for the horizontal layout.");
     is(gView._instrumentsPane.parentNode.id, "editor-and-instruments-pane",
       "The instruments pane's parent is correct for the horizontal layout.");
-    is(gDebugger.document.getElementById("promise-debugger-pane").parentNode.id,
-      "debugger-content",
-      "The promise pane's parent is correct for the horizontal layout.");
   } else {
     is(gView._workersAndSourcesPane.parentNode.id, "vertical-layout-panes-container",
       "The workers and sources pane's parent is correct for the vertical layout.");
     is(gView._instrumentsPane.parentNode.id, "vertical-layout-panes-container",
       "The instruments pane's parent is correct for the vertical layout.");
-    is(gDebugger.document.getElementById("promise-debugger-pane").parentNode.id,
-      "debugger-content",
-      "The promise pane's parent is correct for the horizontal layout.");
   }
 
   let widgets = gDebugger.document.getElementById("debugger-widgets").childNodes;
   let content = gDebugger.document.getElementById("debugger-content").childNodes;
   let editorPane =
     gDebugger.document.getElementById("editor-and-instruments-pane").childNodes;
   let verticalPane =
     gDebugger.document.getElementById("vertical-layout-panes-container").childNodes;
 
   if (aLayoutType == "horizontal") {
     is(widgets.length, 5, // 1 pane, 1 content box, 2 splitters and a phantom box.
       "Found the correct number of debugger widgets.");
-    is(content.length, 3, // 2 panes, 1 splitter.
+    is(content.length, 1, // 1 pane
       "Found the correct number of debugger content.");
     is(editorPane.length, 3, // 2 panes, 1 splitter
       "Found the correct number of debugger panes.");
     is(verticalPane.length, 1, // 1 lonely splitter in the phantom box.
       "Found the correct number of debugger panes.");
   } else {
     is(widgets.length, 4, // 1 content box, 2 splitters and a phantom box.
       "Found the correct number of debugger widgets.");
-    is(content.length, 3, // 2 panes, 1 splitter.
+    is(content.length, 1, // 1 pane
       "Found the correct number of debugger content.");
     is(editorPane.length, 2, // 1 pane, 1 splitter
       "Found the correct number of debugger panes.");
     is(verticalPane.length, 3, // 2 panes and 1 splitter in the phantom box.
       "Found the correct number of debugger panes.");
   }
 }
 
--- a/devtools/client/framework/test/browser_toolbox_select_event.js
+++ b/devtools/client/framework/test/browser_toolbox_select_event.js
@@ -38,39 +38,64 @@ add_task(function* () {
   yield testSelectEvent("inspector");
   yield testSelectEvent("webconsole");
   yield testSelectEvent("styleeditor");
   yield testSelectEvent("inspector");
   yield testSelectEvent("webconsole");
   yield testSelectEvent("styleeditor");
   yield toolbox.destroy();
 
+  yield testSelectToolRace();
+
   /**
    * Assert that selecting the given toolId raises a select event
    * @param {toolId} Id of the tool to test
    */
-  function testSelectEvent(toolId) {
-    return new Promise(resolve => {
-      toolbox.once("select", (event, id) => {
-        is(id, toolId, toolId + " selected");
-        resolve();
-      });
-      toolbox.selectTool(toolId);
-    });
+  function* testSelectEvent(toolId) {
+    let onSelect = toolbox.once("select");
+    toolbox.selectTool(toolId);
+    let id = yield onSelect;
+    is(id, toolId, toolId + " selected");
   }
 
   /**
    * Assert that selecting the given toolId raises its corresponding
    * selected event
    * @param {toolId} Id of the tool to test
    */
-  function testToolSelectEvent(toolId) {
-    return new Promise(resolve => {
-      toolbox.once(toolId + "-selected", () => {
-        let msg = toolId + " tool selected";
-        is(toolbox.currentToolId, toolId, msg);
-        resolve();
-      });
-      toolbox.selectTool(toolId);
-    });
+  function* testToolSelectEvent(toolId) {
+    let onSelected = toolbox.once(toolId + "-selected");
+    toolbox.selectTool(toolId);
+    yield onSelected;
+    is(toolbox.currentToolId, toolId, toolId + " tool selected");
+  }
+
+  /**
+   * Assert that two calls to selectTool won't race
+   */
+  function* testSelectToolRace() {
+    let toolbox = yield openToolboxForTab(tab, "webconsole");
+    let selected = false;
+    let onSelect = (event, id) => {
+      if (selected) {
+        ok(false, "Got more than one 'select' event");
+      } else {
+        selected = true;
+      }
+    };
+    toolbox.once("select", onSelect);
+    let p1 = toolbox.selectTool("inspector")
+    let p2 = toolbox.selectTool("inspector");
+    // Check that both promises don't resolve too early
+    let checkSelectToolResolution = panel => {
+      ok(selected, "selectTool resolves only after 'select' event is fired");
+      let inspector = toolbox.getPanel("inspector");
+      is(panel, inspector, "selecTool resolves to the panel instance");
+    };
+    p1.then(checkSelectToolResolution);
+    p2.then(checkSelectToolResolution);
+    yield p1;
+    yield p2;
+
+    yield toolbox.destroy();
   }
 });
 
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -1446,21 +1446,29 @@ Toolbox.prototype = {
     let sep = this.doc.getElementById("toolbox-controls-separator");
     if (id === "options") {
       sep.setAttribute("invisible", "true");
     } else {
       sep.removeAttribute("invisible");
     }
 
     if (this.currentToolId == id) {
-      // re-focus tool to get key events again
-      this.focusTool(id);
+      let panel = this._toolPanels.get(id);
+      if (panel) {
+        // We have a panel instance, so the tool is already fully loaded.
+
+        // re-focus tool to get key events again
+        this.focusTool(id);
 
-      // Return the existing panel in order to have a consistent return value.
-      return promise.resolve(this._toolPanels.get(id));
+        // Return the existing panel in order to have a consistent return value.
+        return promise.resolve(panel);
+      }
+      // Otherwise, if there is no panel instance, it is still loading,
+      // so we are racing another call to selectTool with the same id.
+      return this.once("select").then(() => promise.resolve(this._toolPanels.get(id)));
     }
 
     if (!this.isReady) {
       throw new Error("Can't select tool, wait for toolbox 'ready' event");
     }
 
     let tab = this.doc.getElementById("toolbox-tab-" + id);
 
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -128,16 +128,18 @@ subsuite = clipboard
 [browser_inspector_menu-03-paste-items.js]
 subsuite = clipboard
 [browser_inspector_menu-03-paste-items-svg.js]
 subsuite = clipboard
 [browser_inspector_menu-04-use-in-console.js]
 [browser_inspector_menu-05-attribute-items.js]
 [browser_inspector_menu-06-other.js]
 [browser_inspector_navigation.js]
+[browser_inspector_navigate_to_errors.js]
+[browser_inspector_open_on_neterror.js]
 [browser_inspector_pane-toggle-01.js]
 [browser_inspector_pane-toggle-02.js]
 [browser_inspector_pane-toggle-03.js]
 [browser_inspector_pane-toggle-05.js]
 skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard
 [browser_inspector_picker-stop-on-destroy.js]
 [browser_inspector_picker-stop-on-tool-change.js]
 [browser_inspector_portrait_mode.js]
--- a/devtools/client/inspector/test/browser_inspector_destroy-after-navigation.js
+++ b/devtools/client/inspector/test/browser_inspector_destroy-after-navigation.js
@@ -4,24 +4,19 @@
 "use strict";
 
 // Testing that closing the inspector after navigating to a page doesn't fail.
 
 const URL_1 = "data:text/plain;charset=UTF-8,abcde";
 const URL_2 = "data:text/plain;charset=UTF-8,12345";
 
 add_task(function* () {
-  let { toolbox } = yield openInspectorForURL(URL_1);
+  let { inspector, toolbox } = yield openInspectorForURL(URL_1);
 
-  info("Navigating to different URL.");
-  let navigated = toolbox.target.once("navigate");
-  navigateTo(toolbox, URL_2);
-
-  info("Waiting for 'navigate' event from toolbox target.");
-  yield navigated;
+  yield navigateTo(inspector, URL_2);
 
   info("Destroying toolbox");
   try {
     yield toolbox.destroy();
     ok(true, "Toolbox destroyed");
   } catch (e) {
     ok(false, "An exception occured while destroying toolbox");
     console.error(e);
--- a/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-xul.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-xul.js
@@ -6,17 +6,17 @@
 // Test that the eyedropper icons in the toolbar and in the color picker aren't displayed
 // when the page isn't an HTML one.
 
 const TEST_URL = URL_ROOT + "doc_inspector_highlighter_xbl.xul";
 const TEST_URL_2 =
   "data:text/html;charset=utf-8,<h1 style='color:red'>HTML test page</h1>";
 
 add_task(function* () {
-  let {inspector, toolbox} = yield openInspectorForURL(TEST_URL);
+  let {inspector} = yield openInspectorForURL(TEST_URL);
 
   info("Check the inspector toolbar");
   let button = inspector.panelDoc.querySelector("#inspector-eyedropper-toggle");
   ok(isDisabled(button), "The button is hidden in the toolbar");
 
   info("Check the color picker");
   yield selectNode("#scale", inspector);
 
@@ -30,21 +30,17 @@ add_task(function* () {
   let onColorPickerReady = cPicker.once("ready");
   swatchEl.click();
   yield onColorPickerReady;
 
   button = cPicker.tooltip.doc.querySelector("#eyedropper-button");
   ok(isDisabled(button), "The button is disabled in the color picker");
 
   info("Navigate to a HTML document");
-  let navigated = toolbox.target.once("navigate");
-  let markuploaded = inspector.once("markuploaded");
-  navigateTo(toolbox, TEST_URL_2);
-  yield navigated;
-  yield markuploaded;
+  yield navigateTo(inspector, TEST_URL_2);
 
   info("Check the inspector toolbar in HTML document");
   button = inspector.panelDoc.querySelector("#inspector-eyedropper-toggle");
   ok(!isDisabled(button), "The button is enabled in the toolbar");
 
   info("Check the color picker in HTML document");
   // Find the color swatch in the rule-view.
   yield selectNode("h1", inspector);
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_navigate_to_errors.js
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+// Test that inspector works when navigating to error pages.
+
+const TEST_URL_1 = "data:text/html,<html><body id=\"test-doc-1\">page</body></html>";
+const TEST_URL_2 = "http://127.0.0.1:36325/";
+const TEST_URL_3 = "http://www.wronguri.wronguri/";
+const TEST_URL_4 = "data:text/html,<html><body>test-doc-4</body></html>";
+
+add_task(function* () {
+  // Open the inspector on a valid URL
+  let { inspector, testActor } = yield openInspectorForURL(TEST_URL_1);
+
+  info("Navigate to closed port");
+  yield navigateTo(inspector, TEST_URL_2);
+
+  let documentURI = yield testActor.eval("document.documentURI;");
+  ok(documentURI.startsWith("about:neterror"), "content is correct.");
+
+  let hasPage = yield getNodeFront("#test-doc-1", inspector);
+  ok(!hasPage, "Inspector actor is no longer able to reach previous page DOM node");
+
+  let hasNetErrorNode = yield getNodeFront("#errorShortDesc", inspector);
+  ok(hasNetErrorNode, "Inspector actor is able to reach error page DOM node");
+
+  let bundle = Services.strings.createBundle("chrome://global/locale/appstrings.properties");
+  let domain = TEST_URL_2.match(/^http:\/\/(.*)\/$/)[1];
+  let errorMsg = bundle.formatStringFromName("connectionFailure",
+                                             [domain], 1);
+  is(yield getDisplayedNodeTextContent("#errorShortDescText", inspector), errorMsg,
+     "Inpector really inspects the error page");
+
+  info("Navigate to unknown domain");
+  yield navigateTo(inspector, TEST_URL_3);
+
+  domain = TEST_URL_3.match(/^http:\/\/(.*)\/$/)[1];
+  errorMsg = bundle.formatStringFromName("dnsNotFound",
+                                         [domain], 1);
+  is(yield getDisplayedNodeTextContent("#errorShortDescText", inspector), errorMsg,
+     "Inspector really inspects the new error page");
+
+  info("Navigate to a valid url");
+  yield navigateTo(inspector, TEST_URL_4);
+
+  is(yield getDisplayedNodeTextContent("body", inspector), "test-doc-4",
+     "Inspector really inspects the valid url");
+});
--- a/devtools/client/inspector/test/browser_inspector_navigation.js
+++ b/devtools/client/inspector/test/browser_inspector_navigation.js
@@ -9,35 +9,35 @@
 
 const TEST_URL_FILE = "browser/devtools/client/inspector/test/" +
   "doc_inspector_breadcrumbs.html";
 
 const TEST_URL_1 = "http://test1.example.org/" + TEST_URL_FILE;
 const TEST_URL_2 = "http://test2.example.org/" + TEST_URL_FILE;
 
 add_task(function* () {
-  let { inspector, toolbox, testActor } = yield openInspectorForURL(TEST_URL_1);
-  let markuploaded = inspector.once("markuploaded");
+  let { inspector, testActor } = yield openInspectorForURL(TEST_URL_1);
 
   yield selectNode("#i1", inspector);
 
   info("Navigating to a different page.");
-  yield navigateTo(toolbox, TEST_URL_2);
-
-  info("Waiting for markup view to load after navigation.");
-  yield markuploaded;
+  yield navigateTo(inspector, TEST_URL_2);
 
   ok(true, "New page loaded");
   yield selectNode("#i1", inspector);
 
-  markuploaded = inspector.once("markuploaded");
+  let markuploaded = inspector.once("markuploaded");
+  let onUpdated = inspector.once("inspector-updated");
 
   info("Going back in history");
   yield testActor.eval("history.go(-1)");
 
   info("Waiting for markup view to load after going back in history.");
   yield markuploaded;
 
+  info("Check that the inspector updates");
+  yield onUpdated;
+
   ok(true, "Old page loaded");
   is((yield testActor.eval("location.href;")), TEST_URL_1, "URL is correct.");
 
   yield selectNode("#i1", inspector);
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_open_on_neterror.js
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+// Test that inspector works correctly when opened against a net error page
+
+const TEST_URL_1 = "http://127.0.0.1:36325/";
+const TEST_URL_2 = "data:text/html,<html><body>test-doc-2</body></html>";
+
+add_task(function* () {
+  // Unfortunately, net error page are not firing load event, so that we can't
+  // use addTab helper and have to do that:
+  let tab = gBrowser.selectedTab = gBrowser.addTab("data:text/html,empty");
+  yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  yield ContentTask.spawn(tab.linkedBrowser, { url: TEST_URL_1 }, function* ({ url }) {
+    // Also, the neterror being privileged, the DOMContentLoaded only fires on
+    // the chromeEventHandler.
+    let { chromeEventHandler } = docShell; // eslint-disable-line no-undef
+    let onDOMContentLoaded = ContentTaskUtils.waitForEvent(chromeEventHandler,
+      "DOMContentLoaded", true);
+    content.location = url;
+    yield onDOMContentLoaded;
+  });
+
+  let { inspector, testActor } = yield openInspector();
+  ok(true, "Inspector loaded on the already opened net error");
+
+  let documentURI = yield testActor.eval("document.documentURI;");
+  ok(documentURI.startsWith("about:neterror"), "content is really a net error page.");
+
+  info("Navigate to a valid url");
+  yield navigateTo(inspector, TEST_URL_2);
+
+  is(yield getDisplayedNodeTextContent("body", inspector), "test-doc-2",
+     "Inspector really inspects the valid url");
+});
--- a/devtools/client/inspector/test/browser_inspector_select-last-selected.js
+++ b/devtools/client/inspector/test/browser_inspector_select-last-selected.js
@@ -59,43 +59,37 @@ add_task(function* () {
   let { inspector, toolbox, testActor } = yield openInspectorForURL(PAGE_1);
 
   for (let { url, nodeToSelect, selectedNode } of TEST_DATA) {
     if (nodeToSelect) {
       info("Selecting node " + nodeToSelect + " before navigation.");
       yield selectNode(nodeToSelect, inspector);
     }
 
-    let onNewRoot = inspector.once("new-root");
     yield navigateToAndWaitForNewRoot(url);
 
-    info("Waiting for new root.");
-    yield onNewRoot;
-
-    info("Waiting for inspector to update after new-root event.");
-    yield inspector.once("inspector-updated");
-
     let nodeFront = yield getNodeFront(selectedNode, inspector);
     ok(nodeFront, "Got expected node front");
     is(inspector.selection.nodeFront, nodeFront,
        selectedNode + " is selected after navigation.");
   }
 
-  function navigateToAndWaitForNewRoot(url) {
+  function* navigateToAndWaitForNewRoot(url) {
     info("Navigating and waiting for new-root event after navigation.");
 
-    let newRoot = inspector.once("new-root");
+    let current = yield testActor.eval("location.href");
+    if (url == current) {
+      info("Reloading page.");
+      let markuploaded = inspector.once("markuploaded");
+      let onNewRoot = inspector.once("new-root");
+      let onUpdated = inspector.once("inspector-updated");
 
-    return testActor.eval("location.href")
-      .then(current => {
-        if (url == current) {
-          info("Reloading page.");
-          let activeTab = toolbox.target.activeTab;
-          return activeTab.reload();
-        }
-
-        info("Navigating to " + url);
-        navigateTo(toolbox, url);
-
-        return newRoot;
-      });
+      let activeTab = toolbox.target.activeTab;
+      yield activeTab.reload();
+      info("Waiting for inspector to be ready.");
+      yield markuploaded;
+      yield onNewRoot;
+      yield onUpdated;
+    } else {
+      yield navigateTo(inspector, url);
+    }
   }
 });
--- a/devtools/client/inspector/test/head.js
+++ b/devtools/client/inspector/test/head.js
@@ -49,20 +49,34 @@ registerCleanupFunction(() => {
 registerCleanupFunction(function* () {
   // Move the mouse outside inspector. If the test happened fake a mouse event
   // somewhere over inspector the pointer is considered to be there when the
   // next test begins. This might cause unexpected events to be emitted when
   // another test moves the mouse.
   EventUtils.synthesizeMouseAtPoint(1, 1, {type: "mousemove"}, window);
 });
 
-var navigateTo = function (toolbox, url) {
-  let activeTab = toolbox.target.activeTab;
-  return activeTab.navigateTo(url);
-};
+var navigateTo = Task.async(function* (inspector, url) {
+  let markuploaded = inspector.once("markuploaded");
+  let onNewRoot = inspector.once("new-root");
+  let onUpdated = inspector.once("inspector-updated");
+
+  info("Navigating to: " + url);
+  let activeTab = inspector.toolbox.target.activeTab;
+  yield activeTab.navigateTo(url);
+
+  info("Waiting for markup view to load after navigation.");
+  yield markuploaded;
+
+  info("Waiting for new root.");
+  yield onNewRoot;
+
+  info("Waiting for inspector to update after new-root event.");
+  yield onUpdated;
+});
 
 /**
  * Start the element picker and focus the content window.
  * @param {Toolbox} toolbox
  * @param {Boolean} skipFocus - Allow tests to bypass the focus event.
  */
 var startPicker = Task.async(function* (toolbox, skipFocus) {
   info("Start the element picker");
@@ -687,8 +701,31 @@ function openContextMenuAndGetAllItems(i
  *        The child node index of the element to get
  * @return {DOMNode} The rule editor if any at this index
  */
 function getRuleViewRuleEditor(view, childrenIndex, nodeIndex) {
   return nodeIndex !== undefined ?
     view.element.children[childrenIndex].childNodes[nodeIndex]._ruleEditor :
     view.element.children[childrenIndex]._ruleEditor;
 }
+
+/**
+ * Get the text displayed for a given DOM Element's textContent within the
+ * markup view.
+ *
+ * @param {String} selector
+ * @param {InspectorPanel} inspector
+ * @return {String} The text displayed in the markup view
+ */
+function* getDisplayedNodeTextContent(selector, inspector) {
+  // We have to ensure that the textContent is displayed, for that the DOM
+  // Element has to be selected in the markup view and to be expanded.
+  yield selectNode(selector, inspector);
+
+  let container = yield getContainerForSelector(selector, inspector);
+  yield inspector.markup.expandNode(container.node);
+  yield waitForMultipleChildrenUpdates(inspector);
+  if (container) {
+    let textContainer = container.elt.querySelector("pre");
+    return textContainer.textContent;
+  }
+  return null;
+}
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -96,19 +96,16 @@ devtools.jar:
     content/performance/views/details-waterfall.js (performance/views/details-waterfall.js)
     content/performance/views/details-js-call-tree.js (performance/views/details-js-call-tree.js)
     content/performance/views/details-js-flamegraph.js (performance/views/details-js-flamegraph.js)
     content/performance/views/details-memory-call-tree.js (performance/views/details-memory-call-tree.js)
     content/performance/views/details-memory-flamegraph.js (performance/views/details-memory-flamegraph.js)
     content/performance/views/recordings.js (performance/views/recordings.js)
     content/memory/memory.xhtml (memory/memory.xhtml)
     content/memory/initializer.js (memory/initializer.js)
-    content/promisedebugger/promise-controller.js (promisedebugger/promise-controller.js)
-    content/promisedebugger/promise-panel.js (promisedebugger/promise-panel.js)
-    content/promisedebugger/promise-debugger.xhtml (promisedebugger/promise-debugger.xhtml)
     content/commandline/commandline.css (commandline/commandline.css)
     content/commandline/commandlineoutput.xhtml (commandline/commandlineoutput.xhtml)
     content/commandline/commandlinetooltip.xhtml (commandline/commandlinetooltip.xhtml)
     content/framework/toolbox-window.xul (framework/toolbox-window.xul)
     content/framework/toolbox-options.xhtml (framework/toolbox-options.xhtml)
     content/framework/toolbox.xul (framework/toolbox.xul)
     content/framework/toolbox-init.js (framework/toolbox-init.js)
     content/framework/options-panel.css (framework/options-panel.css)
--- a/devtools/client/locales/en-US/animationinspector.properties
+++ b/devtools/client/locales/en-US/animationinspector.properties
@@ -65,16 +65,34 @@ player.infiniteIterationCountText=∞
 # LOCALIZATION NOTE (player.animationIterationStartLabel):
 # This string is displayed in a tooltip that appears when hovering over
 # animations in the timeline. It is the label displayed before the animation
 # iterationStart value.
 # %1$S will be replaced by the original iteration start value
 # %2$S will be replaced by the actual time of iteration start
 player.animationIterationStartLabel=Iteration start: %1$S (%2$Ss)
 
+# LOCALIZATION NOTE (player.animationEasingLabel):
+# This string is displayed in a tooltip that appears when hovering over
+# animations in the timeline. It is the label displayed before the animation
+# easing value.
+player.animationEasingLabel=Easing:
+
+# LOCALIZATION NOTE (player.animationFillLabel):
+# This string is displayed in a tooltip that appears when hovering over
+# animations in the timeline. It is the label displayed before the animation
+# fill mode value.
+player.animationFillLabel=Fill:
+
+# LOCALIZATION NOTE (player.animationDirectionLabel):
+# This string is displayed in a tooltip that appears when hovering over
+# animations in the timeline. It is the label displayed before the animation
+# direction value.
+player.animationDirectionLabel=Direction:
+
 # LOCALIZATION NOTE (player.timeLabel):
 # This string is displayed in each animation player widget, to indicate either
 # how long (in seconds) the animation lasts, or what is the animation's current
 # time (in seconds too);
 player.timeLabel=%Ss
 
 # LOCALIZATION NOTE (player.playbackRateLabel):
 # This string is displayed in each animation player widget, as the label of
--- a/devtools/client/locales/en-US/debugger.dtd
+++ b/devtools/client/locales/en-US/debugger.dtd
@@ -45,20 +45,16 @@
   -  checkbox that toggles auto pretty print. -->
 <!ENTITY debuggerUI.autoPrettyPrint     "Auto Prettify Minified Sources">
 <!ENTITY debuggerUI.autoPrettyPrint.accesskey "P">
 
 <!-- LOCALIZATION NOTE (debuggerUI.sources.toggleBreakpoints): This is the tooltip for the
   -  button that toggles all breakpoints for all sources. -->
 <!ENTITY debuggerUI.sources.toggleBreakpoints "Enable/disable all breakpoints">
 
-<!-- LOCALIZATION NOTE (debuggerUI.sources.togglePromiseDebugger): This is the
-  -  tooltip for the button that toggles the promise debugger. -->
-<!ENTITY debuggerUI.sources.togglePromiseDebugger "Toggle Promise Debugger">
-
 <!-- LOCALIZATION NOTE (debuggerUI.clearButton): This is the label for
   -  the button that clears the collected tracing data in the tracing tab. -->
 <!ENTITY debuggerUI.clearButton "Clear">
 
 <!-- LOCALIZATION NOTE (debuggerUI.clearButton.tooltip): This is the tooltip for
   -  the button that clears the collected tracing data in the tracing tab. -->
 <!ENTITY debuggerUI.clearButton.tooltip "Clear the collected traces">
 
deleted file mode 100644
--- a/devtools/client/locales/en-US/promisedebugger.dtd
+++ /dev/null
@@ -1,15 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- LOCALIZATION NOTE : FILE This file contains the Promise debugger panel
-     strings. The Promise debugger panel is part of the debugger -->
-<!-- LOCALIZATION NOTE : FILE Do not translate commandkey -->
-
-<!-- LOCALIZATION NOTE : FILE The correct localization of this file might be to
-  - keep it in English, or another language commonly spoken among web developers.
-  - You want to make that choice consistent across the developer tools.
-  - A good criteria is the language in which you'd find the best
-  - documentation on web development on the web. -->
-
-<!ENTITY title "Promise Debugger">
--- a/devtools/client/moz.build
+++ b/devtools/client/moz.build
@@ -17,17 +17,16 @@ DIRS += [
     'inspector',
     'jsonview',
     'locales',
     'memory',
     'netmonitor',
     'performance',
     'preferences',
     'projecteditor',
-    'promisedebugger',
     'responsive.html',
     'responsivedesign',
     'scratchpad',
     'shadereditor',
     'shared',
     'shims',
     'sourceeditor',
     'storage',
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -95,17 +95,16 @@ pref("devtools.debugger.remote-timeout",
 pref("devtools.debugger.pause-on-exceptions", false);
 pref("devtools.debugger.ignore-caught-exceptions", true);
 pref("devtools.debugger.source-maps-enabled", true);
 pref("devtools.debugger.client-source-maps-enabled", true);
 pref("devtools.debugger.pretty-print-enabled", true);
 pref("devtools.debugger.auto-pretty-print", false);
 pref("devtools.debugger.auto-black-box", true);
 pref("devtools.debugger.workers", false);
-pref("devtools.debugger.promise", false);
 
 #if defined(NIGHTLY_BUILD)
 pref("devtools.debugger.new-debugger-frontend", true);
 #else
 pref("devtools.debugger.new-debugger-frontend", false);
 #endif
 
 // The default Debugger UI settings
deleted file mode 100644
--- a/devtools/client/promisedebugger/moz.build
+++ /dev/null
@@ -1,8 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-DevToolsModules(
-)
deleted file mode 100644
--- a/devtools/client/promisedebugger/promise-controller.js
+++ /dev/null
@@ -1,102 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/* global PromisesPanel */
-
-"use strict";
-
-var { utils: Cu } = Components;
-const { loader, require } =
-  Cu.import("resource://devtools/shared/Loader.jsm", {});
-
-const { Task } = require("devtools/shared/task");
-
-loader.lazyRequireGetter(this, "promise");
-loader.lazyRequireGetter(this, "EventEmitter",
-  "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "DevToolsUtils",
-  "devtools/shared/DevToolsUtils");
-loader.lazyRequireGetter(this, "PromisesFront",
-  "devtools/server/actors/promises", true);
-
-// Global toolbox, set when startup is called.
-var gToolbox;
-
-/**
- * Initialize the promise debugger controller and view upon loading the iframe.
- */
-var startup = Task.async(function* (toolbox) {
-  gToolbox = toolbox;
-
-  yield PromisesController.initialize(toolbox);
-  yield PromisesPanel.initialize();
-});
-
-/**
- * Destroy the promise debugger controller and view when unloading the iframe.
- */
-var shutdown = Task.async(function* () {
-  yield PromisesController.destroy();
-  yield PromisesPanel.destroy();
-
-  gToolbox = null;
-});
-
-function setPanel(toolbox) {
-  return startup(toolbox).catch(e =>
-    DevToolsUtils.reportException("setPanel", e));
-}
-
-function destroy() {
-  return shutdown().catch(e => DevToolsUtils.reportException("destroy", e));
-}
-
-/**
- * The promisedebugger controller's job is to retrieve PromisesFronts from the
- * server.
- */
-var PromisesController = {
-  initialize: Task.async(function* () {
-    if (this.initialized) {
-      return this.initialized.promise;
-    }
-
-    this.initialized = promise.defer();
-
-    let target = gToolbox.target;
-    this.promisesFront = new PromisesFront(target.client, target.form);
-    yield this.promisesFront.attach();
-
-    if (this.destroyed) {
-      console.warn("Could not fully initialize the PromisesController");
-      return null;
-    }
-
-    this.initialized.resolve();
-  }),
-
-  destroy: Task.async(function* () {
-    if (!this.initialized) {
-      return null;
-    }
-
-    if (this.destroyed) {
-      return this.destroyed.promise;
-    }
-
-    this.destroyed = promise.defer();
-
-    if (this.promisesFront) {
-      yield this.promisesFront.detach();
-      this.promisesFront.destroy();
-      this.promisesFront = null;
-    }
-
-    this.destroyed.resolve();
-  }),
-};
-
-EventEmitter.decorate(PromisesController);
deleted file mode 100644
--- a/devtools/client/promisedebugger/promise-debugger.xhtml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This Source Code Form is subject to the terms of the Mkozilla 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/. -->
-
-<!DOCTYPE html [
-  <!ENTITY % promisedebuggerDTD SYSTEM "chrome://devtools/locale/promisedebugger.dtd">
-  %promisedebuggerDTD;
-]>
-
-<html xmlns="http://www.w3.org/1999/xhtml">
-  <head>
-    <title>&title;</title>
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"/>
-  </head>
-  <body class="devtools-monospace" role="application">
-    <script type="application/javascript;version=1.8" src="promise-controller.js"></script>
-    <script type="application/javascript;version=1.8" src="promise-panel.js"></script>
-  </body>
-</html>
deleted file mode 100644
--- a/devtools/client/promisedebugger/promise-panel.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/* global PromisesController, promise */
-/* import-globals-from promise-controller.js */
-
-"use strict";
-
-/**
- * The main promise debugger UI.
- */
-var PromisesPanel = {
-  PANEL_INITIALIZED: "panel-initialized",
-
-  initialize: Task.async(function* () {
-    if (PromisesController.destroyed) {
-      return null;
-    }
-    if (this.initialized) {
-      return this.initialized.promise;
-    }
-    this.initialized = promise.defer();
-
-    this.initialized.resolve();
-
-    this.emit(this.PANEL_INITIALIZED);
-  }),
-
-  destroy: Task.async(function* () {
-    if (!this.initialized) {
-      return null;
-    }
-    if (this.destroyed) {
-      return this.destroyed.promise;
-    }
-    this.destroyed = promise.defer();
-
-    this.destroyed.resolve();
-  }),
-};
-
-EventEmitter.decorate(PromisesPanel);
deleted file mode 100644
--- a/devtools/client/promisedebugger/test/.eslintrc.js
+++ /dev/null
@@ -1,6 +0,0 @@
-"use strict";
-
-module.exports = {
-  // Extend from the shared list of defined globals for mochitests.
-  "extends": "../../../.eslintrc.mochitests.js"
-};
deleted file mode 100644
--- a/devtools/client/promisedebugger/test/head.js
+++ /dev/null
@@ -1,4 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
--- a/devtools/client/responsive.html/browser/tunnel.js
+++ b/devtools/client/responsive.html/browser/tunnel.js
@@ -104,16 +104,27 @@ function tunnelToInnerBrowser(outer, inn
             return outer[FRAME_LOADER];
           }
           return inner.frameLoader;
         },
         configurable: true,
         enumerable: true,
       });
 
+      // The `outerWindowID` of the content is used by browser actions like view source
+      // and print.  They send the ID down to the client to find the right content frame
+      // to act on.
+      Object.defineProperty(outer, "outerWindowID", {
+        get() {
+          return inner.outerWindowID;
+        },
+        configurable: true,
+        enumerable: true,
+      });
+
       // The `permanentKey` property on a <xul:browser> is used to index into various maps
       // held by the session store.  When you swap content around with
       // `_swapBrowserDocShells`, these keys are also swapped so they follow the content.
       // This means the key that matches the content is on the inner browser.  Since we
       // want the browser UI to believe the page content is part of the outer browser, we
       // copy the content's `permanentKey` up to the outer browser.
       copyPermanentKey(outer, inner);
 
@@ -269,16 +280,17 @@ function tunnelToInnerBrowser(outer, inn
       mmTunnel.destroy();
       mmTunnel = null;
 
       // Reset overridden XBL properties and methods.  Deleting the override
       // means it will fallback to the original XBL binding definitions which
       // are on the prototype.
       delete outer.frameLoader;
       delete outer[FRAME_LOADER];
+      delete outer.outerWindowID;
 
       // Invalidate outer's permanentKey so that SessionStore stops associating
       // things that happen to the outer browser with the content inside in the
       // inner browser.
       outer.permanentKey = { id: "zombie" };
 
       browserWindow = null;
       gBrowser = null;
@@ -401,31 +413,55 @@ MessageManagerTunnel.prototype = {
     "PageVisibility:Show",
     // Messages sent to SessionStore.jsm
     "SessionStore:update",
     // Messages sent to BrowserTestUtils.jsm
     "browser-test-utils:loadEvent",
   ],
 
   OUTER_TO_INNER_MESSAGE_PREFIXES: [
+    // Messages sent from nsContextMenu.js
+    "ContextMenu:",
     // Messages sent from DevTools
     "debug:",
     // Messages sent from findbar.xml
     "Findbar:",
     // Messages sent from RemoteFinder.jsm
     "Finder:",
+    // Messages sent from InlineSpellChecker.jsm
+    "InlineSpellChecker:",
+    // Messages sent from pageinfo.js
+    "PageInfo:",
+    // Messsage sent from printUtils.js
+    "Printing:",
+    // Messages sent from browser-social.js
+    "Social:",
+    "PageMetadata:",
+    // Messages sent from viewSourceUtils.js
+    "ViewSource:",
   ],
 
   INNER_TO_OUTER_MESSAGE_PREFIXES: [
+    // Messages sent to nsContextMenu.js
+    "ContextMenu:",
     // Messages sent to DevTools
     "debug:",
     // Messages sent to findbar.xml
     "Findbar:",
     // Messages sent to RemoteFinder.jsm
     "Finder:",
+    // Messages sent to pageinfo.js
+    "PageInfo:",
+    // Messsage sent from printUtils.js
+    "Printing:",
+    // Messages sent to browser-social.js
+    "Social:",
+    "PageMetadata:",
+    // Messages sent to viewSourceUtils.js
+    "ViewSource:",
   ],
 
   OUTER_TO_INNER_FRAME_SCRIPTS: [
     // DevTools server for OOP frames
     "resource://devtools/server/child.js"
   ],
 
   get outerParentMM() {
--- a/devtools/client/themes/animationinspector.css
+++ b/devtools/client/themes/animationinspector.css
@@ -40,16 +40,22 @@
   --keyframes-marker-size: 10px;
   /* The color of the time graduation borders */
   --time-graduation-border-color: rgba(128, 136, 144, .5);
 }
 
 .animation {
   --timeline-border-color: var(--theme-body-color);
   --timeline-background-color: var(--theme-splitter-color);
+  /* The color of the endDelay hidden progress */
+  --enddelay-hidden-progress-color: var(--theme-graphs-grey);
+  /* The color of none fill mode */
+  --fill-none-color: var(--theme-highlight-gray);
+  /* The color of enable fill mode */
+  --fill-enable-color: var(--timeline-border-color);
 }
 
 .animation.cssanimation {
   --timeline-border-color: var(--theme-highlight-lightorange);
   --timeline-background-color: var(--theme-contrast-background);
 }
 
 .animation.csstransition {
@@ -339,76 +345,61 @@ body {
 .animation-timeline .animation-target {
   background-color: transparent;
 }
 
 .animation-timeline .animation .time-block {
   cursor: pointer;
 }
 
-/* Animation iterations */
-
-.animation-timeline .animation .iterations {
+/* Animation summary graph */
+.animation-timeline .animation .summary {
   position: absolute;
+  width: 100%;
   height: 100%;
-  box-sizing: border-box;
-
-  /* Iterations of the animation are displayed with a repeating linear-gradient
-     which size is dynamically changed from JS. The gradient only draws 1px
-     borders between each iteration. These borders must have the same color as
-     the border of this element */
-  background-image:
-    linear-gradient(to left,
-                    var(--timeline-border-color) 0,
-                    var(--timeline-border-color) 1px,
-                    transparent 1px,
-                    transparent 2px);
-  border: 1px solid var(--timeline-border-color);
-  /* Border-right is already handled by the gradient */
-  border-width: 1px 0 1px 1px;
-
-  /* The background color is set independently */
-  background-color: var(--timeline-background-color);
 }
 
-.animation-timeline .animation .iterations.infinite::before,
-.animation-timeline .animation .iterations.infinite::after {
-  content: "";
-  position: absolute;
-  top: 0;
-  right: 0;
-  width: 0;
-  height: 0;
-  border-right: 4px solid var(--theme-body-background);
-  border-top: 4px solid transparent;
-  border-bottom: 4px solid transparent;
+.animation-timeline .animation .summary path {
+  fill: var(--timeline-background-color);
+  stroke: var(--timeline-border-color);
 }
 
-.animation-timeline .animation .iterations.infinite::after {
-  bottom: 0;
-  top: unset;
+.animation-timeline .animation .summary .infinity.copied {
+  opacity: .3;
+}
+
+.animation-timeline .animation .summary path.delay-path.negative,
+.animation-timeline .animation .summary path.enddelay-path.negative {
+  fill: none;
+  stroke: var(--enddelay-hidden-progress-color);
+  stroke-dasharray: 2, 2;
 }
 
 .animation-timeline .animation .name {
   position: absolute;
-  color: var(--theme-selection-color);
+  color: var(--theme-selection-color3);
+  top: 0px;
+  left: 0px;
   height: 100%;
+  width: 100%;
   display: flex;
   align-items: center;
   padding: 0 2px;
   box-sizing: border-box;
   --fast-track-icon-width: 15px;
   z-index: 1;
 }
 
 .animation-timeline .animation .name div {
   /* Flex items don't support text-overflow, so a child div is used */
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
+  background-color: rgba(255, 255, 255, 0.7);
+  max-width: 50%;
 }
 
 .animation-timeline .fast-track .name div {
   width: calc(100% - var(--fast-track-icon-width));
 }
 
 .animation-timeline .fast-track .name::after {
   /* Animations running on the compositor have the fast-track background image*/
@@ -418,64 +409,67 @@ body {
   top: 1px;
   right: 0;
   height: 100%;
   width: var(--fast-track-icon-width);
   z-index: 1;
 }
 
 .animation-timeline .all-properties .name::after {
-  background-color: white;
+  background-color: var(--theme-content-color3);
   clip-path: url(images/animation-fast-track.svg#thunderbolt);
   background-repeat: no-repeat;
   background-position: center;
 }
 
 .animation-timeline .some-properties .name::after {
   background-color: var(--theme-content-color3);
   clip-path: url(images/animation-fast-track.svg#thunderbolt);
   background-repeat: no-repeat;
   background-position: center;
 }
 
 .animation-timeline .animation .delay,
 .animation-timeline .animation .end-delay {
   position: absolute;
-  height: 100%;
-  border: 1px solid var(--timeline-border-color);
-  box-sizing: border-box;
+  border-bottom: 3px solid var(--fill-none-color);
+  bottom: -0.5px;
 }
 
-.animation-timeline .animation .delay {
-  border-width: 1px 0 1px 1px;
-  background-image: repeating-linear-gradient(45deg,
-                                              transparent,
-                                              transparent 1px,
-                                              var(--theme-selection-color) 1px,
-                                              var(--theme-selection-color) 4px);
-  background-color: var(--timeline-border-color);
+.animation-timeline .animation .delay::after,
+.animation-timeline .animation .end-delay::after {
+  content: "";
+  position: absolute;
+  top: -2px;
+  width: 3px;
+  height: 3px;
+  border: 2px solid var(--fill-none-color);
+  background-color: var(--fill-none-color);
+  border-radius: 50%;
 }
 
-.animation-timeline .animation .end-delay {
-  border-width: 1px 1px 1px 0;
-  background-image: repeating-linear-gradient(
-                      -45deg,
-                      transparent,
-                      transparent 3px,
-                      var(--timeline-border-color) 3px,
-                      var(--timeline-border-color) 4px);
+.animation-timeline .animation .negative.delay::after,
+.animation-timeline .animation .positive.end-delay::after {
+  right: -3px;
+}
+
+.animation-timeline .animation .positive.delay::after,
+.animation-timeline .animation .negative.end-delay::after {
+  left: -3px;
 }
 
-.animation-timeline .animation .delay.negative,
-.animation-timeline .animation .end-delay.negative {
-  /* Negative delays are displayed on top of the animation, so they need a
-     right border. Whereas normal delays are displayed just before the
-     animation, so there's already the animation's left border that serves as
-     a separation. */
-  border-width: 1px;
+.animation-timeline .animation .fill.delay,
+.animation-timeline .animation .fill.end-delay {
+  border-color: var(--fill-enable-color);
+}
+
+.animation-timeline .animation .fill.delay::after,
+.animation-timeline .animation .fill.end-delay::after {
+  border-color: var(--fill-enable-color);
+  background-color: var(--fill-enable-color);
 }
 
 /* Animation target node gutter, contains a preview of the dom node */
 
 .animation-target {
   background-color: var(--theme-toolbar-background);
   padding: 0 4px;
   box-sizing: border-box;
--- a/devtools/client/themes/debugger.css
+++ b/devtools/client/themes/debugger.css
@@ -124,20 +124,16 @@
   -moz-image-region: rect(0,16px,16px,0);
 }
 
 #toggle-breakpoints[checked] > image {
   /* This button has a special checked image, don't make it blue */
   filter: none;
 }
 
-#toggle-promise-debugger {
-  /* TODO Bug 1186119: Add a toggle promise debugger image */
-}
-
 #sources .black-boxed {
   color: rgba(128,128,128,0.4);
 }
 
 #sources .selected .black-boxed {
   color: rgba(255,255,255,0.4);
 }
 
--- a/devtools/server/actors/animation.js
+++ b/devtools/server/actors/animation.js
@@ -233,16 +233,40 @@ var AnimationPlayerActor = protocol.Acto
    * Get the animation iterationStart from this player, in ratio.
    * That is offset of starting position of the animation.
    * @return {Number}
    */
   getIterationStart: function () {
     return this.player.effect.getComputedTiming().iterationStart;
   },
 
+  /**
+   * Get the animation easing from this player.
+   * @return {String}
+   */
+  getEasing: function () {
+    return this.player.effect.timing.easing;
+  },
+
+  /**
+   * Get the animation fill mode from this player.
+   * @return {String}
+   */
+  getFill: function () {
+    return this.player.effect.getComputedTiming().fill;
+  },
+
+  /**
+   * Get the animation direction from this player.
+   * @return {String}
+   */
+  getDirection: function () {
+    return this.player.effect.getComputedTiming().direction;
+  },
+
   getPropertiesCompositorStatus: function () {
     let properties = this.player.effect.getProperties();
     return properties.map(prop => {
       return {
         property: prop.property,
         runningOnCompositor: prop.runningOnCompositor,
         warning: prop.warning
       };
@@ -275,16 +299,19 @@ var AnimationPlayerActor = protocol.Acto
       playState: this.player.playState,
       playbackRate: this.player.playbackRate,
       name: this.getName(),
       duration: this.getDuration(),
       delay: this.getDelay(),
       endDelay: this.getEndDelay(),
       iterationCount: this.getIterationCount(),
       iterationStart: this.getIterationStart(),
+      fill: this.getFill(),
+      easing: this.getEasing(),
+      direction: this.getDirection(),
       // animation is hitting the fast path or not. Returns false whenever the
       // animation is paused as it is taken off the compositor then.
       isRunningOnCompositor:
         this.getPropertiesCompositorStatus()
             .some(propState => propState.runningOnCompositor),
       propertyState: this.getPropertiesCompositorStatus(),
       // The document timeline's currentTime is being sent along too. This is
       // not strictly related to the node's animationPlayer, but is useful to
@@ -392,16 +419,30 @@ var AnimationPlayerActor = protocol.Acto
   ready: function () {
     return this.player.ready;
   },
 
   /**
    * Set the current time of the animation player.
    */
   setCurrentTime: function (currentTime) {
+    // The spec is that the progress of animation is changed
+    // if the time of setCurrentTime is during the endDelay.
+    // We should prevent the time
+    // to make the same animation behavior as the original.
+    // Likewise, in case the time is less than 0.
+    const timing = this.player.effect.getComputedTiming();
+    if (timing.delay < 0) {
+      currentTime += timing.delay;
+    }
+    if (currentTime < 0) {
+      currentTime = 0;
+    } else if (currentTime * this.player.playbackRate > timing.endTime) {
+      currentTime = timing.endTime;
+    }
     this.player.currentTime = currentTime * this.player.playbackRate;
   },
 
   /**
    * Set the playback rate of the animation player.
    */
   setPlaybackRate: function (playbackRate) {
     this.player.playbackRate = playbackRate;
--- a/devtools/server/actors/highlighters.js
+++ b/devtools/server/actors/highlighters.js
@@ -191,17 +191,19 @@ var HighlighterActor = exports.Highlight
       this._highlighter.hide();
     }
   },
 
   /**
    * Hide the box model highlighting if it was shown before
    */
   hideBoxModel: function () {
-    this._highlighter.hide();
+    if (this._highlighter) {
+      this._highlighter.hide();
+    }
   },
 
   /**
    * Returns `true` if the event was dispatched from a window included in
    * the current highlighter environment; or if the highlighter environment has
    * chrome privileges
    *
    * The method is specifically useful on B2G, where we do not want that events
--- a/devtools/server/actors/highlighters/utils/markup.js
+++ b/devtools/server/actors/highlighters/utils/markup.js
@@ -237,17 +237,22 @@ exports.createNode = createNode;
 function CanvasFrameAnonymousContentHelper(highlighterEnv, nodeBuilder) {
   this.highlighterEnv = highlighterEnv;
   this.nodeBuilder = nodeBuilder;
   this.anonymousContentDocument = this.highlighterEnv.document;
   // XXX the next line is a wallpaper for bug 1123362.
   this.anonymousContentGlobal = Cu.getGlobalForObject(
                                 this.anonymousContentDocument);
 
-  this._insert();
+  // Only try to create the highlighter when the document is loaded,
+  // otherwise, wait for the navigate event to fire.
+  let doc = this.highlighterEnv.document;
+  if (doc.documentElement && doc.readyState != "uninitialized") {
+    this._insert();
+  }
 
   this._onNavigate = this._onNavigate.bind(this);
   this.highlighterEnv.on("navigate", this._onNavigate);
 
   this.listeners = new Map();
 }
 
 CanvasFrameAnonymousContentHelper.prototype = {
--- a/devtools/server/actors/webbrowser.js
+++ b/devtools/server/actors/webbrowser.js
@@ -3,17 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 /* global XPCNativeWrapper */
 
-var { Ci, Cu } = require("chrome");
+var { Ci, Cu, Cr } = require("chrome");
 var Services = require("Services");
 var { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
 var promise = require("promise");
 var {
   ActorPool, createExtraActors, appendExtraActors, GeneratedLocation
 } = require("devtools/server/actors/common");
 var { DebuggerServer } = require("devtools/server/main");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
@@ -2490,19 +2490,37 @@ DebuggerProgressListener.prototype = {
     let window = progress.DOMWindow;
     if (isDocument && isStart) {
       // One of the earliest events that tells us a new URI
       // is being loaded in this window.
       let newURI = request instanceof Ci.nsIChannel ? request.URI.spec : null;
       this._tabActor._willNavigate(window, newURI, request);
     }
     if (isWindow && isStop) {
-      // Somewhat equivalent of load event.
-      // (window.document.readyState == complete)
-      this._tabActor._navigate(window);
+      // Don't dispatch "navigate" event just yet when there is a redirect to
+      // about:neterror page.
+      if (request.status != Cr.NS_OK) {
+        // Instead, listen for DOMContentLoaded as about:neterror is loaded
+        // with LOAD_BACKGROUND flags and never dispatches load event.
+        // That may be the same reason why there is no onStateChange event
+        // for about:neterror loads.
+        let handler = getDocShellChromeEventHandler(progress);
+        let onLoad = evt => {
+          // Ignore events from iframes
+          if (evt.target == window.document) {
+            handler.removeEventListener("DOMContentLoaded", onLoad, true);
+            this._tabActor._navigate(window);
+          }
+        };
+        handler.addEventListener("DOMContentLoaded", onLoad, true);
+      } else {
+        // Somewhat equivalent of load event.
+        // (window.document.readyState == complete)
+        this._tabActor._navigate(window);
+      }
     }
   }, "DebuggerProgressListener.prototype.onStateChange")
 };
 
 exports.register = function (handle) {
   handle.setRootActor(createRootActor);
 };
 
--- a/devtools/server/tests/browser/animation.html
+++ b/devtools/server/tests/browser/animation.html
@@ -23,28 +23,31 @@
   .multiple-animations {
     display: inline-block;
 
     width: 50px;
     height: 50px;
     border-radius: 50%;
     background: #eee;
 
-    animation: move 200s infinite, glow 100s 5;
+    animation: move 200s infinite , glow 100s 5;
+    animation-timing-function: ease-out;
+    animation-direction: reverse;
+    animation-fill-mode: both;
   }
 
   .transition {
     display: inline-block;
 
     width: 50px;
     height: 50px;
     border-radius: 50%;
     background: #f06;
 
-    transition: width 500s;
+    transition: width 500s ease-out;
   }
   .transition.get-round {
     width: 200px;
   }
 
   .long-animation {
     display: inline-block;
 
--- a/devtools/server/tests/browser/browser_animation_playerState.js
+++ b/devtools/server/tests/browser/browser_animation_playerState.js
@@ -26,71 +26,98 @@ function* playerHasAnInitialState(walker
   ok("currentTime" in player.initialState, "Player's state has currentTime");
   ok("playState" in player.initialState, "Player's state has playState");
   ok("playbackRate" in player.initialState, "Player's state has playbackRate");
   ok("name" in player.initialState, "Player's state has name");
   ok("duration" in player.initialState, "Player's state has duration");
   ok("delay" in player.initialState, "Player's state has delay");
   ok("iterationCount" in player.initialState,
      "Player's state has iterationCount");
+  ok("fill" in player.initialState, "Player's state has fill");
+  ok("easing" in player.initialState, "Player's state has easing");
+  ok("direction" in player.initialState, "Player's state has direction");
   ok("isRunningOnCompositor" in player.initialState,
      "Player's state has isRunningOnCompositor");
   ok("type" in player.initialState, "Player's state has type");
   ok("documentCurrentTime" in player.initialState,
      "Player's state has documentCurrentTime");
 }
 
 function* playerStateIsCorrect(walker, animations) {
   info("Checking the state of the simple animation");
 
-  let state = yield getAnimationStateForNode(walker, animations,
-                                             ".simple-animation", 0);
+  let player = yield getAnimationPlayerForNode(walker, animations,
+                                               ".simple-animation", 0);
+  let state = yield player.getCurrentState();
   is(state.name, "move", "Name is correct");
   is(state.duration, 200000, "Duration is correct");
   // null = infinite count
   is(state.iterationCount, null, "Iteration count is correct");
+  is(state.fill, "none", "Fill is correct");
+  is(state.easing, "linear", "Easing is correct");
+  is(state.direction, "normal", "Direction is correct");
   is(state.playState, "running", "PlayState is correct");
   is(state.playbackRate, 1, "PlaybackRate is correct");
   is(state.type, "cssanimation", "Type is correct");
 
   info("Checking the state of the transition");
 
-  state = yield getAnimationStateForNode(walker, animations, ".transition", 0);
+  player =
+    yield getAnimationPlayerForNode(walker, animations, ".transition", 0);
+  state = yield player.getCurrentState();
   is(state.name, "width", "Transition name matches transition property");
   is(state.duration, 500000, "Transition duration is correct");
   // transitions run only once
   is(state.iterationCount, 1, "Transition iteration count is correct");
+  is(state.fill, "backwards", "Transition fill is correct");
+  is(state.easing, "linear", "Transition easing is correct");
+  is(state.direction, "normal", "Transition direction is correct");
   is(state.playState, "running", "Transition playState is correct");
   is(state.playbackRate, 1, "Transition playbackRate is correct");
   is(state.type, "csstransition", "Transition type is correct");
+  // chech easing in keyframe
+  let keyframes = yield player.getFrames();
+  is(keyframes.length, 2, "Transition length of keyframe is correct");
+  is(keyframes[0].easing,
+     "ease-out", "Transition kerframes's easing is correct");
 
   info("Checking the state of one of multiple animations on a node");
 
   // Checking the 2nd player
-  state = yield getAnimationStateForNode(walker, animations,
-                                         ".multiple-animations", 1);
+  player = yield getAnimationPlayerForNode(walker, animations,
+                                           ".multiple-animations", 1);
+  state = yield player.getCurrentState();
   is(state.name, "glow", "The 2nd animation's name is correct");
   is(state.duration, 100000, "The 2nd animation's duration is correct");
   is(state.iterationCount, 5, "The 2nd animation's iteration count is correct");
+  is(state.fill, "both", "The 2nd animation's fill is correct");
+  is(state.easing, "linear", "The 2nd animation's easing is correct");
+  is(state.direction, "reverse", "The 2nd animation's direction is correct");
   is(state.playState, "running", "The 2nd animation's playState is correct");
   is(state.playbackRate, 1, "The 2nd animation's playbackRate is correct");
+  // chech easing in keyframe
+  keyframes = yield player.getFrames();
+  is(keyframes.length, 2, "The 2nd animation's length of keyframe is correct");
+  is(keyframes[0].easing,
+     "ease-out", "The 2nd animation's easing of kerframes is correct");
 
   info("Checking the state of an animation with delay");
 
-  state = yield getAnimationStateForNode(walker, animations,
-                                         ".delayed-animation", 0);
+  player = yield getAnimationPlayerForNode(walker, animations,
+                                           ".delayed-animation", 0);
+  state = yield player.getCurrentState();
   is(state.delay, 5000, "The animation delay is correct");
 
   info("Checking the state of an transition with delay");
 
-  state = yield getAnimationStateForNode(walker, animations,
-                                         ".delayed-transition", 0);
+  player = yield getAnimationPlayerForNode(walker, animations,
+                                           ".delayed-transition", 0);
+  state = yield player.getCurrentState();
   is(state.delay, 3000, "The transition delay is correct");
 }
 
-function* getAnimationStateForNode(walker, animations, nodeSelector, index) {
+function* getAnimationPlayerForNode(walker, animations, nodeSelector, index) {
   let node = yield walker.querySelector(walker.rootNode, nodeSelector);
   let players = yield animations.getAnimationPlayersForNode(node);
   let player = players[index];
   yield player.ready();
-  let state = yield player.getCurrentState();
-  return state;
+  return player;
 }
--- a/devtools/shared/fronts/animation.js
+++ b/devtools/shared/fronts/animation.js
@@ -60,16 +60,19 @@ const AnimationPlayerFront = FrontClassW
       playState: this._form.playState,
       playbackRate: this._form.playbackRate,
       name: this._form.name,
       duration: this._form.duration,
       delay: this._form.delay,
       endDelay: this._form.endDelay,
       iterationCount: this._form.iterationCount,
       iterationStart: this._form.iterationStart,
+      easing: this._form.easing,
+      fill: this._form.fill,
+      direction: this._form.direction,
       isRunningOnCompositor: this._form.isRunningOnCompositor,
       propertyState: this._form.propertyState,
       documentCurrentTime: this._form.documentCurrentTime
     };
   },
 
   /**
    * Executed when the AnimationPlayerActor emits a "changed" event. Used to
--- a/docshell/shistory/moz.build
+++ b/docshell/shistory/moz.build
@@ -1,16 +1,19 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 XPIDL_SOURCES += [
     'nsIBFCacheEntry.idl',
+    'nsIGroupedSHistory.idl',
+    'nsIPartialSHistory.idl',
+    'nsIPartialSHistoryListener.idl',
     'nsISHContainer.idl',
     'nsISHEntry.idl',
     'nsISHistory.idl',
     'nsISHistoryInternal.idl',
     'nsISHistoryListener.idl',
     'nsISHTransaction.idl',
 ]
 
new file mode 100644
--- /dev/null
+++ b/docshell/shistory/nsIGroupedSHistory.idl
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFrameLoader;
+interface nsIPartialSHistory;
+
+/**
+ * nsIGroupedSHistory represent a combined session history across multiple
+ * root docshells (usually browser tabs). The participating nsISHistory can
+ * either be in chrome process or in content process, but nsIGroupedSHistory
+ * itself lives in chrome process. The communication is proxyed through
+ * nsIPartialSHistory.
+ */
+[scriptable, builtinclass, uuid(813e498d-73a8-449a-be09-6187e62c5352)]
+interface nsIGroupedSHistory : nsISupports
+{
+  // The total number of entries of all its partial session histories.
+  [infallible] readonly attribute unsigned long count;
+
+  /**
+   * Remove all partial histories after currently active one (if any) and then
+   * append the given partial session history to the end of the list.
+   */
+  void appendPartialSessionHistory(in nsIPartialSHistory aPartialHistory);
+
+  /**
+   * Notify the grouped session history that the active partial session history
+   * has been modified. All partial session histories after the active one
+   * will be removed and destroy.
+   */
+  void onPartialSessionHistoryChange(in nsIPartialSHistory aPartialHistory);
+
+  /**
+   * Find the proper partial session history and navigate to the entry
+   * corresponding to the given global index. Note it doesn't swap frameloaders,
+   * but rather return the target loader for the caller to swap.
+   *
+   * @param aGlobalIndex        The global index to navigate to.
+   * @param aTargetLoaderToSwap The owner frameloader of the to-be-navigate
+   *                            partial session history.
+   */
+  void gotoIndex(in unsigned long aGlobalIndex, out nsIFrameLoader aTargetLoaderToSwap);
+};
new file mode 100644
--- /dev/null
+++ b/docshell/shistory/nsIPartialSHistory.idl
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFrameLoader;
+
+/**
+ * nsIPartialSHistory represents a part of nsIGroupedSHistory. It associates to
+ * a "partial" nsISHistory in either local or remote process.
+ */
+[scriptable, builtinclass, uuid(5cd75e28-838c-4a0a-972e-6005f736ef7a)]
+interface nsIPartialSHistory : nsISupports
+{
+  // The number of entries of its corresponding nsISHistory.
+  [infallible] readonly attribute unsigned long count;
+
+  // If it's part of a grouped session history, globalIndexOffset denotes the
+  // number of entries ahead.
+  [infallible] readonly attribute unsigned long globalIndexOffset;
+
+  // The frameloader which owns this partial session history.
+  readonly attribute nsIFrameLoader ownerFrameLoader;
+
+  /**
+   * Notify that it's been added to a grouped session history. It also implies
+   * it's becoming the active partial history of the group.
+   *
+   * @param aOffset                The number of entries in preceding partial
+   *                               session histories.
+   */
+  void onAttachGroupedSessionHistory(in unsigned long aOffset);
+
+  /**
+   * Notify that one or more entries in its associated nsISHistory object
+   * have been changed (i.e. add / remove / replace). It's mainly used for
+   * cross-process case, since in the in-process case we can just register an
+   * nsISHistoryListener instead.
+   *
+   * @param aCount The number of entries in the associated session history.
+   *               It can be the same as the old value if entries were replaced.
+   */
+  void onSessionHistoryChange(in unsigned long aCount);
+
+  /**
+   * Notify that the partial session history has been swapped in as the active
+   * session history. Only an active session history can possibly add / remove /
+   * replace its history entries.
+   *
+   * @param aGlobalLength      The up-to-date global length.
+   * @param aTargetLocalIndex  The local index to navigate to.
+   */
+  void onActive(in unsigned long aGlobalLength, in unsigned long aTargetLocalIndex);
+
+  /**
+   * Notify that the partial session history has been swapped out and is no
+   * longer active.
+   */
+  void onDeactive();
+};
new file mode 100644
--- /dev/null
+++ b/docshell/shistory/nsIPartialSHistoryListener.idl
@@ -0,0 +1,24 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "nsISupports.idl"
+
+/**
+ * Listener to handle cross partial nsISHistory navigation requests.
+ */
+[scriptable, uuid(be0cd2b6-6f03-4366-9fe2-184c914ff3df)]
+interface nsIPartialSHistoryListener : nsISupports
+{
+  /**
+   * Called when the navigation target belongs to another nsISHistory within
+   * the same nsIGroupedSHistory, and it needs to initiate cross nsISHistory
+   * navigation.
+   *
+   * @param aIndex The index of complete history to navigate to.
+   */
+   void onRequestCrossBrowserNavigation(in unsigned long aIndex);
+};
--- a/docshell/shistory/nsISHistory.idl
+++ b/docshell/shistory/nsISHistory.idl
@@ -3,16 +3,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsISHEntry;
 interface nsISHistoryListener;
 interface nsISimpleEnumerator;
+interface nsIPartialSHistoryListener;
+
 /**
  * An interface to the primary properties of the Session History
  * component. In an embedded browser environment, the nsIWebBrowser
  * object creates an instance of session history for each open window.
  * A handle to the session history object can be obtained from
  * nsIWebNavigation. In a non-embedded situation, the  owner of the
  * session history component must create a instance of it and set
  * it in the nsIWebNavigation object.
@@ -26,24 +28,51 @@ interface nsISimpleEnumerator;
 
 #define NS_SHISTORY_CONTRACTID "@mozilla.org/browser/shistory;1"
 %}
 
 [scriptable, uuid(7b807041-e60a-4384-935f-af3061d8b815)]
 interface nsISHistory: nsISupports
 {
   /**
+   * An attribute denoting whether the nsISHistory is associated to a grouped
+   * session history.
+   *
+   * The abstraction of grouped session history is implemented at
+   * nsIWebNavigation level, so those canGoBack / canGoForward / gotoIndex
+   * functions work transparently;
+   *
+   * On the other hand, nsISHistory works on partial session history directly.
+   * Unless otherwise specified, count / index attributes and parameters all
+   * indicate local count / index, so we won't mess up docshell.
+   */
+   readonly attribute bool isPartial;
+
+  /**
    * A readonly property of the interface that returns 
    * the number of toplevel documents currently available
    * in session history.
    */
    readonly attribute long count;
 
   /**
-   * A readonly property of the interface that returns 
+   * If isPartial, globalCount denotes the total number of entries in the
+   * grouped session history; Otherwise it has the same value as count.
+   */
+   readonly attribute long globalCount;
+
+  /**
+   * A readonly property which represents the difference between global indices
+   * of grouped session history and local indices of this particular session
+   * history object.
+   */
+   readonly attribute long globalIndexOffset;
+
+  /**
+   * A readonly property of the interface that returns
    * the index of the current document in session history.
    */
    readonly attribute long index;
 
   /**
    * A readonly property of the interface that returns 
    * the index of the last document that started to load and
    * didn't finished yet. When document finishes the loading
@@ -119,17 +148,23 @@ interface nsISHistory: nsISupports
    * @note                    A listener object must implement 
    *                          nsISHistoryListener and nsSupportsWeakReference
    * @see nsISHistoryListener
    * @see nsSupportsWeakReference
    */ 
    void removeSHistoryListener(in nsISHistoryListener aListener);
 
   /**
-   * Called to obtain a enumerator for all the  documents stored in 
+   * Set the listener to handle cross nsISHistory navigation when it works
+   * in "partial" mode.
+   */
+   void setPartialSHistoryListener(in nsIPartialSHistoryListener aListener);
+
+  /**
+   * Called to obtain a enumerator for all the  documents stored in
    * session history. The enumerator object thus returned by this method
    * can be traversed using nsISimpleEnumerator. 
    *
    * @note  To access individual history entries of the enumerator, perform the
    *        following steps:
    *        1) Call nsISHistory->GetSHistoryEnumerator() to obtain handle 
    *           the nsISimpleEnumerator object.
    *        2) Use nsISimpleEnumerator->GetNext() on the object returned
@@ -154,9 +189,33 @@ interface nsISHistory: nsISupports
    * @param aEntry            The entry to obtain the index of.
    *
    * @return                  <code>NS_OK</code> index for the history entry
    *                          is obtained successfully.
    *                          <code>NS_ERROR_FAILURE</code> Error in obtaining
    *                          index for the given history entry.
    */
    long getIndexOfEntry(in nsISHEntry aEntry);
+
+  /**
+   * Called when this nsISHistory has became the active history of a grouped
+   * session history.
+   *
+   * @param globalLength      The up to date number of entries in the grouped
+   *                          session history.
+   * @param targetIndex       The local index to navigate to.
+   */
+   void onPartialSessionHistoryActive(in long globalLength, in long targetIndex);
+
+  /**
+   * Called when this nsISHistory has became inactive history of a grouped
+   * session history.
+   */
+   void onPartialSessionHistoryDeactive();
+
+  /**
+   * Called when it's attached to a nsIGroupedSHistory instance.
+   *
+   * @param offset            The number of entries in the grouped session
+   *                          history before this session history object.
+   */
+   void onAttachGroupedSessionHistory(in long offset);
 };
--- a/docshell/shistory/nsISHistoryListener.idl
+++ b/docshell/shistory/nsISHistoryListener.idl
@@ -93,9 +93,16 @@ interface nsISHistoryListener : nsISuppo
    * Called when an entry is replaced in the session history. Entries are
    * replaced when navigating away from non-persistent history entries (such as
    * about pages) and when history.replaceState is called.
    *
    * @param aIndex        The index in session history of the entry being
   *                       replaced
    */
    void OnHistoryReplaceEntry(in long aIndex);
+
+   /**
+    * Called when nsISHistory::count has been updated. Unlike OnHistoryNewEntry
+    * and OnHistoryPurge which happen before the modifications are actually done
+    * and maybe cancellable, this function is called after these modifications.
+    */
+   void OnLengthChange(in long aCount);
 };
--- a/docshell/shistory/nsSHistory.cpp
+++ b/docshell/shistory/nsSHistory.cpp
@@ -221,16 +221,19 @@ EvictContentViewerForTransaction(nsISHTr
 }
 
 } // namespace
 
 nsSHistory::nsSHistory()
   : mIndex(-1)
   , mLength(0)
   , mRequestedIndex(-1)
+  , mIsPartial(false)
+  , mGlobalIndexOffset(0)
+  , mEntriesInFollowingPartialHistories(0)
   , mRootDocShell(nullptr)
 {
   // Add this new SHistory object to the list
   PR_APPEND_LINK(this, &gSHistoryList);
 }
 
 nsSHistory::~nsSHistory()
 {
@@ -414,40 +417,105 @@ nsSHistory::AddEntry(nsISHEntry* aSHEntr
   // parent will properly set the parent child relationship
   txn->SetPersist(aPersist);
   NS_ENSURE_SUCCESS(txn->Create(aSHEntry, currentTxn), NS_ERROR_FAILURE);
 
   // A little tricky math here...  Basically when adding an object regardless of
   // what the length was before, it should always be set back to the current and
   // lop off the forward.
   mLength = (++mIndex + 1);
+  NOTIFY_LISTENERS(OnLengthChange, (mLength));
+
+  // Much like how mLength works above, when changing our entries, all following
+  // partial histories should be purged, so we just reset the number to zero.
+  mEntriesInFollowingPartialHistories = 0;
 
   // If this is the very first transaction, initialize the list
   if (!mListRoot) {
     mListRoot = txn;
   }
 
   // Purge History list if it is too long
   if (gHistoryMaxSize >= 0 && mLength > gHistoryMaxSize) {
     PurgeHistory(mLength - gHistoryMaxSize);
   }
 
   RemoveDynEntries(mIndex - 1, mIndex);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsSHistory::GetIsPartial(bool* aResult)
+{
+  NS_ENSURE_ARG_POINTER(aResult);
+  *aResult = mIsPartial;
+  return NS_OK;
+}
+
 /* Get size of the history list */
 NS_IMETHODIMP
 nsSHistory::GetCount(int32_t* aResult)
 {
   NS_ENSURE_ARG_POINTER(aResult);
   *aResult = mLength;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsSHistory::GetGlobalCount(int32_t* aResult)
+{
+  NS_ENSURE_ARG_POINTER(aResult);
+  *aResult = mGlobalIndexOffset + mLength + mEntriesInFollowingPartialHistories;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::GetGlobalIndexOffset(int32_t* aResult)
+{
+  NS_ENSURE_ARG_POINTER(aResult);
+  *aResult = mGlobalIndexOffset;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::OnPartialSessionHistoryActive(int32_t aGlobalLength, int32_t aTargetIndex)
+{
+  NS_ENSURE_TRUE(mIsPartial, NS_ERROR_UNEXPECTED);
+
+  int32_t extraLength = aGlobalLength - mLength - mGlobalIndexOffset;
+  NS_ENSURE_TRUE(extraLength >= 0, NS_ERROR_UNEXPECTED);
+
+  if (extraLength != mEntriesInFollowingPartialHistories) {
+    mEntriesInFollowingPartialHistories = extraLength;
+  }
+
+  if (mIndex == aTargetIndex) {
+    // TODO When we finish OnPartialSessionHistoryDeactive, we'll need to active
+    // the suspended document here.
+
+    // Fire location change to update canGoBack / canGoForward.
+    NS_DispatchToCurrentThread(NewRunnableMethod(static_cast<nsDocShell*>(mRootDocShell),
+                                                 &nsDocShell::FireDummyOnLocationChange));
+    return NS_OK;
+  }
+
+  return LoadEntry(aTargetIndex, nsIDocShellLoadInfo::loadHistory,
+                   HIST_CMD_GOTOINDEX);
+}
+
+NS_IMETHODIMP
+nsSHistory::OnPartialSessionHistoryDeactive()
+{
+  NS_ENSURE_TRUE(mIsPartial, NS_ERROR_UNEXPECTED);
+
+  // TODO We need to suspend current document first. Much like what happens when
+  // loading a new page. Move the ownership of the document to nsISHEntry or so.
+  return NS_OK;
+}
+
 /* Get index of the history list */
 NS_IMETHODIMP
 nsSHistory::GetIndex(int32_t* aResult)
 {
   NS_PRECONDITION(aResult, "null out param?");
   *aResult = mIndex;
   return NS_OK;
 }
@@ -696,16 +764,20 @@ nsSHistory::PurgeHistory(int32_t aEntrie
     mListRoot = nextTxn;
     if (mListRoot) {
       mListRoot->SetPrev(nullptr);
     }
     cnt++;
   }
   mLength -= cnt;
   mIndex -= cnt;
+  NOTIFY_LISTENERS(OnLengthChange, (mLength));
+
+  // All following partial histories will be deleted in this case.
+  mEntriesInFollowingPartialHistories = 0;
 
   // Now if we were not at the end of the history, mIndex could have
   // become far too negative.  If so, just set it to -1.
   if (mIndex < -1) {
     mIndex = -1;
   }
 
   if (mRootDocShell) {
@@ -737,16 +809,23 @@ nsSHistory::RemoveSHistoryListener(nsISH
 {
   // Make sure the listener that wants to be removed is the
   // one we have in store.
   nsWeakPtr listener = do_GetWeakReference(aListener);
   mListeners.RemoveElement(listener);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsSHistory::SetPartialSHistoryListener(nsIPartialSHistoryListener* aListener)
+{
+  mPartialHistoryListener = do_GetWeakReference(aListener);
+  return NS_OK;
+}
+
 /* Replace an entry in the History list at a particular index.
  * Do not update index or count.
  */
 NS_IMETHODIMP
 nsSHistory::ReplaceEntry(int32_t aIndex, nsISHEntry* aReplaceEntry)
 {
   NS_ENSURE_ARG(aReplaceEntry);
   nsresult rv;
@@ -803,43 +882,53 @@ nsSHistory::EvictAllContentViewers()
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHistory::GetCanGoBack(bool* aCanGoBack)
 {
   NS_ENSURE_ARG_POINTER(aCanGoBack);
-  *aCanGoBack = false;
+
+  if (mGlobalIndexOffset) {
+    *aCanGoBack = true;
+    return NS_OK;
+  }
 
   int32_t index = -1;
   NS_ENSURE_SUCCESS(GetIndex(&index), NS_ERROR_FAILURE);
   if (index > 0) {
     *aCanGoBack = true;
+    return NS_OK;
   }
 
+  *aCanGoBack = false;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHistory::GetCanGoForward(bool* aCanGoForward)
 {
   NS_ENSURE_ARG_POINTER(aCanGoForward);
-  *aCanGoForward = false;
+
+  if (mEntriesInFollowingPartialHistories) {
+    *aCanGoForward = true;
+    return NS_OK;
+  }
 
   int32_t index = -1;
   int32_t count = -1;
-
   NS_ENSURE_SUCCESS(GetIndex(&index), NS_ERROR_FAILURE);
   NS_ENSURE_SUCCESS(GetCount(&count), NS_ERROR_FAILURE);
-
   if (index >= 0 && index < (count - 1)) {
     *aCanGoForward = true;
+    return NS_OK;
   }
 
+  *aCanGoForward = false;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHistory::GoBack()
 {
   bool canGoBack = false;
 
@@ -1353,16 +1442,18 @@ nsSHistory::RemoveDuplicate(int32_t aInd
     // NB: We don't need to guard on mRequestedIndex being nonzero here,
     // because either they're strictly greater than aIndex which is at least
     // zero, or they are equal to aIndex in which case aKeepNext must be true
     // if aIndex is zero.
     if (mRequestedIndex > aIndex || (mRequestedIndex == aIndex && !aKeepNext)) {
       mRequestedIndex = mRequestedIndex - 1;
     }
     --mLength;
+    mEntriesInFollowingPartialHistories = 0;
+    NOTIFY_LISTENERS(OnLengthChange, (mLength));
     return true;
   }
   return false;
 }
 
 NS_IMETHODIMP_(void)
 nsSHistory::RemoveEntries(nsTArray<uint64_t>& aIDs, int32_t aStartIndex)
 {
@@ -1508,19 +1599,21 @@ nsSHistory::LoadURI(const char16_t* aURI
                     nsIURI* aReferringURI,
                     nsIInputStream* aPostStream,
                     nsIInputStream* aExtraHeaderStream)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsSHistory::GotoIndex(int32_t aIndex)
+nsSHistory::GotoIndex(int32_t aGlobalIndex)
 {
-  return LoadEntry(aIndex, nsIDocShellLoadInfo::loadHistory,
+  // We provide abstraction of grouped session history for nsIWebNavigation
+  // functions, so the index passed in here is global index.
+  return LoadEntry(aGlobalIndex - mGlobalIndexOffset, nsIDocShellLoadInfo::loadHistory,
                    HIST_CMD_GOTOINDEX);
 }
 
 nsresult
 nsSHistory::LoadNextPossibleEntry(int32_t aNewIndex, long aLoadType,
                                   uint32_t aHistCmd)
 {
   mRequestedIndex = -1;
@@ -1535,16 +1628,36 @@ nsSHistory::LoadNextPossibleEntry(int32_
 
 NS_IMETHODIMP
 nsSHistory::LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd)
 {
   if (!mRootDocShell) {
     return NS_ERROR_FAILURE;
   }
 
+  if (aIndex < 0 || aIndex >= mLength) {
+    if (aIndex + mGlobalIndexOffset < 0) {
+      // The global index is negative.
+      return NS_ERROR_FAILURE;
+    }
+
+    if (aIndex - mLength >= mEntriesInFollowingPartialHistories) {
+      // The global index exceeds max possible value.
+      return NS_ERROR_FAILURE;
+    }
+
+    // The global index is valid. trigger cross browser navigation.
+    nsCOMPtr<nsIPartialSHistoryListener> listener =
+      do_QueryReferent(mPartialHistoryListener);
+    if (!listener) {
+      return NS_ERROR_FAILURE;
+    }
+    return listener->OnRequestCrossBrowserNavigation(aIndex + mGlobalIndexOffset);
+  }
+
   // Keep note of requested history index in mRequestedIndex.
   mRequestedIndex = aIndex;
 
   nsCOMPtr<nsISHEntry> prevEntry;
   GetEntryAtIndex(mIndex, false, getter_AddRefs(prevEntry));
 
   nsCOMPtr<nsISHEntry> nextEntry;
   GetEntryAtIndex(mRequestedIndex, false, getter_AddRefs(nextEntry));
@@ -1750,16 +1863,36 @@ NS_IMETHODIMP
 nsSHistory::GetSHistoryEnumerator(nsISimpleEnumerator** aEnumerator)
 {
   NS_ENSURE_ARG_POINTER(aEnumerator);
   RefPtr<nsSHEnumerator> iterator = new nsSHEnumerator(this);
   iterator.forget(aEnumerator);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsSHistory::OnAttachGroupedSessionHistory(int32_t aOffset)
+{
+  NS_ENSURE_TRUE(!mIsPartial, NS_ERROR_UNEXPECTED);
+  NS_ENSURE_TRUE(aOffset >= 0, NS_ERROR_ILLEGAL_VALUE);
+
+  mIsPartial = true;
+  mGlobalIndexOffset = aOffset;
+
+  // The last attached history is always at the end of the group.
+  mEntriesInFollowingPartialHistories = 0;
+
+  // Setting grouped history info may change canGoBack / canGoForward.
+  // Send a location change to update these values.
+  NS_DispatchToCurrentThread(NewRunnableMethod(static_cast<nsDocShell*>(mRootDocShell),
+                                               &nsDocShell::FireDummyOnLocationChange));
+  return NS_OK;
+
+}
+
 nsSHEnumerator::nsSHEnumerator(nsSHistory* aSHistory) : mIndex(-1)
 {
   mSHistory = aSHistory;
 }
 
 nsSHEnumerator::~nsSHEnumerator()
 {
   mSHistory = nullptr;
--- a/docshell/shistory/nsSHistory.h
+++ b/docshell/shistory/nsSHistory.h
@@ -9,16 +9,17 @@
 
 #include "nsCOMPtr.h"
 #include "nsISHistory.h"
 #include "nsISHistoryInternal.h"
 #include "nsIWebNavigation.h"
 #include "nsISimpleEnumerator.h"
 #include "nsTObserverArray.h"
 #include "nsWeakPtr.h"
+#include "nsIPartialSHistoryListener.h"
 
 #include "prclist.h"
 
 class nsIDocShell;
 class nsSHEnumerator;
 class nsSHistoryObserver;
 class nsISHEntry;
 class nsISHTransaction;
@@ -41,17 +42,17 @@ public:
   static void UpdatePrefs();
 
   // Max number of total cached content viewers.  If the pref
   // browser.sessionhistory.max_total_viewers is negative, then
   // this value is calculated based on the total amount of memory.
   // Otherwise, it comes straight from the pref.
   static uint32_t GetMaxTotalViewers() { return sHistoryMaxTotalViewers; }
 
-protected:
+private:
   virtual ~nsSHistory();
   friend class nsSHEnumerator;
   friend class nsSHistoryObserver;
 
   // Could become part of nsIWebNavigation
   NS_IMETHOD GetTransactionAtIndex(int32_t aIndex, nsISHTransaction** aResult);
   nsresult LoadDifferingEntries(nsISHEntry* aPrevEntry, nsISHEntry* aNextEntry,
                                 nsIDocShell* aRootDocShell, long aLoadType,
@@ -75,28 +76,41 @@ protected:
   // content viewers to cache, based on amount of total memory
   static uint32_t CalcMaxTotalViewers();
 
   void RemoveDynEntries(int32_t aOldIndex, int32_t aNewIndex);
 
   nsresult LoadNextPossibleEntry(int32_t aNewIndex, long aLoadType,
                                  uint32_t aHistCmd);
 
-protected:
   // aIndex is the index of the transaction which may be removed.
   // If aKeepNext is true, aIndex is compared to aIndex + 1,
   // otherwise comparison is done to aIndex - 1.
   bool RemoveDuplicate(int32_t aIndex, bool aKeepNext);
 
   nsCOMPtr<nsISHTransaction> mListRoot;
   int32_t mIndex;
   int32_t mLength;
   int32_t mRequestedIndex;
+
+  // Set to true if attached to a grouped session history.
+  bool mIsPartial;
+
+  // The number of entries before this session history object.
+  int32_t mGlobalIndexOffset;
+
+  // The number of entries after this session history object.
+  int32_t mEntriesInFollowingPartialHistories;
+
   // Session History listeners
   nsAutoTObserverArray<nsWeakPtr, 2> mListeners;
+
+  // Partial session history listener
+  nsWeakPtr mPartialHistoryListener;
+
   // Weak reference. Do not refcount this.
   nsIDocShell* mRootDocShell;
 
   // Max viewers allowed total, across all SHistory objects
   static int32_t sHistoryMaxTotalViewers;
 };
 
 class nsSHEnumerator : public nsISimpleEnumerator
new file mode 100644
--- /dev/null
+++ b/dom/base/GroupedSHistory.cpp
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GroupedSHistory.h"
+#include "TabParent.h"
+#include "PartialSHistory.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION(GroupedSHistory, mPartialHistories)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(GroupedSHistory)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(GroupedSHistory)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(GroupedSHistory)
+  NS_INTERFACE_MAP_ENTRY(nsIGroupedSHistory)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIGroupedSHistory)
+NS_INTERFACE_MAP_END
+
+GroupedSHistory::GroupedSHistory()
+  : mCount(0),
+    mIndexOfActivePartialHistory(-1)
+{
+}
+
+NS_IMETHODIMP
+GroupedSHistory::GetCount(uint32_t* aResult)
+{
+  MOZ_ASSERT(aResult);
+  *aResult = mCount;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+GroupedSHistory::AppendPartialSessionHistory(nsIPartialSHistory* aPartialHistory)
+{
+  if (!aPartialHistory) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+
+  nsCOMPtr<nsIPartialSHistory> partialHistory(aPartialHistory);
+  if (!partialHistory || mPartialHistories.Contains(partialHistory)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Remove all items after active one and deactive it, unless it's the first
+  // call and no active partial history has been set yet.
+  if (mIndexOfActivePartialHistory >= 0) {
+    PurgePartialHistories(mIndexOfActivePartialHistory);
+    nsCOMPtr<nsIPartialSHistory> prevPartialHistory =
+      mPartialHistories[mIndexOfActivePartialHistory];
+    if (NS_WARN_IF(!prevPartialHistory)) {
+      // Cycle collected?
+      return NS_ERROR_UNEXPECTED;
+    }
+    prevPartialHistory->OnDeactive();
+  }
+
+  // Attach the partial history.
+  uint32_t offset = mCount;
+  mCount += partialHistory->GetCount();
+  mPartialHistories.AppendElement(partialHistory);
+  partialHistory->OnAttachGroupedSessionHistory(offset);
+  mIndexOfActivePartialHistory = mPartialHistories.Count() - 1;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+GroupedSHistory::OnPartialSessionHistoryChange(
+  nsIPartialSHistory* aPartialSessionHistory)
+{
+  if (!aPartialSessionHistory) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+
+  nsCOMPtr<nsIPartialSHistory> partialHistory(aPartialSessionHistory);
+  int32_t index = mPartialHistories.IndexOf(partialHistory);
+  if (NS_WARN_IF(index != mIndexOfActivePartialHistory) ||
+      NS_WARN_IF(index < 0)) {
+    // Non-active or not attached partialHistory
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  PurgePartialHistories(index);
+
+  // Update global count.
+  uint32_t count = partialHistory->GetCount();
+  uint32_t offset = partialHistory->GetGlobalIndexOffset();
+  mCount = count + offset;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+GroupedSHistory::GotoIndex(uint32_t aGlobalIndex,
+                           nsIFrameLoader** aTargetLoaderToSwap)
+{
+  nsCOMPtr<nsIPartialSHistory> currentPartialHistory =
+    mPartialHistories[mIndexOfActivePartialHistory];
+  if (!currentPartialHistory) {
+    // Cycle collected?
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  for (uint32_t i = 0; i < mPartialHistories.Length(); i++) {
+    nsCOMPtr<nsIPartialSHistory> partialHistory = mPartialHistories[i];
+    if (NS_WARN_IF(!partialHistory)) {
+      // Cycle collected?
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    // Examine index range.
+    uint32_t offset = partialHistory->GetGlobalIndexOffset();
+    uint32_t count = partialHistory->GetCount();
+    if (offset <= aGlobalIndex && (offset + count) > aGlobalIndex) {
+      uint32_t targetIndex = aGlobalIndex - offset;
+      partialHistory->GetOwnerFrameLoader(aTargetLoaderToSwap);
+      if ((size_t)mIndexOfActivePartialHistory == i) {
+        return NS_OK;
+      }
+      mIndexOfActivePartialHistory = i;
+      if (NS_FAILED(currentPartialHistory->OnDeactive()) ||
+          NS_FAILED(partialHistory->OnActive(mCount, targetIndex))) {
+        return NS_ERROR_FAILURE;
+      }
+      return NS_OK;
+    }
+  }
+
+  // Index not found.
+  NS_WARNING("Out of index request!");
+  return NS_ERROR_FAILURE;
+}
+
+void
+GroupedSHistory::PurgePartialHistories(uint32_t aLastPartialIndexToKeep)
+{
+  uint32_t lastIndex = mPartialHistories.Length() - 1;
+  if (aLastPartialIndexToKeep >= lastIndex) {
+    // Nothing to remove.
+    return;
+  }
+
+  // Close tabs.
+  for (uint32_t i = lastIndex; i > aLastPartialIndexToKeep; i--) {
+    nsCOMPtr<nsIPartialSHistory> partialHistory = mPartialHistories[i];
+    if (!partialHistory) {
+      // Cycle collected?
+      return;
+    }
+
+    nsCOMPtr<nsIFrameLoader> loader;
+    partialHistory->GetOwnerFrameLoader(getter_AddRefs(loader));
+    loader->RequestFrameLoaderClose();
+  }
+
+  // Remove references.
+  mPartialHistories.RemoveElementsAt(aLastPartialIndexToKeep + 1,
+                                     lastIndex - aLastPartialIndexToKeep);
+}
+
+/* static */ bool
+GroupedSHistory::GroupedHistoryEnabled() {
+  return Preferences::GetBool("browser.groupedhistory.enabled", false);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/GroupedSHistory.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GroupedSHistory_h
+#define GroupedSHistory_h
+
+#include "nsIFrameLoader.h"
+#include "nsIGroupedSHistory.h"
+#include "nsIPartialSHistory.h"
+#include "nsTArray.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace dom {
+
+
+/**
+ * GroupedSHistory connects session histories across multiple frameloaders.
+ * Each frameloader has a PartialSHistory, and GroupedSHistory has an array
+ * refering to all participating PartialSHistory(s).
+ *
+ * The following figure illustrates the idea. In this case, the GroupedSHistory
+ * is composed of 3 frameloaders, and the active one is frameloader 1.
+ * GroupedSHistory is always attached to the active frameloader.
+ *
+ *            +----------------------------------------------------+
+ *            |                                                    |
+ *            |                                                    v
+ *  +------------------+      +-------------------+       +-----------------+
+ *  |  FrameLoader 1   |      | PartialSHistory 1 |       | GroupedSHistory |
+ *  |     (active)     |----->|     (active)      |<--+---|                 |
+ *  +------------------+      +-------------------+   |   +-----------------+
+ *                                                    |
+ *  +------------------+      +-------------------+   |
+ *  |  FrameLoader 2   |      | PartialSHistory 2 |   |
+ *  |    (inactive)    |----->|    (inactive)     |<--+
+ *  +------------------+      +-------------------+   |
+ *                                                    |
+ *  +------------------+      +-------------------+   |
+ *  |  FrameLoader 3   |      | PartialSHistory 3 |   |
+ *  |    (inactive)    |----->|    (inactive)     |<--+
+ *  +------------------+      +-------------------+
+ *
+ * If a history navigation leads to frameloader 3, it becomes the active one,
+ * and GroupedSHistory is re-attached to frameloader 3.
+ *
+ *  +------------------+      +-------------------+
+ *  |  FrameLoader 1   |      | PartialSHistory 1 |
+ *  |    (inactive)    |----->|    (inactive)     |<--+
+ *  +------------------+      +-------------------+   |
+ *                                                    |
+ *  +------------------+      +-------------------+   |
+ *  |  FrameLoader 2   |      | PartialSHistory 2 |   |
+ *  |    (inactive)    |----->|    (inactive)     |<--+
+ *  +------------------+      +-------------------+   |
+ *                                                    |
+ *  +------------------+      +-------------------+   |   +-----------------+
+ *  |  FrameLoader 3   |      | PartialSHistory 3 |   |   | GroupedSHistory |
+ *  |     (active)     |----->|     (active)      |<--+---|                 |
+ *  +------------------+      +-------------------+       +-----------------+
+ *            |                                                    ^
+ *            |                                                    |
+ *            +----------------------------------------------------+
+ */
+class GroupedSHistory final : public nsIGroupedSHistory
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(GroupedSHistory)
+  NS_DECL_NSIGROUPEDSHISTORY
+  GroupedSHistory();
+
+  /**
+   * Get the value of preference "browser.groupedhistory.enabled" to determine
+   * if grouped session history should be enabled.
+   */
+  static bool GroupedHistoryEnabled();
+
+private:
+  ~GroupedSHistory() {}
+
+  /**
+   * Remove all partial histories and close tabs after the given index (of
+   * mPartialHistories, not the index of session history entries).
+   */
+  void PurgePartialHistories(uint32_t aLastPartialIndexToKeep);
+
+  // The total number of entries in all partial histories.
+  uint32_t mCount;
+
+  // The index of currently active partial history in mPartialHistories.
+  // Use int32_t as we have invalid index and nsCOMArray also uses int32_t.
+  int32_t mIndexOfActivePartialHistory;
+
+  // All participating nsIPartialSHistory objects.
+  nsCOMArray<nsIPartialSHistory> mPartialHistories;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* GroupedSHistory_h */
new file mode 100644
--- /dev/null
+++ b/dom/base/PartialSHistory.cpp
@@ -0,0 +1,292 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PartialSHistory.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION(PartialSHistory, mOwnerFrameLoader)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PartialSHistory)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PartialSHistory)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PartialSHistory)
+  NS_INTERFACE_MAP_ENTRY(nsIPartialSHistory)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPartialSHistory)
+  NS_INTERFACE_MAP_ENTRY(nsISHistoryListener)
+  NS_INTERFACE_MAP_ENTRY(nsIPartialSHistoryListener)
+  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+PartialSHistory::PartialSHistory(nsIFrameLoader* aOwnerFrameLoader)
+  : mCount(0),
+    mGlobalIndexOffset(0),
+    mOwnerFrameLoader(aOwnerFrameLoader)
+{
+  MOZ_ASSERT(aOwnerFrameLoader);
+}
+
+already_AddRefed<nsISHistory>
+PartialSHistory::GetSessionHistory()
+{
+  if (!mOwnerFrameLoader) {
+    // Cycle collected?
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIDocShell> docShell;
+  mOwnerFrameLoader->GetDocShell(getter_AddRefs(docShell));
+  if (!docShell) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(docShell));
+  nsCOMPtr<nsISHistory> shistory;
+  webNav->GetSessionHistory(getter_AddRefs(shistory));
+  return shistory.forget();
+}
+
+already_AddRefed<TabParent>
+PartialSHistory::GetTabParent()
+{
+  if (!mOwnerFrameLoader) {
+    // Cycle collected?
+    return nullptr;
+  }
+
+  nsCOMPtr<nsITabParent> tabParent;
+  mOwnerFrameLoader->GetTabParent(getter_AddRefs(tabParent));
+  return RefPtr<TabParent>(static_cast<TabParent*>(tabParent.get())).forget();
+}
+
+NS_IMETHODIMP
+PartialSHistory::GetCount(uint32_t* aResult)
+{
+  if (!aResult) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+
+  // If we have direct reference to nsISHistory, simply pass through.
+  nsCOMPtr<nsISHistory> shistory(GetSessionHistory());
+  if (shistory) {
+    int32_t count;
+    nsresult rv = shistory->GetCount(&count);
+    if (NS_FAILED(rv) || count < 0) {
+      *aResult = 0;
+      return NS_ERROR_FAILURE;
+    }
+    *aResult = count;
+    return NS_OK;
+  }
+
+  // Otherwise use the cached value.
+  *aResult = mCount;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PartialSHistory::GetGlobalIndexOffset(uint32_t* aResult)
+{
+  if (!aResult) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+
+  // If we have direct reference to nsISHistory, simply pass through.
+  nsCOMPtr<nsISHistory> shistory(GetSessionHistory());
+  if (shistory) {
+    int32_t offset;
+    nsresult rv = shistory->GetGlobalIndexOffset(&offset);
+    if (NS_FAILED(rv) || offset < 0) {
+      *aResult = 0;
+      return NS_ERROR_FAILURE;
+    }
+    *aResult = offset;
+    return NS_OK;
+  }
+
+  // Otherwise use the cached value.
+  *aResult = mGlobalIndexOffset;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PartialSHistory::GetOwnerFrameLoader(nsIFrameLoader** aResult)
+{
+  nsCOMPtr<nsIFrameLoader> loader(mOwnerFrameLoader);
+  loader.forget(aResult);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnAttachGroupedSessionHistory(uint32_t aOffset)
+{
+  mGlobalIndexOffset = aOffset;
+
+  // If we have direct reference to nsISHistory, simply pass through.
+  nsCOMPtr<nsISHistory> shistory(GetSessionHistory());
+  if (shistory) {
+    // nsISHistory uses int32_t
+    if (aOffset > INT32_MAX) {
+      return NS_ERROR_FAILURE;
+    }
+    return shistory->OnAttachGroupedSessionHistory(aOffset);
+  }
+
+  // Otherwise notify through TabParent.
+  RefPtr<TabParent> tabParent(GetTabParent());
+  if (!tabParent) {
+    // We have neither shistory nor tabParent?
+    NS_WARNING("Unable to get shitory nor tabParent!");
+    return NS_ERROR_UNEXPECTED;
+  }
+  Unused << tabParent->SendNotifyAttachGroupedSessionHistory(aOffset);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnSessionHistoryChange(uint32_t aCount)
+{
+  mCount = aCount;
+  return OnLengthChange(aCount);
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnActive(uint32_t aGlobalLength, uint32_t aTargetLocalIndex)
+{
+  // In-process case.
+  nsCOMPtr<nsISHistory> shistory(GetSessionHistory());
+  if (shistory) {
+    // nsISHistory uses int32_t
+    if (aGlobalLength > INT32_MAX || aTargetLocalIndex > INT32_MAX) {
+      return NS_ERROR_FAILURE;
+    }
+    return shistory->OnPartialSessionHistoryActive(aGlobalLength,
+                                                   aTargetLocalIndex);
+  }
+
+  // Cross-process case.
+  RefPtr<TabParent> tabParent(GetTabParent());
+  if (!tabParent) {
+    // We have neither shistory nor tabParent?
+    NS_WARNING("Unable to get shitory nor tabParent!");
+    return NS_ERROR_UNEXPECTED;
+  }
+  Unused << tabParent->SendNotifyPartialSessionHistoryActive(aGlobalLength,
+                                                             aTargetLocalIndex);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnDeactive()
+{
+  // In-process case.
+  nsCOMPtr<nsISHistory> shistory(GetSessionHistory());
+  if (shistory) {
+    if (NS_FAILED(shistory->OnPartialSessionHistoryDeactive())) {
+      return NS_ERROR_FAILURE;
+    }
+    return NS_OK;
+  }
+
+  // Cross-process case.
+  RefPtr<TabParent> tabParent(GetTabParent());
+  if (!tabParent) {
+    // We have neither shistory nor tabParent?
+    NS_WARNING("Unable to get shitory nor tabParent!");
+    return NS_ERROR_UNEXPECTED;
+  }
+  Unused << tabParent->SendNotifyPartialSessionHistoryDeactive();
+  return NS_OK;
+}
+
+/*******************************************************************************
+ * nsIPartialSHistoryListener
+ ******************************************************************************/
+
+NS_IMETHODIMP
+PartialSHistory::OnRequestCrossBrowserNavigation(uint32_t aIndex)
+{
+  if (!mOwnerFrameLoader) {
+    // Cycle collected?
+    return NS_ERROR_UNEXPECTED;
+  }
+  return mOwnerFrameLoader->RequestGroupedHistoryNavigation(aIndex);
+}
+
+/*******************************************************************************
+ * nsISHistoryListener
+ ******************************************************************************/
+
+NS_IMETHODIMP
+PartialSHistory::OnLengthChange(int32_t aCount)
+{
+  if (!mOwnerFrameLoader) {
+    // Cycle collected?
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  if (aCount < 0) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIGroupedSHistory> groupedHistory;
+  mOwnerFrameLoader->GetGroupedSessionHistory(getter_AddRefs(groupedHistory));
+  if (!groupedHistory) {
+    // Maybe we're not the active partial history, but in this case we shouldn't
+    // receive any update from session history object either.
+    return NS_ERROR_FAILURE;
+  }
+
+  groupedHistory->OnPartialSessionHistoryChange(this);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnHistoryNewEntry(nsIURI *aNewURI, int32_t aOldIndex)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnHistoryGoBack(nsIURI *aBackURI, bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnHistoryGoForward(nsIURI *aForwardURI, bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnHistoryReload(nsIURI *aReloadURI, uint32_t aReloadFlags, bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnHistoryGotoIndex(int32_t aIndex, nsIURI *aGotoURI, bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnHistoryPurge(int32_t aNumEntries, bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnHistoryReplaceEntry(int32_t aIndex)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/PartialSHistory.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef PartialSHistory_h
+#define PartialSHistory_h
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsFrameLoader.h"
+#include "nsIGroupedSHistory.h"
+#include "nsIPartialSHistoryListener.h"
+#include "nsIPartialSHistory.h"
+#include "nsISHistory.h"
+#include "nsISHistoryListener.h"
+#include "TabParent.h"
+
+namespace mozilla {
+namespace dom {
+
+class PartialSHistory final : public nsIPartialSHistory,
+                              public nsISHistoryListener,
+                              public nsIPartialSHistoryListener,
+                              public nsSupportsWeakReference
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(PartialSHistory, nsIPartialSHistory)
+  NS_DECL_NSIPARTIALSHISTORY
+  NS_DECL_NSIPARTIALSHISTORYLISTENER
+  NS_DECL_NSISHISTORYLISTENER
+
+  /**
+   * Note that PartialSHistory must be constructed after frameloader has
+   * created a valid docshell or tabparent.
+   */
+  explicit PartialSHistory(nsIFrameLoader* aOwnerFrameLoader);
+
+private:
+  ~PartialSHistory() {}
+  already_AddRefed<nsISHistory> GetSessionHistory();
+  already_AddRefed<TabParent> GetTabParent();
+
+  // The cache of number of entries in corresponding nsISHistory. It's only
+  // used for remote process case. If nsISHistory is in-process, mCount will not
+  // be used at all.
+  uint32_t mCount;
+
+  // The cache of globalIndexOffset in corresponding nsISHistory. It's only
+  // used for remote process case.
+  uint32_t mGlobalIndexOffset;
+
+  // The frameloader which owns this PartialSHistory.
+  nsCOMPtr<nsIFrameLoader> mOwnerFrameLoader;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* PartialSHistory_h */
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -177,28 +177,30 @@ EXPORTS.mozilla.dom += [
     'ElementInlines.h',
     'EventSource.h',
     'File.h',
     'FileList.h',
     'FileReader.h',
     'FormData.h',
     'FragmentOrElement.h',
     'FromParser.h',
+    'GroupedSHistory.h',
     'ImageEncoder.h',
     'ImageTracker.h',
     'ImportManager.h',
     'Link.h',
     'Location.h',
     'MutableBlobStorage.h',
     'MutableBlobStreamListener.h',
     'NameSpaceConstants.h',
     'Navigator.h',
     'NodeInfo.h',
     'NodeInfoInlines.h',
     'NodeIterator.h',
+    'PartialSHistory.h',
     'ProcessGlobal.h',
     'ResponsiveImageSelector.h',
     'SameProcessMessageQueue.h',
     'ScreenOrientation.h',
     'ScriptSettings.h',
     'ShadowRoot.h',
     'StructuredCloneHolder.h',
     'StructuredCloneTags.h',
@@ -240,16 +242,17 @@ UNIFIED_SOURCES += [
     'DOMStringList.cpp',
     'Element.cpp',
     'EventSource.cpp',
     'File.cpp',
     'FileList.cpp',
     'FileReader.cpp',
     'FormData.cpp',
     'FragmentOrElement.cpp',
+    'GroupedSHistory.cpp',
     'ImageEncoder.cpp',
     'ImageTracker.cpp',
     'ImportManager.cpp',
     'Link.cpp',
     'Location.cpp',
     'MultipartBlobImpl.cpp',
     'MutableBlobStorage.cpp',
     'MutableBlobStreamListener.cpp',
@@ -326,16 +329,17 @@ UNIFIED_SOURCES += [
     'nsTreeSanitizer.cpp',
     'nsViewportInfo.cpp',
     'nsWindowMemoryReporter.cpp',
     'nsWindowRoot.cpp',
     'nsWrapperCache.cpp',
     'nsXHTMLContentSerializer.cpp',
     'nsXMLContentSerializer.cpp',
     'nsXMLNameSpaceMap.cpp',
+    'PartialSHistory.cpp',
     'PostMessageEvent.cpp',
     'ProcessGlobal.cpp',
     'ResponsiveImageSelector.cpp',
     'SameProcessMessageQueue.cpp',
     'ScreenOrientation.cpp',
     'ScriptSettings.cpp',
     'ShadowRoot.cpp',
     'StructuredCloneHolder.cpp',
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -53,16 +53,18 @@
 #include "nsIMozBrowserFrame.h"
 #include "nsISHistory.h"
 #include "nsNullPrincipal.h"
 #include "nsIScriptError.h"
 #include "nsGlobalWindow.h"
 #include "nsPIWindowRoot.h"
 #include "nsLayoutUtils.h"
 #include "nsView.h"
+#include "GroupedSHistory.h"
+#include "PartialSHistory.h"
 
 #include "nsIURI.h"
 #include "nsIURL.h"
 #include "nsNetUtil.h"
 
 #include "nsGkAtoms.h"
 #include "nsNameSpaceManager.h"
 
@@ -129,17 +131,22 @@ typedef FrameMetrics::ViewID ViewID;
 // does not count chrome frames when determining depth, nor does it
 // prevent chrome recursion.  Number is fairly arbitrary, but meant to
 // keep number of shells to a reasonable number on accidental recursion with a
 // small (but not 1) branching factor.  With large branching factors the number
 // of shells can rapidly become huge and run us out of memory.  To solve that,
 // we'd need to re-institute a fixed version of bug 98158.
 #define MAX_DEPTH_CONTENT_FRAMES 10
 
-NS_IMPL_CYCLE_COLLECTION(nsFrameLoader, mDocShell, mMessageManager, mChildMessageManager)
+NS_IMPL_CYCLE_COLLECTION(nsFrameLoader,
+                         mDocShell,
+                         mMessageManager,
+                         mChildMessageManager,
+                         mPartialSessionHistory,
+                         mGroupedSessionHistory)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameLoader)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameLoader)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameLoader)
   NS_INTERFACE_MAP_ENTRY(nsIFrameLoader)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFrameLoader)
   NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersistable)
 NS_INTERFACE_MAP_END
@@ -364,16 +371,127 @@ nsFrameLoader::MakePrerenderedLoaderActi
 
     nsresult rv = mDocShell->SetIsActive(true);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsFrameLoader::GetPartialSessionHistory(nsIPartialSHistory** aResult)
+{
+  if (mRemoteBrowser && !mPartialSessionHistory) {
+    // For remote case we can lazy initialize PartialSHistory since
+    // it doens't need to be registered as a listener to nsISHistory directly.
+    mPartialSessionHistory = new PartialSHistory(this);
+  }
+
+  nsCOMPtr<nsIPartialSHistory> partialHistory(mPartialSessionHistory);
+  partialHistory.forget(aResult);
+  return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsFrameLoader::GetGroupedSessionHistory(nsIGroupedSHistory** aResult)
+{
+  nsCOMPtr<nsIGroupedSHistory> groupedHistory(mGroupedSessionHistory);
+  groupedHistory.forget(aResult);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::AppendPartialSessionHistoryAndSwap(nsIFrameLoader* aOther)
+{
+  if (!aOther) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+
+  nsCOMPtr<nsIGroupedSHistory> otherGroupedHistory;
+  aOther->GetGroupedSessionHistory(getter_AddRefs(otherGroupedHistory));
+  MOZ_ASSERT(!otherGroupedHistory,
+             "Cannot append a GroupedSHistory owner to another.");
+  if (otherGroupedHistory) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  // Append ourselves.
+  nsresult rv;
+  if (!mGroupedSessionHistory) {
+    mGroupedSessionHistory = new GroupedSHistory();
+    rv = mGroupedSessionHistory->AppendPartialSessionHistory(mPartialSessionHistory);
+    if (NS_FAILED(rv)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  if (aOther == this) {
+    return NS_OK;
+  }
+
+  // Append the other.
+  RefPtr<nsFrameLoader> otherLoader = static_cast<nsFrameLoader*>(aOther);
+  rv = mGroupedSessionHistory->
+         AppendPartialSessionHistory(otherLoader->mPartialSessionHistory);
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Swap loaders through our owner, so the owner's listeners will be correctly
+  // setup.
+  nsCOMPtr<nsIBrowser> ourBrowser = do_QueryInterface(mOwnerContent);
+  nsCOMPtr<nsIBrowser> otherBrowser = do_QueryInterface(otherLoader->mOwnerContent);
+  if (!ourBrowser || !otherBrowser) {
+    return NS_ERROR_FAILURE;
+  }
+  if (NS_FAILED(ourBrowser->SwapBrowsers(otherBrowser))) {
+    return NS_ERROR_FAILURE;
+  }
+  mGroupedSessionHistory.swap(otherLoader->mGroupedSessionHistory);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::RequestGroupedHistoryNavigation(uint32_t aGlobalIndex)
+{
+  if (!mGroupedSessionHistory) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  nsCOMPtr<nsIFrameLoader> targetLoader;
+  nsresult rv = mGroupedSessionHistory->
+                  GotoIndex(aGlobalIndex, getter_AddRefs(targetLoader));
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  RefPtr<nsFrameLoader> otherLoader = static_cast<nsFrameLoader*>(targetLoader.get());
+  if (!targetLoader) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (targetLoader == this) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIBrowser> ourBrowser = do_QueryInterface(mOwnerContent);
+  nsCOMPtr<nsIBrowser> otherBrowser = do_QueryInterface(otherLoader->mOwnerContent);
+  if (!ourBrowser || !otherBrowser) {
+    return NS_ERROR_FAILURE;
+  }
+  if (NS_FAILED(ourBrowser->SwapBrowsers(otherBrowser))) {
+    return NS_ERROR_FAILURE;
+  }
+  mGroupedSessionHistory.swap(otherLoader->mGroupedSessionHistory);
+
+  return NS_OK;
+}
+
 nsresult
 nsFrameLoader::ReallyStartLoading()
 {
   nsresult rv = ReallyStartLoadingInternal();
   if (NS_FAILED(rv)) {
     FireErrorEvent();
   }
 
@@ -2027,16 +2145,25 @@ nsFrameLoader::MaybeCreateDocShell()
       !mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::disablehistory)) {
     nsresult rv;
     nsCOMPtr<nsISHistory> sessionHistory =
       do_CreateInstance(NS_SHISTORY_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell));
     webNav->SetSessionHistory(sessionHistory);
+
+
+    if (GroupedSHistory::GroupedHistoryEnabled()) {
+      mPartialSessionHistory = new PartialSHistory(this);
+      nsCOMPtr<nsISHistoryListener> listener(do_QueryInterface(mPartialSessionHistory));
+      nsCOMPtr<nsIPartialSHistoryListener> partialListener(do_QueryInterface(mPartialSessionHistory));
+      sessionHistory->AddSHistoryListener(listener);
+      sessionHistory->SetPartialSHistoryListener(partialListener);
+    }
   }
 
   DocShellOriginAttributes attrs;
   if (docShell->ItemType() == mDocShell->ItemType()) {
     attrs = nsDocShell::Cast(docShell)->GetOriginAttributes();
   }
 
   // Inherit origin attributes from parent document if
@@ -3090,16 +3217,28 @@ nsFrameLoader::RequestNotifyAfterRemoteP
   if (mRemoteBrowser) {
     Unused << mRemoteBrowser->SendRequestNotifyAfterRemotePaint();
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsFrameLoader::RequestFrameLoaderClose()
+{
+  nsCOMPtr<nsIBrowser> browser = do_QueryInterface(mOwnerContent);
+  if (NS_WARN_IF(!browser)) {
+    // OwnerElement other than nsIBrowser is not supported yet.
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  return browser->CloseBrowser();
+}
+
+NS_IMETHODIMP
 nsFrameLoader::Print(uint64_t aOuterWindowID,
                      nsIPrintSettings* aPrintSettings,
                      nsIWebProgressListener* aProgressListener)
 {
 #if defined(NS_PRINTING)
   if (mRemoteBrowser) {
     RefPtr<embedding::PrintingParent> printingParent =
       mRemoteBrowser->Manager()->AsContentParent()->GetPrintingParent();
--- a/dom/base/nsFrameLoader.h
+++ b/dom/base/nsFrameLoader.h
@@ -20,16 +20,17 @@
 #include "nsIURI.h"
 #include "nsFrameMessageManager.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/Attributes.h"
 #include "nsStubMutationObserver.h"
 #include "Units.h"
 #include "nsIWebBrowserPersistable.h"
 #include "nsIFrame.h"
+#include "nsIGroupedSHistory.h"
 
 class nsIURI;
 class nsSubDocumentFrame;
 class nsView;
 class nsIInProcessContentFrameMessageManager;
 class AutoResetInShow;
 class AutoResetInFrameSwap;
 class nsITabParent;
@@ -359,16 +360,19 @@ private:
 
   // See nsIFrameLoader.idl. EVENT_MODE_NORMAL_DISPATCH automatically
   // forwards some input events to out-of-process content.
   uint32_t mEventMode;
 
   // Holds the last known size of the frame.
   mozilla::ScreenIntSize mLazySize;
 
+  nsCOMPtr<nsIPartialSHistory> mPartialSessionHistory;
+  nsCOMPtr<nsIGroupedSHistory> mGroupedSessionHistory;
+
   bool mIsPrerendered : 1;
   bool mDepthTooGreat : 1;
   bool mIsTopLevelContent : 1;
   bool mDestroyCalled : 1;
   bool mNeedsAsyncDestroy : 1;
   bool mInSwap : 1;
   bool mInShow : 1;
   bool mHideCalled : 1;
--- a/dom/base/nsIFrameLoader.idl
+++ b/dom/base/nsIFrameLoader.idl
@@ -13,16 +13,18 @@ interface nsIFrame;
 interface nsSubDocumentFrame;
 interface nsIMessageSender;
 interface nsIVariant;
 interface nsIDOMElement;
 interface nsITabParent;
 interface nsILoadContext;
 interface nsIPrintSettings;
 interface nsIWebProgressListener;
+interface nsIGroupedSHistory;
+interface nsIPartialSHistory;
 
 [scriptable, builtinclass, uuid(1645af04-1bc7-4363-8f2c-eb9679220ab1)]
 interface nsIFrameLoader : nsISupports
 {
   /**
    * Get the docshell from the frame loader.
    */
   readonly attribute nsIDocShell docShell;
@@ -67,16 +69,27 @@ interface nsIFrameLoader : nsISupports
   void setIsPrerendered();
 
   /**
    * Make the prerendered frameloader being active (and clear isPrerendered flag).
    */
   void makePrerenderedLoaderActive();
 
   /**
+   * Append partial session history from another frame loader.
+   */
+  void appendPartialSessionHistoryAndSwap(in nsIFrameLoader aOther);
+
+  /**
+   * If grouped session history is applied, use this function to navigate to
+   * an entry of session history object of another frameloader.
+   */
+  void requestGroupedHistoryNavigation(in unsigned long aGlobalIndex);
+
+  /**
    * Destroy the frame loader and everything inside it. This will
    * clear the weak owner content reference.
    */
   void destroy();
 
   /**
    * Find out whether the loader's frame is at too great a depth in
    * the frame tree.  This can be used to decide what operations may
@@ -134,16 +147,21 @@ interface nsIFrameLoader : nsISupports
   /**
    * Request that the next time a remote layer transaction has been
    * received by the Compositor, a MozAfterRemoteFrame event be sent
    * to the window.
    */
   void requestNotifyAfterRemotePaint();
 
   /**
+   * Close the window through the ownerElement.
+   */
+  void requestFrameLoaderClose();
+
+  /**
    * Print the current document.
    *
    * @param aOuterWindowID the ID of the outer window to print
    * @param aPrintSettings optional print settings to use; printSilent can be
    *                       set to prevent prompting.
    * @param aProgressListener optional print progress listener.
    */
   void print(in unsigned long long aOuterWindowID,
@@ -223,16 +241,27 @@ interface nsIFrameLoader : nsISupports
 
   /**
    * The last known height of the frame. Reading this property will not trigger
    * a reflow, and therefore may not reflect the current state of things. It
    * should only be used in asynchronous APIs where values are not guaranteed
    * to be up-to-date when received.
    */
   readonly attribute unsigned long lazyHeight;
+
+  /**
+   * The partial session history.
+   */
+  readonly attribute nsIPartialSHistory partialSessionHistory;
+
+  /**
+   * The grouped session history composed of multiple session history objects
+   * across root docshells.
+   */
+  readonly attribute nsIGroupedSHistory groupedSessionHistory;
 };
 
 %{C++
 class nsFrameLoader;
 %}
 
 native alreadyAddRefed_nsFrameLoader(already_AddRefed<nsFrameLoader>);
 
--- a/dom/base/test/chrome/chrome.ini
+++ b/dom/base/test/chrome/chrome.ini
@@ -19,16 +19,17 @@ support-files =
   fileconstructor_file.png
   frame_bug814638.xul
   frame_registerElement_content.html
   registerElement_ep.js
   host_bug814638.xul
   window_nsITextInputProcessor.xul
   title_window.xul
   window_swapFrameLoaders.xul
+  window_groupedSHistory.xul
 
 [test_bug120684.xul]
 [test_bug206691.xul]
 [test_bug289714.xul]
 [test_bug339494.xul]
 [test_bug357450.xul]
 support-files = ../file_bug357450.js
 [test_bug380418.html]
@@ -67,8 +68,9 @@ skip-if = buildapp == 'mulet'
 [test_registerElement_ep.xul]
 [test_domparsing.xul]
 [test_fileconstructor.xul]
 [test_fileconstructor_tempfile.xul]
 [test_nsITextInputProcessor.xul]
 [test_title.xul]
 [test_windowroot.xul]
 [test_swapFrameLoaders.xul]
+[test_groupedSHistory.xul]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/chrome/test_groupedSHistory.xul
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1276553
+-->
+<window title="Mozilla Bug 1276553"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="loadTest();">
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1276553"
+     target="_blank">Mozilla Bug 1276553</a>
+  </body>
+
+  <!-- test code goes here -->
+  <script type="application/javascript">
+  <![CDATA[
+
+  /** Test for GroupedSHistory **/
+  SimpleTest.waitForExplicitFinish();
+  function loadTest() {
+    window.open("window_groupedSHistory.xul", "", "width=360,height=240,chrome");
+  }
+
+  ]]>
+  </script>
+</window>
new file mode 100644
--- /dev/null
+++ b/dom/base/test/chrome/window_groupedSHistory.xul
@@ -0,0 +1,331 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1276553
+-->
+<window title="Mozilla Bug 1276553"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="run();">
+
+  <!-- test code goes here -->
+  <script type="application/javascript">
+  <![CDATA[
+
+  const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components;
+  Cu.import("resource://testing-common/TestUtils.jsm");
+  Cu.import("resource://testing-common/ContentTask.jsm");
+  Cu.import("resource://testing-common/BrowserTestUtils.jsm");
+  Cu.import("resource://gre/modules/Task.jsm");
+  ContentTask.setTestScope(window.opener.wrappedJSObject);
+
+  let imports = ['SimpleTest', 'SpecialPowers', 'ok', 'is', 'info'];
+  for (let name of imports) {
+    window[name] = window.opener.wrappedJSObject[name];
+  }
+
+  /** Test for Bug 1276553 **/
+  function run() {
+    new Promise(resolve => SpecialPowers.pushPrefEnv(
+      {'set' : [[ 'browser.groupedhistory.enabled', true ]]}, resolve))
+    .then(() => test(false))
+    .then(() => test(true))
+    .then(() => {
+      window.close();
+      SimpleTest.finish();
+    });
+  }
+
+  function test(remote) {
+    let act, bg1, bg2;
+    return Promise.resolve()
+
+    // create first browser with 1 entry (which will always be the active one)
+    .then(() => info('TEST-INFO | test create active browser, remote=' + remote))
+    .then(() => createBrowser('pen', remote))
+    .then(b => act = b)
+    .then(() => verifyBrowser(act, 'pen'       /* title */,
+                                    0          /* index */,
+                                    1          /* length */,
+                                    false      /* canGoBack */,
+                                    false      /* canGoForward */,
+                                    false      /* partial */ ))
+
+    // create background browser 1 with 1 entry
+    .then(() => info('TEST-INFO | test create background browser 1, remote=' + remote))
+    .then(() => createBrowser('pineapple', remote))
+    .then(b => bg1 = b)
+    .then(() => verifyBrowser(bg1, 'pineapple' /* title */,
+                                    0          /* index */,
+                                    1          /* length */,
+                                    false      /* canGoBack */,
+                                    false      /* canGoForward */,
+                                    false      /* partial */ ))
+
+     // create background browser 2 with 2 entries
+    .then(() => info('TEST-INFO | test create background browser 2, remote=' + remote))
+    .then(() => createBrowser('apple', remote))
+    .then(b => bg2 = b)
+    .then(() => verifyBrowser(bg2, 'apple'     /* title */,
+                                    0          /* index */,
+                                    1          /* length */,
+                                    false      /* canGoBack */,
+                                    false      /* canGoForward */,
+                                    false      /* partial */ ))
+    .then(() => loadURI(bg2, getDummyHtml('pencil')))
+    .then(() => verifyBrowser(bg2, 'pencil'    /* title */,
+                                    1          /* index */,
+                                    2          /* length */,
+                                    true       /* canGoBack */,
+                                    false      /* canGoForward */,
+                                    false      /* partial */ ))
+
+    // merge to 2 entries pen-pineapple
+    .then(() => info('TEST-INFO | test merge history, remote=' + remote))
+    .then(() => mergeHistory(act, bg1))
+    .then(() => verifyBrowser(act, 'pineapple' /* title */,
+                                    0          /* index */,
+                                    1          /* length */,
+                                    true       /* canGoBack */,
+                                    false      /* canGoForward */,
+                                    true       /* partial */,
+                                    1          /* offset */,
+                                    2          /* globalLength */ ))
+
+    // merge to 4 entries pen-pineapple-apple-pencil
+    .then(() => mergeHistory(act, bg2))
+    .then(() => verifyBrowser(act, 'pencil'    /* title */,
+                                    1          /* index */,
+                                    2          /* length */,
+                                    true       /* canGoBack */,
+                                    false      /* canGoForward */,
+                                    true       /* partial */,
+                                    2          /* offset */,
+                                    4          /* globalLength */ ))
+
+    // test go back
+    .then(() => info('TEST-INFO | test history go back, remote=' + remote))
+    .then(() => wrapHistoryNavFn(act, act.goBack.bind(act)))
+    .then(() => verifyBrowser(act, 'apple'     /* title */,
+                                    0          /* index */,
+                                    2          /* length */,
+                                    true       /* canGoBack */,
+                                    true       /* canGoForward */,
+                                    true       /* partial */,
+                                    2          /* offset */,
+                                    4          /* globalLength */ ))
+    // XXX The 2nd pageshow comes from reload as current index of the active
+    // partial history remains the same
+    .then(() => wrapHistoryNavFn(act, act.goBack.bind(act), true))
+    .then(() => verifyBrowser(act, 'pineapple' /* title */,
+                                    0          /* index */,
+                                    1          /* length */,
+                                    true       /* canGoBack */,
+                                    true       /* canGoForward */,
+                                    true       /* partial */,
+                                    1          /* offset */,
+                                    4          /* globalLength */ ))
+    .then(() => wrapHistoryNavFn(act, act.goBack.bind(act), true))
+    .then(() => verifyBrowser(act, 'pen'       /* title */,
+                                    0          /* index */,
+                                    1          /* length */,
+                                    false      /* canGoBack */,
+                                    true       /* canGoForward */,
+                                    true       /* partial */,
+                                    0          /* offset */,
+                                    4          /* globalLength */ ))
+
+    // test go forward
+    .then(() => info('TEST-INFO | test history go forward, remote=' + remote))
+    .then(() => wrapHistoryNavFn(act, act.goForward.bind(act), true))
+    .then(() => verifyBrowser(act, 'pineapple' /* title */,
+                                    0          /* index */,
+                                    1          /* length */,
+                                    true       /* canGoBack */,
+                                    true       /* canGoForward */,
+                                    true       /* partial */,
+                                    1          /* offset */,
+                                    4          /* globalLength */ ))
+    .then(() => wrapHistoryNavFn(act, act.goForward.bind(act), true))
+    .then(() => verifyBrowser(act, 'apple'     /* title */,
+                                    0          /* index */,
+                                    2          /* length */,
+                                    true       /* canGoBack */,
+                                    true       /* canGoForward */,
+                                    true       /* partial */,
+                                    2          /* offset */,
+                                    4          /* globalLength */ ))
+    .then(() => wrapHistoryNavFn(act, act.goForward.bind(act)))
+    .then(() => verifyBrowser(act, 'pencil'    /* title */,
+                                    1          /* index */,
+                                    2          /* length */,
+                                    true       /* canGoBack */,
+                                    false      /* canGoForward */,
+                                    true       /* partial */,
+                                    2          /* offset */,
+                                    4          /* globalLength */ ))
+
+    // test goto index
+    .then(() => info('TEST-INFO | test history goto index, remote=' + remote))
+    .then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 0), true))
+    .then(() => verifyBrowser(act, 'pen'       /* title */,
+                                    0          /* index */,
+                                    1          /* length */,
+                                    false      /* canGoBack */,
+                                    true       /* canGoForward */,
+                                    true       /* partial */,
+                                    0          /* offset */,
+                                    4          /* globalLength */ ))
+    // expect 2 pageshow since we're also changing mIndex of the partial history
+    .then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 2), true, 2))
+    .then(() => verifyBrowser(act, 'apple'     /* title */,
+                                    0          /* index */,
+                                    2          /* length */,
+                                    true       /* canGoBack */,
+                                    true       /* canGoForward */,
+                                    true       /* partial */,
+                                    2          /* offset */,
+                                    4          /* globalLength */ ))
+    .then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 1), true))
+    .then(() => verifyBrowser(act, 'pineapple' /* title */,
+                                    0          /* index */,
+                                    1          /* length */,
+                                    true       /* canGoBack */,
+                                    true       /* canGoForward */,
+                                    true       /* partial */,
+                                    1          /* offset */,
+                                    4          /* globalLength */ ))
+    // expect 2 pageshow since we're also changing mIndex of the partial history
+    .then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 3), true, 2))
+    .then(() => verifyBrowser(act, 'pencil'    /* title */,
+                                    1          /* index */,
+                                    2          /* length */,
+                                    true       /* canGoBack */,
+                                    false      /* canGoForward */,
+                                    true       /* partial */,
+                                    2          /* offset */,
+                                    4          /* globalLength */ ))
+
+    // test history change to 3 entries pen-pineapple-banana
+    .then(() => info('TEST-INFO | test history change, remote=' + remote))
+    .then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 1), true))
+    .then(() => verifyBrowser(act, 'pineapple' /* title */,
+                                    0          /* index */,
+                                    1          /* length */,
+                                    true       /* canGoBack */,
+                                    true       /* canGoForward */,
+                                    true       /* partial */,
+                                    1          /* offset */,
+                                    4          /* globalLength */ ))
+    .then(() => loadURI(act, getDummyHtml('banana')))
+    .then(() => verifyBrowser(act, 'banana'    /* title */,
+                                    1          /* index */,
+                                    2          /* length */,
+                                    true       /* canGoBack */,
+                                    false      /* canGoForward */,
+                                    true       /* partial */,
+                                    1          /* offset */,
+                                    3          /* globalLength */ ))
+  }
+
+  function getDummyHtml(title) {
+    return 'data:text/html;charset=UTF-8,' +
+     '<html><head><title>' + title + '</title></head></html>'
+  }
+
+  function createBrowser(title, remote) {
+    let browser = document.createElement('browser');
+    browser.setAttribute('type', 'content');
+    browser.setAttribute('remote', remote);
+    browser.setAttribute('src', getDummyHtml(title));
+    document.getElementById('stack').appendChild(browser);
+    return BrowserTestUtils.browserLoaded(browser)
+           .then(() => {
+             browser.messageManager.loadFrameScript('data:,' +
+               'addEventListener("pageshow", () => sendAsyncMessage("test:pageshow", null), false);' +
+               'addEventListener("pagehide", () => sendAsyncMessage("test:pagehide", null), false);',
+               true);
+           })
+           .then(() => {
+             // a trick to ensure webProgress object is created for e10s case
+             ok(browser.webProgress, 'check browser.webProgress exists');
+             return browser;
+           });
+  }
+
+  function loadURI(browser, uri) {
+    let promise = BrowserTestUtils.browserLoaded(browser, false, uri);
+    browser.loadURI(uri);
+    return promise;
+  }
+
+  function mergeHistory(b1, b2) {
+    let promises = [];
+    let pagehide1, pagehide2;
+
+    // For swapping there should be a pagehide followed by a pageshow.
+    promises.push(BrowserTestUtils.waitForMessage(b1.messageManager, 'test:pagehide', msg => pagehide1 = true));
+    promises.push(BrowserTestUtils.waitForMessage(b2.messageManager, 'test:pagehide', msg => pagehide2 = true));
+    promises.push(BrowserTestUtils.waitForMessage(b1.messageManager, 'test:pageshow', msg => pagehide1));
+    promises.push(BrowserTestUtils.waitForMessage(b2.messageManager, 'test:pageshow', msg => pagehide2));
+    promises.push(Promise.resolve().then(() => {
+      let f1 = b1.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
+      let f2 = b2.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
+      f1.appendPartialSessionHistoryAndSwap(f2);
+    }));
+
+    return Promise.all(promises);
+  }
+
+  function wrapHistoryNavFn(browser, navFn, expectSwap = false, expectPageshowCount = 1) {
+    let promises = [];
+    let pagehide = false;
+    let pageshowCount = 0;
+
+    if (expectSwap) {
+      // For swapping there should be a pagehide followed by a pageshow.
+      promises.push(BrowserTestUtils.waitForMessage(browser.messageManager,
+        'test:pagehide', msg => pagehide = true));
+    }
+    promises.push(BrowserTestUtils.waitForMessage(browser.messageManager,
+      'test:pageshow', msg => {
+        // Only count events after pagehide for swapping case.
+        if (!expectSwap || pagehide) {
+          return !--expectPageshowCount;
+        }
+        return false;
+      }));
+    promises.push(Task.spawn(navFn));
+
+    return Promise.all(promises);
+  }
+
+  function verifyBrowser(browser, title, index, length, canGoBack, canGoForward,
+                         partial, offset = 0, globalLength = length) {
+    is(browser.canGoBack, canGoBack, 'check browser.canGoBack');
+    is(browser.canGoForward, canGoForward, 'check browser.canGoForward');
+    if (partial) {
+      let frameLoader = browser.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
+      is(frameLoader.groupedSessionHistory.count, globalLength, 'check groupedSHistory.count');
+    }
+
+    return ContentTask.spawn(browser,
+                             { title, index, length, canGoBack, canGoForward, partial, offset, globalLength },
+                             ({ title, index, length, canGoBack, canGoForward, partial, offset, globalLength }) => {
+      let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+      let shistory = webNav.sessionHistory;
+      is(content.document.title, title, 'check title');
+      is(webNav.canGoBack, canGoBack, 'check webNav.canGoBack');
+      is(webNav.canGoForward, canGoForward, 'check webNav.canGoForward');
+      is(shistory.index, index, 'check shistory.index');
+      is(shistory.count, length, 'check shistory.count');
+      is(shistory.isPartial, partial, 'check shistory.isPartial');
+      is(shistory.globalIndexOffset, offset, 'check shistory.globalIndexOffset');
+      is(shistory.globalCount, globalLength, 'check shistory.globalCount');
+    });
+  }
+
+  ]]>
+  </script>
+  <stack id="stack" flex="1" />
+</window>
--- a/dom/interfaces/base/moz.build
+++ b/dom/interfaces/base/moz.build
@@ -1,16 +1,17 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 XPIDL_SOURCES += [
     'domstubs.idl',
+    'nsIBrowser.idl',
     'nsIBrowserDOMWindow.idl',
     'nsIContentPermissionPrompt.idl',
     'nsIContentPrefService.idl',
     'nsIContentPrefService2.idl',
     'nsIContentURIGrouper.idl',
     'nsIDOMChromeWindow.idl',
     'nsIDOMClientRect.idl',
     'nsIDOMClientRectList.idl',
rename from dom/ipc/nsIBrowser.idl
rename to dom/interfaces/base/nsIBrowser.idl
--- a/dom/ipc/nsIBrowser.idl
+++ b/dom/interfaces/base/nsIBrowser.idl
@@ -19,9 +19,28 @@ interface nsIBrowser : nsISupports
    * Called by the child to inform the parent that links are dropped into
    * content area.
    *
    * @param linksCount length of links
    * @param links a flat array of url, name, and type for each link
    */
   void dropLinks(in unsigned long linksCount,
                  [array, size_is(linksCount)] in wstring links);
+
+  /**
+   * Swapping of frameloaders are usually initiated from a frameloader owner
+   * or other components operating on frameloader owners. This is done by calling
+   * swapFrameLoaders at MozFrameLoaderOwner webidl interface.
+   *
+   * This function aimed to provide the other way around -
+   * if the swapping is initiated from frameloader itself or other platform level
+   * components, it uses this interface to delegate the swapping request to
+   * frameloader owners and ask them to re-initiate frameloader swapping, so that
+   * frameloader owners such as <xul:browser> can setup their properties and /
+   * or listeners properly on swapping.
+   */
+  void swapBrowsers(in nsIBrowser aOtherBrowser);
+
+  /**
+   * Close the browser (usually means to remove a tab).
+   */
+  void closeBrowser();
 };
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -593,16 +593,33 @@ parent:
 
     // After a compositor reset, it is necessary to reconnect each layers ID to
     // the compositor of the widget that will render those layers. Note that
     // this is sync so we can ensure that messages to the window compositor
     // arrive before the TabChild attempts to use its cross-process compositor
     // bridge.
     sync EnsureLayersConnected();
 
+    /**
+     * Notify parent that one or more entries have been added / removed from
+     * the child session history.
+     *
+     * @param aCount the updated number of entries in child session history
+     */
+    async NotifySessionHistoryChange(uint32_t aCount);
+
+    /**
+     * When the session history is across multiple root docshells, this function
+     * is used to notify parent that it needs to navigate to an entry out of
+     * local index of the child.
+     *
+     * @param aGlobalIndex The global index of history entry to navigate to.
+     */
+    async RequestCrossBrowserNavigation(uint32_t aGlobalIndex);
+
 child:
     /**
      * Notify the remote browser that it has been Show()n on this
      * side, with the given |visibleRect|.  This message is expected
      * to trigger creation of the remote browser's "widget".
      *
      * |Show()| and |Move()| take IntSizes rather than Rects because
      * content processes always render to a virtual <0, 0> top-left
@@ -853,16 +870,42 @@ child:
     /**
      * Update the child with the tab's current top-level native window handle.
      * This is used by a11y objects who must expose their native window.
      *
      * @param aNewHandle The native window handle of the tab's top-level window.
      */
     async UpdateNativeWindowHandle(uintptr_t aNewHandle);
 
+    /**
+     * Called when the session history of this particular PBrowser has been
+     * attached to a grouped session history.
+     *
+     * @param aOffset           The number of entries in the grouped session
+     *                          history before this session history object.
+     */
+    async NotifyAttachGroupedSessionHistory(uint32_t aOffset);
+
+    /**
+     * Notify that the session history associated to this PBrowser has become
+     * the active history in the grouped session history.
+     *
+     * @param aGlobalLength      The up-to-date number of entries in the grouped
+     *                           session history.
+     * @param aTargetLocalIndex  The target local index to navigate to.
+     */
+    async NotifyPartialSessionHistoryActive(uint32_t aGlobalLength,
+                                            uint32_t aTargetLocalIndex);
+
+    /**
+     * Notify that the session history asssociates to this PBrowser has become
+     * an inactive history in the grouped session history.
+     */
+    async NotifyPartialSessionHistoryDeactive();
+
 /*
  * FIXME: write protocol!
 
 state LIVE:
     send LoadURL goto LIVE;
 //etc.
     send Destroy goto DYING;
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -111,16 +111,19 @@
 #include "nsIURILoader.h"
 #include "nsIScriptError.h"
 #include "mozilla/EventForwards.h"
 #include "nsDeviceContext.h"
 #include "nsSandboxFlags.h"
 #include "FrameLayerBuilder.h"
 #include "VRManagerChild.h"
 #include "nsICommandParams.h"
+#include "nsISHistory.h"
+#include "nsQueryObject.h"
+#include "GroupedSHistory.h"
 
 #ifdef NS_PRINTING
 #include "nsIPrintSession.h"
 #include "nsIPrintSettings.h"
 #include "nsIPrintSettingsService.h"
 #include "nsIWebBrowserPrint.h"
 #endif
 
@@ -138,16 +141,20 @@ using namespace mozilla::ipc;
 using namespace mozilla::layers;
 using namespace mozilla::layout;
 using namespace mozilla::docshell;
 using namespace mozilla::widget;
 using namespace mozilla::jsipc;
 using mozilla::layers::GeckoContentController;
 
 NS_IMPL_ISUPPORTS(ContentListener, nsIDOMEventListener)
+NS_IMPL_ISUPPORTS(TabChildSHistoryListener,
+                  nsISHistoryListener,
+                  nsIPartialSHistoryListener,
+                  nsISupportsWeakReference)
 
 static const CSSSize kDefaultViewportSize(980, 480);
 
 static const char BEFORE_FIRST_PAINT[] = "before-first-paint";
 
 typedef nsDataHashtable<nsUint64HashKey, TabChild*> TabChildMap;
 static TabChildMap* sTabChildren;
 
@@ -825,16 +832,30 @@ TabChild::Init()
         if (nsCOMPtr<nsITabChild> tabChild = do_QueryReferent(weakPtrThis)) {
           static_cast<TabChild*>(tabChild.get())->ContentReceivedInputBlock(aGuid, aInputBlockId, aPreventDefault);
         }
       });
   mAPZEventState = new APZEventState(mPuppetWidget, Move(callback));
 
   mIPCOpen = true;
 
+  if (GroupedSHistory::GroupedHistoryEnabled()) {
+    // Set session history listener.
+    nsCOMPtr<nsISHistory> shistory;
+    mWebNav->GetSessionHistory(getter_AddRefs(shistory));
+    if (!shistory) {
+      return NS_ERROR_FAILURE;
+    }
+    mHistoryListener = new TabChildSHistoryListener(this);
+    nsCOMPtr<nsISHistoryListener> listener(do_QueryObject(mHistoryListener));
+    shistory->AddSHistoryListener(listener);
+    nsCOMPtr<nsIPartialSHistoryListener> partialListener(do_QueryObject(mHistoryListener));
+    shistory->SetPartialSHistoryListener(partialListener);
+  }
+
   return NS_OK;
 }
 
 void
 TabChild::NotifyTabContextUpdated(bool aIsPreallocated)
 {
   nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
   MOZ_ASSERT(docShell);
@@ -1286,22 +1307,26 @@ TabChild::ActorDestroy(ActorDestroyReaso
 
   if (GetTabId() != 0) {
     NestedTabChildMap().erase(GetTabId());
   }
 }
 
 TabChild::~TabChild()
 {
-    DestroyWindow();
-
-    nsCOMPtr<nsIWebBrowser> webBrowser = do_QueryInterface(WebNavigation());
-    if (webBrowser) {
-      webBrowser->SetContainerWindow(nullptr);
-    }
+  DestroyWindow();
+
+  nsCOMPtr<nsIWebBrowser> webBrowser = do_QueryInterface(WebNavigation());
+  if (webBrowser) {
+    webBrowser->SetContainerWindow(nullptr);
+  }
+
+  if (mHistoryListener) {
+    mHistoryListener->ClearTabChild();
+  }
 }
 
 void
 TabChild::SetProcessNameToAppName()
 {
   nsCOMPtr<mozIApplication> app = GetOwnApp();
   if (!app) {
     return;
@@ -1836,16 +1861,58 @@ TabChild::RecvStopIMEStateManagement()
 bool
 TabChild::RecvMenuKeyboardListenerInstalled(const bool& aInstalled)
 {
   IMEStateManager::OnInstalledMenuKeyboardListener(aInstalled);
   return true;
 }
 
 bool
+TabChild::RecvNotifyAttachGroupedSessionHistory(const uint32_t& aOffset)
+{
+  // nsISHistory uses int32_t
+  if (NS_WARN_IF(aOffset > INT32_MAX)) {
+    return false;
+  }
+
+  nsCOMPtr<nsISHistory> shistory;
+  mWebNav->GetSessionHistory(getter_AddRefs(shistory));
+  NS_ENSURE_TRUE(shistory, false);
+
+  return NS_SUCCEEDED(shistory->OnAttachGroupedSessionHistory(aOffset));
+}
+
+bool
+TabChild::RecvNotifyPartialSessionHistoryActive(const uint32_t& aGlobalLength,
+                                                const uint32_t& aTargetLocalIndex)
+{
+  // nsISHistory uses int32_t
+  if (NS_WARN_IF(aGlobalLength > INT32_MAX || aTargetLocalIndex > INT32_MAX)) {
+    return false;
+  }
+
+  nsCOMPtr<nsISHistory> shistory;
+  mWebNav->GetSessionHistory(getter_AddRefs(shistory));
+  NS_ENSURE_TRUE(shistory, false);
+
+  return NS_SUCCEEDED(shistory->OnPartialSessionHistoryActive(aGlobalLength,
+                                                              aTargetLocalIndex));
+}
+
+bool
+TabChild::RecvNotifyPartialSessionHistoryDeactive()
+{
+  nsCOMPtr<nsISHistory> shistory;
+  mWebNav->GetSessionHistory(getter_AddRefs(shistory));
+  NS_ENSURE_TRUE(shistory, false);
+
+  return NS_SUCCEEDED(shistory->OnPartialSessionHistoryDeactive());
+}
+
+bool
 TabChild::RecvMouseEvent(const nsString& aType,
                          const float&    aX,
                          const float&    aY,
                          const int32_t&  aButton,
                          const int32_t&  aClickCount,
                          const int32_t&  aModifiers,
                          const bool&     aIgnoreRootScrollFrame)
 {
@@ -3338,16 +3405,90 @@ TabChild::ForcePaint(uint64_t aLayerObse
     // message on the PContent channel.
     return;
   }
 
   nsAutoScriptBlocker scriptBlocker;
   RecvSetDocShellIsActive(true, false, aLayerObserverEpoch);
 }
 
+/*******************************************************************************
+ * nsISHistoryListener
+ ******************************************************************************/
+
+NS_IMETHODIMP
+TabChildSHistoryListener::OnHistoryNewEntry(nsIURI *aNewURI, int32_t aOldIndex)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TabChildSHistoryListener::OnHistoryGoBack(nsIURI *aBackURI, bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TabChildSHistoryListener::OnHistoryGoForward(nsIURI *aForwardURI, bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TabChildSHistoryListener::OnHistoryReload(nsIURI *aReloadURI, uint32_t aReloadFlags, bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TabChildSHistoryListener::OnHistoryGotoIndex(int32_t aIndex, nsIURI *aGotoURI, bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TabChildSHistoryListener::OnHistoryPurge(int32_t aNumEntries, bool *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TabChildSHistoryListener::OnHistoryReplaceEntry(int32_t aIndex)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TabChildSHistoryListener::OnLengthChange(int32_t aCount)
+{
+  RefPtr<TabChild> tabChild(mTabChild);
+  if (!tabChild) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (aCount < 0) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return tabChild->SendNotifySessionHistoryChange(aCount) ?
+           NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+TabChildSHistoryListener::OnRequestCrossBrowserNavigation(uint32_t aIndex)
+{
+  RefPtr<TabChild> tabChild(mTabChild);
+  if (!tabChild) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return tabChild->SendRequestCrossBrowserNavigation(aIndex) ?
+           NS_OK : NS_ERROR_FAILURE;
+}
+
 TabChildGlobal::TabChildGlobal(TabChildBase* aTabChild)
 : mTabChild(aTabChild)
 {
   SetIsNotDOMBinding();
 }
 
 TabChildGlobal::~TabChildGlobal()
 {
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -33,16 +33,18 @@
 #include "mozilla/EventForwards.h"
 #include "mozilla/layers/CompositorTypes.h"
 #include "mozilla/layers/APZCCallbackHelper.h"
 #include "nsIWebBrowserChrome3.h"
 #include "mozilla/dom/ipc/IdType.h"
 #include "AudioChannelService.h"
 #include "PuppetWidget.h"
 #include "mozilla/layers/GeckoContentController.h"
+#include "nsISHistoryListener.h"
+#include "nsIPartialSHistoryListener.h"
 
 class nsICachedFileDescriptorListener;
 class nsIDOMWindowUtils;
 
 namespace mozilla {
 namespace layout {
 class RenderFrameChild;
 } // namespace layout
@@ -160,16 +162,37 @@ public:
   explicit ContentListener(TabChild* aTabChild) : mTabChild(aTabChild) {}
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMEVENTLISTENER
 protected:
   ~ContentListener() {}
   TabChild* mTabChild;
 };
 
+/**
+ * Listens on session history change, and sends NotifySessionHistoryChange to
+ * parent process.
+ */
+class TabChildSHistoryListener final : public nsISHistoryListener,
+                                       public nsIPartialSHistoryListener,
+                                       public nsSupportsWeakReference
+{
+public:
+  explicit TabChildSHistoryListener(TabChild* aTabChild) : mTabChild(aTabChild) {}
+  void ClearTabChild() { mTabChild = nullptr; }
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISHISTORYLISTENER
+  NS_DECL_NSIPARTIALSHISTORYLISTENER
+
+private:
+  ~TabChildSHistoryListener() {}
+  TabChild* mTabChild;
+};
+
 // This is base clase which helps to share Viewport and touch related
 // functionality between b2g/android FF/embedlite clients implementation.
 // It make sense to place in this class all helper functions, and functionality
 // which could be shared between Cross-process/Cross-thread implmentations.
 class TabChildBase : public nsISupports,
                      public nsMessageManagerScriptExecutor,
                      public ipc::MessageManagerCallback
 {
@@ -676,16 +699,23 @@ protected:
   virtual bool RecvSetKeyboardIndicators(const UIStateChangeType& aShowAccelerators,
                                          const UIStateChangeType& aShowFocusRings) override;
 
   virtual bool RecvStopIMEStateManagement() override;
 
   virtual bool RecvMenuKeyboardListenerInstalled(
                  const bool& aInstalled) override;
 
+  virtual bool RecvNotifyAttachGroupedSessionHistory(const uint32_t& aOffset) override;
+
+  virtual bool RecvNotifyPartialSessionHistoryActive(const uint32_t& aGlobalLength,
+                                                     const uint32_t& aTargetLocalIndex) override;
+
+  virtual bool RecvNotifyPartialSessionHistoryDeactive() override;
+
 private:
   void HandleDoubleTap(const CSSPoint& aPoint, const Modifiers& aModifiers,
                        const ScrollableLayerGuid& aGuid);
 
   // Notify others that our TabContext has been updated.
   //
   // You should call this after calling TabContext::SetTabContext().  We also
   // call this during Init().
@@ -728,16 +758,17 @@ private:
   class DelayedDeleteRunnable;
 
   TextureFactoryIdentifier mTextureFactoryIdentifier;
   nsCOMPtr<nsIWebNavigation> mWebNav;
   RefPtr<PuppetWidget> mPuppetWidget;
   nsCOMPtr<nsIURI> mLastURI;
   RenderFrameChild* mRemoteFrame;
   RefPtr<nsIContentChild> mManager;
+  RefPtr<TabChildSHistoryListener> mHistoryListener;
   uint32_t mChromeFlags;
   int32_t mActiveSuppressDisplayport;
   uint64_t mLayersId;
   CSSRect mUnscaledOuterRect;
   // Whether we have already received a FileDescriptor for the app package.
   bool mAppPackageFileDescriptorRecved;
   // At present only 1 of these is really expected.
   AutoTArray<nsAutoPtr<CachedFileDescriptorInfo>, 1>
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -95,16 +95,18 @@
 #include "nsILoginManagerPrompter.h"
 #include "nsPIWindowRoot.h"
 #include "nsIAuthPrompt2.h"
 #include "gfxDrawable.h"
 #include "ImageOps.h"
 #include "UnitTransforms.h"
 #include <algorithm>
 #include "mozilla/WebBrowserPersistDocumentParent.h"
+#include "nsIGroupedSHistory.h"
+#include "PartialSHistory.h"
 
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 using namespace mozilla::layers;
 using namespace mozilla::layout;
 using namespace mozilla::services;
 using namespace mozilla::widget;
 using namespace mozilla::jsipc;
@@ -3446,16 +3448,50 @@ TabParent::RecvLookUpDictionary(const ns
     return true;
   }
 
   widget->LookUpDictionary(aText, aFontRangeArray, aIsVertical,
                            aPoint - GetChildProcessOffset());
   return true;
 }
 
+bool
+TabParent::RecvNotifySessionHistoryChange(const uint32_t& aCount)
+{
+  RefPtr<nsFrameLoader> frameLoader(GetFrameLoader());
+  if (!frameLoader) {
+    // FrameLoader can be nullptr if the it is destroying.
+    // In this case session history change can simply be ignored.
+    return true;
+  }
+
+  nsCOMPtr<nsIPartialSHistory> partialHistory;
+  frameLoader->GetPartialSessionHistory(getter_AddRefs(partialHistory));
+  if (!partialHistory) {
+    // PartialSHistory is not enabled
+    return true;
+  }
+
+  partialHistory->OnSessionHistoryChange(aCount);
+  return true;
+}
+
+bool
+TabParent::RecvRequestCrossBrowserNavigation(const uint32_t& aGlobalIndex)
+{
+  RefPtr<nsFrameLoader> frameLoader(GetFrameLoader());
+  if (!frameLoader) {
+    // FrameLoader can be nullptr if the it is destroying.
+    // In this case we can ignore the request.
+    return true;
+  }
+
+  return NS_SUCCEEDED(frameLoader->RequestGroupedHistoryNavigation(aGlobalIndex));
+}
+
 NS_IMETHODIMP
 FakeChannel::OnAuthAvailable(nsISupports *aContext, nsIAuthInformation *aAuthInfo)
 {
   nsAuthInformationHolder* holder =
     static_cast<nsAuthInformationHolder*>(aAuthInfo);
 
   if (!net::gNeckoChild->SendOnAuthAvailable(mCallbackId,
                                              holder->User(),
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -28,16 +28,17 @@
 #include "nsISecureBrowserUI.h"
 #include "nsITabParent.h"
 #include "nsIWebBrowserPersistable.h"
 #include "nsIXULBrowserWindow.h"
 #include "nsRefreshDriver.h"
 #include "nsWeakReference.h"
 #include "Units.h"
 #include "nsIWidget.h"
+#include "nsIPartialSHistory.h"
 
 class nsFrameLoader;
 class nsIFrameLoader;
 class nsIContent;
 class nsIPrincipal;
 class nsIURI;
 class nsILoadContext;
 class nsIDocShell;
@@ -625,16 +626,20 @@ protected:
                                  const int32_t& aX, const int32_t& aY,
                                  const int32_t& aCx, const int32_t& aCy) override;
 
   virtual bool RecvGetTabCount(uint32_t* aValue) override;
 
   virtual bool RecvAudioChannelActivityNotification(const uint32_t& aAudioChannel,
                                                     const bool& aActive) override;
 
+  virtual bool RecvNotifySessionHistoryChange(const uint32_t& aCount) override;
+
+  virtual bool RecvRequestCrossBrowserNavigation(const uint32_t& aGlobalIndex) override;
+
   ContentCacheInParent mContentCache;
 
   nsIntRect mRect;
   ScreenIntSize mDimensions;
   ScreenOrientationInternal mOrientation;
   float mDPI;
   int32_t mRounding;
   CSSToLayoutDeviceScale mDefaultScale;
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -1,16 +1,15 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 XPIDL_SOURCES += [
-    'nsIBrowser.idl',
     'nsIHangReport.idl',
 ]
 
 XPIDL_MODULE = 'dom'
 
 EXPORTS += [
     'nsICachedFileDescriptorListener.h',
 ]
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -214,16 +214,18 @@ public:
   virtual bool HandleAudioCaptured() { return false; }
 
   virtual RefPtr<ShutdownPromise> HandleShutdown();
 
   virtual void HandleVideoSuspendTimeout() = 0;
 
   virtual void HandleResumeVideoDecoding();
 
+  virtual void HandlePlayStateChanged(MediaDecoder::PlayState aPlayState) {}
+
   virtual void DumpDebugInfo() {}
 
 protected:
   using Master = MediaDecoderStateMachine;
   explicit StateObject(Master* aPtr) : mMaster(aPtr) {}
   TaskQueue* OwnerThread() const { return mMaster->mTaskQueue; }
   MediaResource* Resource() const { return mMaster->mResource; }
   MediaDecoderReaderWrapper* Reader() const { return mMaster->mReader; }
@@ -625,16 +627,24 @@ public:
   {
     if (mMaster->HasVideo()) {
       mMaster->mVideoDecodeSuspended = true;
       mMaster->mOnPlaybackEvent.Notify(MediaEventType::EnterVideoSuspend);
       Reader()->SetVideoBlankDecode(true);
     }
   }
 
+  void HandlePlayStateChanged(MediaDecoder::PlayState aPlayState) override
+  {
+    if (aPlayState == MediaDecoder::PLAY_STATE_PLAYING) {
+      // Schedule Step() to check if we can start playback.
+      mMaster->ScheduleStateMachine();
+    }
+  }
+
   void DumpDebugInfo() override
   {
     SDUMP("mIsPrerolling=%d", mIsPrerolling);
   }
 
 private:
   void MaybeStartBuffering();
 
@@ -1046,16 +1056,24 @@ public:
     return true;
   }
 
   void HandleVideoSuspendTimeout() override
   {
     // Do nothing since no decoding is going on.
   }
 
+  void HandlePlayStateChanged(MediaDecoder::PlayState aPlayState) override
+  {
+    if (aPlayState == MediaDecoder::PLAY_STATE_PLAYING) {
+      // Schedule Step() to check if we can start playback.
+      mMaster->ScheduleStateMachine();
+    }
+  }
+
 private:
   bool mSentPlaybackEndedEvent = false;
 };
 
 /**
  * Purpose: release all resources allocated by MDSM.
  *
  * Transition to:
@@ -2452,44 +2470,25 @@ MediaDecoderStateMachine::Shutdown()
 }
 
 void MediaDecoderStateMachine::PlayStateChanged()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING) {
     mVideoDecodeSuspendTimer.Reset();
-    return;
-  }
-
-  // Once we start playing, we don't want to minimize our prerolling, as we
-  // assume the user is likely to want to keep playing in future. This needs to
-  // happen before we invoke StartDecoding().
-  if (mMinimizePreroll) {
+  } else if (mMinimizePreroll) {
+    // Once we start playing, we don't want to minimize our prerolling, as we
+    // assume the user is likely to want to keep playing in future. This needs to
+    // happen before we invoke StartDecoding().
     mMinimizePreroll = false;
     DispatchDecodeTasksIfNeeded();
   }
 
-  // Some state transitions still happen synchronously on the main thread. So
-  // if the main thread invokes Play() and then Seek(), the seek will initiate
-  // synchronously on the main thread, and the asynchronous PlayInternal task
-  // will arrive when it's no longer valid. The proper thing to do is to move
-  // all state transitions to the state machine task queue, but for now we just
-  // make sure that none of the possible main-thread state transitions (Seek(),
-  // SetDormant(), and Shutdown()) have not occurred.
-  if (mState != DECODER_STATE_DECODING &&
-      mState != DECODER_STATE_DECODING_FIRSTFRAME &&
-      mState != DECODER_STATE_BUFFERING &&
-      mState != DECODER_STATE_COMPLETED)
-  {
-    DECODER_LOG("Unexpected state - Bailing out of PlayInternal()");
-    return;
-  }
-
-  ScheduleStateMachine();
+  mStateObj->HandlePlayStateChanged(mPlayState);
 }
 
 void MediaDecoderStateMachine::VisibilityChanged()
 {
   MOZ_ASSERT(OnTaskQueue());
   DECODER_LOG("VisibilityChanged: mIsVisible=%d, "
               "mVideoDecodeSuspended=%c, mIsReaderSuspended=%d",
               mIsVisible.Ref(), mVideoDecodeSuspended ? 'T' : 'F', mIsReaderSuspended.Ref());
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -554,21 +554,21 @@ RTCPeerConnection.prototype = {
 
     rtcConfig.iceServers.forEach(server => {
       if (!server.urls) {
         throw new this._win.DOMException(msg + " - missing urls", "InvalidAccessError");
       }
       server.urls.forEach(urlStr => {
         let url = nicerNewURI(urlStr);
         if (url.scheme in { turn:1, turns:1 }) {
-          if (!server.username) {
+          if (server.username == undefined) {
             throw new this._win.DOMException(msg + " - missing username: " + urlStr,
                                              "InvalidAccessError");
           }
-          if (!server.credential) {
+          if (server.credential == undefined) {
             throw new this._win.DOMException(msg + " - missing credential: " + urlStr,
                                              "InvalidAccessError");
           }
           if (server.credentialType != "password") {
             this.logWarning("RTCConfiguration TURN credentialType \""+
                             server.credentialType +
                             "\" is not yet implemented. Treating as password."+
                             " https://bugzil.la/1247616");
--- a/dom/media/eme/MediaKeySystemAccess.cpp
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -32,17 +32,19 @@
 #include "gmp-audio-decode.h"
 #include "gmp-video-decode.h"
 #include "DecoderDoctorDiagnostics.h"
 #include "WebMDecoder.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "nsUnicharUtils.h"
 #include "mozilla/dom/MediaSource.h"
-
+#ifdef MOZ_WIDGET_ANDROID
+#include "FennecJNIWrappers.h"
+#endif
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeySystemAccess,
                                       mParent)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccess)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccess)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeySystemAccess)
@@ -292,112 +294,122 @@ MediaKeySystemAccess::GetKeySystemStatus
         aOutMessage = NS_LITERAL_CSTRING("Minimum Windows version (Vista) not met for Adobe EME");
         return MediaKeySystemStatus::Cdm_not_supported;
       }
 #endif
       return EnsureMinCDMVersion(mps, aKeySystem, aMinCdmVersion, aOutMessage, aOutCdmVersion);
     }
   }
 
-  if (Preferences::GetBool("media.gmp-widevinecdm.visible", false)) {
-    if (IsWidevineKeySystem(aKeySystem)) {
+  if (IsWidevineKeySystem(aKeySystem)) {
+    if (Preferences::GetBool("media.gmp-widevinecdm.visible", false)) {
 #ifdef XP_WIN
       // Win Vista and later only.
       if (!IsVistaOrLater()) {
         aOutMessage = NS_LITERAL_CSTRING("Minimum Windows version (Vista) not met for Widevine EME");
         return MediaKeySystemStatus::Cdm_not_supported;
       }
 #endif
       if (!Preferences::GetBool("media.gmp-widevinecdm.enabled", false)) {
         aOutMessage = NS_LITERAL_CSTRING("Widevine EME disabled");
         return MediaKeySystemStatus::Cdm_disabled;
       }
       return EnsureMinCDMVersion(mps, aKeySystem, aMinCdmVersion, aOutMessage, aOutCdmVersion);
+#ifdef MOZ_WIDGET_ANDROID
+    } else if (Preferences::GetBool("media.mediadrm-widevinecdm.visible", false)) {
+        nsCString keySystem = NS_ConvertUTF16toUTF8(aKeySystem);
+        bool supported = mozilla::java::MediaDrmProxy::IsSchemeSupported(keySystem);
+        if (!supported) {
+          aOutMessage = NS_LITERAL_CSTRING("Widevine CDM is not available");
+          return MediaKeySystemStatus::Cdm_not_installed;
+        }
+        return MediaKeySystemStatus::Available;
+#endif
     }
   }
 
   return MediaKeySystemStatus::Cdm_not_supported;
 }
 
-typedef nsCString GMPCodecString;
+typedef nsCString EMECodecString;
 
-#define GMP_CODEC_AAC NS_LITERAL_CSTRING("aac")
-#define GMP_CODEC_OPUS NS_LITERAL_CSTRING("opus")
-#define GMP_CODEC_VORBIS NS_LITERAL_CSTRING("vorbis")
-#define GMP_CODEC_H264 NS_LITERAL_CSTRING("h264")
-#define GMP_CODEC_VP8 NS_LITERAL_CSTRING("vp8")
-#define GMP_CODEC_VP9 NS_LITERAL_CSTRING("vp9")
+static NS_NAMED_LITERAL_CSTRING(EME_CODEC_AAC, "aac");
+static NS_NAMED_LITERAL_CSTRING(EME_CODEC_OPUS, "opus");
+static NS_NAMED_LITERAL_CSTRING(EME_CODEC_VORBIS, "vorbis");
+static NS_NAMED_LITERAL_CSTRING(EME_CODEC_H264, "h264");
+static NS_NAMED_LITERAL_CSTRING(EME_CODEC_VP8, "vp8");
+static NS_NAMED_LITERAL_CSTRING(EME_CODEC_VP9, "vp9");
 
-GMPCodecString
-ToGMPAPICodecString(const nsString& aCodec)
+EMECodecString
+ToEMEAPICodecString(const nsString& aCodec)
 {
   if (IsAACCodecString(aCodec)) {
-    return GMP_CODEC_AAC;
+    return EME_CODEC_AAC;
   }
   if (aCodec.EqualsLiteral("opus")) {
-    return GMP_CODEC_OPUS;
+    return EME_CODEC_OPUS;
   }
   if (aCodec.EqualsLiteral("vorbis")) {
-    return GMP_CODEC_VORBIS;
+    return EME_CODEC_VORBIS;
   }
   if (IsH264CodecString(aCodec)) {
-    return GMP_CODEC_H264;
+    return EME_CODEC_H264;
   }
   if (IsVP8CodecString(aCodec)) {
-    return GMP_CODEC_VP8;
+    return EME_CODEC_VP8;
   }
   if (IsVP9CodecString(aCodec)) {
-    return GMP_CODEC_VP9;
+    return EME_CODEC_VP9;
   }
   return EmptyCString();
 }
 
 // A codec can be decrypted-and-decoded by the CDM, or only decrypted
 // by the CDM and decoded by Gecko. Not both.
 struct KeySystemContainerSupport
 {
   bool IsSupported() const
   {
     return !mCodecsDecoded.IsEmpty() || !mCodecsDecrypted.IsEmpty();
   }
 
   // CDM decrypts and decodes using a DRM robust decoder, and passes decoded
   // samples back to Gecko for rendering.
-  bool DecryptsAndDecodes(GMPCodecString aCodec) const
+  bool DecryptsAndDecodes(EMECodecString aCodec) const
   {
     return mCodecsDecoded.Contains(aCodec);
   }
 
   // CDM decrypts and passes the decrypted samples back to Gecko for decoding.
-  bool Decrypts(GMPCodecString aCodec) const
+  bool Decrypts(EMECodecString aCodec) const
   {
     return mCodecsDecrypted.Contains(aCodec);
   }
 
-  void SetCanDecryptAndDecode(GMPCodecString aCodec)
+  void SetCanDecryptAndDecode(EMECodecString aCodec)
   {
     // Can't both decrypt and decrypt-and-decode a codec.
     MOZ_ASSERT(!Decrypts(aCodec));
     // Prevent duplicates.
     MOZ_ASSERT(!DecryptsAndDecodes(aCodec));
     mCodecsDecoded.AppendElement(aCodec);
   }
 
-  void SetCanDecrypt(GMPCodecString aCodec)
+  void SetCanDecrypt(EMECodecString aCodec)
   {
     // Prevent duplicates.
     MOZ_ASSERT(!Decrypts(aCodec));
     // Can't both decrypt and decrypt-and-decode a codec.
     MOZ_ASSERT(!DecryptsAndDecodes(aCodec));
     mCodecsDecrypted.AppendElement(aCodec);
   }
 
 private:
-  nsTArray<GMPCodecString> mCodecsDecoded;
-  nsTArray<GMPCodecString> mCodecsDecrypted;
+  nsTArray<EMECodecString> mCodecsDecoded;
+  nsTArray<EMECodecString> mCodecsDecrypted;
 };
 
 enum class KeySystemFeatureSupport
 {
   Prohibited = 1,
   Requestable = 2,
   Required = 3,
 };
@@ -410,153 +422,205 @@ struct KeySystemConfig
   KeySystemFeatureSupport mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
   nsTArray<MediaKeySessionType> mSessionTypes;
   nsTArray<nsString> mVideoRobustness;
   nsTArray<nsString> mAudioRobustness;
   KeySystemContainerSupport mMP4;
   KeySystemContainerSupport mWebM;
 };
 
-StaticAutoPtr<nsTArray<KeySystemConfig>> sKeySystemConfigs;
+bool
+HavePluginForKeySystem(const nsCString& aKeySystem)
+{
+  bool havePlugin = false;
+  nsCOMPtr<mozIGeckoMediaPluginService> mps =
+    do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+  if (mps) {
+    havePlugin = HaveGMPFor(mps,
+                            aKeySystem,
+                            NS_LITERAL_CSTRING(GMP_API_DECRYPTOR));
+  }
+#ifdef MOZ_WIDGET_ANDROID
+  // Check if we can use MediaDrm for this keysystem.
+  if (!havePlugin) {
+     havePlugin = mozilla::java::MediaDrmProxy::IsSchemeSupported(aKeySystem);
+  }
+#endif
+  return havePlugin;
+}
 
-static const nsTArray<KeySystemConfig>&
+static nsTArray<KeySystemConfig>
 GetSupportedKeySystems()
 {
-  if (!sKeySystemConfigs) {
-    sKeySystemConfigs = new nsTArray<KeySystemConfig>();
-    ClearOnShutdown(&sKeySystemConfigs);
+  nsTArray<KeySystemConfig> keySystemConfigs;
 
-    {
+  {
+    if (HavePluginForKeySystem(kEMEKeySystemClearkey)) {
       KeySystemConfig clearkey;
       clearkey.mKeySystem = NS_ConvertUTF8toUTF16(kEMEKeySystemClearkey);
       clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
       clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("keyids"));
       clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("webm"));
       clearkey.mPersistentState = KeySystemFeatureSupport::Requestable;
       clearkey.mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
       clearkey.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
       clearkey.mSessionTypes.AppendElement(MediaKeySessionType::Persistent_license);
 #if defined(XP_WIN)
       // Clearkey CDM uses WMF decoders on Windows.
       if (WMFDecoderModule::HasAAC()) {
-        clearkey.mMP4.SetCanDecryptAndDecode(GMP_CODEC_AAC);
+        clearkey.mMP4.SetCanDecryptAndDecode(EME_CODEC_AAC);
       } else {
-        clearkey.mMP4.SetCanDecrypt(GMP_CODEC_AAC);
+        clearkey.mMP4.SetCanDecrypt(EME_CODEC_AAC);
       }
       if (WMFDecoderModule::HasH264()) {
-        clearkey.mMP4.SetCanDecryptAndDecode(GMP_CODEC_H264);
+        clearkey.mMP4.SetCanDecryptAndDecode(EME_CODEC_H264);
       } else {
-        clearkey.mMP4.SetCanDecrypt(GMP_CODEC_H264);
+        clearkey.mMP4.SetCanDecrypt(EME_CODEC_H264);
       }
 #else
-      clearkey.mMP4.SetCanDecrypt(GMP_CODEC_AAC);
-      clearkey.mMP4.SetCanDecrypt(GMP_CODEC_H264);
+      clearkey.mMP4.SetCanDecrypt(EME_CODEC_AAC);
+      clearkey.mMP4.SetCanDecrypt(EME_CODEC_H264);
 #endif
-      clearkey.mWebM.SetCanDecrypt(GMP_CODEC_VORBIS);
-      clearkey.mWebM.SetCanDecrypt(GMP_CODEC_OPUS);
-      clearkey.mWebM.SetCanDecrypt(GMP_CODEC_VP8);
-      clearkey.mWebM.SetCanDecrypt(GMP_CODEC_VP9);
-      sKeySystemConfigs->AppendElement(Move(clearkey));
+      clearkey.mWebM.SetCanDecrypt(EME_CODEC_VORBIS);
+      clearkey.mWebM.SetCanDecrypt(EME_CODEC_OPUS);
+      clearkey.mWebM.SetCanDecrypt(EME_CODEC_VP8);
+      clearkey.mWebM.SetCanDecrypt(EME_CODEC_VP9);
+      keySystemConfigs.AppendElement(Move(clearkey));
     }
-    {
+  }
+  {
+    if (HavePluginForKeySystem(kEMEKeySystemWidevine)) {
       KeySystemConfig widevine;
       widevine.mKeySystem = NS_ConvertUTF8toUTF16(kEMEKeySystemWidevine);
       widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
       widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("keyids"));
       widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("webm"));
       widevine.mPersistentState = KeySystemFeatureSupport::Requestable;
       widevine.mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
       widevine.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
+#ifdef MOZ_WIDGET_ANDROID
+      widevine.mSessionTypes.AppendElement(MediaKeySessionType::Persistent_license);
+#endif
       widevine.mAudioRobustness.AppendElement(NS_LITERAL_STRING("SW_SECURE_CRYPTO"));
       widevine.mVideoRobustness.AppendElement(NS_LITERAL_STRING("SW_SECURE_DECODE"));
 #if defined(XP_WIN)
       // Widevine CDM doesn't include an AAC decoder. So if WMF can't
       // decode AAC, and a codec wasn't specified, be conservative
       // and reject the MediaKeys request, since our policy is to prevent
       //  the Adobe GMP's unencrypted AAC decoding path being used to
       // decode content decrypted by the Widevine CDM.
       if (WMFDecoderModule::HasAAC()) {
-        widevine.mMP4.SetCanDecrypt(GMP_CODEC_AAC);
+        widevine.mMP4.SetCanDecrypt(EME_CODEC_AAC);
+      }
+#elif !defined(MOZ_WIDGET_ANDROID)
+      widevine.mMP4.SetCanDecrypt(EME_CODEC_AAC);
+#endif
+
+#if defined(MOZ_WIDGET_ANDROID)
+      using namespace mozilla::java;
+      // MediaDrm.isCryptoSchemeSupported only allows passing
+      // "video/mp4" or "video/webm" for mimetype string.
+      // See https://developer.android.com/reference/android/media/MediaDrm.html#isCryptoSchemeSupported(java.util.UUID, java.lang.String)
+      // for more detail.
+      typedef struct {
+        const nsCString& mMimeType;
+        const nsCString& mEMECodecType;
+        const char16_t* mCodecType;
+        KeySystemContainerSupport* mSupportType;
+      } DataForValidation;
+
+      DataForValidation validationList[] = {
+        { nsCString("video/mp4"), EME_CODEC_H264, MediaDrmProxy::AVC, &widevine.mMP4 },
+        { nsCString("audio/mp4"), EME_CODEC_AAC, MediaDrmProxy::AAC, &widevine.mMP4 },
+        { nsCString("video/webm"), EME_CODEC_VP8, MediaDrmProxy::VP8, &widevine.mWebM },
+        { nsCString("video/webm"), EME_CODEC_VP9, MediaDrmProxy::VP9, &widevine.mWebM},
+        { nsCString("audio/webm"), EME_CODEC_VORBIS, MediaDrmProxy::VORBIS, &widevine.mWebM},
+        { nsCString("audio/webm"), EME_CODEC_OPUS, MediaDrmProxy::OPUS, &widevine.mWebM},
+      };
+
+      for (const auto& data: validationList) {
+        if (MediaDrmProxy::IsCryptoSchemeSupported(kEMEKeySystemWidevine,
+                                                   data.mMimeType)) {
+          if (MediaDrmProxy::CanDecode(data.mCodecType)) {
+            data.mSupportType->SetCanDecryptAndDecode(data.mEMECodecType);
+          } else {
+            data.mSupportType->SetCanDecrypt(data.mEMECodecType);
+          }
+        }
       }
 #else
-      widevine.mMP4.SetCanDecrypt(GMP_CODEC_AAC);
+      widevine.mMP4.SetCanDecryptAndDecode(EME_CODEC_H264);
+      widevine.mWebM.SetCanDecrypt(EME_CODEC_VORBIS);
+      widevine.mWebM.SetCanDecrypt(EME_CODEC_OPUS);
+      widevine.mWebM.SetCanDecryptAndDecode(EME_CODEC_VP8);
+      widevine.mWebM.SetCanDecryptAndDecode(EME_CODEC_VP9);
 #endif
-      widevine.mMP4.SetCanDecryptAndDecode(GMP_CODEC_H264);
-      widevine.mWebM.SetCanDecrypt(GMP_CODEC_VORBIS);
-      widevine.mWebM.SetCanDecrypt(GMP_CODEC_OPUS);
-      widevine.mWebM.SetCanDecryptAndDecode(GMP_CODEC_VP8);
-      widevine.mWebM.SetCanDecryptAndDecode(GMP_CODEC_VP9);
-      sKeySystemConfigs->AppendElement(Move(widevine));
+      keySystemConfigs.AppendElement(Move(widevine));
     }
-    {
+  }
+  {
+    if (HavePluginForKeySystem(kEMEKeySystemPrimetime)) {
       KeySystemConfig primetime;
       primetime.mKeySystem = NS_ConvertUTF8toUTF16(kEMEKeySystemPrimetime);
       primetime.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
       primetime.mPersistentState = KeySystemFeatureSupport::Required;
       primetime.mDistinctiveIdentifier = KeySystemFeatureSupport::Required;
       primetime.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
-      primetime.mMP4.SetCanDecryptAndDecode(GMP_CODEC_AAC);
-      primetime.mMP4.SetCanDecryptAndDecode(GMP_CODEC_H264);
-      sKeySystemConfigs->AppendElement(Move(primetime));
+      primetime.mMP4.SetCanDecryptAndDecode(EME_CODEC_AAC);
+      primetime.mMP4.SetCanDecryptAndDecode(EME_CODEC_H264);
+      keySystemConfigs.AppendElement(Move(primetime));
     }
   }
-  return *sKeySystemConfigs;
+
+  return keySystemConfigs;
 }
 
-static const KeySystemConfig*
-GetKeySystemConfig(const nsAString& aKeySystem)
+static bool
+GetKeySystemConfig(const nsAString& aKeySystem, KeySystemConfig& aOutKeySystemConfig)
 {
-  for (const KeySystemConfig& config : GetSupportedKeySystems()) {
+  for (auto&& config : GetSupportedKeySystems()) {
     if (config.mKeySystem.Equals(aKeySystem)) {
-      return &config;
+      aOutKeySystemConfig = mozilla::Move(config);
+      return true;
     }
   }
-  return nullptr;
+  // No matching key system found.
+  return false;
 }
 
 /* static */
 bool
 MediaKeySystemAccess::KeySystemSupportsInitDataType(const nsAString& aKeySystem,
                                                     const nsAString& aInitDataType)
 {
-  const KeySystemConfig* implementation = GetKeySystemConfig(aKeySystem);
-  return implementation &&
-         implementation->mInitDataTypes.Contains(aInitDataType);
+  KeySystemConfig implementation;
+  return GetKeySystemConfig(aKeySystem, implementation) &&
+         implementation.mInitDataTypes.Contains(aInitDataType);
 }
 
 enum CodecType
 {
   Audio,
   Video,
   Invalid
 };
 
 static bool
-CanDecryptAndDecode(mozIGeckoMediaPluginService* aGMPService,
-                    const nsString& aKeySystem,
+CanDecryptAndDecode(const nsString& aKeySystem,
                     const nsString& aContentType,
                     CodecType aCodecType,
                     const KeySystemContainerSupport& aContainerSupport,
-                    const nsTArray<GMPCodecString>& aCodecs,
+                    const nsTArray<EMECodecString>& aCodecs,
                     DecoderDoctorDiagnostics* aDiagnostics)
 {
   MOZ_ASSERT(aCodecType != Invalid);
-  MOZ_ASSERT(HaveGMPFor(aGMPService,
-                        NS_ConvertUTF16toUTF8(aKeySystem),
-                        NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
-  for (const GMPCodecString& codec : aCodecs) {
+  for (const EMECodecString& codec : aCodecs) {
     MOZ_ASSERT(!codec.IsEmpty());
 
-    nsCString api = (aCodecType == Audio) ? NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER)
-                                          : NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER);
-    if (aContainerSupport.DecryptsAndDecodes(codec) &&
-        HaveGMPFor(aGMPService,
-                   NS_ConvertUTF16toUTF8(aKeySystem),
-                   api,
-                   codec)) {
+    if (aContainerSupport.DecryptsAndDecodes(codec)) {
       // GMP can decrypt-and-decode this codec.
       continue;
     }
 
     if (aContainerSupport.Decrypts(codec) &&
         NS_SUCCEEDED(MediaSource::IsTypeSupported(aContentType, aDiagnostics))) {
       // GMP can decrypt and is allowed to return compressed samples to
       // Gecko to decode, and Gecko has a decoder.
@@ -567,17 +631,17 @@ CanDecryptAndDecode(mozIGeckoMediaPlugin
     // support this codec.
 
 #if defined(XP_WIN)
     // Widevine CDM doesn't include an AAC decoder. So if WMF can't
     // decode AAC, and a codec wasn't specified, be conservative
     // and reject the MediaKeys request, since our policy is to prevent
     //  the Adobe GMP's unencrypted AAC decoding path being used to
     // decode content decrypted by the Widevine CDM.
-    if (codec == GMP_CODEC_AAC &&
+    if (codec == EME_CODEC_AAC &&
         IsWidevineKeySystem(aKeySystem) &&
         !WMFDecoderModule::HasAAC()) {
       if (aDiagnostics) {
         aDiagnostics->SetKeySystemIssue(
           DecoderDoctorDiagnostics::eWidevineWithNoWMF);
       }
     }
 #endif
@@ -620,35 +684,35 @@ GetMajorType(const nsAString& aContentTy
   }
   if (CaseInsensitiveFindInReadable(NS_LITERAL_STRING("video/"), aContentType)) {
     return Video;
   }
   return Invalid;
 }
 
 static CodecType
-GetCodecType(const GMPCodecString& aCodec)
+GetCodecType(const EMECodecString& aCodec)
 {
-  if (aCodec.Equals(GMP_CODEC_AAC) ||
-      aCodec.Equals(GMP_CODEC_OPUS) ||
-      aCodec.Equals(GMP_CODEC_VORBIS)) {
+  if (aCodec.Equals(EME_CODEC_AAC) ||
+      aCodec.Equals(EME_CODEC_OPUS) ||
+      aCodec.Equals(EME_CODEC_VORBIS)) {
     return Audio;
   }
-  if (aCodec.Equals(GMP_CODEC_H264) ||
-      aCodec.Equals(GMP_CODEC_VP8) ||
-      aCodec.Equals(GMP_CODEC_VP9)) {
+  if (aCodec.Equals(EME_CODEC_H264) ||
+      aCodec.Equals(EME_CODEC_VP8) ||
+      aCodec.Equals(EME_CODEC_VP9)) {
     return Video;
   }
   return Invalid;
 }
 
 static bool
-AllCodecsOfType(const nsTArray<GMPCodecString>& aCodecs, const CodecType aCodecType)
+AllCodecsOfType(const nsTArray<EMECodecString>& aCodecs, const CodecType aCodecType)
 {
-  for (const GMPCodecString& codec : aCodecs) {
+  for (const EMECodecString& codec : aCodecs) {
     if (GetCodecType(codec) != aCodecType) {
       return false;
     }
   }
   return true;
 }
 
 static bool
@@ -680,17 +744,16 @@ IsParameterUnrecognized(const nsAString&
     }
   }
   return false;
 }
 
 // 3.1.2.3 Get Supported Capabilities for Audio/Video Type
 static Sequence<MediaKeySystemMediaCapability>
 GetSupportedCapabilities(const CodecType aCodecType,
-                         mozIGeckoMediaPluginService* aGMPService,
                          const nsTArray<MediaKeySystemMediaCapability>& aRequestedCapabilities,
                          const MediaKeySystemConfiguration& aPartialConfig,
                          const KeySystemConfig& aKeySystem,
                          DecoderDoctorDiagnostics* aDiagnostics)
 {
   // Let local accumulated configuration be a local copy of partial configuration.
   // (Note: It's not necessary for us to maintain a local copy, as we don't need
   // to test whether capabilites from previous calls to this algorithm work with
@@ -725,31 +788,31 @@ GetSupportedCapabilities(const CodecType
               "MediaKeySystemMediaCapability('%s','%s') unsupported; "
               "failed to parse contentType as MIME type.",
               NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
               NS_ConvertUTF16toUTF8(contentType).get(),
               NS_ConvertUTF16toUTF8(robustness).get());
       continue;
     }
     bool invalid = false;
-    nsTArray<GMPCodecString> codecs;
+    nsTArray<EMECodecString> codecs;
     for (const nsString& codecString : codecStrings) {
-      GMPCodecString gmpCodec = ToGMPAPICodecString(codecString);
-      if (gmpCodec.IsEmpty()) {
+      EMECodecString emeCodec = ToEMEAPICodecString(codecString);
+      if (emeCodec.IsEmpty()) {
         invalid = true;
         EME_LOG("MediaKeySystemConfiguration (label='%s') "
                 "MediaKeySystemMediaCapability('%s','%s') unsupported; "
                 "'%s' is an invalid codec string.",
                 NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
                 NS_ConvertUTF16toUTF8(contentType).get(),
                 NS_ConvertUTF16toUTF8(robustness).get(),
                 NS_ConvertUTF16toUTF8(codecString).get());
         break;
       }
-      codecs.AppendElement(gmpCodec);
+      codecs.AppendElement(emeCodec);
     }
     if (invalid) {
       continue;
     }
 
     // If the user agent does not support container, continue to the next iteration.
     // The case-sensitivity of string comparisons is determined by the appropriate RFC.
     // (Note: Per RFC 6838 [RFC6838], "Both top-level type and subtype names are
@@ -800,25 +863,25 @@ GetSupportedCapabilities(const CodecType
     // (Note: codecs array is 'parameter').
 
     // If media types is empty:
     if (codecs.IsEmpty()) {
       // If container normatively implies a specific set of codecs and codec constraints:
       // Let parameters be that set.
       if (isMP4) {
         if (aCodecType == Audio) {
-          codecs.AppendElement(GMP_CODEC_AAC);
+          codecs.AppendElement(EME_CODEC_AAC);
         } else if (aCodecType == Video) {
-          codecs.AppendElement(GMP_CODEC_H264);
+          codecs.AppendElement(EME_CODEC_H264);
         }
       } else if (isWebM) {
         if (aCodecType == Audio) {
-          codecs.AppendElement(GMP_CODEC_VORBIS);
+          codecs.AppendElement(EME_CODEC_VORBIS);
         } else if (aCodecType == Video) {
-          codecs.AppendElement(GMP_CODEC_VP8);
+          codecs.AppendElement(EME_CODEC_VP8);
         }
       }
       // Otherwise: Continue to the next iteration.
       // (Note: all containers we support have implied codecs, so don't continue here.)
     }
 
     // If content type is not strictly a audio/video type, continue to the next iteration.
     const auto majorType = GetMajorType(container);
@@ -866,18 +929,17 @@ GetSupportedCapabilities(const CodecType
       // Note: specified robustness requirements are satisfied.
     }
 
     // If the user agent and implementation definitely support playback of
     // encrypted media data for the combination of container, media types,
     // robustness and local accumulated configuration in combination with
     // restrictions...
     const auto& containerSupport = isMP4 ? aKeySystem.mMP4 : aKeySystem.mWebM;
-    if (!CanDecryptAndDecode(aGMPService,
-                             aKeySystem.mKeySystem,
+    if (!CanDecryptAndDecode(aKeySystem.mKeySystem,
                              contentType,
                              majorType,
                              containerSupport,
                              codecs,
                              aDiagnostics)) {
         EME_LOG("MediaKeySystemConfiguration (label='%s') "
                 "MediaKeySystemMediaCapability('%s','%s') unsupported; "
                 "codec unsupported by CDM requested.",
@@ -969,18 +1031,17 @@ UnboxSessionTypes(const Optional<Sequenc
     // Note: fallible. Results in an empty array.
     sessionTypes.AppendElement(NS_ConvertUTF8toUTF16(nsDependentCString(temporary)), mozilla::fallible);
   }
   return sessionTypes;
 }
 
 // 3.1.2.2 Get Supported Configuration and Consent
 static bool
-GetSupportedConfig(mozIGeckoMediaPluginService* aGMPService,
-                   const KeySystemConfig& aKeySystem,
+GetSupportedConfig(const KeySystemConfig& aKeySystem,
                    const MediaKeySystemConfiguration& aCandidate,
                    MediaKeySystemConfiguration& aOutConfig,
                    DecoderDoctorDiagnostics* aDiagnostics)
 {
   // Let accumulated configuration be a new MediaKeySystemConfiguration dictionary.
   MediaKeySystemConfiguration config;
   // Set the label member of accumulated configuration to equal the label member of
   // candidate configuration.
@@ -1089,17 +1150,16 @@ GetSupportedConfig(mozIGeckoMediaPluginS
   // If the videoCapabilities member in candidate configuration is non-empty:
   if (!aCandidate.mVideoCapabilities.IsEmpty()) {
     // Let video capabilities be the result of executing the Get Supported
     // Capabilities for Audio/Video Type algorithm on Video, candidate
     // configuration's videoCapabilities member, accumulated configuration,
     // and restrictions.
     Sequence<MediaKeySystemMediaCapability> caps =
       GetSupportedCapabilities(Video,
-                               aGMPService,
                                aCandidate.mVideoCapabilities,
                                config,
                                aKeySystem,
                                aDiagnostics);
     // If video capabilities is null, return NotSupported.
     if (caps.IsEmpty()) {
       EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
               "no supported video capabilities.",
@@ -1115,17 +1175,16 @@ GetSupportedConfig(mozIGeckoMediaPluginS
 
   // If the audioCapabilities member in candidate configuration is non-empty:
   if (!aCandidate.mAudioCapabilities.IsEmpty()) {
     // Let audio capabilities be the result of executing the Get Supported Capabilities
     // for Audio/Video Type algorithm on Audio, candidate configuration's audioCapabilities
     // member, accumulated configuration, and restrictions.
     Sequence<MediaKeySystemMediaCapability> caps =
       GetSupportedCapabilities(Audio,
-                               aGMPService,
                                aCandidate.mAudioCapabilities,
                                config,
                                aKeySystem,
                                aDiagnostics);
     // If audio capabilities is null, return NotSupported.
     if (caps.IsEmpty()) {
       EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
               "no supported audio capabilities.",
@@ -1198,31 +1257,22 @@ GetSupportedConfig(mozIGeckoMediaPluginS
 
 /* static */
 bool
 MediaKeySystemAccess::GetSupportedConfig(const nsAString& aKeySystem,
                                          const Sequence<MediaKeySystemConfiguration>& aConfigs,
                                          MediaKeySystemConfiguration& aOutConfig,
                                          DecoderDoctorDiagnostics* aDiagnostics)
 {
-  nsCOMPtr<mozIGeckoMediaPluginService> mps =
-    do_GetService("@mozilla.org/gecko-media-plugin-service;1");
-  if (NS_WARN_IF(!mps)) {
-    return false;
-  }
-  const KeySystemConfig* implementation = nullptr;
-  if (!HaveGMPFor(mps,
-                  NS_ConvertUTF16toUTF8(aKeySystem),
-                  NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)) ||
-      !(implementation = GetKeySystemConfig(aKeySystem))) {
+  KeySystemConfig implementation;
+  if (!GetKeySystemConfig(aKeySystem, implementation)) {
     return false;
   }
   for (const MediaKeySystemConfiguration& candidate : aConfigs) {
-    if (mozilla::dom::GetSupportedConfig(mps,
-                                         *implementation,
+    if (mozilla::dom::GetSupportedConfig(implementation,
                                          candidate,
                                          aOutConfig,
                                          aDiagnostics)) {
       return true;
     }
   }
 
   return false;
--- a/dom/media/tests/mochitest/test_peerConnection_bug825703.html
+++ b/dom/media/tests/mochitest/test_peerConnection_bug825703.html
@@ -61,16 +61,18 @@ runNetworkTest(() => {
   makePC({ iceServers: [{ urls:"" }] }, "SyntaxError");
 
   makePC({ iceServers: [
     { urls:"stun:127.0.0.1" },
     { urls:"stun:localhost", foo:"" },
     { urls: ["stun:127.0.0.1", "stun:localhost"] },
     { urls:"stuns:localhost", foo:"" },
     { urls:"turn:[::1]:3478", username:"p", credential:"p" },
+    { urls:"turn:[::1]:3478", username:"", credential:"" },
+    { urls:"turns:[::1]:3478", username:"", credential:"" },
     { urls:"turn:localhost:3478?transport=udp", username:"p", credential:"p" },
     { urls: ["turn:[::1]:3478", "turn:localhost"], username:"p", credential:"p" },
     { urls:"turns:localhost:3478?transport=udp", username:"p", credential:"p" },
     { url:"stun:localhost", foo:"" },
     { url:"turn:localhost", username:"p", credential:"p" }
   ]});
 
   makePC({ iceServers: [{ urls: ["stun:127.0.0.1", ""] }] }, "SyntaxError");
--- a/editor/composer/test/test_bug1205983.html
+++ b/editor/composer/test/test_bug1205983.html
@@ -1,126 +1,126 @@
-<!DOCTYPE html>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=1205983
--->
-<head>
-  <title>Test for Bug 1205983</title>
-  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1205983">Mozilla Bug 1205983</a>
-<p id="display"></p>
-</div>
-
-<div contenteditable id="de-DE" lang="de-DE" onfocus="deFocus()">German heute ist ein guter Tag</div>
-<textarea id="en-US" lang="en-US" onfocus="enFocus()">Nogoodword today is a nice day</textarea>
-
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-function getMisspelledWords(editor) {
-  return editor.selectionController.getSelection(Components.interfaces.nsISelectionController.SELECTION_SPELLCHECK).toString();
-}
-
-var elem_de;
-var editor_de;
-var selcon_de;
-var de_DE;
-var hunspell;
-
-/** Test for Bug 1205983 **/
-SimpleTest.waitForExplicitFinish();
-SimpleTest.waitForFocus(function() {
-  Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm");
-
-  var dir = Components.classes["@mozilla.org/file/directory_service;1"]
-                      .getService(Components.interfaces.nsIProperties)
-                      .get("CurWorkD", Components.interfaces.nsIFile);
-  dir.append("tests");
-  dir.append("editor");
-  dir.append("composer");
-  dir.append("test");
-
-  hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"]
-                       .getService(Components.interfaces.mozISpellCheckingEngine);
-
-  // Install de-DE dictionary.
-  de_DE = dir.clone();
-  de_DE.append("de-DE");
-  is(de_DE.exists(), true, "true expected (de_DE directory should exist)");
-  hunspell.addDirectory(de_DE);
-
-  document.getElementById('de-DE').focus();
-});
-
-function deFocus() {
-  elem_de = document.getElementById('de-DE');
-
-  onSpellCheck(elem_de, function () {
-    var Ci = Components.interfaces;
-    var editingSession = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                               .getInterface(Ci.nsIWebNavigation)
-                               .QueryInterface(Ci.nsIInterfaceRequestor)
-                               .getInterface(Ci.nsIEditingSession);
-    editor_de = editingSession.getEditorForWindow(window);
-    selcon_de = editor_de.selectionController;
-    var sel = selcon_de.getSelection(selcon_de.SELECTION_SPELLCHECK);
-
-    // Check that we spelled in German, so there is only one misspelled word.
-    is(sel.toString(), "German", "one misspelled word expected: German");
-
-    // Now focus the textarea, which requires English spelling.
-    document.getElementById('en-US').focus();
-  });
-}
-
-function enFocus() {
-  var elem_en = document.getElementById('en-US');
-  var editor_en = elem_en.QueryInterface(Components.interfaces.nsIDOMNSEditableElement).editor;
-  editor_en.setSpellcheckUserOverride(true);
-  var inlineSpellChecker = editor_en.getInlineSpellChecker(true);
-
-  onSpellCheck(elem_en, function () {
-    var spellchecker = inlineSpellChecker.spellChecker;
-    try {
-      currentDictonary = spellchecker.GetCurrentDictionary();
-    } catch(e) {}
-
-    // Check that the English dictionary is loaded and that the spell check has worked.
-    is(currentDictonary, "en-US", "expected en-US");
-    is(getMisspelledWords(editor_en), "Nogoodword", "one misspelled word expected: Nogoodword");
-
-    // So far all was boring. The important thing is whether the spell check result
-    // in the de-DE editor is still the same. After losing focus, no spell check
-    // updates should take place there.
-    var sel = selcon_de.getSelection(selcon_de.SELECTION_SPELLCHECK);
-    is(sel.toString(), "German", "one misspelled word expected: German");
-
-    // Remove the fake de_DE dictionary again.
-    hunspell.removeDirectory(de_DE);
-
-    // Focus again, so the spelling gets updated, but before we need to kill the focus handler.
-    elem_de.onfocus = null;
-    elem_de.blur();
-    elem_de.focus();
-
-    // After removal, the de_DE editor should refresh the spelling with en-US.
-    onSpellCheck(elem_de, function () {
-      var sel = selcon_de.getSelection(selcon_de.SELECTION_SPELLCHECK);
-      is(sel.toString(), "heute" + "ist" + "ein" + "guter",
-         "some misspelled words expected: heute ist ein guter");
-
-      // If we don't reset this, we cause massive leaks.
-      selcon_de = null;
-      editor_de = null;
-
-      SimpleTest.finish();
-    });
-  });
-}
-
-</script>
-</pre>
-</body>
-</html>
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1205983
+-->
+<head>
+  <title>Test for Bug 1205983</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1205983">Mozilla Bug 1205983</a>
+<p id="display"></p>
+</div>
+
+<div contenteditable id="de-DE" lang="de-DE" onfocus="deFocus()">German heute ist ein guter Tag</div>
+<textarea id="en-US" lang="en-US" onfocus="enFocus()">Nogoodword today is a nice day</textarea>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function getMisspelledWords(editor) {
+  return editor.selectionController.getSelection(Components.interfaces.nsISelectionController.SELECTION_SPELLCHECK).toString();
+}
+
+var elem_de;
+var editor_de;
+var selcon_de;
+var de_DE;
+var hunspell;
+
+/** Test for Bug 1205983 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+  Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm");
+
+  var dir = Components.classes["@mozilla.org/file/directory_service;1"]
+                      .getService(Components.interfaces.nsIProperties)
+                      .get("CurWorkD", Components.interfaces.nsIFile);
+  dir.append("tests");
+  dir.append("editor");
+  dir.append("composer");
+  dir.append("test");
+
+  hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"]
+                       .getService(Components.interfaces.mozISpellCheckingEngine);
+
+  // Install de-DE dictionary.
+  de_DE = dir.clone();
+  de_DE.append("de-DE");
+  is(de_DE.exists(), true, "true expected (de_DE directory should exist)");
+  hunspell.addDirectory(de_DE);
+
+  document.getElementById('de-DE').focus();
+});
+
+function deFocus() {
+  elem_de = document.getElementById('de-DE');
+
+  onSpellCheck(elem_de, function () {
+    var Ci = Components.interfaces;
+    var editingSession = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                               .getInterface(Ci.nsIWebNavigation)
+                               .QueryInterface(Ci.nsIInterfaceRequestor)
+                               .getInterface(Ci.nsIEditingSession);
+    editor_de = editingSession.getEditorForWindow(window);
+    selcon_de = editor_de.selectionController;
+    var sel = selcon_de.getSelection(selcon_de.SELECTION_SPELLCHECK);
+
+    // Check that we spelled in German, so there is only one misspelled word.
+    is(sel.toString(), "German", "one misspelled word expected: German");
+
+    // Now focus the textarea, which requires English spelling.
+    document.getElementById('en-US').focus();
+  });
+}
+
+function enFocus() {
+  var elem_en = document.getElementById('en-US');
+  var editor_en = elem_en.QueryInterface(Components.interfaces.nsIDOMNSEditableElement).editor;
+  editor_en.setSpellcheckUserOverride(true);
+  var inlineSpellChecker = editor_en.getInlineSpellChecker(true);
+
+  onSpellCheck(elem_en, function () {
+    var spellchecker = inlineSpellChecker.spellChecker;
+    try {
+      currentDictonary = spellchecker.GetCurrentDictionary();
+    } catch(e) {}
+
+    // Check that the English dictionary is loaded and that the spell check has worked.
+    is(currentDictonary, "en-US", "expected en-US");
+    is(getMisspelledWords(editor_en), "Nogoodword", "one misspelled word expected: Nogoodword");
+
+    // So far all was boring. The important thing is whether the spell check result
+    // in the de-DE editor is still the same. After losing focus, no spell check
+    // updates should take place there.
+    var sel = selcon_de.getSelection(selcon_de.SELECTION_SPELLCHECK);
+    is(sel.toString(), "German", "one misspelled word expected: German");
+
+    // Remove the fake de_DE dictionary again.
+    hunspell.removeDirectory(de_DE);
+
+    // Focus again, so the spelling gets updated, but before we need to kill the focus handler.
+    elem_de.onfocus = null;
+    elem_de.blur();
+    elem_de.focus();
+
+    // After removal, the de_DE editor should refresh the spelling with en-US.
+    onSpellCheck(elem_de, function () {
+      var sel = selcon_de.getSelection(selcon_de.SELECTION_SPELLCHECK);
+      is(sel.toString(), "heute" + "ist" + "ein" + "guter",
+         "some misspelled words expected: heute ist ein guter");
+
+      // If we don't reset this, we cause massive leaks.
+      selcon_de = null;
+      editor_de = null;
+
+      SimpleTest.finish();
+    });
+  });
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/gfx/angle/src/commit.h
+++ b/gfx/angle/src/commit.h
@@ -1,3 +1,3 @@
-#define ANGLE_COMMIT_HASH ""
-#define ANGLE_COMMIT_HASH_SIZE 12
-#define ANGLE_COMMIT_DATE ""
+#define ANGLE_COMMIT_HASH ""
+#define ANGLE_COMMIT_HASH_SIZE 12
+#define ANGLE_COMMIT_DATE ""
old mode 100755
new mode 100644
old mode 100755
new mode 100644
old mode 100755
new mode 100644
old mode 100755
new mode 100644
old mode 100755
new mode 100644
old mode 100755
new mode 100644
old mode 100755
new mode 100644
old mode 100755
new mode 100644
old mode 100755
new mode 100644
old mode 100755
new mode 100644
old mode 100755
new mode 100644
old mode 100755
new mode 100644
old mode 100755
new mode 100644
--- a/gfx/cairo/cairo/src/moz.build
+++ b/gfx/cairo/cairo/src/moz.build
@@ -217,16 +217,17 @@ if CONFIG['MOZ_TREE_FREETYPE']:
 if CONFIG['GNU_CC'] or CONFIG['CLANG_CL']:
     CFLAGS += [
         '-Wno-enum-compare',
         '-Wno-int-to-pointer-cast',
         '-Wno-sign-compare',
         '-Wno-type-limits',
         '-Wno-missing-field-initializers',
         '-Wno-conversion',
+        '-Wno-unused-but-set-variable',
     ]
 if CONFIG['CLANG_CXX'] or CONFIG['CLANG_CL']:
     CFLAGS += [
         '-Wno-incompatible-pointer-types',
         '-Wno-tautological-compare',
         '-Wno-tautological-constant-out-of-range-compare',
         '-Wno-error=uninitialized',
     ]
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -820,16 +820,17 @@ GLContext::InitWithPrefixImpl(const char
         "Adreno (TM) 305",
         "Adreno (TM) 320",
         "Adreno (TM) 330",
         "Adreno (TM) 420",
         "Mali-400 MP",
         "Mali-450 MP",
         "PowerVR SGX 530",
         "PowerVR SGX 540",
+        "PowerVR SGX 544MP",
         "NVIDIA Tegra",
         "Android Emulator",
         "Gallium 0.4 on llvmpipe",
         "Intel HD Graphics 3000 OpenGL Engine",
         "Microsoft Basic Render Driver",
         "Unknown"
     };
 
@@ -1796,16 +1797,27 @@ GLContext::InitExtensions()
 
         if (Vendor() == GLVendor::Imagination &&
             Renderer() == GLRenderer::SGX540)
         {
             // Bug 980048
             MarkExtensionUnsupported(OES_EGL_sync);
         }
 
+#ifdef MOZ_WIDGET_ANDROID
+        if (Vendor() == GLVendor::Imagination &&
+            Renderer() == GLRenderer::SGX544MP &&
+            AndroidBridge::Bridge()->GetAPIVersion() < 21)
+        {
+            // Bug 1026404
+            MarkExtensionUnsupported(OES_EGL_image);
+            MarkExtensionUnsupported(OES_EGL_image_external);
+        }
+#endif
+
         if (Vendor() == GLVendor::ARM &&
             (Renderer() == GLRenderer::Mali400MP ||
              Renderer() == GLRenderer::Mali450MP))
         {
             // Bug 1264505
             MarkExtensionUnsupported(OES_EGL_image_external);
         }
 
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -175,16 +175,17 @@ enum class GLRenderer {
     AdrenoTM305,
     AdrenoTM320,
     AdrenoTM330,
     AdrenoTM420,
     Mali400MP,
     Mali450MP,
     SGX530,
     SGX540,
+    SGX544MP,
     Tegra,
     AndroidEmulator,
     GalliumLlvmpipe,
     IntelHD3000,
     MicrosoftBasicRenderDriver,
     Other
 };
 
--- a/gfx/harfbuzz/Makefile.am
+++ b/gfx/harfbuzz/Makefile.am
@@ -1,68 +1,72 @@
 # Process this file with automake to produce Makefile.in
 
 NULL =
 
-SUBDIRS = src util test
+ACLOCAL_AMFLAGS = -I m4
 
-pkgconfigdir = $(libdir)/pkgconfig
-pkgconfig_DATA = harfbuzz.pc
+SUBDIRS = src util test docs win32
 
 EXTRA_DIST = \
 	autogen.sh \
 	harfbuzz.doap \
+	Android.mk \
+	README.python \
+	BUILD.md \
 	$(NULL)
 
 MAINTAINERCLEANFILES = \
+	$(GITIGNORE_MAINTAINERCLEANFILES_TOPLEVEL) \
+	$(GITIGNORE_MAINTAINERCLEANFILES_M4_LIBTOOL) \
+	$(GITIGNORE_MAINTAINERCLEANFILES_MAKEFILE_IN) \
 	$(srcdir)/INSTALL \
-	$(srcdir)/aclocal.m4 \
-	$(srcdir)/autoscan.log \
-	$(srcdir)/compile \
-	$(srcdir)/config.guess \
-	$(srcdir)/config.h.in \
-	$(srcdir)/config.sub \
-	$(srcdir)/configure.scan \
-	$(srcdir)/depcomp \
-	$(srcdir)/install-sh \
-	$(srcdir)/ltmain.sh \
-	$(srcdir)/missing \
-	$(srcdir)/mkinstalldirs \
 	$(srcdir)/ChangeLog \
-	`find "$(srcdir)" -type f -name Makefile.in -print`
+	$(srcdir)/gtk-doc.make \
+	$(srcdir)/m4/gtk-doc.m4 \
+	$(NULL)
 
 
 #
 # ChangeLog generation
 #
 CHANGELOG_RANGE =
 ChangeLog: $(srcdir)/ChangeLog
 $(srcdir)/ChangeLog:
-	$(AM_V_GEN) if test -d "$(srcdir)/.git"; then \
-	  (GIT_DIR=$(top_srcdir)/.git ./missing --run \
-	   git log $(CHANGELOG_RANGE) --stat) | fmt --split-only > $@.tmp \
-	  && mv -f $@.tmp $@ \
+	$(AM_V_GEN) if test -d "$(top_srcdir)/.git"; then \
+	  (GIT_DIR=$(top_srcdir)/.git \
+	   $(GIT) log $(CHANGELOG_RANGE) --stat) | fmt --split-only > $@.tmp \
+	  && mv -f $@.tmp "$(srcdir)/ChangeLog" \
 	  || ($(RM) $@.tmp; \
 	      echo Failed to generate ChangeLog, your ChangeLog may be outdated >&2; \
-	      (test -f $@ || echo git-log is required to generate this file >> $@)); \
+	      (test -f $@ || echo git-log is required to generate this file >> "$(srcdir)/$@")); \
 	else \
 	  test -f $@ || \
 	  (echo A git checkout and git-log is required to generate ChangeLog >&2 && \
-	  echo A git checkout and git-log is required to generate this file >> $@); \
+	  echo A git checkout and git-log is required to generate this file >> "$(srcdir)/$@"); \
 	fi
-.PHONY: $(srcdir)/ChangeLog
+.PHONY: ChangeLog $(srcdir)/ChangeLog
 
 
 #
 # Release engineering
 #
 
+DISTCHECK_CONFIGURE_FLAGS = \
+	--enable-gtk-doc \
+	--disable-doc-cross-references \
+	--with-gobject \
+	--enable-introspection \
+	$(NULL)
+
 # TODO: Copy infrastructure from cairo
 
+# TAR_OPTIONS is not set as env var for 'make dist'.  How to fix that?
 TAR_OPTIONS = --owner=0 --group=0
+
 dist-hook: dist-clear-sticky-bits
 # Clean up any sticky bits we may inherit from parent dir
 dist-clear-sticky-bits:
 	chmod -R a-s $(distdir)
 
 
 tar_file = $(PACKAGE_TARNAME)-$(VERSION).tar.bz2
 sha256_file = $(tar_file).sha256
--- a/gfx/harfbuzz/README
+++ b/gfx/harfbuzz/README
@@ -1,7 +1,12 @@
+[![Build Status](https://travis-ci.org/behdad/harfbuzz.svg)](https://travis-ci.org/behdad/harfbuzz)
+[![Build Status](https://ci.appveyor.com/api/projects/status/4oaq58ns2h0m2soa?svg=true)](https://ci.appveyor.com/project/behdad/harfbuzz)
+[![Coverage Status](https://img.shields.io/coveralls/behdad/harfbuzz.svg)](https://coveralls.io/r/behdad/harfbuzz)
+[ABI Tracker](http://abi-laboratory.pro/tracker/timeline/harfbuzz/)
+
 This is HarfBuzz, a text shaping library.
 
 For bug reports, mailing list, and other information please visit:
 
   http://harfbuzz.org/
 
 For license information, see the file COPYING.
--- a/gfx/harfbuzz/README-mozilla
+++ b/gfx/harfbuzz/README-mozilla
@@ -1,17 +1,14 @@
-gfx/harfbuzz status as of 2016-08-21:
+gfx/harfbuzz status as of 2016-10-26:
 
 This directory contains the harfbuzz source from the 'master' branch of
 https://github.com/behdad/harfbuzz.
 
-Current version: 1.3.0 + recent fixes from trunk,
-up to 547ddb0721365dca985aef5b759d08718f7c5f82
-+ the relevant part of https://github.com/behdad/harfbuzz/pull/334.
-
+Current version: 1.3.3
 
 UPDATING:
 
 Note that gfx/harfbuzz/src/hb-version.h is not present in the upstream Git
 repository. It is created at build time by the harfbuzz build system;
 but as we don't use that build system in mozilla, it is necessary to refresh
 this file when updating harfbuzz, and check it into the mozilla tree.
 
--- a/gfx/harfbuzz/TODO
+++ b/gfx/harfbuzz/TODO
@@ -1,85 +1,69 @@
 General fixes:
 =============
 
-- Move feature parsing from util into the library
+- AAT 'morx' implementation.
+
+- Return "safe-to-break" bit from shaping.
+
+- Implement 'rand' feature.
+
+- mask propagation? (when ligation, "or" the masks).
+
+
+API issues:
+===========
+
+- API to accept a list of languages?
+
+- Add init_func to font_funcs.  Adjust ft.
 
 - 'const' for getter APIs? (use mutable internally)
 
-- Fix TT 'kern' on/off and GPOS interaction (move kerning before GPOS)
-
-- Do proper rounding when scaling from font space?
-
-- Misc features:
-  * init/medi/fina/isol for non-cursive scripts
-  * vkna,hkna etc for kana, *jmo for hangul, etc
-
-- Move non-native direction and normalization handling to the generic non-OT
-  layer, such that uniscribe and other backends can use.
-
-- Uniscribe backend needs to enforce one direction only, otherwise cluster
-  values can confused the user.
+- Remove hb_ot_shape_glyphs_closure()?
 
 
-API issues to fix before 1.0:
-============================
+API additions
+=============
 
-- Rename all internal symbols (static ones even) to have _hb prefix?
+- Language to/from script.
 
-- Add pkg-config files for glue codes (harfbuzz-glib, etc)
-
-- Figure out how many .so objects, how to link, etc
+- blob_from_file?
 
 - Add hb-cairo glue
 
 - Add sanitize API (and a cached version, that saves result on blob user-data)
 
-- Add glib GBoxedType stuff and introspection
-
-
-API to add (maybe after 1.0):
-============================
-
-- Add Uniscribe face / font get API
-
 - BCP 47 language handling / API (language_matches?)
 
-- Add hb_face_get_glyph_count()?
-
-- Add hb_font_create_linear()?
+- Add hb_font_create_unscaled()?
 
-- Add hb_shape_plan()/hb_shape_execute()
-
-- Add query API for aalt-like features?
+- Add query / enumeration API for aalt-like features?
 
 - SFNT api? get_num_faces? get_table_tags? (there's something in stash)
 
 - Add segmentation API
 
-- Add hb-fribidi?
+- Add hb-fribidi glue?
 
 
-hb-view enhancements:
-====================
+hb-view / hb-shape enhancements:
+===============================
 
-- Add --format
-- Add --width, --height, --auto-size, --align, etc?
-- Port to GOption, --help
-- Add XML and JSON formats
+- Add --width, --height, --auto-size, --ink-box, --align, etc?
 
 
 Tests to write:
 ==============
 
 - ot-layout enumeration API (needs font)
 
 - Finish test-shape.c, grep for TODO
 
 - Finish test-unicode.c, grep for TODO
 
-
-Optimizations:
-=============
+- GObject, FreeType, etc
 
-- Avoid allocating blob objects internally for for_data() faces?
+- hb_cache_t and relatives
 
-- Add caching layer to hb-ft
+- hb_feature_to/from_string
+- hb_buffer_[sg]et_contents
--- a/gfx/harfbuzz/autogen.sh
+++ b/gfx/harfbuzz/autogen.sh
@@ -14,19 +14,32 @@ which ragel || {
 }
 
 echo -n "checking for pkg-config... "
 which pkg-config || {
 	echo "*** No pkg-config found, please install it ***"
 	exit 1
 }
 
+echo -n "checking for libtoolize... "
+which glibtoolize || which libtoolize || {
+	echo "*** No libtoolize (libtool) found, please install it ***"
+	exit 1
+}
+echo -n "checking for gtkdocize... "
+if which gtkdocize ; then
+	gtkdocize --copy || exit 1
+else
+	echo "*** No gtkdocize (gtk-doc) found, skipping documentation ***"
+	echo "EXTRA_DIST = " > gtk-doc.make
+fi
+
 echo -n "checking for autoreconf... "
 which autoreconf || {
-	echo "*** No autoreconf found, please install it ***"
+	echo "*** No autoreconf (autoconf) found, please install it ***"
 	exit 1
 }
 
 echo "running autoreconf --force --install --verbose"
 autoreconf --force --install --verbose || exit $?
 
 cd $olddir
 echo "running configure $@"
--- a/gfx/harfbuzz/configure.ac
+++ b/gfx/harfbuzz/configure.ac
@@ -1,29 +1,37 @@
 AC_PREREQ([2.64])
-AC_INIT([harfbuzz],
-        [0.7.0],
-        [http://bugs.freedesktop.org/enter_bug.cgi?product=harfbuzz],
+AC_INIT([HarfBuzz],
+        [1.3.3],
+        [https://github.com/behdad/harfbuzz/issues/new],
         [harfbuzz],
         [http://harfbuzz.org/])
 
-AC_CONFIG_SRCDIR([harfbuzz.pc.in])
+AC_CONFIG_MACRO_DIR([m4])
+AC_CONFIG_SRCDIR([src/harfbuzz.pc.in])
 AC_CONFIG_HEADERS([config.h])
 
-AM_INIT_AUTOMAKE([1.11.1 gnu dist-bzip2 no-dist-gzip -Wall no-define])
+AM_INIT_AUTOMAKE([1.11.1 gnits tar-ustar dist-bzip2 no-dist-gzip -Wall no-define color-tests -Wno-portability])
+AM_CONDITIONAL(AUTOMAKE_OLDER_THAN_1_13, test $am__api_version = 1.11 -o $am__api_version = 1.12)
 AM_SILENT_RULES([yes])
 
+# Initialize libtool
+m4_ifdef([AM_PROG_AR], [AM_PROG_AR])
+LT_PREREQ([2.2])
+LT_INIT([disable-static])
+
 # Check for programs
+AC_USE_SYSTEM_EXTENSIONS
 AC_PROG_CC
 AM_PROG_CC_C_O
 AC_PROG_CXX
-
-# Initialize libtool
-LT_PREREQ([2.2])
-LT_INIT([disable-static])
+AC_SYS_LARGEFILE
+PKG_PROG_PKG_CONFIG([0.20])
+AM_MISSING_PROG([RAGEL], [ragel])
+AM_MISSING_PROG([GIT], [git])
 
 # Version
 m4_define(hb_version_triplet,m4_split(AC_PACKAGE_VERSION,[[.]]))
 m4_define(hb_version_major,m4_argn(1,hb_version_triplet))
 m4_define(hb_version_minor,m4_argn(2,hb_version_triplet))
 m4_define(hb_version_micro,m4_argn(3,hb_version_triplet))
 HB_VERSION_MAJOR=hb_version_major
 HB_VERSION_MINOR=hb_version_minor
@@ -40,150 +48,476 @@ m4_define([hb_version_int],
 m4_if(m4_eval(hb_version_minor % 2), [1],
       dnl for unstable releases
       [m4_define([hb_libtool_revision], 0)],
       dnl for stable releases
       [m4_define([hb_libtool_revision], hb_version_micro)])
 m4_define([hb_libtool_age],
 	  m4_eval(hb_version_int - hb_libtool_revision))
 m4_define([hb_libtool_current],
-	  m4_eval(hb_version_major + hb_libtool_age))
+	  m4_eval(hb_libtool_age))
 HB_LIBTOOL_VERSION_INFO=hb_libtool_current:hb_libtool_revision:hb_libtool_age
 AC_SUBST(HB_LIBTOOL_VERSION_INFO)
 
-dnl GTK_DOC_CHECK([1.15],[--flavour no-tmpl])
+# Documentation
+have_gtk_doc=false
+m4_ifdef([GTK_DOC_CHECK], [
+GTK_DOC_CHECK([1.15],[--flavour no-tmpl])
+	if test "x$enable_gtk_doc" = xyes; then
+		have_gtk_doc=true
+	fi
+], [
+	AM_CONDITIONAL([ENABLE_GTK_DOC], false)
+])
 
 # Functions and headers
-AC_CHECK_FUNCS(mprotect sysconf getpagesize mmap _setmode)
-AC_CHECK_HEADERS(unistd.h sys/mman.h io.h)
+AC_CHECK_FUNCS(atexit mprotect sysconf getpagesize mmap isatty)
+AC_CHECK_HEADERS(unistd.h sys/mman.h)
 
 # Compiler flags
 AC_CANONICAL_HOST
+AC_CHECK_ALIGNOF([struct{char;}])
 if test "x$GCC" = "xyes"; then
 
 	# Make symbols link locally
 	LDFLAGS="$LDFLAGS -Bsymbolic-functions"
 
 	# Make sure we don't link to libstdc++
 	CXXFLAGS="$CXXFLAGS -fno-rtti -fno-exceptions"
 
+	# Assorted warnings
+	CXXFLAGS="$CXXFLAGS -Wcast-align"
+
+	case "$host" in
+		*-*-mingw*)
+		;;
+		*)
+			# Hide inline methods
+			CXXFLAGS="$CXXFLAGS -fvisibility-inlines-hidden"
+		;;
+	esac
+
 	case "$host" in
 		arm-*-*)
-			# Request byte alignment on arm
-			CXXFLAGS="$CXXFLAGS -mstructure-size-boundary=8"
+			if test "x$ac_cv_alignof_struct_char__" != x1; then
+				# Request byte alignment
+				CXXFLAGS="$CXXFLAGS -mstructure-size-boundary=8"
+			fi
 		;;
 	esac
 fi
 
+AM_CONDITIONAL(HAVE_GCC, test "x$GCC" = "xyes")
+
+hb_os_win32=no
+AC_MSG_CHECKING([for native Win32])
+case "$host" in
+  *-*-mingw*)
+    hb_os_win32=yes
+    ;;
+esac
+AC_MSG_RESULT([$hb_os_win32])
+AM_CONDITIONAL(OS_WIN32, test "$hb_os_win32" = "yes")
+
+have_pthread=false
+if test "$hb_os_win32" = no; then
+	AX_PTHREAD([have_pthread=true])
+fi
+if $have_pthread; then
+	AC_DEFINE(HAVE_PTHREAD, 1, [Have POSIX threads])
+fi
+AM_CONDITIONAL(HAVE_PTHREAD, $have_pthread)
+
 dnl ==========================================================================
 
-PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.16, have_glib=true, have_glib=false)
+have_ot=true
+if $have_ot; then
+	AC_DEFINE(HAVE_OT, 1, [Have native OpenType Layout backend])
+fi
+AM_CONDITIONAL(HAVE_OT, $have_ot)
+
+have_fallback=true
+if $have_fallback; then
+	AC_DEFINE(HAVE_FALLBACK, 1, [Have simple TrueType Layout backend])
+fi
+AM_CONDITIONAL(HAVE_FALLBACK, $have_fallback)
+
+dnl ===========================================================================
+
+AC_ARG_WITH(glib,
+	[AS_HELP_STRING([--with-glib=@<:@yes/no/auto@:>@],
+			[Use glib @<:@default=auto@:>@])],,
+	[with_glib=auto])
+have_glib=false
+GLIB_DEPS="glib-2.0 >= 2.38"
+AC_SUBST(GLIB_DEPS)
+if test "x$with_glib" = "xyes" -o "x$with_glib" = "xauto"; then
+	PKG_CHECK_MODULES(GLIB, $GLIB_DEPS, have_glib=true, :)
+fi
+if test "x$with_glib" = "xyes" -a "x$have_glib" != "xtrue"; then
+	AC_MSG_ERROR([glib support requested but glib-2.0 not found])
+fi
 if $have_glib; then
 	AC_DEFINE(HAVE_GLIB, 1, [Have glib2 library])
 fi
 AM_CONDITIONAL(HAVE_GLIB, $have_glib)
 
-PKG_CHECK_MODULES(GTHREAD, gthread-2.0, have_gthread=true, have_gthread=false)
-if $have_gthread; then
-	AC_DEFINE(HAVE_GTHREAD, 1, [Have gthread2 library])
+dnl ===========================================================================
+
+AC_ARG_WITH(gobject,
+	[AS_HELP_STRING([--with-gobject=@<:@yes/no/auto@:>@],
+			[Use gobject @<:@default=auto@:>@])],,
+	[with_gobject=no])
+have_gobject=false
+if test "x$with_gobject" = "xyes" -o "x$with_gobject" = "xauto"; then
+	PKG_CHECK_MODULES(GOBJECT, gobject-2.0 glib-2.0, have_gobject=true, :)
 fi
-AM_CONDITIONAL(HAVE_GTHREAD, $have_gthread)
-
-PKG_CHECK_MODULES(GOBJECT, gobject-2.0 glib-2.0 >= 2.16, have_gobject=true, have_gobject=false)
+if test "x$with_gobject" = "xyes" -a "x$have_gobject" != "xtrue"; then
+	AC_MSG_ERROR([gobject support requested but gobject-2.0 / glib-2.0 not found])
+fi
 if $have_gobject; then
 	AC_DEFINE(HAVE_GOBJECT, 1, [Have gobject2 library])
 	GLIB_MKENUMS=`$PKG_CONFIG --variable=glib_mkenums glib-2.0`
 	AC_SUBST(GLIB_MKENUMS)
 fi
 AM_CONDITIONAL(HAVE_GOBJECT, $have_gobject)
 
+dnl ===========================================================================
+
+
+dnl ===========================================================================
+# Gobject-Introspection
+have_introspection=false
+m4_ifdef([GOBJECT_INTROSPECTION_CHECK], [
+	if $have_gobject; then
+		GOBJECT_INTROSPECTION_CHECK([1.34.0])
+		if test "x$found_introspection" = xyes; then
+			have_introspection=true
+		fi
+	else
+		AM_CONDITIONAL([HAVE_INTROSPECTION], false)
+	fi
+], [
+	AM_CONDITIONAL([HAVE_INTROSPECTION], false)
+])
+
 dnl ==========================================================================
 
-PKG_CHECK_MODULES(CAIRO, cairo >= 1.8.0, have_cairo=true, have_cairo=false)
+AC_ARG_WITH(cairo,
+	[AS_HELP_STRING([--with-cairo=@<:@yes/no/auto@:>@],
+			[Use cairo @<:@default=auto@:>@])],,
+	[with_cairo=auto])
+have_cairo=false
+if test "x$with_cairo" = "xyes" -o "x$with_cairo" = "xauto"; then
+	PKG_CHECK_MODULES(CAIRO, cairo >= 1.8.0, have_cairo=true, :)
+fi
+if test "x$with_cairo" = "xyes" -a "x$have_cairo" != "xtrue"; then
+	AC_MSG_ERROR([cairo support requested but not found])
+fi
 if $have_cairo; then
 	AC_DEFINE(HAVE_CAIRO, 1, [Have cairo graphics library])
 fi
 AM_CONDITIONAL(HAVE_CAIRO, $have_cairo)
 
-PKG_CHECK_MODULES(CAIRO_FT, cairo-ft, have_cairo_ft=true, have_cairo_ft=false)
+have_cairo_ft=false
+if $have_cairo; then
+	PKG_CHECK_MODULES(CAIRO_FT, cairo-ft, have_cairo_ft=true, :)
+fi
 if $have_cairo_ft; then
 	AC_DEFINE(HAVE_CAIRO_FT, 1, [Have cairo-ft support in cairo graphics library])
 fi
 AM_CONDITIONAL(HAVE_CAIRO_FT, $have_cairo_ft)
 
 dnl ==========================================================================
 
-PKG_CHECK_MODULES(ICU, icu, have_icu=true, [
-	have_icu=true
-	AC_CHECK_HEADERS(unicode/uchar.h,, have_icu=false)
-	AC_MSG_CHECKING([for libicuuc])
-	LIBS_old=$LIBS
-	LIBS="$LIBS -licuuc"
-	AC_TRY_LINK([#include <unicode/uchar.h>],
-		    [u_getIntPropertyValue (0, (UProperty)0);],
-		    AC_MSG_RESULT(yes),
-		    AC_MSG_RESULT(no);have_icu=false)
-	LIBS=$LIBS_old
-	if $have_icu; then
-		ICU_CFLAGS=-D_REENTRANT
-		ICU_LIBS="-licuuc"
-		AC_SUBST(ICU_CFLAGS)
-		AC_SUBST(ICU_LIBS)
-	fi
-])
-if $have_icu; then
-	AC_DEFINE(HAVE_ICU, 1, [Have ICU library])
+AC_ARG_WITH(fontconfig,
+	[AS_HELP_STRING([--with-fontconfig=@<:@yes/no/auto@:>@],
+			[Use fontconfig @<:@default=auto@:>@])],,
+	[with_fontconfig=auto])
+have_fontconfig=false
+if test "x$with_fontconfig" = "xyes" -o "x$with_fontconfig" = "xauto"; then
+	PKG_CHECK_MODULES(FONTCONFIG, fontconfig, have_fontconfig=true, :)
 fi
-AM_CONDITIONAL(HAVE_ICU, $have_icu)
+if test "x$with_fontconfig" = "xyes" -a "x$have_fontconfig" != "xtrue"; then
+	AC_MSG_ERROR([fontconfig support requested but not found])
+fi
+if $have_fontconfig; then
+	AC_DEFINE(HAVE_FONTCONFIG, 1, [Have fontconfig library])
+fi
+AM_CONDITIONAL(HAVE_FONTCONFIG, $have_fontconfig)
 
 dnl ==========================================================================
 
-PKG_CHECK_MODULES(GRAPHITE, graphite2, have_graphite=true, have_graphite=false)
-if $have_graphite; then
-    AC_DEFINE(HAVE_GRAPHITE, 1, [Have Graphite library])
+AC_ARG_WITH(icu,
+	[AS_HELP_STRING([--with-icu=@<:@yes/no/builtin/auto@:>@],
+			[Use ICU @<:@default=auto@:>@])],,
+	[with_icu=auto])
+have_icu=false
+if test "x$with_icu" = "xyes" -o "x$with_icu" = "xbuiltin" -o "x$with_icu" = "xauto"; then
+	PKG_CHECK_MODULES(ICU, icu-uc, have_icu=true, :)
+
+	dnl Fallback to icu-config if ICU pkg-config files could not be found
+	if test "$have_icu" != "true"; then
+		AC_CHECK_TOOL(ICU_CONFIG, icu-config, no)
+		AC_MSG_CHECKING([for ICU by using icu-config fallback])
+		if test "$ICU_CONFIG" != "no" && "$ICU_CONFIG" --version >/dev/null; then
+			have_icu=true
+			# We don't use --cflags as this gives us a lot of things that we don't
+			# necessarily want, like debugging and optimization flags
+			# See man (1) icu-config for more info.
+			ICU_CFLAGS=`$ICU_CONFIG --cppflags`
+			ICU_LIBS=`$ICU_CONFIG --ldflags-searchpath --ldflags-libsonly`
+			AC_SUBST(ICU_CFLAGS)
+			AC_SUBST(ICU_LIBS)
+			AC_MSG_RESULT([yes])
+		else
+			AC_MSG_RESULT([no])
+		fi
+	fi
 fi
-AM_CONDITIONAL(HAVE_GRAPHITE, $have_graphite)
+if test \( "x$with_icu" = "xyes" -o "x$with_icu" = "xbuiltin" \) -a "x$have_icu" != "xtrue"; then
+	AC_MSG_ERROR([icu support requested but icu-uc not found])
+fi
+
+if $have_icu; then
+	CXXFLAGS="$CXXFLAGS `$PKG_CONFIG --variable=CXXFLAGS icu-uc`"
+	AC_DEFINE(HAVE_ICU, 1, [Have ICU library])
+	if test "x$with_icu" = "xbuiltin"; then
+		AC_DEFINE(HAVE_ICU_BUILTIN, 1, [Use hb-icu Unicode callbacks])
+	fi
+fi
+AM_CONDITIONAL(HAVE_ICU, $have_icu)
+AM_CONDITIONAL(HAVE_ICU_BUILTIN, $have_icu && test "x$with_icu" = "xbuiltin")
+
+dnl ===========================================================================
+
+have_ucdn=true
+if $have_glib || test \( $have_icu -a "x$with_icu" = "xbuiltin" \); then
+	have_ucdn=false
+fi
+if $have_ucdn; then
+	AC_DEFINE(HAVE_UCDN, 1, [Have UCDN Unicode functions])
+fi
+AM_CONDITIONAL(HAVE_UCDN, $have_ucdn)
 
 dnl ==========================================================================
 
-PKG_CHECK_MODULES(FREETYPE, freetype2 >= 2.3.8, have_freetype=true, have_freetype=false)
+AC_ARG_WITH(graphite2,
+	[AS_HELP_STRING([--with-graphite2=@<:@yes/no/auto@:>@],
+			[Use the graphite2 library @<:@default=no@:>@])],,
+	[with_graphite2=no])
+have_graphite2=false
+GRAPHITE2_DEPS="graphite2"
+AC_SUBST(GRAPHITE2_DEPS)
+if test "x$with_graphite2" = "xyes" -o "x$with_graphite2" = "xauto"; then
+	PKG_CHECK_MODULES(GRAPHITE2, $GRAPHITE2_DEPS, have_graphite2=true, :)
+	if test "x$have_graphite2" != "xtrue"; then
+                # If pkg-config is not available, graphite2 can still be there
+		ac_save_CFLAGS="$CFLAGS"
+		ac_save_CPPFLAGS="$CPPFLAGS"
+		CFLAGS="$CFLAGS $GRAPHITE2_CFLAGS"
+		CPPFLAGS="$CPPFLAGS $GRAPHITE2_CFLAGS"
+		AC_CHECK_HEADER(graphite2/Segment.h, have_graphite2=true, :)
+		CPPFLAGS="$ac_save_CPPFLAGS"
+		CFLAGS="$ac_save_CFLAGS"
+	fi
+fi
+if test "x$with_graphite2" = "xyes" -a "x$have_graphite2" != "xtrue"; then
+	AC_MSG_ERROR([graphite2 support requested but libgraphite2 not found])
+fi
+if $have_graphite2; then
+	AC_DEFINE(HAVE_GRAPHITE2, 1, [Have Graphite2 library])
+fi
+AM_CONDITIONAL(HAVE_GRAPHITE2, $have_graphite2)
+
+dnl ==========================================================================
+
+AC_ARG_WITH(freetype,
+	[AS_HELP_STRING([--with-freetype=@<:@yes/no/auto@:>@],
+			[Use the FreeType library @<:@default=auto@:>@])],,
+	[with_freetype=auto])
+have_freetype=false
+FREETYPE_DEPS="freetype2 >= 12.0.6"
+AC_SUBST(FREETYPE_DEPS)
+if test "x$with_freetype" = "xyes" -o "x$with_freetype" = "xauto"; then
+	# See freetype/docs/VERSION.DLL; 12.0.6 means freetype-2.4.2
+	PKG_CHECK_MODULES(FREETYPE, $FREETYPE_DEPS, have_freetype=true, :)
+fi
+if test "x$with_freetype" = "xyes" -a "x$have_freetype" != "xtrue"; then
+	AC_MSG_ERROR([FreeType support requested but libfreetype2 not found])
+fi
 if $have_freetype; then
 	AC_DEFINE(HAVE_FREETYPE, 1, [Have FreeType 2 library])
-	_save_libs="$LIBS"
-	_save_cflags="$CFLAGS"
-	LIBS="$LIBS $FREETYPE_LIBS"
-	CFLAGS="$CFLAGS $FREETYPE_CFLAGS"
-	AC_CHECK_FUNCS(FT_Face_GetCharVariantIndex)
-	LIBS="$_save_libs"
-	CFLAGS="$_save_cflags"
 fi
 AM_CONDITIONAL(HAVE_FREETYPE, $have_freetype)
 
 dnl ===========================================================================
 
-have_ot=true;
-if $have_ot; then
-	AC_DEFINE(HAVE_OT, 1, [Have native OpenType Layout backend])
+AC_ARG_WITH(uniscribe,
+	[AS_HELP_STRING([--with-uniscribe=@<:@yes/no/auto@:>@],
+			[Use the Uniscribe library @<:@default=no@:>@])],,
+	[with_uniscribe=no])
+have_uniscribe=false
+if test "x$with_uniscribe" = "xyes" -o "x$with_uniscribe" = "xauto"; then
+	AC_CHECK_HEADERS(usp10.h windows.h, have_uniscribe=true)
+fi
+if test "x$with_uniscribe" = "xyes" -a "x$have_uniscribe" != "xtrue"; then
+	AC_MSG_ERROR([uniscribe support requested but not found])
+fi
+if $have_uniscribe; then
+	UNISCRIBE_CFLAGS=
+	UNISCRIBE_LIBS="-lusp10 -lgdi32 -lrpcrt4"
+	AC_SUBST(UNISCRIBE_CFLAGS)
+	AC_SUBST(UNISCRIBE_LIBS)
+	AC_DEFINE(HAVE_UNISCRIBE, 1, [Have Uniscribe library])
+fi
+AM_CONDITIONAL(HAVE_UNISCRIBE, $have_uniscribe)
+
+dnl ===========================================================================
+
+AC_ARG_WITH(directwrite,
+	[AS_HELP_STRING([--with-directwrite=@<:@yes/no/auto@:>@],
+			[Use the DirectWrite library (experimental) @<:@default=no@:>@])],,
+	[with_directwrite=no])
+have_directwrite=false
+AC_LANG_PUSH([C++])
+if test "x$with_directwrite" = "xyes" -o "x$with_directwrite" = "xauto"; then
+	AC_CHECK_HEADERS(dwrite.h, have_directwrite=true)
+fi
+AC_LANG_POP([C++])
+if test "x$with_directwrite" = "xyes" -a "x$have_directwrite" != "xtrue"; then
+	AC_MSG_ERROR([directwrite support requested but not found])
 fi
-AM_CONDITIONAL(HAVE_OT, $have_ot)
+if $have_directwrite; then
+	DIRECTWRITE_CXXFLAGS=
+	DIRECTWRITE_LIBS="-ldwrite"
+	AC_SUBST(DIRECTWRITE_CXXFLAGS)
+	AC_SUBST(DIRECTWRITE_LIBS)
+	AC_DEFINE(HAVE_DIRECTWRITE, 1, [Have DirectWrite library])
+fi
+AM_CONDITIONAL(HAVE_DIRECTWRITE, $have_directwrite)
+
+dnl ===========================================================================
+
+AC_ARG_WITH(coretext,
+	[AS_HELP_STRING([--with-coretext=@<:@yes/no/auto@:>@],
+			[Use CoreText @<:@default=no@:>@])],,
+	[with_coretext=no])
+have_coretext=false
+if test "x$with_coretext" = "xyes" -o "x$with_coretext" = "xauto"; then
+	AC_CHECK_TYPE(CTFontRef, have_coretext=true,, [#include <ApplicationServices/ApplicationServices.h>])
+
+	if $have_coretext; then
+		CORETEXT_CFLAGS=
+		CORETEXT_LIBS="-framework ApplicationServices"
+		AC_SUBST(CORETEXT_CFLAGS)
+		AC_SUBST(CORETEXT_LIBS)
+	else
+		# On iOS CoreText and CoreGraphics are stand-alone frameworks
+		if test "x$have_coretext" != "xtrue"; then
+			# Check for a different symbol to avoid getting cached result.
+			AC_CHECK_TYPE(CTRunRef, have_coretext=true,, [#include <CoreText/CoreText.h>])
+		fi
+
+		if $have_coretext; then
+			CORETEXT_CFLAGS=
+			CORETEXT_LIBS="-framework CoreText -framework CoreGraphics"
+			AC_SUBST(CORETEXT_CFLAGS)
+			AC_SUBST(CORETEXT_LIBS)
+		fi
+	fi
+fi
+if test "x$with_coretext" = "xyes" -a "x$have_coretext" != "xtrue"; then
+	AC_MSG_ERROR([CoreText support requested but libcoretext not found])
+fi
+if $have_coretext; then
+	AC_DEFINE(HAVE_CORETEXT, 1, [Have Core Text backend])
+fi
+AM_CONDITIONAL(HAVE_CORETEXT, $have_coretext)
 
 dnl ===========================================================================
 
-AC_CHECK_HEADERS(usp10.h windows.h, have_uniscribe=true, have_uniscribe=false)
-if $have_uniscribe; then
-	UNISCRIBE_CFLAGS=
-	UNISCRIBE_LIBS="-lusp10 -lgdi32"
-	AC_SUBST(UNISCRIBE_CFLAGS)
-	AC_SUBST(UNISCRIBE_LIBS)
-	AC_DEFINE(HAVE_UNISCRIBE, 1, [Have Uniscribe backend])
+AC_CACHE_CHECK([for Intel atomic primitives], hb_cv_have_intel_atomic_primitives, [
+	hb_cv_have_intel_atomic_primitives=false
+	AC_TRY_LINK([
+		void memory_barrier (void) { __sync_synchronize (); }
+		int atomic_add (int *i) { return __sync_fetch_and_add (i, 1); }
+		int mutex_trylock (int *m) { return __sync_lock_test_and_set (m, 1); }
+		void mutex_unlock (int *m) { __sync_lock_release (m); }
+		], [], hb_cv_have_intel_atomic_primitives=true
+	)
+])
+if $hb_cv_have_intel_atomic_primitives; then
+	AC_DEFINE(HAVE_INTEL_ATOMIC_PRIMITIVES, 1, [Have Intel __sync_* atomic primitives])
 fi
-AM_CONDITIONAL(HAVE_UNISCRIBE, $have_uniscribe)
+
+dnl ===========================================================================
+
+AC_CACHE_CHECK([for Solaris atomic operations], hb_cv_have_solaris_atomic_ops, [
+	hb_cv_have_solaris_atomic_ops=false
+	AC_TRY_LINK([
+		#include <atomic.h>
+		/* This requires Solaris Studio 12.2 or newer: */
+		#include <mbarrier.h>
+		void memory_barrier (void) { __machine_rw_barrier (); }
+		int atomic_add (volatile unsigned *i) { return atomic_add_int_nv (i, 1); }
+		void *atomic_ptr_cmpxchg (volatile void **target, void *cmp, void *newval) { return atomic_cas_ptr (target, cmp, newval); }
+		], [], hb_cv_have_solaris_atomic_ops=true
+	)
+])
+if $hb_cv_have_solaris_atomic_ops; then
+	AC_DEFINE(HAVE_SOLARIS_ATOMIC_OPS, 1, [Have Solaris __machine_*_barrier and atomic_* operations])
+fi
+
+if test "$os_win32" = no && ! $have_pthread; then
+	AC_CHECK_HEADERS(sched.h)
+	AC_SEARCH_LIBS(sched_yield,rt,AC_DEFINE(HAVE_SCHED_YIELD, 1, [Have sched_yield]))
+fi
+
+dnl ===========================================================================
 
 AC_CONFIG_FILES([
 Makefile
-harfbuzz.pc
 src/Makefile
 src/hb-version.h
+src/hb-ucdn/Makefile
 util/Makefile
 test/Makefile
+test/api/Makefile
+test/fuzzing/Makefile
+test/shaping/Makefile
+docs/Makefile
+docs/version.xml
+win32/Makefile
+win32/config.h.win32
 ])
 
 AC_OUTPUT
+
+AC_MSG_NOTICE([
+
+Build configuration:
+
+Unicode callbacks (you want at least one):
+	Glib:			${have_glib}
+	ICU:			${have_icu}
+	UCDN:			${have_ucdn}
+
+Font callbacks (the more the better):
+	FreeType:		${have_freetype}
+
+Tools used for command-line utilities:
+	Cairo:			${have_cairo}
+	Fontconfig:		${have_fontconfig}
+
+Additional shapers (the more the better):
+	Graphite2:		${have_graphite2}
+
+Platform shapers (not normally needed):
+	CoreText:		${have_coretext}
+	Uniscribe:		${have_uniscribe}
+	DirectWrite:		${have_directwrite}
+
+Other features:
+	Documentation:		${have_gtk_doc}
+	GObject bindings:	${have_gobject}
+	Introspection:		${have_introspection}
+])
--- a/gfx/harfbuzz/git.mk
+++ b/gfx/harfbuzz/git.mk
@@ -1,184 +1,347 @@
-# git.mk
+# git.mk, a small Makefile to autogenerate .gitignore files
+# for autotools-based projects.
 #
 # Copyright 2009, Red Hat, Inc.
+# Copyright 2010,2011,2012,2013 Behdad Esfahbod
 # Written by Behdad Esfahbod
 #
 # Copying and distribution of this file, with or without modification,
-# are permitted in any medium without royalty provided the copyright
+# is permitted in any medium without royalty provided the copyright
 # notice and this notice are preserved.
 #
-# The canonical source for this file is pango/git.mk, or whereever the
-# header of pango/git.mk suggests in the future.
+# The latest version of this file can be downloaded from:
+GIT_MK_URL = https://raw.githubusercontent.com/behdad/git.mk/master/git.mk
+#
+# Bugs, etc, should be reported upstream at:
+#   https://github.com/behdad/git.mk
 #
 # To use in your project, import this file in your git repo's toplevel,
 # then do "make -f git.mk".  This modifies all Makefile.am files in
-# your project to include git.mk.
+# your project to -include git.mk.  Remember to add that line to new
+# Makefile.am files you create in your project, or just rerun the
+# "make -f git.mk".
 #
 # This enables automatic .gitignore generation.  If you need to ignore
 # more files, add them to the GITIGNOREFILES variable in your Makefile.am.
 # But think twice before doing that.  If a file has to be in .gitignore,
 # chances are very high that it's a generated file and should be in one
 # of MOSTLYCLEANFILES, CLEANFILES, DISTCLEANFILES, or MAINTAINERCLEANFILES.
 #
 # The only case that you need to manually add a file to GITIGNOREFILES is
 # when remove files in one of mostlyclean-local, clean-local, distclean-local,
-# or maintainer-clean-local.
+# or maintainer-clean-local make targets.
 #
 # Note that for files like editor backup, etc, there are better places to
 # ignore them.  See "man gitignore".
 #
 # If "make maintainer-clean" removes the files but they are not recognized
 # by this script (that is, if "git status" shows untracked files still), send
 # me the output of "git status" as well as your Makefile.am and Makefile for
-# the directories involved.
+# the directories involved and I'll diagnose.
 #
 # For a list of toplevel files that should be in MAINTAINERCLEANFILES, see
-# pango/Makefile.am.
+# Makefile.am.sample in the git.mk git repo.
 #
 # Don't EXTRA_DIST this file.  It is supposed to only live in git clones,
 # not tarballs.  It serves no useful purpose in tarballs and clutters the
 # build dir.
 #
 # This file knows how to handle autoconf, automake, libtool, gtk-doc,
-# gnome-doc-utils, intltool.
+# gnome-doc-utils, yelp.m4, mallard, intltool, gsettings, dejagnu, appdata,
+# appstream.
+#
+# This makefile provides the following targets:
 #
+# - all: "make all" will build all gitignore files.
+# - gitignore: makes all gitignore files in the current dir and subdirs.
+# - .gitignore: make gitignore file for the current dir.
+# - gitignore-recurse: makes all gitignore files in the subdirs.
 #
 # KNOWN ISSUES:
 #
 # - Recursive configure doesn't work as $(top_srcdir)/git.mk inside the
 #   submodule doesn't find us.  If you have configure.{in,ac} files in
 #   subdirs, add a proxy git.mk file in those dirs that simply does:
 #   "include $(top_srcdir)/../git.mk".  Add more ..'s to your taste.
 #   And add those files to git.  See vte/gnome-pty-helper/git.mk for
 #   example.
 #
 
+
+
+###############################################################################
+# Variables user modules may want to add to toplevel MAINTAINERCLEANFILES:
+###############################################################################
+
+#
+# Most autotools-using modules should be fine including this variable in their
+# toplevel MAINTAINERCLEANFILES:
+GITIGNORE_MAINTAINERCLEANFILES_TOPLEVEL = \
+	$(srcdir)/aclocal.m4 \
+	$(srcdir)/autoscan.log \
+	$(srcdir)/configure.scan \
+	`AUX_DIR=$(srcdir)/$$(cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_AUX_DIR:$$1' ./configure.ac); \
+	 test "x$$AUX_DIR" = "x$(srcdir)/" && AUX_DIR=$(srcdir); \
+	 for x in \
+		ar-lib \
+		compile \
+		config.guess \
+		config.sub \
+		depcomp \
+		install-sh \
+		ltmain.sh \
+		missing \
+		mkinstalldirs \
+		test-driver \
+		ylwrap \
+	 ; do echo "$$AUX_DIR/$$x"; done` \
+	`cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_HEADERS:$$1' ./configure.ac | \
+	head -n 1 | while read f; do echo "$(srcdir)/$$f.in"; done`
+#
+# All modules should also be fine including the following variable, which
+# removes automake-generated Makefile.in files:
+GITIGNORE_MAINTAINERCLEANFILES_MAKEFILE_IN = \
+	`cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_FILES:$$1' ./configure.ac | \
+	while read f; do \
+	  case $$f in Makefile|*/Makefile) \
+	    test -f "$(srcdir)/$$f.am" && echo "$(srcdir)/$$f.in";; esac; \
+	done`
+#
+# Modules that use libtool and use  AC_CONFIG_MACRO_DIR() may also include this,
+# though it's harmless to include regardless.
+GITIGNORE_MAINTAINERCLEANFILES_M4_LIBTOOL = \
+	`MACRO_DIR=$(srcdir)/$$(cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_MACRO_DIR:$$1' ./configure.ac); \
+	 if test "x$$MACRO_DIR" != "x$(srcdir)/"; then \
+		for x in \
+			libtool.m4 \
+			ltoptions.m4 \
+			ltsugar.m4 \
+			ltversion.m4 \
+			lt~obsolete.m4 \
+		; do echo "$$MACRO_DIR/$$x"; done; \
+	 fi`
+
+
+
+###############################################################################
+# Default rule is to install ourselves in all Makefile.am files:
+###############################################################################
+
 git-all: git-mk-install
 
 git-mk-install:
-	@echo Installing git makefile
-	@any_failed=; find $(top_srcdir) -name Makefile.am | while read x; do \
+	@echo "Installing git makefile"
+	@any_failed=; \
+		find "`test -z "$(top_srcdir)" && echo . || echo "$(top_srcdir)"`" -name Makefile.am | while read x; do \
 		if grep 'include .*/git.mk' $$x >/dev/null; then \
-			echo $$x already includes git.mk; \
+			echo "$$x already includes git.mk"; \
 		else \
 			failed=; \
 			echo "Updating $$x"; \
 			{ cat $$x; \
 			  echo ''; \
 			  echo '-include $$(top_srcdir)/git.mk'; \
 			} > $$x.tmp || failed=1; \
 			if test x$$failed = x; then \
 				mv $$x.tmp $$x || failed=1; \
 			fi; \
 			if test x$$failed = x; then : else \
-				echo Failed updating $$x; >&2 \
+				echo "Failed updating $$x"; >&2 \
 				any_failed=1; \
 			fi; \
 	fi; done; test -z "$$any_failed"
 
-.PHONY: git-all git-mk-install
+git-mk-update:
+	wget $(GIT_MK_URL) -O $(top_srcdir)/git.mk
+
+.PHONY: git-all git-mk-install git-mk-update
+
 
 
-### .gitignore generation
+###############################################################################
+# Actual .gitignore generation:
+###############################################################################
 
 $(srcdir)/.gitignore: Makefile.am $(top_srcdir)/git.mk
-	$(AM_V_GEN) \
-	{ \
+	@echo "git.mk: Generating $@"
+	@{ \
 		if test "x$(DOC_MODULE)" = x -o "x$(DOC_MAIN_SGML_FILE)" = x; then :; else \
 			for x in \
 				$(DOC_MODULE)-decl-list.txt \
 				$(DOC_MODULE)-decl.txt \
 				tmpl/$(DOC_MODULE)-unused.sgml \
 				"tmpl/*.bak" \
+				$(REPORT_FILES) \
+				$(DOC_MODULE).pdf \
 				xml html \
-			; do echo /$$x; done; \
+			; do echo "/$$x"; done; \
+			FLAVOR=$$(cd $(top_srcdir); $(AUTOCONF) --trace 'GTK_DOC_CHECK:$$2' ./configure.ac); \
+			case $$FLAVOR in *no-tmpl*) echo /tmpl;; esac; \
+			if echo "$(SCAN_OPTIONS)" | grep -q "\-\-rebuild-types"; then \
+				echo "/$(DOC_MODULE).types"; \
+			fi; \
+			if echo "$(SCAN_OPTIONS)" | grep -q "\-\-rebuild-sections"; then \
+				echo "/$(DOC_MODULE)-sections.txt"; \
+			fi; \
+			if test "$(abs_srcdir)" != "$(abs_builddir)" ; then \
+				for x in \
+					$(SETUP_FILES) \
+					$(DOC_MODULE).types \
+				; do echo "/$$x"; done; \
+			fi; \
 		fi; \
-		if test "x$(DOC_MODULE)" = x -o "x$(DOC_LINGUAS)" = x; then :; else \
+		if test "x$(DOC_MODULE)$(DOC_ID)" = x -o "x$(DOC_LINGUAS)" = x; then :; else \
+			for lc in $(DOC_LINGUAS); do \
+				for x in \
+					$(if $(DOC_MODULE),$(DOC_MODULE).xml) \
+					$(DOC_PAGES) \
+					$(DOC_INCLUDES) \
+				; do echo "/$$lc/$$x"; done; \
+			done; \
 			for x in \
-				$(_DOC_C_DOCS) \
-				$(_DOC_LC_DOCS) \
 				$(_DOC_OMF_ALL) \
 				$(_DOC_DSK_ALL) \
 				$(_DOC_HTML_ALL) \
-				$(_DOC_POFILES) \
+				$(_DOC_MOFILES) \
+				$(DOC_H_FILE) \
 				"*/.xml2po.mo" \
 				"*/*.omf.out" \
 			; do echo /$$x; done; \
 		fi; \
+		if test "x$(HELP_ID)" = x -o "x$(HELP_LINGUAS)" = x; then :; else \
+			for lc in $(HELP_LINGUAS); do \
+				for x in \
+					$(HELP_FILES) \
+					"$$lc.stamp" \
+					"$$lc.mo" \
+				; do echo "/$$lc/$$x"; done; \
+			done; \
+		fi; \
+		if test "x$(gsettings_SCHEMAS)" = x; then :; else \
+			for x in \
+				$(gsettings_SCHEMAS:.xml=.valid) \
+				$(gsettings__enum_file) \
+			; do echo "/$$x"; done; \
+		fi; \
+		if test "x$(appdata_XML)" = x; then :; else \
+			for x in \
+				$(appdata_XML:.xml=.valid) \
+			; do echo "/$$x"; done; \
+		fi; \
+		if test "x$(appstream_XML)" = x; then :; else \
+			for x in \
+				$(appstream_XML:.xml=.valid) \
+			; do echo "/$$x"; done; \
+		fi; \
 		if test -f $(srcdir)/po/Makefile.in.in; then \
 			for x in \
 				po/Makefile.in.in \
+				po/Makefile.in.in~ \
 				po/Makefile.in \
 				po/Makefile \
+				po/Makevars.template \
 				po/POTFILES \
+				po/Rules-quot \
 				po/stamp-it \
 				po/.intltool-merge-cache \
 				"po/*.gmo" \
+				"po/*.header" \
 				"po/*.mo" \
+				"po/*.sed" \
+				"po/*.sin" \
 				po/$(GETTEXT_PACKAGE).pot \
 				intltool-extract.in \
 				intltool-merge.in \
 				intltool-update.in \
-			; do echo /$$x; done; \
+			; do echo "/$$x"; done; \
 		fi; \
 		if test -f $(srcdir)/configure; then \
 			for x in \
 				autom4te.cache \
 				configure \
 				config.h \
 				stamp-h1 \
 				libtool \
 				config.lt \
-			; do echo /$$x; done; \
+			; do echo "/$$x"; done; \
+		fi; \
+		if test "x$(DEJATOOL)" = x; then :; else \
+			for x in \
+				$(DEJATOOL) \
+			; do echo "/$$x.sum"; echo "/$$x.log"; done; \
+			echo /site.exp; \
+		fi; \
+		if test "x$(am__dirstamp)" = x; then :; else \
+			echo "$(am__dirstamp)"; \
+		fi; \
+		if test "x$(LTCOMPILE)" = x -a "x$(LTCXXCOMPILE)" = x -a "x$(GTKDOC_RUN)" = x; then :; else \
+			for x in \
+				"*.lo" \
+				".libs" "_libs" \
+			; do echo "$$x"; done; \
 		fi; \
 		for x in \
 			.gitignore \
 			$(GITIGNOREFILES) \
 			$(CLEANFILES) \
-			$(PROGRAMS) \
-			$(check_PROGRAMS) \
-			$(EXTRA_PROGRAMS) \
-			$(LTLIBRARIES) \
+			$(PROGRAMS) $(check_PROGRAMS) $(EXTRA_PROGRAMS) \
+			$(LIBRARIES) $(check_LIBRARIES) $(EXTRA_LIBRARIES) \
+			$(LTLIBRARIES) $(check_LTLIBRARIES) $(EXTRA_LTLIBRARIES) \
 			so_locations \
-			.libs _libs \
 			$(MOSTLYCLEANFILES) \
-			"*.$(OBJEXT)" \
-			"*.lo" \
+			$(TEST_LOGS) \
+			$(TEST_LOGS:.log=.trs) \
+			$(TEST_SUITE_LOG) \
+			$(TESTS:=.test) \
+			"*.gcda" \
+			"*.gcno" \
 			$(DISTCLEANFILES) \
 			$(am__CONFIG_DISTCLEAN_FILES) \
 			$(CONFIG_CLEAN_FILES) \
 			TAGS ID GTAGS GRTAGS GSYMS GPATH tags \
 			"*.tab.c" \
 			$(MAINTAINERCLEANFILES) \
 			$(BUILT_SOURCES) \
-			$(DEPDIR) \
+			$(patsubst %.vala,%.c,$(filter %.vala,$(SOURCES))) \
+			$(filter %_vala.stamp,$(DIST_COMMON)) \
+			$(filter %.vapi,$(DIST_COMMON)) \
+			$(filter $(addprefix %,$(notdir $(patsubst %.vapi,%.h,$(filter %.vapi,$(DIST_COMMON))))),$(DIST_COMMON)) \
 			Makefile \
 			Makefile.in \
 			"*.orig" \
 			"*.rej" \
 			"*.bak" \
 			"*~" \
 			".*.sw[nop]" \
-		; do echo /$$x; done; \
+			".dirstamp" \
+		; do echo "/$$x"; done; \
+		for x in \
+			"*.$(OBJEXT)" \
+			$(DEPDIR) \
+		; do echo "$$x"; done; \
 	} | \
 	sed "s@^/`echo "$(srcdir)" | sed 's/\(.\)/[\1]/g'`/@/@" | \
 	sed 's@/[.]/@/@g' | \
 	LC_ALL=C sort | uniq > $@.tmp && \
 	mv $@.tmp $@;
 
 all: $(srcdir)/.gitignore gitignore-recurse-maybe
+gitignore: $(srcdir)/.gitignore gitignore-recurse
+
 gitignore-recurse-maybe:
-	@if test "x$(SUBDIRS)" = "x$(DIST_SUBDIRS)"; then :; else \
-		$(MAKE) $(AM_MAKEFLAGS) gitignore-recurse; \
-	fi;
+	@for subdir in $(DIST_SUBDIRS); do \
+	  case " $(SUBDIRS) " in \
+	    *" $$subdir "*) :;; \
+	    *) test "$$subdir" = . -o -e "$$subdir/.git" || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) gitignore || echo "Skipping $$subdir");; \
+	  esac; \
+	done
 gitignore-recurse:
-	@list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
-	  test "$$subdir" = . || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) .gitignore gitignore-recurse || echo "Skipping $$subdir"); \
+	@for subdir in $(DIST_SUBDIRS); do \
+	    test "$$subdir" = . -o -e "$$subdir/.git" || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) gitignore || echo "Skipping $$subdir"); \
 	done
-gitignore: $(srcdir)/.gitignore gitignore-recurse
 
 maintainer-clean: gitignore-clean
 gitignore-clean:
 	-rm -f $(srcdir)/.gitignore
 
 .PHONY: gitignore-clean gitignore gitignore-recurse gitignore-recurse-maybe
new file mode 100644
--- /dev/null
+++ b/gfx/harfbuzz/harfbuzz.doap
@@ -0,0 +1,24 @@
+<Project xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+         xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
+         xmlns:foaf="http://xmlns.com/foaf/0.1/"
+         xmlns="http://usefulinc.com/ns/doap#">
+
+  <name xml:lang="en">harfbuzz</name>
+  <shortdesc xml:lang="en">Text shaping library</shortdesc>
+
+  <homepage
+  rdf:resource="http://harfbuzz.org/" />
+  <mailing-list
+  rdf:resource="http://lists.freedesktop.org/mailman/listinfo/harfbuzz" />
+  <!--download-page
+  rdf:resource=""/-->
+  <bug-database
+  rdf:resource="http://bugs.freedesktop.org/enter_bug.cgi?product=harfbuzz"/>
+
+  <maintainer>
+    <foaf:Person>
+      <foaf:name>Behdad Esfahbod</foaf:name>
+      <foaf:mbox rdf:resource="mailto:harfbuzz@behdad.org" />
+    </foaf:Person>
+  </maintainer>
+</Project>
deleted file mode 100644
--- a/gfx/harfbuzz/harfbuzz.pc.in
+++ /dev/null
@@ -1,11 +0,0 @@
-prefix=@prefix@
-exec_prefix=@exec_prefix@
-libdir=@libdir@
-includedir=@includedir@
-
-Name: harfbuzz
-Description: Text shaping library
-Version: @VERSION@
-
-Libs: -L${libdir} -lharfbuzz
-Cflags: -I${includedir}/harfbuzz
--- a/gfx/harfbuzz/src/check-symbols.sh
+++ b/gfx/harfbuzz/src/check-symbols.sh
@@ -14,18 +14,18 @@ else
 	exit 77
 fi
 
 echo "Checking that we are not exposing internal symbols"
 tested=false
 for suffix in so dylib; do
 	so=.libs/libharfbuzz.$suffix
 	if ! test -f "$so"; then continue; fi
-	
-	EXPORTED_SYMBOLS="`nm "$so" | grep ' [BCDGINRSTVW] ' | grep -v ' _fini\>\| _init\>\| _fdata\>\| _ftext\>\| _fbss\>\| __bss_start\>\| __bss_start__\>\| __bss_end__\>\| _edata\>\| _end\>\| _bss_end__\>\| __end__\>\| __gcov_flush\>\| llvm_' | cut -d' ' -f3`"
+
+	EXPORTED_SYMBOLS="`nm "$so" | grep ' [BCDGINRSTVW] ' | grep -v ' _fini\>\| _init\>\| _fdata\>\| _ftext\>\| _fbss\>\| __bss_start\>\| __bss_start__\>\| __bss_end__\>\| _edata\>\| _end\>\| _bss_end__\>\| __end__\>\| __gcov_flush\>\| ___gcov_flush\>\| llvm_\| _llvm_' | cut -d' ' -f3`"
 
 	prefix=`basename "$so" | sed 's/libharfbuzz/hb/; s/-/_/g; s/[.].*//'`
 
 	# On mac, C symbols are prefixed with _
 	if test $suffix = dylib; then prefix="_$prefix"; fi
 
 	echo "Processing $so"
 	if echo "$EXPORTED_SYMBOLS" | grep -v "^${prefix}_"; then
--- a/gfx/harfbuzz/src/harfbuzz-icu.pc
+++ b/gfx/harfbuzz/src/harfbuzz-icu.pc
@@ -1,13 +1,13 @@
 prefix=/usr/local
 exec_prefix=/usr/local
 libdir=/usr/local/lib
 includedir=/usr/local/include
 
 Name: harfbuzz
 Description: HarfBuzz text shaping library ICU integration
-Version: 1.0.1
+Version: 1.3.3
 
 Requires: harfbuzz
 Requires.private: icu-uc
 Libs: -L${libdir} -lharfbuzz-icu
 Cflags: -I${includedir}/harfbuzz
--- a/gfx/harfbuzz/src/harfbuzz.pc
+++ b/gfx/harfbuzz/src/harfbuzz.pc
@@ -1,11 +1,13 @@
 prefix=/usr/local
 exec_prefix=/usr/local
 libdir=/usr/local/lib
 includedir=/usr/local/include
 
 Name: harfbuzz
 Description: HarfBuzz text shaping library
-Version: 1.0.1
+Version: 1.3.3
 
 Libs: -L${libdir} -lharfbuzz
+Libs.private:    
+Requires.private: glib-2.0 >= 2.38 
 Cflags: -I${includedir}/harfbuzz
--- a/gfx/harfbuzz/src/hb-coretext.cc
+++ b/gfx/harfbuzz/src/hb-coretext.cc
@@ -144,19 +144,27 @@ create_ct_font (CGFontRef cg_font, CGFlo
   if (unlikely (!ct_font)) {
     DEBUG_MSG (CORETEXT, cg_font, "Font CTFontCreateWithGraphicsFont() failed");
     return NULL;
   }
 
   /* crbug.com/576941 and crbug.com/625902 and the investigation in the latter
    * bug indicate that the cascade list reconfiguration occasionally causes
    * crashes in CoreText on OS X 10.9, thus let's skip this step on older
-   * operating system versions. */
-  if (&CTGetCoreTextVersion != NULL && CTGetCoreTextVersion() <= kCTVersionNumber10_9)
-    return ct_font;
+   * operating system versions. Except for the emoji font, where _not_
+   * reconfiguring the cascade list causes CoreText crashes. For details, see
+   * crbug.com/549610 */
+  // 0x00070000 stands for "kCTVersionNumber10_10", see CoreText.h
+  if (&CTGetCoreTextVersion != NULL && CTGetCoreTextVersion() < 0x00070000) {
+    CFStringRef fontName = CTFontCopyPostScriptName (ct_font);
+    bool isEmojiFont = CFStringCompare (fontName, CFSTR("AppleColorEmoji"), 0) == kCFCompareEqualTo;
+    CFRelease (fontName);
+    if (!isEmojiFont)
+      return ct_font;
+  }
 
   CFURLRef original_url = (CFURLRef)CTFontCopyAttribute(ct_font, kCTFontURLAttribute);
 
   /* Create font copy with cascade list that has LastResort first; this speeds up CoreText
    * font fallback which we don't need anyway. */
   {
     CTFontDescriptorRef last_resort_font_desc = get_last_resort_font_desc ();
     CTFontRef new_ct_font = CTFontCreateCopyWithAttributes (ct_font, 0.0, NULL, last_resort_font_desc);
--- a/gfx/harfbuzz/src/hb-directwrite.cc
+++ b/gfx/harfbuzz/src/hb-directwrite.cc
@@ -24,20 +24,16 @@
 
 #define HB_SHAPER directwrite
 #include "hb-shaper-impl-private.hh"
 
 #include <DWrite_1.h>
 
 #include "hb-directwrite.h"
 
-#include "hb-open-file-private.hh"
-#include "hb-ot-name-table.hh"
-#include "hb-ot-tag.h"
-
 
 #ifndef HB_DEBUG_DIRECTWRITE
 #define HB_DEBUG_DIRECTWRITE (HB_DEBUG+0)
 #endif
 
 HB_SHAPER_DATA_ENSURE_DECLARE(directwrite, face)
 HB_SHAPER_DATA_ENSURE_DECLARE(directwrite, font)
 
--- a/gfx/harfbuzz/src/hb-font-private.hh
+++ b/gfx/harfbuzz/src/hb-font-private.hh
@@ -111,18 +111,22 @@ struct hb_font_t {
   hb_font_funcs_t   *klass;
   void              *user_data;
   hb_destroy_func_t  destroy;
 
   struct hb_shaper_data_t shaper_data;
 
 
   /* Convert from font-space to user-space */
-  inline hb_position_t em_scale_x (int16_t v) { return em_scale (v, this->x_scale); }
-  inline hb_position_t em_scale_y (int16_t v) { return em_scale (v, this->y_scale); }
+  inline int dir_scale (hb_direction_t direction)
+  { return HB_DIRECTION_IS_VERTICAL(direction) ? y_scale : x_scale; }
+  inline hb_position_t em_scale_x (int16_t v) { return em_scale (v, x_scale); }
+  inline hb_position_t em_scale_y (int16_t v) { return em_scale (v, y_scale); }
+  inline hb_position_t em_scale_dir (int16_t v, hb_direction_t direction)
+  { return em_scale (v, dir_scale (direction)); }
 
   /* Convert from parent-font user-space to our user-space */
   inline hb_position_t parent_scale_x_distance (hb_position_t v) {
     if (unlikely (parent && parent->x_scale != x_scale))
       return (hb_position_t) (v * (int64_t) this->x_scale / this->parent->x_scale);
     return v;
   }
   inline hb_position_t parent_scale_y_distance (hb_position_t v) {
@@ -499,17 +503,16 @@ struct hb_font_t {
 	  hb_codepoint_parse (s + 3, len - 3, 16, &unichar) &&
 	  get_nominal_glyph (unichar, glyph))
 	return true;
     }
 
     return false;
   }
 
-  private:
   inline hb_position_t em_scale (int16_t v, int scale)
   {
     int upem = face->get_upem ();
     int64_t scaled = v * (int64_t) scale;
     scaled += scaled >= 0 ? upem/2 : -upem/2; /* Round. */
     return (hb_position_t) (scaled / upem);
   }
 };
--- a/gfx/harfbuzz/src/hb-glib.cc
+++ b/gfx/harfbuzz/src/hb-glib.cc
@@ -377,26 +377,24 @@ hb_glib_get_unicode_funcs (void)
       HB_UNICODE_FUNCS_IMPLEMENT_CALLBACKS
 #undef HB_UNICODE_FUNC_IMPLEMENT
     }
   };
 
   return const_cast<hb_unicode_funcs_t *> (&_hb_glib_unicode_funcs);
 }
 
-#if GLIB_CHECK_VERSION(2,31,10)
 /**
  * hb_glib_blob_create:
  *
  * Since: 0.9.38
  **/
 hb_blob_t *
 hb_glib_blob_create (GBytes *gbytes)
 {
   gsize size = 0;
   gconstpointer data = g_bytes_get_data (gbytes, &size);
   return hb_blob_create ((const char *) data,
 			 size,
 			 HB_MEMORY_MODE_READONLY,
 			 g_bytes_ref (gbytes),
 			 (hb_destroy_func_t) g_bytes_unref);
 }
-#endif
--- a/gfx/harfbuzz/src/hb-glib.h
+++ b/gfx/harfbuzz/src/hb-glib.h
@@ -41,16 +41,15 @@ hb_glib_script_to_script (GUnicodeScript
 
 HB_EXTERN GUnicodeScript
 hb_glib_script_from_script (hb_script_t script);
 
 
 HB_EXTERN hb_unicode_funcs_t *
 hb_glib_get_unicode_funcs (void);
 
-#if GLIB_CHECK_VERSION(2,31,10)
 HB_EXTERN hb_blob_t *
 hb_glib_blob_create (GBytes *gbytes);
-#endif
+
 
 HB_END_DECLS
 
 #endif /* HB_GLIB_H */
--- a/gfx/harfbuzz/src/hb-gobject-structs.cc
+++ b/gfx/harfbuzz/src/hb-gobject-structs.cc
@@ -73,8 +73,11 @@ HB_DEFINE_OBJECT_TYPE (font_funcs)
 HB_DEFINE_OBJECT_TYPE (set)
 HB_DEFINE_OBJECT_TYPE (shape_plan)
 HB_DEFINE_OBJECT_TYPE (unicode_funcs)
 HB_DEFINE_VALUE_TYPE (feature)
 HB_DEFINE_VALUE_TYPE (glyph_info)
 HB_DEFINE_VALUE_TYPE (glyph_position)
 HB_DEFINE_VALUE_TYPE (segment_properties)
 HB_DEFINE_VALUE_TYPE (user_data_key)
+
+HB_DEFINE_VALUE_TYPE (ot_math_glyph_variant)
+HB_DEFINE_VALUE_TYPE (ot_math_glyph_part)
new file mode 100644
--- /dev/null
+++ b/gfx/harfbuzz/src/hb-ot-layout-math-table.hh
@@ -0,0 +1,722 @@
+/*
+ * Copyright © 2016  Igalia S.L.
+ *
+ *  This is part of HarfBuzz, a text shaping library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ * Igalia Author(s): Frédéric Wang
+ */
+
+#ifndef HB_OT_LAYOUT_MATH_TABLE_HH
+#define HB_OT_LAYOUT_MATH_TABLE_HH
+
+#include "hb-open-type-private.hh"
+#include "hb-ot-layout-common-private.hh"
+#include "hb-ot-math.h"
+
+namespace OT {
+
+
+struct MathValueRecord
+{
+  inline hb_position_t get_x_value (hb_font_t *font, const void *base) const
+  { return font->em_scale_x (value) + (base+deviceTable).get_x_delta (font); }
+  inline hb_position_t get_y_value (hb_font_t *font, const void *base) const
+  { return font->em_scale_y (value) + (base+deviceTable).get_y_delta (font); }
+
+  inline bool sanitize (hb_sanitize_context_t *c, const void *base) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this) && deviceTable.sanitize (c, base));
+  }
+
+  protected:
+  SHORT			value;		/* The X or Y value in design units */
+  OffsetTo<Device>	deviceTable;	/* Offset to the device table - from the
+					 * beginning of parent table. May be NULL.
+					 * Suggested format for device table is 1. */
+
+  public:
+  DEFINE_SIZE_STATIC (4);
+};
+
+struct MathConstants
+{
+  inline bool sanitize_math_value_records (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+
+    unsigned int count = ARRAY_LENGTH (mathValueRecords);
+    for (unsigned int i = 0; i < count; i++)
+      if (!mathValueRecords[i].sanitize (c, this))
+	return_trace (false);
+
+    return_trace (true);
+  }
+
+  inline bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this) && sanitize_math_value_records(c));
+  }
+
+  inline hb_position_t get_value (hb_ot_math_constant_t constant,
+				  hb_font_t *font) const
+  {
+    switch (constant) {
+
+    case HB_OT_MATH_CONSTANT_SCRIPT_PERCENT_SCALE_DOWN:
+    case HB_OT_MATH_CONSTANT_SCRIPT_SCRIPT_PERCENT_SCALE_DOWN:
+      return percentScaleDown[constant - HB_OT_MATH_CONSTANT_SCRIPT_PERCENT_SCALE_DOWN];
+
+    case HB_OT_MATH_CONSTANT_DELIMITED_SUB_FORMULA_MIN_HEIGHT:
+    case HB_OT_MATH_CONSTANT_DISPLAY_OPERATOR_MIN_HEIGHT:
+      return font->em_scale_y (minHeight[constant - HB_OT_MATH_CONSTANT_DELIMITED_SUB_FORMULA_MIN_HEIGHT]);
+
+    case HB_OT_MATH_CONSTANT_RADICAL_KERN_AFTER_DEGREE:
+    case HB_OT_MATH_CONSTANT_RADICAL_KERN_BEFORE_DEGREE:
+    case HB_OT_MATH_CONSTANT_SKEWED_FRACTION_HORIZONTAL_GAP:
+    case HB_OT_MATH_CONSTANT_SPACE_AFTER_SCRIPT:
+      return mathValueRecords[constant - HB_OT_MATH_CONSTANT_MATH_LEADING].get_x_value(font, this);
+
+    case HB_OT_MATH_CONSTANT_ACCENT_BASE_HEIGHT:
+    case HB_OT_MATH_CONSTANT_AXIS_HEIGHT:
+    case HB_OT_MATH_CONSTANT_FLATTENED_ACCENT_BASE_HEIGHT:
+    case HB_OT_MATH_CONSTANT_FRACTION_DENOMINATOR_DISPLAY_STYLE_SHIFT_DOWN:
+    case HB_OT_MATH_CONSTANT_FRACTION_DENOMINATOR_GAP_MIN:
+    case HB_OT_MATH_CONSTANT_FRACTION_DENOMINATOR_SHIFT_DOWN:
+    case HB_OT_MATH_CONSTANT_FRACTION_DENOM_DISPLAY_STYLE_GAP_MIN:
+    case HB_OT_MATH_CONSTANT_FRACTION_NUMERATOR_DISPLAY_STYLE_SHIFT_UP:
+    case HB_OT_MATH_CONSTANT_FRACTION_NUMERATOR_GAP_MIN:
+    case HB_OT_MATH_CONSTANT_FRACTION_NUMERATOR_SHIFT_UP:
+    case HB_OT_MATH_CONSTANT_FRACTION_NUM_DISPLAY_STYLE_GAP_MIN:
+    case HB_OT_MATH_CONSTANT_FRACTION_RULE_THICKNESS:
+    case HB_OT_MATH_CONSTANT_LOWER_LIMIT_BASELINE_DROP_MIN:
+    case HB_OT_MATH_CONSTANT_LOWER_LIMIT_GAP_MIN:
+    case HB_OT_MATH_CONSTANT_MATH_LEADING:
+    case HB_OT_MATH_CONSTANT_OVERBAR_EXTRA_ASCENDER:
+    case HB_OT_MATH_CONSTANT_OVERBAR_RULE_THICKNESS:
+    case HB_OT_MATH_CONSTANT_OVERBAR_VERTICAL_GAP:
+    case HB_OT_MATH_CONSTANT_RADICAL_DISPLAY_STYLE_VERTICAL_GAP:
+    case HB_OT_MATH_CONSTANT_RADICAL_EXTRA_ASCENDER:
+    case HB_OT_MATH_CONSTANT_RADICAL_RULE_THICKNESS:
+    case HB_OT_MATH_CONSTANT_RADICAL_VERTICAL_GAP:
+    case HB_OT_MATH_CONSTANT_SKEWED_FRACTION_VERTICAL_GAP:
+    case HB_OT_MATH_CONSTANT_STACK_BOTTOM_DISPLAY_STYLE_SHIFT_DOWN:
+    case HB_OT_MATH_CONSTANT_STACK_BOTTOM_SHIFT_DOWN:
+    case HB_OT_MATH_CONSTANT_STACK_DISPLAY_STYLE_GAP_MIN:
+    case HB_OT_MATH_CONSTANT_STACK_GAP_MIN:
+    case HB_OT_MATH_CONSTANT_STACK_TOP_DISPLAY_STYLE_SHIFT_UP:
+    case HB_OT_MATH_CONSTANT_STACK_TOP_SHIFT_UP:
+    case HB_OT_MATH_CONSTANT_STRETCH_STACK_BOTTOM_SHIFT_DOWN:
+    case HB_OT_MATH_CONSTANT_STRETCH_STACK_GAP_ABOVE_MIN:
+    case HB_OT_MATH_CONSTANT_STRETCH_STACK_GAP_BELOW_MIN:
+    case HB_OT_MATH_CONSTANT_STRETCH_STACK_TOP_SHIFT_UP:
+    case HB_OT_MATH_CONSTANT_SUBSCRIPT_BASELINE_DROP_MIN:
+    case HB_OT_MATH_CONSTANT_SUBSCRIPT_SHIFT_DOWN:
+    case HB_OT_MATH_CONSTANT_SUBSCRIPT_TOP_MAX:
+    case HB_OT_MATH_CONSTANT_SUB_SUPERSCRIPT_GAP_MIN:
+    case HB_OT_MATH_CONSTANT_SUPERSCRIPT_BASELINE_DROP_MAX:
+    case HB_OT_MATH_CONSTANT_SUPERSCRIPT_BOTTOM_MAX_WITH_SUBSCRIPT:
+    case HB_OT_MATH_CONSTANT_SUPERSCRIPT_BOTTOM_MIN:
+    case HB_OT_MATH_CONSTANT_SUPERSCRIPT_SHIFT_UP:
+    case HB_OT_MATH_CONSTANT_SUPERSCRIPT_SHIFT_UP_CRAMPED:
+    case HB_OT_MATH_CONSTANT_UNDERBAR_EXTRA_DESCENDER:
+    case HB_OT_MATH_CONSTANT_UNDERBAR_RULE_THICKNESS:
+    case HB_OT_MATH_CONSTANT_UNDERBAR_VERTICAL_GAP:
+    case HB_OT_MATH_CONSTANT_UPPER_LIMIT_BASELINE_RISE_MIN:
+    case HB_OT_MATH_CONSTANT_UPPER_LIMIT_GAP_MIN:
+      return mathValueRecords[constant - HB_OT_MATH_CONSTANT_MATH_LEADING].get_y_value(font, this);
+
+    case HB_OT_MATH_CONSTANT_RADICAL_DEGREE_BOTTOM_RAISE_PERCENT:
+      return radicalDegreeBottomRaisePercent;
+
+    default:
+      return 0;
+    }
+  }
+
+  protected:
+  SHORT percentScaleDown[2];
+  USHORT minHeight[2];
+  MathValueRecord mathValueRecords[51];
+  SHORT radicalDegreeBottomRaisePercent;
+
+  public:
+  DEFINE_SIZE_STATIC (214);
+};
+
+struct MathItalicsCorrectionInfo
+{
+  inline bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this) &&
+		  coverage.sanitize (c, this) &&
+		  italicsCorrection.sanitize (c, this));
+  }
+
+  inline hb_position_t get_value (hb_codepoint_t glyph,
+				  hb_font_t *font) const
+  {
+    unsigned int index = (this+coverage).get_coverage (glyph);
+    return italicsCorrection[index].get_x_value (font, this);
+  }
+
+  protected:
+  OffsetTo<Coverage>       coverage;		/* Offset to Coverage table -
+						 * from the beginning of
+						 * MathItalicsCorrectionInfo
+						 * table. */
+  ArrayOf<MathValueRecord> italicsCorrection;	/* Array of MathValueRecords
+						 * defining italics correction
+						 * values for each
+						 * covered glyph. */
+
+  public:
+  DEFINE_SIZE_ARRAY (4, italicsCorrection);
+};
+
+struct MathTopAccentAttachment
+{
+  inline bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this) &&
+		  topAccentCoverage.sanitize (c, this) &&
+		  topAccentAttachment.sanitize (c, this));
+  }
+
+  inline hb_position_t get_value (hb_codepoint_t glyph,
+				  hb_font_t *font) const
+  {
+    unsigned int index = (this+topAccentCoverage).get_coverage (glyph);
+    if (index == NOT_COVERED)
+      return font->get_glyph_h_advance (glyph) / 2;
+    return topAccentAttachment[index].get_x_value(font, this);
+  }
+
+  protected:
+  OffsetTo<Coverage>       topAccentCoverage;   /* Offset to Coverage table -
+						 * from the beginning of
+						 * MathTopAccentAttachment
+						 * table. */
+  ArrayOf<MathValueRecord> topAccentAttachment; /* Array of MathValueRecords
+						 * defining top accent
+						 * attachment points for each
+						 * covered glyph. */
+
+  public:
+  DEFINE_SIZE_ARRAY (2 + 2, topAccentAttachment);
+};
+
+struct MathKern
+{
+  inline bool sanitize_math_value_records (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    unsigned int count = 2 * heightCount + 1;
+    for (unsigned int i = 0; i < count; i++)
+      if (!mathValueRecords[i].sanitize (c, this)) return_trace (false);
+    return_trace (true);
+  }
+
+  inline bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this) &&
+		  c->check_array (mathValueRecords,
+				  mathValueRecords[0].static_size,
+				  2 * heightCount + 1) &&
+		  sanitize_math_value_records (c));
+  }
+
+  inline hb_position_t get_value (hb_position_t correction_height, hb_font_t *font) const
+  {
+    const MathValueRecord* correctionHeight = mathValueRecords;
+    const MathValueRecord* kernValue = mathValueRecords + heightCount;
+    int sign = font->y_scale < 0 ? -1 : +1;
+
+    /* The description of the MathKern table is a ambiguous, but interpreting
+     * "between the two heights found at those indexes" for 0 < i < len as
+     *
+     *   correctionHeight[i-1] < correction_height <= correctionHeight[i]
+     *
+     * makes the result consistent with the limit cases and we can just use the
+     * binary search algorithm of std::upper_bound:
+     */
+    unsigned int i = 0;
+    unsigned int count = heightCount;
+    while (count > 0)
+    {
+      unsigned int half = count / 2;
+      hb_position_t height = correctionHeight[i + half].get_y_value(font, this);
+      if (sign * height < sign * correction_height)
+      {
+	i += half + 1;
+	count -= half + 1;
+      } else
+	count = half;
+    }
+    return kernValue[i].get_x_value(font, this);
+  }
+
+  protected:
+  USHORT	  heightCount;
+  MathValueRecord mathValueRecords[VAR]; /* Array of correction heights at
+					  * which the kern value changes.
+					  * Sorted by the height value in
+					  * design units (heightCount entries),
+					  * Followed by:
+					  * Array of kern values corresponding
+					  * to heights. (heightCount+1 entries).
+					  */
+
+  public:
+  DEFINE_SIZE_ARRAY (2, mathValueRecords);
+};
+
+struct MathKernInfoRecord
+{
+  inline bool sanitize (hb_sanitize_context_t *c, const void *base) const
+  {
+    TRACE_SANITIZE (this);
+
+    unsigned int count = ARRAY_LENGTH (mathKern);
+    for (unsigned int i = 0; i < count; i++)
+      if (unlikely (!mathKern[i].sanitize (c, base)))
+	return_trace (false);
+
+    return_trace (true);
+  }
+
+  inline hb_position_t get_kerning (hb_ot_math_kern_t kern,
+				    hb_position_t correction_height,
+				    hb_font_t *font,
+				    const void *base) const
+  {
+    unsigned int idx = kern;
+    if (unlikely (idx >= ARRAY_LENGTH (mathKern))) return 0;
+    return (base+mathKern[idx]).get_value (correction_height, font);
+  }
+
+  protected:
+  /* Offset to MathKern table for each corner -
+   * from the beginning of MathKernInfo table. May be NULL. */
+  OffsetTo<MathKern> mathKern[4];
+
+  public:
+  DEFINE_SIZE_STATIC (8);
+};
+
+struct MathKernInfo
+{
+  inline bool sanitize (hb_sanitize_context_t *c) con