Merge mozilla-central to autoland
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 12 Jul 2016 16:45:24 +0200
changeset 329728 b4484a7069fbc7b6131fa370d96301b00a57b9e8
parent 329727 f95ff5bf4df8130b6deccfb5cf724a85c99bc380 (current diff)
parent 329720 94c926911767cbaf285badaccc65b0365ae5bae0 (diff)
child 329747 53947432322b06f80b079fef73e43d7e6785f81b
child 329772 de9a00cbb60c47756fe66d9a70dd4d4d07f51967
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone50.0a1
Merge mozilla-central to autoland
browser/components/contextualidentity/ContextualIdentityService.jsm
testing/web-platform/meta/XMLHttpRequest/getresponseheader-cookies-and-more.htm.ini
testing/web-platform/meta/cors/response-headers.htm.ini
testing/web-platform/meta/custom-elements/v0/creating-and-passing-registries/share-registry-create-document.html.ini
testing/web-platform/meta/dom/nodes/Document-contentType/contentType/createDocument.html.ini
testing/web-platform/meta/dom/nodes/Document-createElement-namespace.html.ini
testing/web-platform/meta/html/semantics/embedded-content/media-elements/interfaces/TextTrack/cues.html.ini
testing/web-platform/meta/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/getter.html.ini
testing/web-platform/meta/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/length.html.ini
testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cloneNode.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-template-element/additions-to-serializing-xhtml-documents/outerhtml.html.ini
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -52,17 +52,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
 XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
                                    "@mozilla.org/network/dns-service;1",
                                    "nsIDNSService");
 XPCOMUtils.defineLazyServiceGetter(this, "WindowsUIUtils",
                                    "@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
 XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
                                   "resource://gre/modules/LightweightThemeManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
-                                  "resource:///modules/ContextualIdentityService.jsm");
+                                  "resource://gre/modules/ContextualIdentityService.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "gAboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
   return Services.strings.createBundle('chrome://browser/locale/browser.properties');
 });
 XPCOMUtils.defineLazyModuleGetter(this, "AddonWatcher",
                                   "resource://gre/modules/AddonWatcher.jsm");
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -7,17 +7,17 @@
 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm");
 Components.utils.import("resource://gre/modules/LoginManagerContextMenu.jsm");
 Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
-                                  "resource:///modules/ContextualIdentityService.jsm");
+                                  "resource://gre/modules/ContextualIdentityService.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
   "resource://gre/modules/LoginHelper.jsm");
 
 var gContextMenuContentData = null;
 
 function nsContextMenu(aXulMenu, aIsShift) {
   this.shouldDisplay = true;
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -9,17 +9,17 @@ Components.utils.import("resource://gre/
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 Components.utils.import("resource:///modules/RecentWindow.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ShellService",
                                   "resource:///modules/ShellService.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
-                                  "resource:///modules/ContextualIdentityService.jsm");
+                                  "resource://gre/modules/ContextualIdentityService.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 
 this.__defineGetter__("BROWSER_NEW_TAB_URL", () => {
   if (PrivateBrowsingUtils.isWindowPrivate(window) &&
       !PrivateBrowsingUtils.permanentPrivateBrowsing &&
--- a/browser/components/contextualidentity/moz.build
+++ b/browser/components/contextualidentity/moz.build
@@ -3,16 +3,12 @@
 # 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/.
 
 BROWSER_CHROME_MANIFESTS += [
     'test/browser/browser.ini',
 ]
 
-EXTRA_JS_MODULES += [
-    'ContextualIdentityService.jsm',
-]
-
 JAR_MANIFESTS += ['jar.mn']
 
 with Files('**'):
     BUG_COMPONENT = ('Firefox', 'Contextual Identity')
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -23,17 +23,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/ShortcutUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
   "resource://gre/modules/CharsetMenu.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SyncedTabs",
   "resource://services-sync/SyncedTabs.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
-  "resource:///modules/ContextualIdentityService.jsm");
+  "resource://gre/modules/ContextualIdentityService.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "CharsetBundle", function() {
   const kCharsetBundle = "chrome://global/locale/charsetMenu.properties";
   return Services.strings.createBundle(kCharsetBundle);
 });
 XPCOMUtils.defineLazyGetter(this, "BrandBundle", function() {
   const kBrandBundle = "chrome://branding/locale/brand.properties";
   return Services.strings.createBundle(kBrandBundle);
--- a/browser/components/preferences/cookies.js
+++ b/browser/components/preferences/cookies.js
@@ -5,17 +5,17 @@
 
 const nsICookie = Components.interfaces.nsICookie;
 
 Components.utils.import("resource://gre/modules/PluralForm.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm")
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
-                                  "resource:///modules/ContextualIdentityService.jsm");
+                                  "resource://gre/modules/ContextualIdentityService.jsm");
 
 var gCookiesWindow = {
   _cm               : Components.classes["@mozilla.org/cookiemanager;1"]
                                 .getService(Components.interfaces.nsICookieManager),
   _ds               : Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
                                 .getService(Components.interfaces.nsIScriptableDateFormat),
   _hosts            : {},
   _hostOrder        : [],
--- a/devtools/client/inspector/rules/test/head.js
+++ b/devtools/client/inspector/rules/test/head.js
@@ -212,34 +212,16 @@ var waitForSuccess = Task.async(function
       ok(false, "Failure: " + desc);
       break;
     }
     yield new Promise(r => setTimeout(r, 200));
   }
 });
 
 /**
- * Get the dataURL for the font family tooltip.
- *
- * @param {String} font
- *        The font family value.
- * @param {object} nodeFront
- *        The NodeActor that will used to retrieve the dataURL for the
- *        font family tooltip contents.
- */
-var getFontFamilyDataURL = Task.async(function* (font, nodeFront) {
-  let fillStyle = (Services.prefs.getCharPref("devtools.theme") === "light") ?
-      "black" : "white";
-
-  let {data} = yield nodeFront.getFontFamilyDataURL(font, fillStyle);
-  let dataURL = yield data.string();
-  return dataURL;
-});
-
-/**
  * Get the DOMNode for a css rule in the rule-view that corresponds to the given
  * selector
  *
  * @param {CssRuleView} view
  *        The instance of the rule-view panel
  * @param {String} selectorText
  *        The selector in the rule-view for which the rule
  *        object is wanted
--- a/devtools/client/inspector/shared/style-inspector-overlays.js
+++ b/devtools/client/inspector/shared/style-inspector-overlays.js
@@ -7,17 +7,17 @@
 "use strict";
 
 // The style-inspector overlays are:
 // - tooltips that appear when hovering over property values
 // - editor tooltips that appear when clicking color swatches, etc.
 // - in-content highlighters that appear when hovering over property values
 // - etc.
 
-const {getTheme} = require("devtools/client/shared/theme");
+const {getColor} = require("devtools/client/shared/theme");
 const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
 const {
   getImageDimensions,
   setImageTooltip,
   setBrokenImageTooltip,
 } = require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
 const {
   SwatchColorPickerTooltip,
@@ -467,17 +467,17 @@ TooltipsOverlay.prototype = {
     if (!font || !nodeFront || typeof nodeFront.getFontFamilyDataURL !== "function") {
       throw new Error("Unable to create font preview tooltip content.");
     }
 
     font = font.replace(/"/g, "'");
     font = font.replace("!important", "");
     font = font.trim();
 
-    let fillStyle = getTheme() === "light" ? "black" : "white";
+    let fillStyle = getColor("body-color");
     let {data, size: maxDim} = yield nodeFront.getFontFamilyDataURL(font, fillStyle);
 
     let imageUrl = yield data.string();
     let doc = this.view.inspector.panelDoc;
     let {naturalWidth, naturalHeight} = yield getImageDimensions(doc, imageUrl);
 
     yield setImageTooltip(this.previewTooltip, doc, imageUrl,
       {hideDimensionLabel: true, maxDim, naturalWidth, naturalHeight});
--- a/devtools/client/inspector/shared/test/head.js
+++ b/devtools/client/inspector/shared/test/head.js
@@ -1,24 +1,24 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint no-unused-vars: [2, {"vars": "local"}] */
 /* import-globals-from ../../test/head.js */
-
 "use strict";
 
 // Import the inspector's head.js first (which itself imports shared-head.js).
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
   this);
 
 var {CssRuleView} = require("devtools/client/inspector/rules/rules");
 var {getInplaceEditorForSpan: inplaceEditor} =
   require("devtools/client/shared/inplace-editor");
+const {getColor: getThemeColor} = require("devtools/client/shared/theme");
 
 const TEST_URL_ROOT =
   "http://example.com/browser/devtools/client/inspector/shared/test/";
 const TEST_URL_ROOT_SSL =
   "https://example.com/browser/devtools/client/inspector/shared/test/";
 const ROOT_TEST_DIR = getRootDirectory(gTestPath);
 const FRAME_SCRIPT_URL = ROOT_TEST_DIR + "doc_frame_script.js";
 const _STRINGS = Services.strings.createBundle(
@@ -235,18 +235,17 @@ function waitForSuccess(validatorFn, nam
  *
  * @param {String} font
  *        The font family value.
  * @param {object} nodeFront
  *        The NodeActor that will used to retrieve the dataURL for the
  *        font family tooltip contents.
  */
 var getFontFamilyDataURL = Task.async(function* (font, nodeFront) {
-  let fillStyle = (Services.prefs.getCharPref("devtools.theme") === "light") ?
-      "black" : "white";
+  let fillStyle = getThemeColor("body-color");
 
   let {data} = yield nodeFront.getFontFamilyDataURL(font, fillStyle);
   let dataURL = yield data.string();
   return dataURL;
 });
 
 /* *********************************************
  * RULE-VIEW
--- a/dom/base/DOMImplementation.cpp
+++ b/dom/base/DOMImplementation.cpp
@@ -134,18 +134,24 @@ DOMImplementation::CreateDocument(const 
                          true, scriptHandlingObject,
                          DocumentFlavorLegacyGuess);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // When DOMImplementation's createDocument method is invoked with
   // namespace set to HTML Namespace use the registry of the associated
   // document to the new instance.
   nsCOMPtr<nsIDocument> doc = do_QueryInterface(document);
+
   if (aNamespaceURI.EqualsLiteral("http://www.w3.org/1999/xhtml")) {
+    doc->SetContentType(NS_LITERAL_STRING("application/xhtml+xml"));
     doc->UseRegistryFromDocument(mOwner);
+  } else if (aNamespaceURI.EqualsLiteral("http://www.w3.org/2000/svg")) {
+    doc->SetContentType(NS_LITERAL_STRING("image/svg+xml"));
+  } else {
+    doc->SetContentType(NS_LITERAL_STRING("application/xml"));
   }
 
   doc->SetReadyStateInternal(nsIDocument::READYSTATE_COMPLETE);
 
   doc.forget(aDocument);
   document.forget(aDOMDocument);
   return NS_OK;
 }
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -1521,17 +1521,17 @@ Element::BindToTree(nsIDocument* aDocume
     //    aDocument->BindingManager()->ChangeDocumentFor(this, nullptr,
     //                                                   aDocument);
 
     // We no longer need to track the subtree pointer (and in fact we'll assert
     // if we do this any later).
     ClearSubtreeRootPointer();
 
     // Being added to a document.
-    SetInDocument();
+    SetIsInDocument();
 
     // Unset this flag since we now really are in a document.
     UnsetFlags(NODE_FORCE_XBL_BINDINGS |
                // And clear the lazy frame construction bits.
                NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES |
                // And the restyle bits
                ELEMENT_ALL_RESTYLE_FLAGS);
   } else if (IsInShadowTree()) {
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -70,37 +70,37 @@ NS_GetContentList(nsINode* aRootNode,
                   int32_t  aMatchNameSpaceId,
                   const nsAString& aTagname);
 
 #define ELEMENT_FLAG_BIT(n_) NODE_FLAG_BIT(NODE_TYPE_SPECIFIC_BITS_OFFSET + (n_))
 
 // Element-specific flags
 enum {
   // Set if the element has a pending style change.
-  ELEMENT_HAS_PENDING_RESTYLE =                 ELEMENT_FLAG_BIT(0),
+  ELEMENT_HAS_PENDING_RESTYLE =                 NODE_SHARED_RESTYLE_BIT_1,
 
   // Set if the element is a potential restyle root (that is, has a style
   // change pending _and_ that style change will attempt to restyle
   // descendants).
-  ELEMENT_IS_POTENTIAL_RESTYLE_ROOT =           ELEMENT_FLAG_BIT(1),
+  ELEMENT_IS_POTENTIAL_RESTYLE_ROOT =           NODE_SHARED_RESTYLE_BIT_2,
 
   // Set if the element has a pending animation-only style change as
   // part of an animation-only style update (where we update styles from
   // animation to the current refresh tick, but leave everything else as
   // it was).
-  ELEMENT_HAS_PENDING_ANIMATION_ONLY_RESTYLE =  ELEMENT_FLAG_BIT(2),
+  ELEMENT_HAS_PENDING_ANIMATION_ONLY_RESTYLE =  ELEMENT_FLAG_BIT(0),
 
   // Set if the element is a potential animation-only restyle root (that
   // is, has an animation-only style change pending _and_ that style
   // change will attempt to restyle descendants).
-  ELEMENT_IS_POTENTIAL_ANIMATION_ONLY_RESTYLE_ROOT = ELEMENT_FLAG_BIT(3),
+  ELEMENT_IS_POTENTIAL_ANIMATION_ONLY_RESTYLE_ROOT = ELEMENT_FLAG_BIT(1),
 
   // Set if this element has a pending restyle with an eRestyle_SomeDescendants
   // restyle hint.
-  ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR = ELEMENT_FLAG_BIT(4),
+  ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR = ELEMENT_FLAG_BIT(2),
 
   // Just the HAS_PENDING bits, for convenience
   ELEMENT_PENDING_RESTYLE_FLAGS =
     ELEMENT_HAS_PENDING_RESTYLE |
     ELEMENT_HAS_PENDING_ANIMATION_ONLY_RESTYLE,
 
   // Just the IS_POTENTIAL bits, for convenience
   ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS =
@@ -108,20 +108,20 @@ enum {
     ELEMENT_IS_POTENTIAL_ANIMATION_ONLY_RESTYLE_ROOT,
 
   // All of the restyle bits together, for convenience.
   ELEMENT_ALL_RESTYLE_FLAGS = ELEMENT_PENDING_RESTYLE_FLAGS |
                               ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS |
                               ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR,
 
   // Set if this element is marked as 'scrollgrab' (see bug 912666)
-  ELEMENT_HAS_SCROLLGRAB = ELEMENT_FLAG_BIT(5),
+  ELEMENT_HAS_SCROLLGRAB = ELEMENT_FLAG_BIT(3),
 
   // Remaining bits are for subclasses
-  ELEMENT_TYPE_SPECIFIC_BITS_OFFSET = NODE_TYPE_SPECIFIC_BITS_OFFSET + 6
+  ELEMENT_TYPE_SPECIFIC_BITS_OFFSET = NODE_TYPE_SPECIFIC_BITS_OFFSET + 4
 };
 
 #undef ELEMENT_FLAG_BIT
 
 // Make sure we have space for our bits
 ASSERT_NODE_FLAGS_SPACE(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET);
 
 namespace mozilla {
@@ -1445,16 +1445,23 @@ inline mozilla::dom::Element* nsINode::A
 }
 
 inline const mozilla::dom::Element* nsINode::AsElement() const
 {
   MOZ_ASSERT(IsElement());
   return static_cast<const mozilla::dom::Element*>(this);
 }
 
+inline void nsINode::UnsetRestyleFlagsIfGecko()
+{
+  if (IsElement() && !IsStyledByServo()) {
+    UnsetFlags(ELEMENT_ALL_RESTYLE_FLAGS);
+  }
+}
+
 /**
  * Macros to implement Clone(). _elementName is the class for which to implement
  * Clone.
  */
 #define NS_IMPL_ELEMENT_CLONE(_elementName)                                 \
 nsresult                                                                    \
 _elementName::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const \
 {                                                                           \
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1449,20 +1449,19 @@ Navigator::SendBeacon(const nsAString& a
   nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
   nsCOMPtr<nsIInterfaceRequestor> callbacks =
     do_QueryInterface(mWindow->GetDocShell());
   loadGroup->SetNotificationCallbacks(callbacks);
   channel->SetLoadGroup(loadGroup);
 
   RefPtr<BeaconStreamListener> beaconListener = new BeaconStreamListener();
   rv = channel->AsyncOpen2(beaconListener);
-  if (NS_FAILED(rv)) {
-    aRv.Throw(rv);
-    return false;
-  }
+  // do not throw if security checks fail within asyncOpen2
+  NS_ENSURE_SUCCESS(rv, false);
+
   // make the beaconListener hold a strong reference to the loadgroup
   // which is released in ::OnStartRequest
   beaconListener->SetLoadGroup(loadGroup);
 
   return true;
 }
 
 MediaDevices*
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1455,17 +1455,20 @@ nsIDocument::nsIDocument()
     mFontFaceSetDirty(true),
     mGetUserFontSetCalled(false),
     mPostedFlushUserFontSet(false),
     mPartID(0),
     mDidFireDOMContentLoaded(true),
     mHasScrollLinkedEffect(false),
     mUserHasInteracted(false)
 {
-  SetInDocument();
+  SetIsDocument();
+  if (IsStyledByServo()) {
+    SetFlags(NODE_IS_DIRTY_FOR_SERVO | NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
+  }
 
   PR_INIT_CLIST(&mDOMMediaQueryLists);
 }
 
 // NOTE! nsDocument::operator new() zeroes out all members, so don't
 // bother initializing members to 0.
 
 nsDocument::nsDocument(const char* aContentType)
@@ -3168,20 +3171,16 @@ nsDocument::GetContentType(nsAString& aC
   CopyUTF8toUTF16(GetContentTypeInternal(), aContentType);
 
   return NS_OK;
 }
 
 void
 nsDocument::SetContentType(const nsAString& aContentType)
 {
-  NS_ASSERTION(GetContentTypeInternal().IsEmpty() ||
-               GetContentTypeInternal().Equals(NS_ConvertUTF16toUTF8(aContentType)),
-               "Do you really want to change the content-type?");
-
   SetContentTypeInternal(NS_ConvertUTF16toUTF8(aContentType));
 }
 
 nsresult
 nsDocument::GetAllowPlugins(bool * aAllowPlugins)
 {
   // First, we ask our docshell if it allows plugins.
   nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
@@ -13645,19 +13644,32 @@ nsIDocument::ReportHasScrollLinkedEffect
   }
   mHasScrollLinkedEffect = true;
   nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
                                   NS_LITERAL_CSTRING("Async Pan/Zoom"),
                                   this, nsContentUtils::eLAYOUT_PROPERTIES,
                                   "ScrollLinkedEffectFound2");
 }
 
-mozilla::StyleBackendType
-nsIDocument::GetStyleBackendType() const
-{
-  if (!mPresShell) {
+void
+nsIDocument::UpdateStyleBackendType()
+{
+  MOZ_ASSERT(mStyleBackendType == StyleBackendType(0),
+             "no need to call UpdateStyleBackendType now");
 #ifdef MOZ_STYLO
-    NS_WARNING("GetStyleBackendType() called on document without a pres shell");
+  // XXX For now we use a Servo-backed style set only for (X)HTML documents
+  // in content docshells.  This should let us avoid implementing XUL-specific
+  // CSS features.  And apart from not supporting SVG properties in Servo
+  // yet, the root SVG element likes to create a style sheet for an SVG
+  // document before we have a pres shell (i.e. before we make the decision
+  // here about whether to use a Gecko- or Servo-backed style system), so
+  // we avoid Servo-backed style sets for SVG documents.
+  NS_ASSERTION(mDocumentContainer, "stylo: calling UpdateStyleBackendType "
+                                   "before we have a docshell");
+  mStyleBackendType =
+    nsLayoutUtils::SupportsServoStyleBackend(this) &&
+    mDocumentContainer ?
+      StyleBackendType::Servo :
+      StyleBackendType::Gecko;
+#else
+  mStyleBackendType = StyleBackendType::Gecko;
 #endif
-    return StyleBackendType::Gecko;
-  }
-  return mPresShell->StyleSet()->BackendType();
-}
+}
--- a/dom/base/nsGenericDOMDataNode.cpp
+++ b/dom/base/nsGenericDOMDataNode.cpp
@@ -306,17 +306,17 @@ nsGenericDOMDataNode::SetTextInternal(ui
     nsContentUtils::HasMutationListeners(this,
       NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED,
       this);
 
   nsCOMPtr<nsIAtom> oldValue;
   if (haveMutationListeners) {
     oldValue = GetCurrentValueAtom();
   }
-    
+
   if (aNotify) {
     CharacterDataChangeInfo info = {
       aOffset == textLength,
       aOffset,
       endOffset,
       aLength,
       aDetails
     };
@@ -479,17 +479,17 @@ nsGenericDOMDataNode::BindToTree(nsIDocu
                   "Already have a parent.  Unbind first!");
   NS_PRECONDITION(!GetBindingParent() ||
                   aBindingParent == GetBindingParent() ||
                   (!aBindingParent && aParent &&
                    aParent->GetBindingParent() == GetBindingParent()),
                   "Already have a binding parent.  Unbind first!");
   NS_PRECONDITION(aBindingParent != this,
                   "Content must not be its own binding parent");
-  NS_PRECONDITION(!IsRootOfNativeAnonymousSubtree() || 
+  NS_PRECONDITION(!IsRootOfNativeAnonymousSubtree() ||
                   aBindingParent == aParent,
                   "Native anonymous content must have its parent as its "
                   "own binding parent");
 
   if (!aBindingParent && aParent) {
     aBindingParent = aParent->GetBindingParent();
   }
 
@@ -535,17 +535,17 @@ nsGenericDOMDataNode::BindToTree(nsIDocu
 
   // Set document
   if (aDocument) {
     // We no longer need to track the subtree pointer (and in fact we'll assert
     // if we do this any later).
     ClearSubtreeRootPointer();
 
     // XXX See the comment in Element::BindToTree
-    SetInDocument();
+    SetIsInDocument();
     if (mText.IsBidi()) {
       aDocument->SetBidiEnabled();
     }
     // Clear the lazy frame construction bits.
     UnsetFlags(NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES);
   } else if (!IsInShadowTree()) {
     // If we're not in the doc and not in a shadow tree,
     // update our subtree pointer.
@@ -800,17 +800,17 @@ nsGenericDOMDataNode::SaveSubtreeState()
 #ifdef DEBUG
 void
 nsGenericDOMDataNode::List(FILE* out, int32_t aIndent) const
 {
 }
 
 void
 nsGenericDOMDataNode::DumpContent(FILE* out, int32_t aIndent,
-                                  bool aDumpAll) const 
+                                  bool aDumpAll) const
 {
 }
 #endif
 
 bool
 nsGenericDOMDataNode::IsLink(nsIURI** aURI) const
 {
   *aURI = nullptr;
--- a/dom/base/nsHostObjectProtocolHandler.cpp
+++ b/dom/base/nsHostObjectProtocolHandler.cpp
@@ -346,30 +346,24 @@ nsHostObjectProtocolHandler::AddDataEntr
 
   gDataTable->Put(aUri, info);
   return NS_OK;
 }
 
 void
 nsHostObjectProtocolHandler::RemoveDataEntry(const nsACString& aUri)
 {
-  if (gDataTable) {
-    nsCString uriIgnoringRef;
-    int32_t hashPos = aUri.FindChar('#');
-    if (hashPos < 0) {
-      uriIgnoringRef = aUri;
-    }
-    else {
-      uriIgnoringRef = StringHead(aUri, hashPos);
-    }
-    gDataTable->Remove(uriIgnoringRef);
-    if (gDataTable->Count() == 0) {
-      delete gDataTable;
-      gDataTable = nullptr;
-    }
+  if (!gDataTable) {
+    return;
+  }
+
+  gDataTable->Remove(aUri);
+  if (gDataTable->Count() == 0) {
+    delete gDataTable;
+    gDataTable = nullptr;
   }
 }
 
 nsresult
 nsHostObjectProtocolHandler::GenerateURIString(const nsACString &aScheme,
                                                nsIPrincipal* aPrincipal,
                                                nsACString& aUri)
 {
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -1075,17 +1075,29 @@ public:
 
   /**
    * Get this document's CSSLoader.  This is guaranteed to not return null.
    */
   mozilla::css::Loader* CSSLoader() const {
     return mCSSLoader;
   }
 
-  mozilla::StyleBackendType GetStyleBackendType() const;
+  mozilla::StyleBackendType GetStyleBackendType() const {
+    if (mStyleBackendType == mozilla::StyleBackendType(0)) {
+      const_cast<nsIDocument*>(this)->UpdateStyleBackendType();
+    }
+    MOZ_ASSERT(mStyleBackendType != mozilla::StyleBackendType(0));
+    return mStyleBackendType;
+  }
+
+  void UpdateStyleBackendType();
+
+  bool IsStyledByServo() const {
+    return GetStyleBackendType() == mozilla::StyleBackendType::Servo;
+  }
 
   /**
    * Get this document's StyleImageLoader.  This is guaranteed to not return null.
    */
   mozilla::css::ImageLoader* StyleImageLoader() const {
     return mStyleImageLoader;
   }
 
@@ -2907,16 +2919,20 @@ protected:
   nsCompatibility mCompatMode;
 
   // Our readyState
   ReadyState mReadyState;
 
   // Our visibility state
   mozilla::dom::VisibilityState mVisibilityState;
 
+  // Whether this document has (or will have, once we have a pres shell) a
+  // Gecko- or Servo-backed style system.
+  mozilla::StyleBackendType mStyleBackendType;
+
   // True if BIDI is enabled.
   bool mBidiEnabled : 1;
   // True if a MathML element has ever been owned by this document.
   bool mMathMLEnabled : 1;
 
   // True if this document is the initial document for a window.  This should
   // basically be true only for documents that exist in newly-opened windows or
   // documents created to satisfy a GetDocument() on a window when there's no
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -3059,8 +3059,16 @@ nsINode::IsApzAware() const
   return IsNodeApzAware();
 }
 
 bool
 nsINode::IsNodeApzAwareInternal() const
 {
   return EventTarget::IsApzAware();
 }
+
+#ifdef MOZ_STYLO
+bool
+nsINode::IsStyledByServo() const
+{
+  return OwnerDoc()->IsStyledByServo();
+}
+#endif
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -176,18 +176,30 @@ enum {
 
   NODE_ALL_DIRECTION_FLAGS =              NODE_HAS_DIRECTION_LTR |
                                           NODE_HAS_DIRECTION_RTL,
 
   NODE_CHROME_ONLY_ACCESS =               NODE_FLAG_BIT(19),
 
   NODE_IS_ROOT_OF_CHROME_ONLY_ACCESS =    NODE_FLAG_BIT(20),
 
+  // These two bits are shared by Gecko's and Servo's restyle systems for
+  // different purposes. They should not be accessed directly, and access to
+  // them should be properly guarded by asserts.
+  NODE_SHARED_RESTYLE_BIT_1 =             NODE_FLAG_BIT(21),
+  NODE_SHARED_RESTYLE_BIT_2 =             NODE_FLAG_BIT(22),
+
+  // Whether this node is dirty for Servo's style system.
+  NODE_IS_DIRTY_FOR_SERVO =               NODE_SHARED_RESTYLE_BIT_1,
+
+  // Whether this node has dirty descendants for Servo's style system.
+  NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO =  NODE_SHARED_RESTYLE_BIT_2,
+
   // Remaining bits are node type specific.
-  NODE_TYPE_SPECIFIC_BITS_OFFSET =        21
+  NODE_TYPE_SPECIFIC_BITS_OFFSET =        23
 };
 
 // Make sure we have space for our bits
 #define ASSERT_NODE_FLAGS_SPACE(n) \
   static_assert(WRAPPER_CACHE_FLAGS_BITS_USED + (n) <=                          \
                   sizeof(nsWrapperCache::FlagsType) * 8,                        \
                 "Not enough space for our bits")
 ASSERT_NODE_FLAGS_SPACE(NODE_TYPE_SPECIFIC_BITS_OFFSET);
@@ -950,16 +962,55 @@ public:
   using nsIDOMEventTarget::AddSystemEventListener;
 
   virtual bool IsApzAware() const override;
 
   virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindings() override;
   virtual nsIGlobalObject* GetOwnerGlobal() const override;
 
   /**
+   * Returns true if this is a node belonging to a document that uses the Servo
+   * style system.
+   */
+#ifdef MOZ_STYLO
+  bool IsStyledByServo() const;
+#else
+  bool IsStyledByServo() const { return false; }
+#endif
+
+  inline bool IsDirtyForServo() const
+  {
+    MOZ_ASSERT(IsStyledByServo());
+    return HasFlag(NODE_IS_DIRTY_FOR_SERVO);
+  }
+
+  inline bool HasDirtyDescendantsForServo() const
+  {
+    MOZ_ASSERT(IsStyledByServo());
+    return HasFlag(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
+  }
+
+  inline void SetIsDirtyForServo() {
+    MOZ_ASSERT(IsStyledByServo());
+    SetFlags(NODE_IS_DIRTY_FOR_SERVO);
+  }
+
+  inline void SetHasDirtyDescendantsForServo() {
+    MOZ_ASSERT(IsStyledByServo());
+    SetFlags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
+  }
+
+  inline void SetIsDirtyAndHasDirtyDescendantsForServo() {
+    MOZ_ASSERT(IsStyledByServo());
+    SetFlags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO | NODE_IS_DIRTY_FOR_SERVO);
+  }
+
+  inline void UnsetRestyleFlagsIfGecko();
+
+  /**
    * Adds a mutation observer to be notified when this node, or any of its
    * descendants, are modified. The node will hold a weak reference to the
    * observer, which means that it is the responsibility of the observer to
    * remove itself in case it dies before the node.  If an observer is added
    * while observers are being notified, it may also be notified.  In general,
    * adding observers while inside a notification is not a good idea.  An
    * observer that is already observing the node must not be added without
    * being removed first.
@@ -1671,17 +1722,27 @@ public:
 
   void SetMayBeApzAware() { SetBoolFlag(MayBeApzAware); }
   bool NodeMayBeApzAware() const
   {
     return GetBoolFlag(MayBeApzAware);
   }
 protected:
   void SetParentIsContent(bool aValue) { SetBoolFlag(ParentIsContent, aValue); }
-  void SetInDocument() { SetBoolFlag(IsInDocument); }
+  /**
+   * This is a special case of SetIsInDocument used to special-case it for the
+   * document constructor (which can't do the IsStyledByServo() check).
+   */
+  void SetIsDocument() { SetBoolFlag(IsInDocument); }
+  void SetIsInDocument() {
+    if (IsStyledByServo()) {
+      SetIsDirtyAndHasDirtyDescendantsForServo();
+    }
+    SetBoolFlag(IsInDocument);
+  }
   void SetNodeIsContent() { SetBoolFlag(NodeIsContent); }
   void ClearInDocument() { ClearBoolFlag(IsInDocument); }
   void SetIsElement() { SetBoolFlag(NodeIsElement); }
   void SetHasID() { SetBoolFlag(ElementHasID); }
   void ClearHasID() { ClearBoolFlag(ElementHasID); }
   void SetMayHaveStyle() { SetBoolFlag(ElementMayHaveStyle); }
   void SetHasName() { SetBoolFlag(ElementHasName); }
   void ClearHasName() { ClearBoolFlag(ElementHasName); }
@@ -2003,18 +2064,18 @@ public:
     return mServoNodeData;
 #else
     MOZ_CRASH("Accessing servo node data in non-stylo build");
 #endif
   }
 
   void SetServoNodeData(ServoNodeData* aData) {
 #ifdef MOZ_STYLO
-  MOZ_ASSERT(!mServoNodeData);
-  mServoNodeData = aData;
+    MOZ_ASSERT(!mServoNodeData);
+    mServoNodeData = aData;
 #else
     MOZ_CRASH("Setting servo node data in non-stylo build");
 #endif
   }
 
 protected:
   static bool Traverse(nsINode *tmp, nsCycleCollectionTraversalCallback &cb);
   static void Unlink(nsINode *tmp);
--- a/dom/base/test/test_blob_fragment_and_query.html
+++ b/dom/base/test/test_blob_fragment_and_query.html
@@ -7,42 +7,53 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
   <script>
 
 var  blob = new Blob(['hello world']);
 ok(blob, "We have a blob.");
 
-var url = URL.createObjectURL(blob);
-ok(url, "We have a URI");
-
 var tests = [
-  url,
-  url + "?aa",
-  url + "#bb",
-  url + "?cc#dd",
-  url + "#ee?ff",
+  { part: "", revoke: true },
+  { part: "?aa", revoke: false },
+  { part: "#bb", revoke: false },
+  { part: "?cc#dd", revoke: false },
+  { part: "#ee?ff", revoke: false }
 ];
 
 function runTest() {
   if (!tests.length) {
     SimpleTest.finish();
     return;
   }
 
+  var url = URL.createObjectURL(blob);
+  ok(url, "We have a URI");
+
   var test = tests.shift();
 
+  URL.revokeObjectURL(url + test.part);
+
   var xhr = new XMLHttpRequest();
-  xhr.open('GET', test);
+  try {
+  xhr.open('GET', url + test.part);
+  } catch(e) {
+    ok(test.revoke, "This should fail!");
+    runTest();
+    return;
+  }
+
   xhr.onload = function() {
-    is(xhr.responseText, 'hello world', 'URL: ' + test);
+    is(xhr.responseText, 'hello world', 'URL: ' + url + test.part);
     runTest();
   }
+
   xhr.send();
+  ok(!test.revoke, "This should succeed!");
 }
 
 SimpleTest.waitForExplicitFinish();
 runTest();
 
   </script>
 </body>
 </html>
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -6776,30 +6776,30 @@ HTMLInputElement::IntrinsicState() const
     // 4. The element has already been modified or the user tried to submit the
     //    form owner while invalid.
     if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
         (mCanShowValidUI && ShouldShowValidityUI() &&
          (IsValid() || (!state.HasState(NS_EVENT_STATE_MOZ_UI_INVALID) &&
                         !mCanShowInvalidUI)))) {
       state |= NS_EVENT_STATE_MOZ_UI_VALID;
     }
+
+    // :in-range and :out-of-range only apply if the element currently has a range
+    if (mHasRange) {
+      state |= (GetValidityState(VALIDITY_STATE_RANGE_OVERFLOW) ||
+                GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW))
+                 ? NS_EVENT_STATE_OUTOFRANGE
+                 : NS_EVENT_STATE_INRANGE;
+    }
   }
 
   if (mForm && !mForm->GetValidity() && IsSubmitControl()) {
     state |= NS_EVENT_STATE_MOZ_SUBMITINVALID;
   }
 
-  // :in-range and :out-of-range only apply if the element currently has a range.
-  if (mHasRange) {
-    state |= (GetValidityState(VALIDITY_STATE_RANGE_OVERFLOW) ||
-              GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW))
-               ? NS_EVENT_STATE_OUTOFRANGE
-               : NS_EVENT_STATE_INRANGE;
-  }
-
   return state;
 }
 
 void
 HTMLInputElement::AddStates(EventStates aStates)
 {
   if (mType == NS_FORM_INPUT_TEXT) {
     EventStates focusStates(aStates & (NS_EVENT_STATE_FOCUS |
--- a/dom/html/HTMLTrackElement.cpp
+++ b/dom/html/HTMLTrackElement.cpp
@@ -234,20 +234,16 @@ HTMLTrackElement::BindToTree(nsIDocument
                              bool aCompileEventHandlers)
 {
   nsresult rv = nsGenericHTMLElement::BindToTree(aDocument,
                                                  aParent,
                                                  aBindingParent,
                                                  aCompileEventHandlers);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (!aDocument) {
-    return NS_OK;
-  }
-
   LOG(LogLevel::Debug, ("Track Element bound to tree."));
   if (!aParent || !aParent->IsNodeOfType(nsINode::eMEDIA)) {
     return NS_OK;
   }
 
   // Store our parent so we can look up its frame for display.
   if (!mMediaParent) {
     mMediaParent = static_cast<HTMLMediaElement*>(aParent);
@@ -258,17 +254,17 @@ HTMLTrackElement::BindToTree(nsIDocument
 
     // We may already have a TextTrack at this point if GetTrack() has already
     // been called. This happens, for instance, if script tries to get the
     // TextTrack before its mTrackElement has been bound to the DOM tree.
     if (!mTrack) {
       CreateTextTrack();
     }
     RefPtr<Runnable> r = NewRunnableMethod(this, &HTMLTrackElement::LoadResource);
-    mMediaParent->RunInStableState(r);
+    nsContentUtils::RunInStableState(r.forget());
   }
 
   return NS_OK;
 }
 
 void
 HTMLTrackElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -455,16 +455,17 @@ skip-if = (toolkit == 'gonk' && debug) #
 [test_bug885024.html]
 [test_bug893537.html]
 [test_bug95530.html]
 [test_bug969346.html]
 [test_bug982039.html]
 [test_bug1003539.html]
 [test_bug1045270.html]
 [test_bug1146116.html]
+[test_bug1264157.html]
 [test_change_crossorigin.html]
 [test_checked.html]
 [test_dir_attributes_reflection.html]
 [test_dl_attributes_reflection.html]
 [test_element_prototype.html]
 [test_embed_attributes_reflection.html]
 [test_formData.html]
 [test_formSubmission.html]
copy from dom/html/test/test_bug535043.html
copy to dom/html/test/test_bug1264157.html
--- a/dom/html/test/test_bug535043.html
+++ b/dom/html/test/test_bug1264157.html
@@ -3,86 +3,86 @@
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=535043
 -->
 <head>
   <title>Test for Bug 535043</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <style>
+    input {
+      outline: 2px solid lime;
+    }
+    input:in-range {
+      outline: 2px solid red;
+    }
+    input:out-of-range {
+      outline: 2px solid orange;
+    }
+  </style>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=535043">Mozilla Bug 535043</a>
 <p id="display"></p>
 <div id="content">
-  <textarea></textarea>
-  <textarea maxlength="-1"></textarea>
-  <textarea maxlength="0"></textarea>
-  <textarea maxlength="2"></textarea>
+
+</head>
+<body>
+  <input type="number" value=0 min=0 max=10> Active in-range
+  <br><br>
+  <input type="number" value=0 min=0 max=10 disabled> Disabled in-range
+  <br><br>
+  <input type="number" value=0 min=0 max=10 readonly> Read-only in-range
+  <br><br>
+  <input type="number" value=11 min=0 max=10> Active out-of-range
+  <br><br>
+  <input type="number" value=11 min=0 max=10 disabled> Disabled out-of-range
+  <br><br>
+  <input type="number" value=11 min=0 max=10 readonly> Read-only out-of-range
 </div>
 <pre id="test">
 <script type="text/javascript">
 
-/** Test for Bug 535043 **/
-function checkTextArea(textArea) {
-  textArea.value = '';
-  textArea.focus();
-  for (var j = 0; j < 3; j++) {
-    synthesizeKey('x', {});
-  }
-  var htmlMaxLength = textArea.getAttribute('maxlength');
-  var domMaxLength = textArea.maxLength;
-  if (htmlMaxLength == null) {
-    is(domMaxLength, -1,
-      'maxlength is unset but maxLength DOM attribute is not -1');
-  } else if (htmlMaxLength < 0) {
-    // Per the HTML5 spec, out-of-range values are supposed to translate to -1,
-    // not 0, but they don't?
-    is(domMaxLength, -1,
-      'maxlength is out of range but maxLength DOM attribute is not -1');
-  } else {
-    is(domMaxLength, parseInt(htmlMaxLength),
-      'maxlength in DOM does not match provided value');
-  }
-  if (textArea.maxLength == -1) {
-    is(textArea.value.length, 3,
-      'textarea with maxLength -1 should have no length limit');
-  } else {
-    is(textArea.value.length, textArea.maxLength, 'textarea has maxLength ' +
-      textArea.maxLength + ' but length ' + textArea.value.length );
-  }
-}
+/** Test for Bug 1264157 **/
+SimpleTest.waitForFocus(function() {
+  // Check the initial values.
+  let active = [].slice.call(document.querySelectorAll("input:not(:disabled):not(:-moz-read-only)"));
+  let disabled = [].slice.call(document.querySelectorAll("input:disabled"));
+  let readonly = [].slice.call(document.querySelectorAll("input:-moz-read-only"));
+  ok(active.length == 2, "Test is messed up: missing non-disabled/non-readonly inputs");
+  ok(disabled.length == 2, "Test is messed up: missing disabled inputs");
+  ok(readonly.length == 2, "Test is messed up: missing readonly inputs");
+
+  is(document.querySelectorAll("input:in-range").length, 1,
+     "Wrong number of in-range elements selected.");
+  is(document.querySelectorAll("input:out-of-range").length, 1,
+     "Wrong number of out-of-range elements selected.");
 
-SimpleTest.waitForFocus(function() {
-  var textAreas = document.getElementsByTagName('textarea');
-  for (var i = 0; i < textAreas.length; i++) {
-    checkTextArea(textAreas[i]);
-  }
-
-  textArea = textAreas[0];
-  testNums = [-42, -1, 0, 2];
-  for (var i = 0; i < testNums.length; i++) {
-    textArea.removeAttribute('maxlength');
+  // Dynamically change the values to see if that works too.
+  active[0].value = -1;
+  is(document.querySelectorAll("input:in-range").length, 0,
+     "Wrong number of in-range elements selected after value changed.");
+  is(document.querySelectorAll("input:out-of-range").length, 2,
+     "Wrong number of out-of-range elements selected after value changed.");
+  active[0].value = 0;
+  is(document.querySelectorAll("input:in-range").length, 1,
+     "Wrong number of in-range elements selected after value changed back.");
+  is(document.querySelectorAll("input:out-of-range").length, 1,
+     "Wrong number of out-of-range elements selected after value changed back.");
 
-    var caught = false;
-    try {
-      textArea.maxLength = testNums[i];
-    } catch (e) {
-      caught = true;
-    }
-    if (testNums[i] < 0) {
-      ok(caught, 'Setting negative maxLength should throw exception');
-    } else {
-      ok(!caught, 'Setting nonnegative maxLength should not throw exception');
-    }
-    checkTextArea(textArea);
+  // Dynamically change the attributes to see if that works too.
+  disabled.forEach(function(e) { e.removeAttribute("disabled"); });
+  readonly.forEach(function(e) { e.removeAttribute("readonly"); });
+  active.forEach(function(e) { e.setAttribute("readonly", true); });
 
-    textArea.setAttribute('maxlength', testNums[i]);
-    checkTextArea(textArea);
-  }
+  is(document.querySelectorAll("input:in-range").length, 2,
+     "Wrong number of in-range elements selected after attribute changed.");
+  is(document.querySelectorAll("input:out-of-range").length, 2,
+     "Wrong number of out-of-range elements selected after attribute changed.");
 
   SimpleTest.finish();
 });
 
 SimpleTest.waitForExplicitFinish();
 
 </script>
 </pre>
--- a/dom/manifest/test/test_window_oninstall_event.html
+++ b/dom/manifest/test/test_window_oninstall_event.html
@@ -15,17 +15,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   const finish = SimpleTest.finish.bind(SimpleTest);
   enableOnInstallPref()
     .then(createIframe)
     .then(checkImplementation)
     .then(checkOnInstallEventFires)
     .then(checkAddEventListenerFires)
     .then(finish)
     .catch(err => {
-      dump(`${err}: ${err.stack}`);
+      ok(false, err.stack);
       finish();
     });
 
   function enableOnInstallPref() {
     const ops = {
       "set": [
         ["dom.manifest.oninstall", true],
       ],
@@ -54,17 +54,17 @@ https://bugzilla.mozilla.org/show_bug.cg
       // no point in continuing
       if (!hasOnInstallProp) {
         const err = new Error("No 'oninstall' IDL attribute. Aborting early.");
         return reject(err);
       }
       is(ifrWindow.oninstall, null, "window install is initially set to null");
 
       // Check that enumerable, configurable, and has a getter and setter.
-      const objDescriptor = Object.getOwnPropertyDescriptor(window, "oninstall");
+      const objDescriptor = Object.getOwnPropertyDescriptor(ifrWindow, "oninstall");
       ok(objDescriptor.enumerable, "is enumerable");
       ok(objDescriptor.configurable, "is configurable");
       ok(objDescriptor.hasOwnProperty("get"), "has getter");
       ok(objDescriptor.hasOwnProperty("set"), "has setter");
       resolve(ifrWindow);
     });
   }
 
--- a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
+++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
@@ -665,21 +665,41 @@ WMFVideoMFTManager::CreateBasicVideoFram
   b.mPlanes[2].mWidth = halfWidth;
   b.mPlanes[2].mOffset = 0;
   b.mPlanes[2].mSkip = 0;
 
   media::TimeUnit pts = GetSampleTime(aSample);
   NS_ENSURE_TRUE(pts.IsValid(), E_FAIL);
   media::TimeUnit duration = GetSampleDuration(aSample);
   NS_ENSURE_TRUE(duration.IsValid(), E_FAIL);
+  nsIntRect pictureRegion = mVideoInfo.ScaledImageRect(videoWidth, videoHeight);
+
+  if (mLayersBackend != LayersBackend::LAYERS_D3D9 &&
+      mLayersBackend != LayersBackend::LAYERS_D3D11) {
+    RefPtr<VideoData> v = VideoData::Create(mVideoInfo,
+                                            mImageContainer,
+                                            aStreamOffset,
+                                            pts.ToMicroseconds(),
+                                            duration.ToMicroseconds(),
+                                            b,
+                                            false,
+                                            -1,
+                                            pictureRegion);
+    if (twoDBuffer) {
+      twoDBuffer->Unlock2D();
+    } else {
+      buffer->Unlock();
+    }
+    v.forget(aOutVideoData);
+    return S_OK;
+  }
 
   RefPtr<layers::PlanarYCbCrImage> image =
     new IMFYCbCrImage(buffer, twoDBuffer);
 
-  nsIntRect pictureRegion = mVideoInfo.ScaledImageRect(videoWidth, videoHeight);
   VideoData::SetVideoDataToImage(image,
                                  mVideoInfo,
                                  b,
                                  pictureRegion,
                                  false);
 
   RefPtr<VideoData> v =
     VideoData::CreateFromImage(mVideoInfo,
--- a/dom/presentation/PresentationDataChannelSessionTransport.js
+++ b/dom/presentation/PresentationDataChannelSessionTransport.js
@@ -216,18 +216,18 @@ PresentationTransportBuilder.prototype =
   },
 
   onIceCandidate: function(aCandidate) {
     log("onIceCandidate: " + aCandidate + " with role " + this._role);
     let candidate = new this._window.RTCIceCandidate(JSON.parse(aCandidate));
     this._peerConnection.addIceCandidate(candidate).catch(e => this._reportError(e));
   },
 
-  notifyClosed: function(aReason) {
-    log("notifyClosed reason: " + aReason);
+  notifyDisconnected: function(aReason) {
+    log("notifyDisconnected reason: " + aReason);
 
     if (aReason != Cr.NS_OK) {
       this._cleanup(aReason);
     } else if (this._isControlChannelNeeded) {
       this._cleanup(Cr.NS_ERROR_FAILURE);
     }
   },
 };
--- a/dom/presentation/PresentationService.cpp
+++ b/dom/presentation/PresentationService.cpp
@@ -125,17 +125,17 @@ PresentationDeviceRequest::CreateSession
   if (NS_WARN_IF(!info)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   info->SetDevice(aDevice);
 
   // Establish a control channel. If we failed to do so, the callback is called
   // with an error message.
   nsCOMPtr<nsIPresentationControlChannel> ctrlChannel;
-  nsresult rv = aDevice->EstablishControlChannel(mRequestUrl, mId, getter_AddRefs(ctrlChannel));
+  nsresult rv = aDevice->EstablishControlChannel(getter_AddRefs(ctrlChannel));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return info->ReplyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
   // Initialize the session info with the control channel.
   rv = info->Init(ctrlChannel);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return info->ReplyError(NS_ERROR_DOM_OPERATION_ERR);
@@ -278,85 +278,85 @@ PresentationService::HandleSessionReques
   nsresult rv = aRequest->GetControlChannel(getter_AddRefs(ctrlChannel));
   if (NS_WARN_IF(NS_FAILED(rv) || !ctrlChannel)) {
     return rv;
   }
 
   nsAutoString url;
   rv = aRequest->GetUrl(url);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    ctrlChannel->Close(rv);
+    ctrlChannel->Disconnect(rv);
     return rv;
   }
 
   nsAutoString sessionId;
   rv = aRequest->GetPresentationId(sessionId);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    ctrlChannel->Close(rv);
+    ctrlChannel->Disconnect(rv);
     return rv;
   }
 
   nsCOMPtr<nsIPresentationDevice> device;
   rv = aRequest->GetDevice(getter_AddRefs(device));
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    ctrlChannel->Close(rv);
+    ctrlChannel->Disconnect(rv);
     return rv;
   }
 
 #ifdef MOZ_WIDGET_GONK
   // Verify the existence of the app if necessary.
   nsCOMPtr<nsIURI> uri;
   rv = NS_NewURI(getter_AddRefs(uri), url);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    ctrlChannel->Close(NS_ERROR_DOM_BAD_URI);
+    ctrlChannel->Disconnect(NS_ERROR_DOM_BAD_URI);
     return rv;
   }
 
   bool isApp;
   rv = uri->SchemeIs("app", &isApp);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    ctrlChannel->Close(rv);
+    ctrlChannel->Disconnect(rv);
     return rv;
   }
 
   if (NS_WARN_IF(isApp && !IsAppInstalled(uri))) {
-    ctrlChannel->Close(NS_ERROR_DOM_NOT_FOUND_ERR);
+    ctrlChannel->Disconnect(NS_ERROR_DOM_NOT_FOUND_ERR);
     return NS_OK;
   }
 #endif
 
   // Create or reuse session info.
   RefPtr<PresentationSessionInfo> info =
     GetSessionInfo(sessionId, nsIPresentationService::ROLE_RECEIVER);
   if (NS_WARN_IF(info)) {
     // TODO Bug 1195605. Update here after session join/resume becomes supported.
-    ctrlChannel->Close(NS_ERROR_DOM_OPERATION_ERR);
+    ctrlChannel->Disconnect(NS_ERROR_DOM_OPERATION_ERR);
     return NS_ERROR_DOM_ABORT_ERR;
   }
 
   info = new PresentationPresentingInfo(url, sessionId, device);
   rv = info->Init(ctrlChannel);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    ctrlChannel->Close(rv);
+    ctrlChannel->Disconnect(rv);
     return rv;
   }
 
   mSessionInfoAtReceiver.Put(sessionId, info);
 
   // Notify the receiver to launch.
   nsCOMPtr<nsIPresentationRequestUIGlue> glue =
     do_CreateInstance(PRESENTATION_REQUEST_UI_GLUE_CONTRACTID);
   if (NS_WARN_IF(!glue)) {
-    ctrlChannel->Close(NS_ERROR_DOM_OPERATION_ERR);
+    ctrlChannel->Disconnect(NS_ERROR_DOM_OPERATION_ERR);
     return info->ReplyError(NS_ERROR_DOM_OPERATION_ERR);
   }
   nsCOMPtr<nsISupports> promise;
   rv = glue->SendRequest(url, sessionId, device, getter_AddRefs(promise));
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    ctrlChannel->Close(rv);
+    ctrlChannel->Disconnect(rv);
     return info->ReplyError(NS_ERROR_DOM_OPERATION_ERR);
   }
   nsCOMPtr<Promise> realPromise = do_QueryInterface(promise);
   static_cast<PresentationPresentingInfo*>(info.get())->SetPromise(realPromise);
 
   return NS_OK;
 }
 
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -221,17 +221,17 @@ PresentationSessionInfo::Init(nsIPresent
 
 /* virtual */ void
 PresentationSessionInfo::Shutdown(nsresult aReason)
 {
   NS_WARN_IF(NS_FAILED(aReason));
 
   // Close the control channel if any.
   if (mControlChannel) {
-    NS_WARN_IF(NS_FAILED(mControlChannel->Close(aReason)));
+    NS_WARN_IF(NS_FAILED(mControlChannel->Disconnect(aReason)));
   }
 
   // Close the data transport channel if any.
   if (mTransport) {
     // |mIsTransportReady| will be unset once |NotifyTransportClosed| is called.
     NS_WARN_IF(NS_FAILED(mTransport->Close(aReason)));
   }
 
@@ -462,28 +462,28 @@ NS_IMETHODIMP
 PresentationSessionInfo::SendIceCandidate(const nsAString& candidate)
 {
   return mControlChannel->SendIceCandidate(candidate);
 }
 
 NS_IMETHODIMP
 PresentationSessionInfo::Close(nsresult reason)
 {
-  return mControlChannel->Close(reason);
+  return mControlChannel->Disconnect(reason);
 }
 
 /**
  * Implementation of PresentationControllingInfo
  *
  * During presentation session establishment, the sender expects the following
  * after trying to establish the control channel: (The order between step 3 and
  * 4 is not guaranteed.)
  * 1. |Init| is called to open a socket |mServerSocket| for data transport
  *    channel.
- * 2. |NotifyOpened| of |nsIPresentationControlChannelListener| is called to
+ * 2. |NotifyConnected| of |nsIPresentationControlChannelListener| is called to
  *    indicate the control channel is ready to use. Then send the offer to the
  *    receiver via the control channel.
  * 3.1 |OnSocketAccepted| of |nsIServerSocketListener| is called to indicate the
  *     data transport channel is connected. Then initialize |mTransport|.
  * 3.2 |NotifyTransportReady| of |nsIPresentationSessionTransportCallback| is
  *     called.
  * 4. |OnAnswer| of |nsIPresentationControlChannelListener| is called to
  *    indicate the receiver is ready. Close the control channel since it's no
@@ -668,35 +668,40 @@ PresentationControllingInfo::OnAnswer(ns
     }
 
     return builder->OnAnswer(aDescription);
   }
 
   mIsResponderReady = true;
 
   // Close the control channel since it's no longer needed.
-  nsresult rv = mControlChannel->Close(NS_OK);
+  nsresult rv = mControlChannel->Disconnect(NS_OK);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
   // Session might not be ready at this moment (waiting for the establishment of
   // the data transport channel).
   if (IsSessionReady()){
     return ReplySuccess();
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-PresentationControllingInfo::NotifyOpened()
+PresentationControllingInfo::NotifyConnected()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  nsresult rv = mControlChannel->Launch(GetSessionId(), GetUrl());
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
   if (!Preferences::GetBool("dom.presentation.session_transport.data_channel.enable")) {
     // Build TCP session transport
     return GetAddress();
   }
 
   nsPIDOMWindowInner* window = nullptr;
   /**
    * Generally transport is maintained by the chrome process. However, data
@@ -726,36 +731,36 @@ PresentationControllingInfo::NotifyOpene
   // OOP case
   mTransportType = nsIPresentationChannelDescription::TYPE_DATACHANNEL;
 
   nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder>
     dataChannelBuilder(do_QueryInterface(mBuilder));
   if (NS_WARN_IF(!dataChannelBuilder)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
-  nsresult rv = dataChannelBuilder->
-                  BuildDataChannelTransport(nsIPresentationService::ROLE_CONTROLLER,
-                                            window,
-                                            this);
+  rv = dataChannelBuilder->
+         BuildDataChannelTransport(nsIPresentationService::ROLE_CONTROLLER,
+                                   window,
+                                   this);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
-PresentationControllingInfo::NotifyClosed(nsresult aReason)
+PresentationControllingInfo::NotifyDisconnected(nsresult aReason)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mTransportType == nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
     nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder>
       builder = do_QueryInterface(mBuilder);
     if (builder) {
-      NS_WARN_IF(NS_FAILED(builder->NotifyClosed(aReason)));
+      NS_WARN_IF(NS_FAILED(builder->NotifyDisconnected(aReason)));
     }
   }
 
   // Unset control channel here so it won't try to re-close it in potential
   // subsequent |Shutdown| calls.
   SetControlChannel(nullptr);
 
   if (NS_WARN_IF(NS_FAILED(aReason) || !mIsResponderReady)) {
@@ -1127,32 +1132,32 @@ PresentationPresentingInfo::OnIceCandida
 
   nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder>
     builder = do_QueryInterface(mBuilder);
 
   return builder->OnIceCandidate(aCandidate);
 }
 
 NS_IMETHODIMP
-PresentationPresentingInfo::NotifyOpened()
+PresentationPresentingInfo::NotifyConnected()
 {
   // Do nothing.
   return NS_OK;
 }
 
 NS_IMETHODIMP
-PresentationPresentingInfo::NotifyClosed(nsresult aReason)
+PresentationPresentingInfo::NotifyDisconnected(nsresult aReason)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mTransportType == nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
     nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder>
       builder = do_QueryInterface(mBuilder);
     if (builder) {
-      NS_WARN_IF(NS_FAILED(builder->NotifyClosed(aReason)));
+      NS_WARN_IF(NS_FAILED(builder->NotifyDisconnected(aReason)));
     }
   }
 
   // Unset control channel here so it won't try to re-close it in potential
   // subsequent |Shutdown| calls.
   SetControlChannel(nullptr);
 
   if (NS_WARN_IF(NS_FAILED(aReason))) {
--- a/dom/presentation/interfaces/nsIPresentationControlChannel.idl
+++ b/dom/presentation/interfaces/nsIPresentationControlChannel.idl
@@ -49,26 +49,26 @@ interface nsIPresentationControlChannelL
 
   /*
    * Callback for receiving ICE candidate from remote endpoint.
    * @param answer The received answer.
    */
   void onIceCandidate(in DOMString candidate);
 
   /*
-   * The callback for notifying channel opened. This should be async called
+   * The callback for notifying channel connected. This should be async called
    * after nsIPresentationDevice::establishControlChannel.
    */
-  void notifyOpened();
+  void notifyConnected();
 
   /*
-   * The callback for notifying channel closed.
+   * The callback for notifying channel disconnected.
    * @param reason The reason of channel close, NS_OK represents normal close.
    */
-  void notifyClosed(in nsresult reason);
+  void notifyDisconnected(in nsresult reason);
 };
 
 /*
  * The control channel for establishing RTCPeerConnection for a presentation
  * session. SDP Offer/Answer will be exchanged through this interface. The
  * control channel should be in-order.
  */
 [scriptable, uuid(e60e208c-a9f5-4bc6-9a3e-47f3e4ae9c57)]
@@ -98,13 +98,21 @@ interface nsIPresentationControlChannel:
    * Send ICE candidate to remote endpoint. |onIceCandidate| should be invoked
    * on remote endpoint.
    * @param candidate The candidate to send
    * @throws NS_ERROR_FAILURE on failure
    */
   void sendIceCandidate(in DOMString candidate);
 
   /*
-   * Close the transport channel.
-   * @param reason The reason of channel close; NS_OK represents normal.
+   * Launch a presentation on remote endpoint.
+   * @param presentationId The Id for representing this session.
+   * @param url The URL requested to open by remote device.
+   * @throws NS_ERROR_FAILURE on failure
    */
-  void close(in nsresult reason);
+  void launch(in DOMString presentationId, in DOMString url);
+
+  /*
+   * Disconnect the control channel.
+   * @param reason The reason of disconnecting channel; NS_OK represents normal.
+   */
+  void disconnect(in nsresult reason);
 };
--- a/dom/presentation/interfaces/nsIPresentationControlService.idl
+++ b/dom/presentation/interfaces/nsIPresentationControlService.idl
@@ -60,42 +60,48 @@ interface nsIPresentationControlService:
    *          The port of the server socket.  Pass 0 or opt-out to indicate no
    *          preference, and a port will be selected automatically.
    * @throws  NS_ERROR_FAILURE if the server socket has been inited or the
    *          server socket can not be inited.
    */
   void startServer([optional] in uint16_t aPort);
 
   /**
-   * Request session to designated remote presentation control receiver.
+   * Request connection to designated remote presentation control receiver.
    * @param   aDeviceInfo
    *          The remtoe device info for establish connection.
-   * @param   aUrl
-   *          The URL requested to open by remote device.
-   * @param   aPresentationId
-   *          The Id for representing this session.
    * @returns The control channel for this session.
    * @throws  NS_ERROR_FAILURE if the Id hasn't been inited.
    */
-  nsIPresentationControlChannel requestSession(in nsITCPDeviceInfo aDeviceInfo,
-                                               in DOMString aUrl,
-                                               in DOMString aPresentationId);
+  nsIPresentationControlChannel connect(in nsITCPDeviceInfo aDeviceInfo);
+
+  /**
+   * Check the compatibility to remote presentation control server.
+   * @param  aVersion
+   *         The version of remote server.
+   */
+  boolean isCompatibleServer(in uint32_t aVersion);
 
   /**
    * Close server socket and call |listener.onClose(NS_OK)|
    */
   void close();
 
   /**
    * Get the listen port of the TCP socket, valid after |init|. 0 indicates
    * the server socket is not inited or closed.
    */
   readonly attribute uint16_t port;
 
   /**
+   * The protocol version implemented by this server.
+   */
+  readonly attribute uint32_t version;
+
+  /**
    * The id of the TCP presentation server. |requestSession| won't
    * work until the |id| is set.
    */
   attribute AUTF8String id;
 
   /**
    * the listener for handling events of this presentation control server
    */
--- a/dom/presentation/interfaces/nsIPresentationDevice.idl
+++ b/dom/presentation/interfaces/nsIPresentationDevice.idl
@@ -19,21 +19,18 @@ interface nsIPresentationDevice : nsISup
   readonly attribute AUTF8String name;
 
   // TODO expose more info in order to fulfill UX spec
   // The category of this device, could be "wifi", "bluetooth", "hdmi", etc.
   readonly attribute AUTF8String type;
 
   /*
    * Establish a control channel to this device.
-   * @param url The URL requested to open by remote device.
-   * @param presentationId The Id for representing this session.
    * @returns The control channel for this session.
    * @throws  NS_ERROR_FAILURE if the establishment fails
    */
-  nsIPresentationControlChannel establishControlChannel(in DOMString url,
-                                                        in DOMString presentationId);
+  nsIPresentationControlChannel establishControlChannel();
 
   // Do something when presentation session is disconnected.
   void disconnect();
 };
 
 
--- a/dom/presentation/interfaces/nsIPresentationSessionTransportBuilder.idl
+++ b/dom/presentation/interfaces/nsIPresentationSessionTransportBuilder.idl
@@ -50,22 +50,22 @@ interface nsIPresentationTCPSessionTrans
  * Builder for WebRTC data channel session transport
  */
 [scriptable, uuid(8131c4e0-3a8c-4bc1-a92a-8431473d2fe8)]
 interface nsIPresentationDataChannelSessionTransportBuilder : nsIPresentationSessionTransportBuilder
 {
   /**
    * The following creation function will trigger |listener.onSessionTransport|
    * if the session transport is successfully built, |listener.onError| if some
-   * error occurs during creating session transport. The |notifyOpened| of
+   * error occurs during creating session transport. The |notifyConnected| of
    * |aControlChannel| should be called before calling
    * |buildDataChannelTransport|.
    */
   void buildDataChannelTransport(in uint8_t aRole,
                                  in mozIDOMWindow aWindow,
                                  in nsIPresentationSessionTransportBuilderListener aListener);
 
   // Bug 1275150 - Merge TCP builder with the following APIs
   void onOffer(in nsIPresentationChannelDescription offer);
   void onAnswer(in nsIPresentationChannelDescription answer);
   void onIceCandidate(in DOMString candidate);
-  void notifyClosed(in nsresult reason);
+  void notifyDisconnected(in nsresult reason);
 };
--- a/dom/presentation/ipc/PresentationBuilderParent.cpp
+++ b/dom/presentation/ipc/PresentationBuilderParent.cpp
@@ -84,17 +84,17 @@ PresentationBuilderParent::OnAnswer(nsIP
 
   if (NS_WARN_IF(!SendOnAnswer(SDP))){
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
-PresentationBuilderParent::NotifyClosed(nsresult aReason)
+PresentationBuilderParent::NotifyDisconnected(nsresult aReason)
 {
   return NS_OK;
 }
 
 void
 PresentationBuilderParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   mNeedDestroyActor = false;
new file mode 100644
--- /dev/null
+++ b/dom/presentation/provider/ControllerStateMachine.jsm
@@ -0,0 +1,187 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+/* jshint esnext:true, globalstrict:true, moz:true, undef:true, unused:true */
+/* globals Components, dump */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["ControllerStateMachine"]; // jshint ignore:line
+
+const { utils: Cu } = Components;
+
+/* globals State, CommandType */
+Cu.import("resource://gre/modules/presentation/StateMachineHelper.jsm");
+
+const DEBUG = false;
+function debug(str) {
+  dump("-*- ControllerStateMachine: " + str + "\n");
+}
+
+var handlers = [
+  function _initHandler(stateMachine, command) {
+    // shouldn't receive any command at init state.
+    DEBUG && debug("unexpected command: " + JSON.stringify(command)); // jshint ignore:line
+  },
+  function _connectingHandler(stateMachine, command) {
+    switch (command.type) {
+      case CommandType.CONNECT_ACK:
+        stateMachine.state = State.CONNECTED;
+        stateMachine._notifyDeviceConnected();
+        break;
+      case CommandType.DISCONNECT:
+        stateMachine.state = State.CLOSED;
+        stateMachine._notifyDisconnected(command.reason);
+        break;
+      default:
+        debug("unexpected command: " + JSON.stringify(command));
+        // ignore unexpected command.
+        break;
+    }
+  },
+  function _connectedHandler(stateMachine, command) {
+    switch (command.type) {
+      case CommandType.DISCONNECT:
+        stateMachine.state = State.CLOSED;
+        stateMachine._notifyDisconnected(command.reason);
+        break;
+      case CommandType.LAUNCH_ACK:
+        stateMachine._notifyLaunch(command.presentationId);
+        break;
+      case CommandType.ANSWER:
+      case CommandType.ICE_CANDIDATE:
+        stateMachine._notifyChannelDescriptor(command);
+        break;
+      default:
+        debug("unexpected command: " + JSON.stringify(command));
+        // ignore unexpected command.
+        break;
+    }
+  },
+  function _closingHandler(stateMachine, command) {
+    // ignore every command in closing state.
+    DEBUG && debug("unexpected command: " + JSON.stringify(command)); // jshint ignore:line
+  },
+  function _closedHandler(stateMachine, command) {
+    // ignore every command in closed state.
+    DEBUG && debug("unexpected command: " + JSON.stringify(command)); // jshint ignore:line
+  },
+];
+
+function ControllerStateMachine(channel, deviceId) {
+  this.state = State.INIT;
+  this._channel = channel;
+  this._deviceId = deviceId;
+}
+
+ControllerStateMachine.prototype = {
+  launch: function _launch(presentationId, url) {
+    if (this.state === State.CONNECTED) {
+      this._sendCommand({
+        type: CommandType.LAUNCH,
+        presentationId: presentationId,
+        url: url,
+      });
+    }
+  },
+
+  sendOffer: function _sendOffer(offer) {
+    if (this.state === State.CONNECTED) {
+      this._sendCommand({
+        type: CommandType.OFFER,
+        offer: offer,
+      });
+    }
+  },
+
+  sendAnswer: function _sendAnswer() {
+    // answer can only be sent by presenting UA.
+    debug("controller shouldn't generate answer");
+  },
+
+  updateIceCandidate: function _updateIceCandidate(candidate) {
+    if (this.state === State.CONNECTED) {
+      this._sendCommand({
+        type: CommandType.ICE_CANDIDATE,
+        candidate: candidate,
+      });
+    }
+  },
+
+  onCommand: function _onCommand(command) {
+    handlers[this.state](this, command);
+  },
+
+  onChannelReady: function _onChannelReady() {
+    if (this.state === State.INIT) {
+      this._sendCommand({
+        type: CommandType.CONNECT,
+        deviceId: this._deviceId
+      });
+      this.state = State.CONNECTING;
+    }
+  },
+
+  onChannelClosed: function _onChannelClose(reason, isByRemote) {
+    switch (this.state) {
+      case State.CONNECTED:
+        if (isByRemote) {
+          this.state = State.CLOSED;
+          this._notifyDisconnected(reason);
+        } else {
+          this._sendCommand({
+            type: CommandType.DISCONNECT,
+            reason: reason
+          });
+          this.state = State.CLOSING;
+          this._closeReason = reason;
+        }
+        break;
+      case State.CLOSING:
+        if (isByRemote) {
+          this.state = State.CLOSED;
+          if (this._closeReason) {
+            reason = this._closeReason;
+            delete this._closeReason;
+          }
+          this._notifyDisconnected(reason);
+        }
+        break;
+      default:
+        DEBUG && debug("unexpected channel close: " + reason + ", " + isByRemote); // jshint ignore:line
+        break;
+    }
+  },
+
+  _sendCommand: function _sendCommand(command) {
+    this._channel.sendCommand(command);
+  },
+
+  _notifyDeviceConnected: function _notifyDeviceConnected() {
+    //XXX trigger following command
+    this._channel.notifyDeviceConnected();
+  },
+
+  _notifyDisconnected: function _notifyDisconnected(reason) {
+    this._channel.notifyDisconnected(reason);
+  },
+
+  _notifyLaunch: function _notifyLaunch(presentationId) {
+    this._channel.notifyLaunch(presentationId);
+  },
+
+  _notifyChannelDescriptor: function _notifyChannelDescriptor(command) {
+    switch (command.type) {
+      case CommandType.ANSWER:
+        this._channel.notifyAnswer(command.answer);
+        break;
+      case CommandType.ICE_CANDIDATE:
+        this._channel.notifyIceCandidate(command.candidate);
+        break;
+    }
+  },
+};
+
+this.ControllerStateMachine = ControllerStateMachine; // jshint ignore:line
--- a/dom/presentation/provider/DisplayDeviceProvider.cpp
+++ b/dom/presentation/provider/DisplayDeviceProvider.cpp
@@ -87,30 +87,28 @@ NS_IMETHODIMP
 DisplayDeviceProvider::HDMIDisplayDevice::GetWindowId(nsACString& aWindowId)
 {
   aWindowId = mWindowId;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DisplayDeviceProvider::HDMIDisplayDevice
-                     ::EstablishControlChannel(const nsAString& aUrl,
-                                               const nsAString& aPresentationId,
-                                               nsIPresentationControlChannel** aControlChannel)
+                     ::EstablishControlChannel(nsIPresentationControlChannel** aControlChannel)
 {
   nsresult rv = OpenTopLevelWindow();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   RefPtr<DisplayDeviceProvider> provider = mProvider.get();
   if (NS_WARN_IF(!provider)) {
     return NS_ERROR_FAILURE;
   }
-  return provider->RequestSession(this, aUrl, aPresentationId, aControlChannel);
+  return provider->Connect(this, aControlChannel);
 }
 
 NS_IMETHODIMP
 DisplayDeviceProvider::HDMIDisplayDevice::Disconnect()
 {
   nsresult rv = CloseTopLevelWindow();
   if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
@@ -431,31 +429,26 @@ DisplayDeviceProvider::Observe(nsISuppor
       }
     }
   }
 
   return NS_OK;
 }
 
 nsresult
-DisplayDeviceProvider::RequestSession(HDMIDisplayDevice* aDevice,
-                                      const nsAString& aUrl,
-                                      const nsAString& aPresentationId,
-                                      nsIPresentationControlChannel** aControlChannel)
+DisplayDeviceProvider::Connect(HDMIDisplayDevice* aDevice,
+                               nsIPresentationControlChannel** aControlChannel)
 {
   MOZ_ASSERT(aDevice);
   MOZ_ASSERT(mPresentationService);
   NS_ENSURE_ARG_POINTER(aControlChannel);
   *aControlChannel = nullptr;
 
   nsCOMPtr<nsITCPDeviceInfo> deviceInfo = new TCPDeviceInfo(aDevice->Id(),
                                                             aDevice->Address(),
                                                             mPort);
 
-  return mPresentationService->RequestSession(deviceInfo,
-                                              aUrl,
-                                              aPresentationId,
-                                              aControlChannel);
+  return mPresentationService->Connect(deviceInfo, aControlChannel);
 }
 
 } // namespace presentation
 } // namespace dom
 } // namespace mozilla
--- a/dom/presentation/provider/DisplayDeviceProvider.h
+++ b/dom/presentation/provider/DisplayDeviceProvider.h
@@ -88,20 +88,18 @@ private:
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIPRESENTATIONDEVICEPROVIDER
   NS_DECL_NSIPRESENTATIONCONTROLSERVERLISTENER
   // For using WeakPtr when MOZ_REFCOUNTED_LEAK_CHECKING defined
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(DisplayDeviceProvider)
 
-  nsresult RequestSession(HDMIDisplayDevice* aDevice,
-                          const nsAString& aUrl,
-                          const nsAString& aPresentationId,
-                          nsIPresentationControlChannel** aControlChannel);
+  nsresult Connect(HDMIDisplayDevice* aDevice,
+                   nsIPresentationControlChannel** aControlChannel);
 private:
   virtual ~DisplayDeviceProvider();
 
   nsresult Init();
   nsresult Uninit();
 
   nsresult AddExternalScreen();
   nsresult RemoveExternalScreen();
copy from dom/presentation/provider/MulticastDNSDeviceProvider.cpp
copy to dom/presentation/provider/LegacyMDNSDeviceProvider.cpp
--- a/dom/presentation/provider/MulticastDNSDeviceProvider.cpp
+++ b/dom/presentation/provider/LegacyMDNSDeviceProvider.cpp
@@ -1,122 +1,111 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "MulticastDNSDeviceProvider.h"
+#include "LegacyMDNSDeviceProvider.h"
 #include "MainThreadUtils.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 #include "nsComponentManagerUtils.h"
 #include "nsIObserverService.h"
+#include "nsIWritablePropertyBag2.h"
 #include "nsServiceManagerUtils.h"
 #include "nsTCPDeviceInfo.h"
 #include "nsThreadUtils.h"
+#include "nsIPropertyBag2.h"
 
-#ifdef MOZ_WIDGET_ANDROID
-#include "nsIPropertyBag2.h"
-#endif // MOZ_WIDGET_ANDROID
-
-#define PREF_PRESENTATION_DISCOVERY "dom.presentation.discovery.enabled"
+#define PREF_PRESENTATION_DISCOVERY_LEGACY "dom.presentation.discovery.legacy.enabled"
 #define PREF_PRESENTATION_DISCOVERY_TIMEOUT_MS "dom.presentation.discovery.timeout_ms"
-#define PREF_PRESENTATION_DISCOVERABLE "dom.presentation.discoverable"
 #define PREF_PRESENTATION_DEVICE_NAME "dom.presentation.device.name"
 
-#define SERVICE_TYPE "_mozilla_papi._tcp."
+#define LEGACY_SERVICE_TYPE "_mozilla_papi._tcp"
 
-static mozilla::LazyLogModule sMulticastDNSProviderLogModule("MulticastDNSDeviceProvider");
+#define LEGACY_PRESENTATION_CONTROL_SERVICE_CONTACT_ID "@mozilla.org/presentation/legacy-control-service;1"
+
+static mozilla::LazyLogModule sLegacyMDNSProviderLogModule("LegacyMDNSDeviceProvider");
 
 #undef LOG_I
-#define LOG_I(...) MOZ_LOG(sMulticastDNSProviderLogModule, mozilla::LogLevel::Debug, (__VA_ARGS__))
+#define LOG_I(...) MOZ_LOG(sLegacyMDNSProviderLogModule, mozilla::LogLevel::Debug, (__VA_ARGS__))
 #undef LOG_E
-#define LOG_E(...) MOZ_LOG(sMulticastDNSProviderLogModule, mozilla::LogLevel::Error, (__VA_ARGS__))
+#define LOG_E(...) MOZ_LOG(sLegacyMDNSProviderLogModule, mozilla::LogLevel::Error, (__VA_ARGS__))
 
 namespace mozilla {
 namespace dom {
 namespace presentation {
+namespace legacy {
 
 static const char* kObservedPrefs[] = {
-  PREF_PRESENTATION_DISCOVERY,
+  PREF_PRESENTATION_DISCOVERY_LEGACY,
   PREF_PRESENTATION_DISCOVERY_TIMEOUT_MS,
-  PREF_PRESENTATION_DISCOVERABLE,
   PREF_PRESENTATION_DEVICE_NAME,
   nullptr
 };
 
 namespace {
 
-#ifdef MOZ_WIDGET_ANDROID
 static void
 GetAndroidDeviceName(nsACString& aRetVal)
 {
   nsCOMPtr<nsIPropertyBag2> infoService = do_GetService("@mozilla.org/system-info;1");
   MOZ_ASSERT(infoService, "Could not find a system info service");
 
   Unused << NS_WARN_IF(NS_FAILED(infoService->GetPropertyAsACString(
                                    NS_LITERAL_STRING("device"), aRetVal)));
 }
-#endif // MOZ_WIDGET_ANDROID
 
 } //anonymous namespace
 
 /**
  * This wrapper is used to break circular-reference problem.
  */
 class DNSServiceWrappedListener final
   : public nsIDNSServiceDiscoveryListener
-  , public nsIDNSRegistrationListener
   , public nsIDNSServiceResolveListener
-  , public nsIPresentationControlServerListener
 {
 public:
   NS_DECL_ISUPPORTS
   NS_FORWARD_SAFE_NSIDNSSERVICEDISCOVERYLISTENER(mListener)
-  NS_FORWARD_SAFE_NSIDNSREGISTRATIONLISTENER(mListener)
   NS_FORWARD_SAFE_NSIDNSSERVICERESOLVELISTENER(mListener)
-  NS_FORWARD_SAFE_NSIPRESENTATIONCONTROLSERVERLISTENER(mListener)
 
   explicit DNSServiceWrappedListener() = default;
 
-  nsresult SetListener(MulticastDNSDeviceProvider* aListener)
+  nsresult SetListener(LegacyMDNSDeviceProvider* aListener)
   {
     mListener = aListener;
     return NS_OK;
   }
 
 private:
   virtual ~DNSServiceWrappedListener() = default;
 
-  MulticastDNSDeviceProvider* mListener = nullptr;
+  LegacyMDNSDeviceProvider* mListener = nullptr;
 };
 
 NS_IMPL_ISUPPORTS(DNSServiceWrappedListener,
                   nsIDNSServiceDiscoveryListener,
-                  nsIDNSRegistrationListener,
-                  nsIDNSServiceResolveListener,
-                  nsIPresentationControlServerListener)
+                  nsIDNSServiceResolveListener)
 
-NS_IMPL_ISUPPORTS(MulticastDNSDeviceProvider,
+NS_IMPL_ISUPPORTS(LegacyMDNSDeviceProvider,
                   nsIPresentationDeviceProvider,
                   nsIDNSServiceDiscoveryListener,
-                  nsIDNSRegistrationListener,
                   nsIDNSServiceResolveListener,
-                  nsIPresentationControlServerListener,
                   nsIObserver)
 
-MulticastDNSDeviceProvider::~MulticastDNSDeviceProvider()
+LegacyMDNSDeviceProvider::~LegacyMDNSDeviceProvider()
 {
   Uninit();
 }
 
 nsresult
-MulticastDNSDeviceProvider::Init()
+LegacyMDNSDeviceProvider::Init()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mInitialized) {
     return NS_OK;
   }
 
   nsresult rv;
@@ -126,165 +115,76 @@ MulticastDNSDeviceProvider::Init()
     return rv;
   }
 
   mWrappedListener = new DNSServiceWrappedListener();
   if (NS_WARN_IF(NS_FAILED(rv = mWrappedListener->SetListener(this)))) {
     return rv;
   }
 
-  mPresentationService = do_CreateInstance(PRESENTATION_CONTROL_SERVICE_CONTACT_ID, &rv);
+  mPresentationService = do_CreateInstance(LEGACY_PRESENTATION_CONTROL_SERVICE_CONTACT_ID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   mDiscoveryTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   Preferences::AddStrongObservers(this, kObservedPrefs);
 
-  mDiscoveryEnabled = Preferences::GetBool(PREF_PRESENTATION_DISCOVERY);
+  mDiscoveryEnabled = Preferences::GetBool(PREF_PRESENTATION_DISCOVERY_LEGACY);
   mDiscoveryTimeoutMs = Preferences::GetUint(PREF_PRESENTATION_DISCOVERY_TIMEOUT_MS);
-  mDiscoverable = Preferences::GetBool(PREF_PRESENTATION_DISCOVERABLE);
   mServiceName = Preferences::GetCString(PREF_PRESENTATION_DEVICE_NAME);
 
-#ifdef MOZ_WIDGET_ANDROID
   // FIXME: Bug 1185806 - Provide a common device name setting.
   if (mServiceName.IsEmpty()) {
     GetAndroidDeviceName(mServiceName);
     Unused << Preferences::SetCString(PREF_PRESENTATION_DEVICE_NAME, mServiceName);
   }
-#endif // MOZ_WIDGET_ANDROID
 
   Unused << mPresentationService->SetId(mServiceName);
 
   if (mDiscoveryEnabled && NS_WARN_IF(NS_FAILED(rv = ForceDiscovery()))) {
     return rv;
   }
 
-  if (mDiscoverable && NS_WARN_IF(NS_FAILED(rv = RegisterService()))) {
-    return rv;
-  }
-
   mInitialized = true;
   return NS_OK;
 }
 
 nsresult
-MulticastDNSDeviceProvider::Uninit()
+LegacyMDNSDeviceProvider::Uninit()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!mInitialized) {
     return NS_OK;
   }
 
   ClearDevices();
 
   Preferences::RemoveObservers(this, kObservedPrefs);
 
   StopDiscovery(NS_OK);
-  UnregisterService(NS_OK);
 
   mMulticastDNS = nullptr;
 
   if (mWrappedListener) {
     mWrappedListener->SetListener(nullptr);
     mWrappedListener = nullptr;
   }
 
   mInitialized = false;
   return NS_OK;
 }
 
 nsresult
-MulticastDNSDeviceProvider::RegisterService()
-{
-  LOG_I("RegisterService: %s (%d)", mServiceName.get(), mDiscoverable);
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (!mDiscoverable) {
-    return NS_OK;
-  }
-
-  nsresult rv;
-
-  uint16_t servicePort;
-  if (NS_WARN_IF(NS_FAILED(rv = mPresentationService->GetPort(&servicePort)))) {
-    return rv;
-  }
-
-  /**
-    * If |servicePort| is non-zero, it means PresentationServer is running.
-    * Otherwise, we should make it start serving.
-    */
-  if (!servicePort) {
-    if (NS_WARN_IF(NS_FAILED(rv = mPresentationService->SetListener(mWrappedListener)))) {
-      return rv;
-    }
-    if (NS_WARN_IF(NS_FAILED(rv = mPresentationService->StartServer(0)))) {
-      return rv;
-    }
-    if (NS_WARN_IF(NS_FAILED(rv = mPresentationService->GetPort(&servicePort)))) {
-      return rv;
-    }
-  }
-
-  // Cancel on going service registration.
-  if (mRegisterRequest) {
-    mRegisterRequest->Cancel(NS_OK);
-    mRegisterRequest = nullptr;
-  }
-
-  /**
-   * Register the presentation control channel server as an mDNS service.
-   */
-  nsCOMPtr<nsIDNSServiceInfo> serviceInfo =
-    do_CreateInstance(DNSSERVICEINFO_CONTRACT_ID, &rv);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-  if (NS_WARN_IF(NS_FAILED(rv = serviceInfo->SetServiceType(
-      NS_LITERAL_CSTRING(SERVICE_TYPE))))) {
-    return rv;
-  }
-  if (NS_WARN_IF(NS_FAILED(rv = serviceInfo->SetServiceName(mServiceName)))) {
-    return rv;
-  }
-  if (NS_WARN_IF(NS_FAILED(rv = serviceInfo->SetPort(servicePort)))) {
-    return rv;
-  }
-
-  return mMulticastDNS->RegisterService(serviceInfo,
-                                        mWrappedListener,
-                                        getter_AddRefs(mRegisterRequest));
-}
-
-nsresult
-MulticastDNSDeviceProvider::UnregisterService(nsresult aReason)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (mRegisterRequest) {
-    mRegisterRequest->Cancel(aReason);
-    mRegisterRequest = nullptr;
-  }
-
-  if (mPresentationService) {
-    mPresentationService->SetListener(nullptr);
-    mPresentationService->Close();
-  }
-
-  return NS_OK;
-}
-
-nsresult
-MulticastDNSDeviceProvider::StopDiscovery(nsresult aReason)
+LegacyMDNSDeviceProvider::StopDiscovery(nsresult aReason)
 {
   LOG_I("StopDiscovery (0x%08x)", aReason);
 
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mDiscoveryTimer);
 
   Unused << mDiscoveryTimer->Cancel();
 
@@ -292,37 +192,35 @@ MulticastDNSDeviceProvider::StopDiscover
     mDiscoveryRequest->Cancel(aReason);
     mDiscoveryRequest = nullptr;
   }
 
   return NS_OK;
 }
 
 nsresult
-MulticastDNSDeviceProvider::RequestSession(Device* aDevice,
-                                           const nsAString& aUrl,
-                                           const nsAString& aPresentationId,
-                                           nsIPresentationControlChannel** aRetVal)
+LegacyMDNSDeviceProvider::Connect(Device* aDevice,
+                                  nsIPresentationControlChannel** aRetVal)
 {
   MOZ_ASSERT(aDevice);
   MOZ_ASSERT(mPresentationService);
 
   RefPtr<TCPDeviceInfo> deviceInfo = new TCPDeviceInfo(aDevice->Id(),
                                                        aDevice->Address(),
                                                        aDevice->Port());
 
-  return mPresentationService->RequestSession(deviceInfo, aUrl, aPresentationId, aRetVal);
+  return mPresentationService->Connect(deviceInfo, aRetVal);
 }
 
 nsresult
-MulticastDNSDeviceProvider::AddDevice(const nsACString& aId,
-                                      const nsACString& aServiceName,
-                                      const nsACString& aServiceType,
-                                      const nsACString& aAddress,
-                                      const uint16_t aPort)
+LegacyMDNSDeviceProvider::AddDevice(const nsACString& aId,
+                                    const nsACString& aServiceName,
+                                    const nsACString& aServiceType,
+                                    const nsACString& aAddress,
+                                    const uint16_t aPort)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mPresentationService);
 
   RefPtr<Device> device = new Device(aId, /* ID */
                                      aServiceName,
                                      aServiceType,
                                      aAddress,
@@ -336,21 +234,21 @@ MulticastDNSDeviceProvider::AddDevice(co
   }
 
   mDevices.AppendElement(device);
 
   return NS_OK;
 }
 
 nsresult
-MulticastDNSDeviceProvider::UpdateDevice(const uint32_t aIndex,
-                                         const nsACString& aServiceName,
-                                         const nsACString& aServiceType,
-                                         const nsACString& aAddress,
-                                         const uint16_t aPort)
+LegacyMDNSDeviceProvider::UpdateDevice(const uint32_t aIndex,
+                                       const nsACString& aServiceName,
+                                       const nsACString& aServiceType,
+                                       const nsACString& aAddress,
+                                       const uint16_t aPort)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mPresentationService);
 
   if (NS_WARN_IF(aIndex >= mDevices.Length())) {
     return NS_ERROR_INVALID_ARG;
   }
 
@@ -362,17 +260,17 @@ MulticastDNSDeviceProvider::UpdateDevice
   if (NS_SUCCEEDED(GetListener(getter_AddRefs(listener))) && listener) {
     Unused << listener->UpdateDevice(device);
   }
 
   return NS_OK;
 }
 
 nsresult
-MulticastDNSDeviceProvider::RemoveDevice(const uint32_t aIndex)
+LegacyMDNSDeviceProvider::RemoveDevice(const uint32_t aIndex)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mPresentationService);
 
   if (NS_WARN_IF(aIndex >= mDevices.Length())) {
     return NS_ERROR_INVALID_ARG;
   }
 
@@ -385,18 +283,18 @@ MulticastDNSDeviceProvider::RemoveDevice
   if (NS_SUCCEEDED(GetListener(getter_AddRefs(listener))) && listener) {
     Unused << listener->RemoveDevice(device);
   }
 
   return NS_OK;
 }
 
 bool
-MulticastDNSDeviceProvider::FindDeviceById(const nsACString& aId,
-                                           uint32_t& aIndex)
+LegacyMDNSDeviceProvider::FindDeviceById(const nsACString& aId,
+                                         uint32_t& aIndex)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   RefPtr<Device> device = new Device(aId,
                                      /* aName = */ EmptyCString(),
                                      /* aType = */ EmptyCString(),
                                      /* aHost = */ EmptyCString(),
                                      /* aPort = */ 0,
@@ -408,18 +306,18 @@ MulticastDNSDeviceProvider::FindDeviceBy
     return false;
   }
 
   aIndex = index;
   return true;
 }
 
 bool
-MulticastDNSDeviceProvider::FindDeviceByAddress(const nsACString& aAddress,
-                                                uint32_t& aIndex)
+LegacyMDNSDeviceProvider::FindDeviceByAddress(const nsACString& aAddress,
+                                              uint32_t& aIndex)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   RefPtr<Device> device = new Device(/* aId = */ EmptyCString(),
                                      /* aName = */ EmptyCString(),
                                      /* aType = */ EmptyCString(),
                                      aAddress,
                                      /* aPort = */ 0,
@@ -431,54 +329,54 @@ MulticastDNSDeviceProvider::FindDeviceBy
     return false;
   }
 
   aIndex = index;
   return true;
 }
 
 void
-MulticastDNSDeviceProvider::MarkAllDevicesUnknown()
+LegacyMDNSDeviceProvider::MarkAllDevicesUnknown()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   for (auto& device : mDevices) {
     device->ChangeState(DeviceState::eUnknown);
   }
 }
 
 void
-MulticastDNSDeviceProvider::ClearUnknownDevices()
+LegacyMDNSDeviceProvider::ClearUnknownDevices()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   size_t i = mDevices.Length();
   while (i > 0) {
     --i;
     if (mDevices[i]->State() == DeviceState::eUnknown) {
       NS_WARN_IF(NS_FAILED(RemoveDevice(i)));
     }
   }
 }
 
 void
-MulticastDNSDeviceProvider::ClearDevices()
+LegacyMDNSDeviceProvider::ClearDevices()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   size_t i = mDevices.Length();
   while (i > 0) {
     --i;
     NS_WARN_IF(NS_FAILED(RemoveDevice(i)));
   }
 }
 
 // nsIPresentationDeviceProvider
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::GetListener(nsIPresentationDeviceListener** aListener)
+LegacyMDNSDeviceProvider::GetListener(nsIPresentationDeviceListener** aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (NS_WARN_IF(!aListener)) {
     return NS_ERROR_INVALID_POINTER;
   }
 
   nsresult rv;
@@ -489,17 +387,17 @@ MulticastDNSDeviceProvider::GetListener(
   }
 
   listener.forget(aListener);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::SetListener(nsIPresentationDeviceListener* aListener)
+LegacyMDNSDeviceProvider::SetListener(nsIPresentationDeviceListener* aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   mDeviceListener = do_GetWeakReference(aListener);
 
   nsresult rv;
   if (mDeviceListener) {
     if (NS_WARN_IF(NS_FAILED(rv = Init()))) {
@@ -510,17 +408,17 @@ MulticastDNSDeviceProvider::SetListener(
       return rv;
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::ForceDiscovery()
+LegacyMDNSDeviceProvider::ForceDiscovery()
 {
   LOG_I("ForceDiscovery (%d)", mDiscoveryEnabled);
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!mDiscoveryEnabled) {
     return NS_OK;
   }
 
@@ -538,28 +436,28 @@ MulticastDNSDeviceProvider::ForceDiscove
         return rv;
     }
     return NS_OK;
   }
 
   StopDiscovery(NS_OK);
 
   if (NS_WARN_IF(NS_FAILED(rv = mMulticastDNS->StartDiscovery(
-      NS_LITERAL_CSTRING(SERVICE_TYPE),
+      NS_LITERAL_CSTRING(LEGACY_SERVICE_TYPE),
       mWrappedListener,
       getter_AddRefs(mDiscoveryRequest))))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 // nsIDNSServiceDiscoveryListener
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnDiscoveryStarted(const nsACString& aServiceType)
+LegacyMDNSDeviceProvider::OnDiscoveryStarted(const nsACString& aServiceType)
 {
   LOG_I("OnDiscoveryStarted");
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mDiscoveryTimer);
 
   MarkAllDevicesUnknown();
 
   nsresult rv;
@@ -570,30 +468,30 @@ MulticastDNSDeviceProvider::OnDiscoveryS
   }
 
   mIsDiscovering = true;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnDiscoveryStopped(const nsACString& aServiceType)
+LegacyMDNSDeviceProvider::OnDiscoveryStopped(const nsACString& aServiceType)
 {
   LOG_I("OnDiscoveryStopped");
   MOZ_ASSERT(NS_IsMainThread());
 
   ClearUnknownDevices();
 
   mIsDiscovering = false;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnServiceFound(nsIDNSServiceInfo* aServiceInfo)
+LegacyMDNSDeviceProvider::OnServiceFound(nsIDNSServiceInfo* aServiceInfo)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (NS_WARN_IF(!aServiceInfo)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsresult rv ;
@@ -611,17 +509,17 @@ MulticastDNSDeviceProvider::OnServiceFou
       return rv;
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnServiceLost(nsIDNSServiceInfo* aServiceInfo)
+LegacyMDNSDeviceProvider::OnServiceLost(nsIDNSServiceInfo* aServiceInfo)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (NS_WARN_IF(!aServiceInfo)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsresult rv;
@@ -647,103 +545,38 @@ MulticastDNSDeviceProvider::OnServiceLos
   if (NS_WARN_IF(NS_FAILED(rv = RemoveDevice(index)))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnStartDiscoveryFailed(const nsACString& aServiceType,
-                                                   int32_t aErrorCode)
+LegacyMDNSDeviceProvider::OnStartDiscoveryFailed(const nsACString& aServiceType,
+                                                 int32_t aErrorCode)
 {
   LOG_E("OnStartDiscoveryFailed: %d", aErrorCode);
   MOZ_ASSERT(NS_IsMainThread());
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnStopDiscoveryFailed(const nsACString& aServiceType,
-                                                  int32_t aErrorCode)
+LegacyMDNSDeviceProvider::OnStopDiscoveryFailed(const nsACString& aServiceType,
+                                                int32_t aErrorCode)
 {
   LOG_E("OnStopDiscoveryFailed: %d", aErrorCode);
   MOZ_ASSERT(NS_IsMainThread());
 
   return NS_OK;
 }
 
-// nsIDNSRegistrationListener
-NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnServiceRegistered(nsIDNSServiceInfo* aServiceInfo)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (NS_WARN_IF(!aServiceInfo)) {
-    return NS_ERROR_INVALID_ARG;
-  }
-  nsresult rv;
-
-  nsAutoCString name;
-  if (NS_WARN_IF(NS_FAILED(rv = aServiceInfo->GetServiceName(name)))) {
-    return rv;
-  }
-
-  LOG_I("OnServiceRegistered (%s)",  name.get());
-  mRegisteredName = name;
-
-  if (mMulticastDNS) {
-    if (NS_WARN_IF(NS_FAILED(rv = mMulticastDNS->ResolveService(
-        aServiceInfo, mWrappedListener)))) {
-      return rv;
-    }
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnServiceUnregistered(nsIDNSServiceInfo* aServiceInfo)
-{
-  LOG_I("OnServiceUnregistered");
-  MOZ_ASSERT(NS_IsMainThread());
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnRegistrationFailed(nsIDNSServiceInfo* aServiceInfo,
-                                                 int32_t aErrorCode)
-{
-  LOG_E("OnRegistrationFailed: %d", aErrorCode);
-  MOZ_ASSERT(NS_IsMainThread());
-
-  mRegisterRequest = nullptr;
-
-  if (aErrorCode == nsIDNSRegistrationListener::ERROR_SERVICE_NOT_RUNNING) {
-    return NS_DispatchToMainThread(
-             NewRunnableMethod(this, &MulticastDNSDeviceProvider::RegisterService));
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnUnregistrationFailed(nsIDNSServiceInfo* aServiceInfo,
-                                                   int32_t aErrorCode)
-{
-  LOG_E("OnUnregistrationFailed: %d", aErrorCode);
-  MOZ_ASSERT(NS_IsMainThread());
-
-  return NS_OK;
-}
-
 // nsIDNSServiceResolveListener
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnServiceResolved(nsIDNSServiceInfo* aServiceInfo)
+LegacyMDNSDeviceProvider::OnServiceResolved(nsIDNSServiceInfo* aServiceInfo)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (NS_WARN_IF(!aServiceInfo)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsresult rv;
@@ -755,26 +588,16 @@ MulticastDNSDeviceProvider::OnServiceRes
 
   LOG_I("OnServiceResolved: %s", serviceName.get());
 
   nsAutoCString host;
   if (NS_WARN_IF(NS_FAILED(rv = aServiceInfo->GetHost(host)))) {
     return rv;
   }
 
-  if (mRegisteredName == serviceName) {
-    LOG_I("ignore self");
-
-    if (NS_WARN_IF(NS_FAILED(rv = mPresentationService->SetId(host)))) {
-      return rv;
-    }
-
-    return NS_OK;
-  }
-
   nsAutoCString address;
   if (NS_WARN_IF(NS_FAILED(rv = aServiceInfo->GetAddress(address)))) {
     return rv;
   }
 
   uint16_t port;
   if (NS_WARN_IF(NS_FAILED(rv = aServiceInfo->GetPort(&port)))) {
     return rv;
@@ -799,218 +622,135 @@ MulticastDNSDeviceProvider::OnServiceRes
                      address,
                      port);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnResolveFailed(nsIDNSServiceInfo* aServiceInfo,
-                                            int32_t aErrorCode)
+LegacyMDNSDeviceProvider::OnResolveFailed(nsIDNSServiceInfo* aServiceInfo,
+                                          int32_t aErrorCode)
 {
   LOG_E("OnResolveFailed: %d", aErrorCode);
   MOZ_ASSERT(NS_IsMainThread());
 
   return NS_OK;
 }
 
-// nsIPresentationControlServerListener
-NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnPortChange(uint16_t aPort)
-{
-  LOG_I("OnPortChange: %d", aPort);
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (mDiscoverable) {
-    RegisterService();
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnSessionRequest(nsITCPDeviceInfo* aDeviceInfo,
-                                             const nsAString& aUrl,
-                                             const nsAString& aPresentationId,
-                                             nsIPresentationControlChannel* aControlChannel)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsAutoCString address;
-  Unused << aDeviceInfo->GetAddress(address);
-
-  LOG_I("OnSessionRequest: %s", address.get());
-
-  RefPtr<Device> device;
-  uint32_t index;
-  if (FindDeviceByAddress(address, index)) {
-    device = mDevices[index];
-  } else {
-    // create a one-time device object for non-discoverable controller
-    // this device will not be listed in available device list and cannot
-    // be used for requesting session.
-    nsAutoCString id;
-    Unused << aDeviceInfo->GetId(id);
-    uint16_t port;
-    Unused << aDeviceInfo->GetPort(&port);
-
-    device = new Device(id,
-                        /* aName = */ id,
-                        /* aType = */ EmptyCString(),
-                        address,
-                        port,
-                        DeviceState::eActive,
-                        /* aProvider = */ nullptr);
-  }
-
-  nsCOMPtr<nsIPresentationDeviceListener> listener;
-  if (NS_SUCCEEDED(GetListener(getter_AddRefs(listener))) && listener) {
-    Unused << listener->OnSessionRequest(device, aUrl, aPresentationId,
-                                         aControlChannel);
-  }
-
-  return NS_OK;
-}
-
 // nsIObserver
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::Observe(nsISupports* aSubject,
-                                    const char* aTopic,
-                                    const char16_t* aData)
+LegacyMDNSDeviceProvider::Observe(nsISupports* aSubject,
+                                  const char* aTopic,
+                                  const char16_t* aData)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   NS_ConvertUTF16toUTF8 data(aData);
   LOG_I("Observe: topic = %s, data = %s", aTopic, data.get());
 
   if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
-    if (data.EqualsLiteral(PREF_PRESENTATION_DISCOVERY)) {
-      OnDiscoveryChanged(Preferences::GetBool(PREF_PRESENTATION_DISCOVERY));
+    if (data.EqualsLiteral(PREF_PRESENTATION_DISCOVERY_LEGACY)) {
+      OnDiscoveryChanged(Preferences::GetBool(PREF_PRESENTATION_DISCOVERY_LEGACY));
     } else if (data.EqualsLiteral(PREF_PRESENTATION_DISCOVERY_TIMEOUT_MS)) {
       OnDiscoveryTimeoutChanged(Preferences::GetUint(PREF_PRESENTATION_DISCOVERY_TIMEOUT_MS));
-    } else if (data.EqualsLiteral(PREF_PRESENTATION_DISCOVERABLE)) {
-      OnDiscoverableChanged(Preferences::GetBool(PREF_PRESENTATION_DISCOVERABLE));
     } else if (data.EqualsLiteral(PREF_PRESENTATION_DEVICE_NAME)) {
       nsAdoptingCString newServiceName = Preferences::GetCString(PREF_PRESENTATION_DEVICE_NAME);
       if (!mServiceName.Equals(newServiceName)) {
         OnServiceNameChanged(newServiceName);
       }
     }
   } else if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) {
     StopDiscovery(NS_OK);
   }
 
   return NS_OK;
 }
 
 nsresult
-MulticastDNSDeviceProvider::OnDiscoveryChanged(bool aEnabled)
+LegacyMDNSDeviceProvider::OnDiscoveryChanged(bool aEnabled)
 {
   LOG_I("DiscoveryEnabled = %d\n", aEnabled);
   MOZ_ASSERT(NS_IsMainThread());
 
   mDiscoveryEnabled = aEnabled;
 
   if (mDiscoveryEnabled) {
     return ForceDiscovery();
   }
 
   return StopDiscovery(NS_OK);
 }
 
 nsresult
-MulticastDNSDeviceProvider::OnDiscoveryTimeoutChanged(uint32_t aTimeoutMs)
+LegacyMDNSDeviceProvider::OnDiscoveryTimeoutChanged(uint32_t aTimeoutMs)
 {
   LOG_I("OnDiscoveryTimeoutChanged = %d\n", aTimeoutMs);
   MOZ_ASSERT(NS_IsMainThread());
 
   mDiscoveryTimeoutMs = aTimeoutMs;
 
   return NS_OK;
 }
 
 nsresult
-MulticastDNSDeviceProvider::OnDiscoverableChanged(bool aEnabled)
-{
-  LOG_I("Discoverable = %d\n", aEnabled);
-  MOZ_ASSERT(NS_IsMainThread());
-
-  mDiscoverable = aEnabled;
-
-  if (mDiscoverable) {
-    return RegisterService();
-  }
-
-  return UnregisterService(NS_OK);
-}
-
-nsresult
-MulticastDNSDeviceProvider::OnServiceNameChanged(const nsACString& aServiceName)
+LegacyMDNSDeviceProvider::OnServiceNameChanged(const nsACString& aServiceName)
 {
   LOG_I("serviceName = %s\n", PromiseFlatCString(aServiceName).get());
   MOZ_ASSERT(NS_IsMainThread());
 
   mServiceName = aServiceName;
-
-  nsresult rv;
-  if (NS_WARN_IF(NS_FAILED(rv = UnregisterService(NS_OK)))) {
-    return rv;
-  }
-
-  if (mDiscoverable) {
-    return RegisterService();
-  }
+  mPresentationService->SetId(mServiceName);
 
   return NS_OK;
 }
 
-// MulticastDNSDeviceProvider::Device
-NS_IMPL_ISUPPORTS(MulticastDNSDeviceProvider::Device,
+// LegacyMDNSDeviceProvider::Device
+NS_IMPL_ISUPPORTS(LegacyMDNSDeviceProvider::Device,
                   nsIPresentationDevice)
 
 // nsIPresentationDevice
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::Device::GetId(nsACString& aId)
+LegacyMDNSDeviceProvider::Device::GetId(nsACString& aId)
 {
   aId = mId;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::Device::GetName(nsACString& aName)
+LegacyMDNSDeviceProvider::Device::GetName(nsACString& aName)
 {
   aName = mName;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::Device::GetType(nsACString& aType)
+LegacyMDNSDeviceProvider::Device::GetType(nsACString& aType)
 {
   aType = mType;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::Device::EstablishControlChannel(const nsAString& aUrl,
-                                                            const nsAString& aPresentationId,
-                                                            nsIPresentationControlChannel** aRetVal)
+LegacyMDNSDeviceProvider::Device::EstablishControlChannel(
+                                        nsIPresentationControlChannel** aRetVal)
 {
   if (!mProvider) {
     return NS_ERROR_FAILURE;
   }
 
-  return mProvider->RequestSession(this, aUrl, aPresentationId, aRetVal);
+  return mProvider->Connect(this, aRetVal);
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::Device::Disconnect()
+LegacyMDNSDeviceProvider::Device::Disconnect()
 {
   // No need to do anything when disconnect.
   return NS_OK;
 }
 
+} // namespace legacy
 } // namespace presentation
 } // namespace dom
 } // namespace mozilla
copy from dom/presentation/provider/MulticastDNSDeviceProvider.h
copy to dom/presentation/provider/LegacyMDNSDeviceProvider.h
--- a/dom/presentation/provider/MulticastDNSDeviceProvider.h
+++ b/dom/presentation/provider/LegacyMDNSDeviceProvider.h
@@ -1,15 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifndef mozilla_dom_presentation_provider_MulticastDNSDeviceProvider_h
-#define mozilla_dom_presentation_provider_MulticastDNSDeviceProvider_h
+#ifndef mozilla_dom_presentation_provider_LegacyMDNSDeviceProvider_h
+#define mozilla_dom_presentation_provider_LegacyMDNSDeviceProvider_h
 
 #include "mozilla/RefPtr.h"
 #include "nsCOMPtr.h"
 #include "nsICancelable.h"
 #include "nsIDNSServiceDiscovery.h"
 #include "nsIObserver.h"
 #include "nsIPresentationDevice.h"
 #include "nsIPresentationDeviceProvider.h"
@@ -17,38 +17,35 @@
 #include "nsITimer.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsWeakPtr.h"
 
 namespace mozilla {
 namespace dom {
 namespace presentation {
+namespace legacy {
 
 class DNSServiceWrappedListener;
 class MulticastDNSService;
 
-class MulticastDNSDeviceProvider final
+class LegacyMDNSDeviceProvider final
   : public nsIPresentationDeviceProvider
   , public nsIDNSServiceDiscoveryListener
-  , public nsIDNSRegistrationListener
   , public nsIDNSServiceResolveListener
-  , public nsIPresentationControlServerListener
   , public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPRESENTATIONDEVICEPROVIDER
   NS_DECL_NSIDNSSERVICEDISCOVERYLISTENER
-  NS_DECL_NSIDNSREGISTRATIONLISTENER
   NS_DECL_NSIDNSSERVICERESOLVELISTENER
-  NS_DECL_NSIPRESENTATIONCONTROLSERVERLISTENER
   NS_DECL_NSIOBSERVER
 
-  explicit MulticastDNSDeviceProvider() = default;
+  explicit LegacyMDNSDeviceProvider() = default;
   nsresult Init();
   nsresult Uninit();
 
 private:
   enum class DeviceState : uint32_t {
     eUnknown,
     eActive
   };
@@ -60,17 +57,17 @@ private:
     NS_DECL_NSIPRESENTATIONDEVICE
 
     explicit Device(const nsACString& aId,
                     const nsACString& aName,
                     const nsACString& aType,
                     const nsACString& aAddress,
                     const uint16_t aPort,
                     DeviceState aState,
-                    MulticastDNSDeviceProvider* aProvider)
+                    LegacyMDNSDeviceProvider* aProvider)
       : mId(aId)
       , mName(aName)
       , mType(aType)
       , mAddress(aAddress)
       , mPort(aPort)
       , mState(aState)
       , mProvider(aProvider)
     {
@@ -116,39 +113,35 @@ private:
     virtual ~Device() = default;
 
     nsCString mId;
     nsCString mName;
     nsCString mType;
     nsCString mAddress;
     uint16_t mPort;
     DeviceState mState;
-    MulticastDNSDeviceProvider* mProvider;
+    LegacyMDNSDeviceProvider* mProvider;
   };
 
   struct DeviceIdComparator {
     bool Equals(const RefPtr<Device>& aA, const RefPtr<Device>& aB) const {
       return aA->Id() == aB->Id();
     }
   };
 
   struct DeviceAddressComparator {
     bool Equals(const RefPtr<Device>& aA, const RefPtr<Device>& aB) const {
       return aA->Address() == aB->Address();
     }
   };
 
-  virtual ~MulticastDNSDeviceProvider();
-  nsresult RegisterService();
-  nsresult UnregisterService(nsresult aReason);
+  virtual ~LegacyMDNSDeviceProvider();
   nsresult StopDiscovery(nsresult aReason);
-  nsresult RequestSession(Device* aDevice,
-                          const nsAString& aUrl,
-                          const nsAString& aPresentationId,
-                          nsIPresentationControlChannel** aRetVal);
+  nsresult Connect(Device* aDevice,
+                   nsIPresentationControlChannel** aRetVal);
 
   // device manipulation
   nsresult AddDevice(const nsACString& aId,
                      const nsACString& aServiceName,
                      const nsACString& aServiceType,
                      const nsACString& aAddress,
                      const uint16_t aPort);
   nsresult UpdateDevice(const uint32_t aIndex,
@@ -165,38 +158,34 @@ private:
 
   void MarkAllDevicesUnknown();
   void ClearUnknownDevices();
   void ClearDevices();
 
   // preferences
   nsresult OnDiscoveryChanged(bool aEnabled);
   nsresult OnDiscoveryTimeoutChanged(uint32_t aTimeoutMs);
-  nsresult OnDiscoverableChanged(bool aEnabled);
   nsresult OnServiceNameChanged(const nsACString& aServiceName);
 
   bool mInitialized = false;
   nsWeakPtr mDeviceListener;
   nsCOMPtr<nsIPresentationControlService> mPresentationService;
   nsCOMPtr<nsIDNSServiceDiscovery> mMulticastDNS;
   RefPtr<DNSServiceWrappedListener> mWrappedListener;
 
   nsCOMPtr<nsICancelable> mDiscoveryRequest;
-  nsCOMPtr<nsICancelable> mRegisterRequest;
 
   nsTArray<RefPtr<Device>> mDevices;
 
   bool mDiscoveryEnabled = false;
   bool mIsDiscovering = false;
   uint32_t mDiscoveryTimeoutMs;
   nsCOMPtr<nsITimer> mDiscoveryTimer;
 
-  bool mDiscoverable = false;
-
   nsCString mServiceName;
-  nsCString mRegisteredName;
 };
 
+} // namespace legacy
 } // namespace presentation
 } // namespace dom
 } // namespace mozilla
 
-#endif // mozilla_dom_presentation_provider_MulticastDNSDeviceProvider_h
+#endif // mozilla_dom_presentation_provider_LegacyMDNSDeviceProvider_h
new file mode 100644
--- /dev/null
+++ b/dom/presentation/provider/LegacyPresentationControlService.js
@@ -0,0 +1,476 @@
+/* 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/. */
+/* jshint esnext:true, globalstrict:true, moz:true, undef:true, unused:true */
+/* globals Components, dump */
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+/* globals XPCOMUtils */
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+/* globals Services */
+Cu.import("resource://gre/modules/Services.jsm");
+/* globals NetUtil */
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+const DEBUG = Services.prefs.getBoolPref("dom.presentation.tcp_server.debug");
+function log(aMsg) {
+  dump("-*- LegacyPresentationControlService.js: " + aMsg + "\n");
+}
+
+function LegacyPresentationControlService() {
+  DEBUG && log("LegacyPresentationControlService - ctor"); //jshint ignore:line
+  this._id = null;
+}
+
+LegacyPresentationControlService.prototype = {
+  startServer: function() {
+    DEBUG && log("LegacyPresentationControlService - doesn't support receiver mode"); //jshint ignore:line
+    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+  },
+
+  get id() {
+    return this._id;
+  },
+
+  set id(aId) {
+    this._id = aId;
+  },
+
+  get port() {
+    return 0;
+  },
+
+  get version() {
+    return 0;
+  },
+
+  set listener(aListener) { //jshint ignore:line
+    DEBUG && log("LegacyPresentationControlService - doesn't support receiver mode"); //jshint ignore:line
+    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+  },
+
+  get listener() {
+    return null;
+  },
+
+  connect: function(aDeviceInfo) {
+    if (!this.id) {
+      DEBUG && log("LegacyPresentationControlService - Id has not initialized; requestSession fails"); //jshint ignore:line
+      return null;
+    }
+    DEBUG && log("LegacyPresentationControlService - requestSession to " + aDeviceInfo.id); //jshint ignore:line
+
+    let sts = Cc["@mozilla.org/network/socket-transport-service;1"]
+                .getService(Ci.nsISocketTransportService);
+
+    let socketTransport;
+    try {
+      socketTransport = sts.createTransport(null,
+                                            0,
+                                            aDeviceInfo.address,
+                                            aDeviceInfo.port,
+                                            null);
+    } catch (e) {
+      DEBUG && log("LegacyPresentationControlService - createTransport throws: " + e); //jshint ignore:line
+      // Pop the exception to |TCPDevice.establishControlChannel|
+      throw Cr.NS_ERROR_FAILURE;
+    }
+    return new LegacyTCPControlChannel(this.id,
+                                       socketTransport,
+                                       aDeviceInfo);
+  },
+
+  close: function() {
+    DEBUG && log("LegacyPresentationControlService - close"); //jshint ignore:line
+  },
+
+  classID: Components.ID("{b21816fe-8aff-4811-86d2-85a7444c557e}"),
+  QueryInterface : XPCOMUtils.generateQI([Ci.nsIPresentationControlService]),
+};
+
+function ChannelDescription(aInit) {
+  this._type = aInit.type;
+  switch (this._type) {
+    case Ci.nsIPresentationChannelDescription.TYPE_TCP:
+      this._tcpAddresses = Cc["@mozilla.org/array;1"]
+                           .createInstance(Ci.nsIMutableArray);
+      for (let address of aInit.tcpAddress) {
+        let wrapper = Cc["@mozilla.org/supports-cstring;1"]
+                      .createInstance(Ci.nsISupportsCString);
+        wrapper.data = address;
+        this._tcpAddresses.appendElement(wrapper, false);
+      }
+
+      this._tcpPort = aInit.tcpPort;
+      break;
+    case Ci.nsIPresentationChannelDescription.TYPE_DATACHANNEL:
+      this._dataChannelSDP = aInit.dataChannelSDP;
+      break;
+  }
+}
+
+ChannelDescription.prototype = {
+  _type: 0,
+  _tcpAddresses: null,
+  _tcpPort: 0,
+  _dataChannelSDP: "",
+
+  get type() {
+    return this._type;
+  },
+
+  get tcpAddress() {
+    return this._tcpAddresses;
+  },
+
+  get tcpPort() {
+    return this._tcpPort;
+  },
+
+  get dataChannelSDP() {
+    return this._dataChannelSDP;
+  },
+
+  classID: Components.ID("{d69fc81c-4f40-47a3-97e6-b4cf5db2294e}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationChannelDescription]),
+};
+
+// Helper function: transfer nsIPresentationChannelDescription to json
+function discriptionAsJson(aDescription) {
+  let json = {};
+  json.type = aDescription.type;
+  switch(aDescription.type) {
+    case Ci.nsIPresentationChannelDescription.TYPE_TCP:
+      let addresses = aDescription.tcpAddress.QueryInterface(Ci.nsIArray);
+      json.tcpAddress = [];
+      for (let idx = 0; idx < addresses.length; idx++) {
+        let address = addresses.queryElementAt(idx, Ci.nsISupportsCString);
+        json.tcpAddress.push(address.data);
+      }
+      json.tcpPort = aDescription.tcpPort;
+      break;
+    case Ci.nsIPresentationChannelDescription.TYPE_DATACHANNEL:
+      json.dataChannelSDP = aDescription.dataChannelSDP;
+      break;
+  }
+  return json;
+}
+
+function LegacyTCPControlChannel(id,
+                                 transport,
+                                 deviceInfo) {
+  DEBUG && log("create LegacyTCPControlChannel"); //jshint ignore:line
+  this._deviceInfo = deviceInfo;
+  this._transport = transport;
+
+  this._id = id;
+
+  let currentThread = Services.tm.currentThread;
+  transport.setEventSink(this, currentThread);
+
+  this._input = this._transport.openInputStream(0, 0, 0)
+                               .QueryInterface(Ci.nsIAsyncInputStream);
+  this._input.asyncWait(this.QueryInterface(Ci.nsIStreamListener),
+                        Ci.nsIAsyncInputStream.WAIT_CLOSURE_ONLY,
+                        0,
+                        currentThread);
+
+  this._output = this._transport
+                     .openOutputStream(Ci.nsITransport.OPEN_UNBUFFERED, 0, 0);
+}
+
+LegacyTCPControlChannel.prototype = {
+  _connected: false,
+  _pendingOpen: false,
+  _pendingAnswer: null,
+  _pendingClose: null,
+  _pendingCloseReason: null,
+
+  _sendMessage: function(aJSONData, aOnThrow) {
+    if (!aOnThrow) {
+      aOnThrow = function(e) {throw e.result;};
+    }
+
+    if (!aJSONData) {
+      aOnThrow();
+      return;
+    }
+
+    if (!this._connected) {
+      DEBUG && log("LegacyTCPControlChannel - send" + aJSONData.type + " fails"); //jshint ignore:line
+      throw Cr.NS_ERROR_FAILURE;
+    }
+
+    try {
+      this._send(aJSONData);
+    } catch (e) {
+      aOnThrow(e);
+    }
+  },
+
+  _sendInit: function() {
+    let msg = {
+      type: "requestSession:Init",
+      presentationId: this._presentationId,
+      url: this._url,
+      id: this._id,
+    };
+
+    this._sendMessage(msg, function(e) {
+      this.disconnect();
+      this._notifyDisconnected(e.result);
+    });
+  },
+
+  launch: function(aPresentationId, aUrl) {
+    this._presentationId = aPresentationId;
+    this._url = aUrl;
+
+    this._sendInit();
+  },
+
+  sendOffer: function(aOffer) {
+    let msg = {
+      type: "requestSession:Offer",
+      presentationId: this._presentationId,
+      offer: discriptionAsJson(aOffer),
+    };
+    this._sendMessage(msg);
+  },
+
+  sendAnswer: function(aAnswer) { //jshint ignore:line
+    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+  },
+
+  sendIceCandidate: function(aCandidate) {
+    let msg = {
+      type: "requestSession:IceCandidate",
+      presentationId: this._presentationId,
+      iceCandidate: aCandidate,
+    };
+    this._sendMessage(msg);
+  },
+  // may throw an exception
+  _send: function(aMsg) {
+    DEBUG && log("LegacyTCPControlChannel - Send: " + JSON.stringify(aMsg, null, 2)); //jshint ignore:line
+
+    /**
+     * XXX In TCP streaming, it is possible that more than one message in one
+     * TCP packet. We use line delimited JSON to identify where one JSON encoded
+     * object ends and the next begins. Therefore, we do not allow newline
+     * characters whithin the whole message, and add a newline at the end.
+     * Please see the parser code in |onDataAvailable|.
+     */
+    let message = JSON.stringify(aMsg).replace(["\n"], "") + "\n";
+    try {
+      this._output.write(message, message.length);
+    } catch(e) {
+      DEBUG && log("LegacyTCPControlChannel - Failed to send message: " + e.name); //jshint ignore:line
+      throw e;
+    }
+  },
+
+  // nsIAsyncInputStream (Triggered by nsIInputStream.asyncWait)
+  // Only used for detecting connection refused
+  onInputStreamReady: function(aStream) {
+    try {
+      aStream.available();
+    } catch (e) {
+      DEBUG && log("LegacyTCPControlChannel - onInputStreamReady error: " + e.name); //jshint ignore:line
+      // NS_ERROR_CONNECTION_REFUSED
+      this._listener.notifyDisconnected(e.result);
+    }
+  },
+
+  // nsITransportEventSink (Triggered by nsISocketTransport.setEventSink)
+  onTransportStatus: function(aTransport, aStatus, aProg, aProgMax) { //jshint ignore:line
+    DEBUG && log("LegacyTCPControlChannel - onTransportStatus: "
+                 + aStatus.toString(16)); //jshint ignore:line
+    if (aStatus === Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
+      this._connected = true;
+
+      if (!this._pump) {
+        this._createInputStreamPump();
+      }
+
+      this._notifyConnected();
+    }
+  },
+
+  // nsIRequestObserver (Triggered by nsIInputStreamPump.asyncRead)
+  onStartRequest: function() {
+    DEBUG && log("LegacyTCPControlChannel - onStartRequest"); //jshint ignore:line
+  },
+
+  // nsIRequestObserver (Triggered by nsIInputStreamPump.asyncRead)
+  onStopRequest: function(aRequest, aContext, aStatus) {
+    DEBUG && log("LegacyTCPControlChannel - onStopRequest: " + aStatus); //jshint ignore:line
+    this.disconnect(aStatus);
+    this._notifyDisconnected(aStatus);
+  },
+
+  // nsIStreamListener (Triggered by nsIInputStreamPump.asyncRead)
+  onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) { //jshint ignore:line
+    let data = NetUtil.readInputStreamToString(aInputStream,
+                                               aInputStream.available());
+    DEBUG && log("LegacyTCPControlChannel - onDataAvailable: " + data); //jshint ignore:line
+
+    // Parser of line delimited JSON. Please see |_send| for more informaiton.
+    let jsonArray = data.split("\n");
+    jsonArray.pop();
+    for (let json of jsonArray) {
+      let msg;
+      try {
+        msg = JSON.parse(json);
+      } catch (e) {
+        DEBUG && log("LegacyTCPSignalingChannel - error in parsing json: " + e); //jshint ignore:line
+      }
+
+      this._handleMessage(msg);
+    }
+  },
+
+  _createInputStreamPump: function() {
+    DEBUG && log("LegacyTCPControlChannel - create pump"); //jshint ignore:line
+    this._pump = Cc["@mozilla.org/network/input-stream-pump;1"].
+               createInstance(Ci.nsIInputStreamPump);
+    this._pump.init(this._input, -1, -1, 0, 0, false);
+    this._pump.asyncRead(this, null);
+  },
+
+  // Handle command from remote side
+  _handleMessage: function(aMsg) {
+    DEBUG && log("LegacyTCPControlChannel - handleMessage from "
+                 + JSON.stringify(this._deviceInfo) + ": " + JSON.stringify(aMsg)); //jshint ignore:line
+    switch (aMsg.type) {
+      case "requestSession:Answer": {
+        this._onAnswer(aMsg.answer);
+        break;
+      }
+      case "requestSession:IceCandidate": {
+        this._listener.onIceCandidate(aMsg.iceCandidate);
+        break;
+      }
+      case "requestSession:CloseReason": {
+        this._pendingCloseReason = aMsg.reason;
+        break;
+      }
+    }
+  },
+
+  get listener() {
+    return this._listener;
+  },
+
+  set listener(aListener) {
+    DEBUG && log("LegacyTCPControlChannel - set listener: " + aListener); //jshint ignore:line
+    if (!aListener) {
+      this._listener = null;
+      return;
+    }
+
+    this._listener = aListener;
+    if (this._pendingOpen) {
+      this._pendingOpen = false;
+      DEBUG && log("LegacyTCPControlChannel - notify pending opened"); //jshint ignore:line
+      this._listener.notifyConnected();
+    }
+
+    if (this._pendingAnswer) {
+      let answer = this._pendingAnswer;
+      DEBUG && log("LegacyTCPControlChannel - notify pending answer: " +
+                   JSON.stringify(answer)); // jshint ignore:line
+      this._listener.onAnswer(new ChannelDescription(answer));
+      this._pendingAnswer = null;
+    }
+
+    if (this._pendingClose) {
+      DEBUG && log("LegacyTCPControlChannel - notify pending closed"); //jshint ignore:line
+      this._notifyDisconnected(this._pendingCloseReason);
+      this._pendingClose = null;
+    }
+  },
+
+  /**
+   * These functions are designed to handle the interaction with listener
+   * appropriately. |_FUNC| is to handle |this._listener.FUNC|.
+   */
+  _onAnswer: function(aAnswer) {
+    if (!this._connected) {
+      return;
+    }
+    if (!this._listener) {
+      this._pendingAnswer = aAnswer;
+      return;
+    }
+    DEBUG && log("LegacyTCPControlChannel - notify answer: " + JSON.stringify(aAnswer)); //jshint ignore:line
+    this._listener.onAnswer(new ChannelDescription(aAnswer));
+  },
+
+  _notifyConnected: function() {
+    this._connected = true;
+    this._pendingClose = false;
+    this._pendingCloseReason = Cr.NS_OK;
+
+    if (!this._listener) {
+      this._pendingOpen = true;
+      return;
+    }
+
+    DEBUG && log("LegacyTCPControlChannel - notify opened"); //jshint ignore:line
+    this._listener.notifyConnected();
+  },
+
+  _notifyDisconnected: function(aReason) {
+    this._connected = false;
+    this._pendingOpen = false;
+    this._pendingAnswer = null;
+
+    // Remote endpoint closes the control channel with abnormal reason.
+    if (aReason == Cr.NS_OK && this._pendingCloseReason != Cr.NS_OK) {
+      aReason = this._pendingCloseReason;
+    }
+
+    if (!this._listener) {
+      this._pendingClose = true;
+      this._pendingCloseReason = aReason;
+      return;
+    }
+
+    DEBUG && log("LegacyTCPControlChannel - notify closed"); //jshint ignore:line
+    this._listener.notifyDisconnected(aReason);
+  },
+
+  disconnect: function(aReason) {
+    DEBUG && log("LegacyTCPControlChannel - close with reason: " + aReason); //jshint ignore:line
+
+    if (this._connected) {
+      // default reason is NS_OK
+      if (typeof aReason !== "undefined" && aReason !== Cr.NS_OK) {
+        let msg = {
+          type: "requestSession:CloseReason",
+          presentationId: this._presentationId,
+          reason: aReason,
+        };
+        this._sendMessage(msg);
+        this._pendingCloseReason = aReason;
+      }
+
+      this._transport.setEventSink(null, null);
+      this._pump = null;
+
+      this._input.close();
+      this._output.close();
+
+      this._connected = false;
+    }
+  },
+
+  classID: Components.ID("{4027ce3d-06e3-4d06-a235-df329cb0d411}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel,
+                                         Ci.nsIStreamListener]),
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([LegacyPresentationControlService]); //jshint ignore:line
new file mode 100644
--- /dev/null
+++ b/dom/presentation/provider/LegacyProviders.manifest
@@ -0,0 +1,2 @@
+component {b21816fe-8aff-4811-86d2-85a7444c557e} LegacyPresentationControlService.js
+contract @mozilla.org/presentation/legacy-control-service;1 {b21816fe-8aff-4811-86d2-85a7444c557e}
--- a/dom/presentation/provider/MulticastDNSDeviceProvider.cpp
+++ b/dom/presentation/provider/MulticastDNSDeviceProvider.cpp
@@ -6,30 +6,32 @@
 #include "MulticastDNSDeviceProvider.h"
 #include "MainThreadUtils.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 #include "nsComponentManagerUtils.h"
 #include "nsIObserverService.h"
+#include "nsIWritablePropertyBag2.h"
 #include "nsServiceManagerUtils.h"
 #include "nsTCPDeviceInfo.h"
 #include "nsThreadUtils.h"
 
 #ifdef MOZ_WIDGET_ANDROID
 #include "nsIPropertyBag2.h"
 #endif // MOZ_WIDGET_ANDROID
 
 #define PREF_PRESENTATION_DISCOVERY "dom.presentation.discovery.enabled"
 #define PREF_PRESENTATION_DISCOVERY_TIMEOUT_MS "dom.presentation.discovery.timeout_ms"
 #define PREF_PRESENTATION_DISCOVERABLE "dom.presentation.discoverable"
 #define PREF_PRESENTATION_DEVICE_NAME "dom.presentation.device.name"
 
-#define SERVICE_TYPE "_mozilla_papi._tcp."
+#define SERVICE_TYPE "_presentation-ctrl._tcp"
+#define PROTOCOL_VERSION_TAG "version"
 
 static mozilla::LazyLogModule sMulticastDNSProviderLogModule("MulticastDNSDeviceProvider");
 
 #undef LOG_I
 #define LOG_I(...) MOZ_LOG(sMulticastDNSProviderLogModule, mozilla::LogLevel::Debug, (__VA_ARGS__))
 #undef LOG_E
 #define LOG_E(...) MOZ_LOG(sMulticastDNSProviderLogModule, mozilla::LogLevel::Error, (__VA_ARGS__))
 
@@ -250,16 +252,31 @@ MulticastDNSDeviceProvider::RegisterServ
   }
   if (NS_WARN_IF(NS_FAILED(rv = serviceInfo->SetServiceName(mServiceName)))) {
     return rv;
   }
   if (NS_WARN_IF(NS_FAILED(rv = serviceInfo->SetPort(servicePort)))) {
     return rv;
   }
 
+  nsCOMPtr<nsIWritablePropertyBag2> propBag =
+    do_CreateInstance("@mozilla.org/hash-property-bag;1");
+  MOZ_ASSERT(propBag);
+
+  uint32_t version;
+  rv = mPresentationService->GetVersion(&version);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+  rv = propBag->SetPropertyAsUint32(NS_LITERAL_STRING(PROTOCOL_VERSION_TAG),
+                                    version);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+  if (NS_WARN_IF(NS_FAILED(rv = serviceInfo->SetAttributes(propBag)))) {
+    return rv;
+  }
+
   return mMulticastDNS->RegisterService(serviceInfo,
                                         mWrappedListener,
                                         getter_AddRefs(mRegisterRequest));
 }
 
 nsresult
 MulticastDNSDeviceProvider::UnregisterService(nsresult aReason)
 {
@@ -292,29 +309,53 @@ MulticastDNSDeviceProvider::StopDiscover
     mDiscoveryRequest->Cancel(aReason);
     mDiscoveryRequest = nullptr;
   }
 
   return NS_OK;
 }
 
 nsresult
-MulticastDNSDeviceProvider::RequestSession(Device* aDevice,
-                                           const nsAString& aUrl,
-                                           const nsAString& aPresentationId,
-                                           nsIPresentationControlChannel** aRetVal)
+MulticastDNSDeviceProvider::Connect(Device* aDevice,
+                                    nsIPresentationControlChannel** aRetVal)
 {
   MOZ_ASSERT(aDevice);
   MOZ_ASSERT(mPresentationService);
 
   RefPtr<TCPDeviceInfo> deviceInfo = new TCPDeviceInfo(aDevice->Id(),
                                                        aDevice->Address(),
                                                        aDevice->Port());
 
-  return mPresentationService->RequestSession(deviceInfo, aUrl, aPresentationId, aRetVal);
+  return mPresentationService->Connect(deviceInfo, aRetVal);
+}
+
+bool
+MulticastDNSDeviceProvider::IsCompatibleServer(nsIDNSServiceInfo* aServiceInfo)
+{
+  MOZ_ASSERT(aServiceInfo);
+
+  nsCOMPtr<nsIPropertyBag2> propBag;
+  if (NS_WARN_IF(NS_FAILED(
+          aServiceInfo->GetAttributes(getter_AddRefs(propBag)))) || !propBag) {
+    return false;
+  }
+
+  uint32_t remoteVersion;
+  if (NS_WARN_IF(NS_FAILED(
+          propBag->GetPropertyAsUint32(NS_LITERAL_STRING(PROTOCOL_VERSION_TAG),
+                                       &remoteVersion)))) {
+    return false;
+  }
+
+  bool isCompatible = false;
+  Unused << NS_WARN_IF(NS_FAILED(
+                mPresentationService->IsCompatibleServer(remoteVersion,
+                                                         &isCompatible)));
+
+  return isCompatible;
 }
 
 nsresult
 MulticastDNSDeviceProvider::AddDevice(const nsACString& aId,
                                       const nsACString& aServiceName,
                                       const nsACString& aServiceType,
                                       const nsACString& aAddress,
                                       const uint16_t aPort)
@@ -765,16 +806,21 @@ MulticastDNSDeviceProvider::OnServiceRes
 
     if (NS_WARN_IF(NS_FAILED(rv = mPresentationService->SetId(host)))) {
       return rv;
     }
 
     return NS_OK;
   }
 
+  if (!IsCompatibleServer(aServiceInfo)) {
+    LOG_I("ignore incompatible service: %s", serviceName.get());
+    return NS_OK;
+  }
+
   nsAutoCString address;
   if (NS_WARN_IF(NS_FAILED(rv = aServiceInfo->GetAddress(address)))) {
     return rv;
   }
 
   uint16_t port;
   if (NS_WARN_IF(NS_FAILED(rv = aServiceInfo->GetPort(&port)))) {
     return rv;
@@ -988,25 +1034,24 @@ NS_IMETHODIMP
 MulticastDNSDeviceProvider::Device::GetType(nsACString& aType)
 {
   aType = mType;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::Device::EstablishControlChannel(const nsAString& aUrl,
-                                                            const nsAString& aPresentationId,
-                                                            nsIPresentationControlChannel** aRetVal)
+MulticastDNSDeviceProvider::Device::EstablishControlChannel(
+                                        nsIPresentationControlChannel** aRetVal)
 {
   if (!mProvider) {
     return NS_ERROR_FAILURE;
   }
 
-  return mProvider->RequestSession(this, aUrl, aPresentationId, aRetVal);
+  return mProvider->Connect(this, aRetVal);
 }
 
 NS_IMETHODIMP
 MulticastDNSDeviceProvider::Device::Disconnect()
 {
   // No need to do anything when disconnect.
   return NS_OK;
 }
--- a/dom/presentation/provider/MulticastDNSDeviceProvider.h
+++ b/dom/presentation/provider/MulticastDNSDeviceProvider.h
@@ -135,20 +135,19 @@ private:
       return aA->Address() == aB->Address();
     }
   };
 
   virtual ~MulticastDNSDeviceProvider();
   nsresult RegisterService();
   nsresult UnregisterService(nsresult aReason);
   nsresult StopDiscovery(nsresult aReason);
-  nsresult RequestSession(Device* aDevice,
-                          const nsAString& aUrl,
-                          const nsAString& aPresentationId,
-                          nsIPresentationControlChannel** aRetVal);
+  nsresult Connect(Device* aDevice,
+                   nsIPresentationControlChannel** aRetVal);
+  bool IsCompatibleServer(nsIDNSServiceInfo* aServiceInfo);
 
   // device manipulation
   nsresult AddDevice(const nsACString& aId,
                      const nsACString& aServiceName,
                      const nsACString& aServiceType,
                      const nsACString& aAddress,
                      const uint16_t aPort);
   nsresult UpdateDevice(const uint32_t aIndex,
--- a/dom/presentation/provider/PresentationControlService.js
+++ b/dom/presentation/provider/PresentationControlService.js
@@ -1,19 +1,33 @@
 /* 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/. */
+/* jshint esnext:true, globalstrict:true, moz:true, undef:true, unused:true */
+/* globals Components, dump */
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
+/* globals XPCOMUtils */
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+/* globals Services */
 Cu.import("resource://gre/modules/Services.jsm");
+/* globals NetUtil */
 Cu.import("resource://gre/modules/NetUtil.jsm");
 
+/* globals ControllerStateMachine */
+XPCOMUtils.defineLazyModuleGetter(this, "ControllerStateMachine", // jshint ignore:line
+                                  "resource://gre/modules/presentation/ControllerStateMachine.jsm");
+/* global ReceiverStateMachine */
+XPCOMUtils.defineLazyModuleGetter(this, "ReceiverStateMachine", // jshint ignore:line
+                                  "resource://gre/modules/presentation/ReceiverStateMachine.jsm");
+
+const kProtocolVersion = 1; // need to review isCompatibleServer while fiddling the version number.
+
 const DEBUG = Services.prefs.getBoolPref("dom.presentation.tcp_server.debug");
 function log(aMsg) {
   dump("-*- PresentationControlService.js: " + aMsg + "\n");
 }
 
 function TCPDeviceInfo(aAddress, aPort, aId) {
   this.address = aAddress;
   this.port = aPort;
@@ -31,140 +45,143 @@ PresentationControlService.prototype = {
    * If a user agent connects to this server, we create a control channel but
    * hand it to |TCPDevice.listener| when the initial information exchange
    * finishes. Therefore, we hold the control channels in this period.
    */
   _controlChannels: [],
 
   startServer: function(aPort) {
     if (this._isServiceInit()) {
-      DEBUG && log("PresentationControlService - server socket has been initialized");
+      DEBUG && log("PresentationControlService - server socket has been initialized");  // jshint ignore:line
       throw Cr.NS_ERROR_FAILURE;
     }
 
     /**
      * 0 or undefined indicates opt-out parameter, and a port will be selected
      * automatically.
      */
     let serverSocketPort = (typeof aPort !== "undefined" && aPort !== 0) ? aPort : -1;
 
     this._serverSocket = Cc["@mozilla.org/network/server-socket;1"]
                          .createInstance(Ci.nsIServerSocket);
 
     if (!this._serverSocket) {
-      DEBUG && log("PresentationControlService - create server socket fail.");
+      DEBUG && log("PresentationControlService - create server socket fail."); // jshint ignore:line
       throw Cr.NS_ERROR_FAILURE;
     }
 
     try {
       this._serverSocket.init(serverSocketPort, false, -1);
       this._serverSocket.asyncListen(this);
     } catch (e) {
       // NS_ERROR_SOCKET_ADDRESS_IN_USE
-      DEBUG && log("PresentationControlService - init server socket fail: " + e);
+      DEBUG && log("PresentationControlService - init server socket fail: " + e); // jshint ignore:line
       throw Cr.NS_ERROR_FAILURE;
     }
 
     this._port = this._serverSocket.port;
 
-    DEBUG && log("PresentationControlService - service start on port: " + this._port);
+    DEBUG && log("PresentationControlService - service start on port: " + this._port); // jshint ignore:line
 
     // Monitor network interface change to restart server socket.
     // Only B2G has nsINetworkManager
     Services.obs.addObserver(this, "network-active-changed", false);
     Services.obs.addObserver(this, "network:offline-status-changed", false);
   },
 
+  isCompatibleServer: function(aVersion) {
+    // No compatibility issue for the first version of control protocol
+    return this.version === aVersion;
+  },
+
   get id() {
     return this._id;
   },
 
   set id(aId) {
     this._id = aId;
   },
 
   get port() {
     return this._port;
   },
 
+  get version() {
+    return kProtocolVersion;
+  },
+
   set listener(aListener) {
     this._listener = aListener;
   },
 
   get listener() {
     return this._listener;
   },
 
   _isServiceInit: function() {
     return this._serverSocket !== null;
   },
 
-  requestSession: function(aDeviceInfo, aUrl, aPresentationId) {
+  connect: function(aDeviceInfo) {
     if (!this.id) {
-      DEBUG && log("PresentationControlService - Id has not initialized; requestSession fails");
+      DEBUG && log("PresentationControlService - Id has not initialized; connect fails"); // jshint ignore:line
       return null;
     }
-    DEBUG && log("PresentationControlService - requestSession to " + aDeviceInfo.id
-                 + ": " + aUrl + ", " + aPresentationId);
+    DEBUG && log("PresentationControlService - connect to " + aDeviceInfo.id); // jshint ignore:line
 
     let sts = Cc["@mozilla.org/network/socket-transport-service;1"]
-                .getService(Ci.nsISocketTransportService)
+                .getService(Ci.nsISocketTransportService);
 
     let socketTransport;
     try {
       socketTransport = sts.createTransport(null,
                                             0,
                                             aDeviceInfo.address,
                                             aDeviceInfo.port,
                                             null);
     } catch (e) {
-      DEBUG && log("PresentationControlService - createTransport throws: " + e);
+      DEBUG && log("PresentationControlService - createTransport throws: " + e);  // jshint ignore:line
       // Pop the exception to |TCPDevice.establishControlChannel|
       throw Cr.NS_ERROR_FAILURE;
     }
     return new TCPControlChannel(this,
                                  socketTransport,
                                  aDeviceInfo,
-                                 aPresentationId,
-                                 "sender",
-                                 aUrl);
+                                 "sender");
   },
 
   responseSession: function(aDeviceInfo, aSocketTransport) {
     if (!this._isServiceInit()) {
       DEBUG && log("PresentationControlService - should never receive remote " +
-                   "session request before server socket initialization");
+                   "session request before server socket initialization"); // jshint ignore:line
       return null;
     }
-    DEBUG && log("PresentationControlService - responseSession to "
-                 + JSON.stringify(aDeviceInfo));
+    DEBUG && log("PresentationControlService - responseSession to " +
+                 JSON.stringify(aDeviceInfo)); // jshint ignore:line
     return new TCPControlChannel(this,
                                  aSocketTransport,
                                  aDeviceInfo,
-                                 null, // presentation ID
-                                 "receiver",
-                                 null // url
-                                 );
+                                 "receiver");
   },
 
   // Triggered by TCPControlChannel
   onSessionRequest: function(aDeviceInfo, aUrl, aPresentationId, aControlChannel) {
-    DEBUG && log("PresentationControlService - onSessionRequest: "
-                 + aDeviceInfo.address + ":" + aDeviceInfo.port);
+    DEBUG && log("PresentationControlService - onSessionRequest: " +
+                 aDeviceInfo.address + ":" + aDeviceInfo.port); // jshint ignore:line
     this.listener.onSessionRequest(aDeviceInfo,
                                    aUrl,
                                    aPresentationId,
                                    aControlChannel);
     this.releaseControlChannel(aControlChannel);
   },
 
   // nsIServerSocketListener (Triggered by nsIServerSocket.init)
   onSocketAccepted: function(aServerSocket, aClientSocket) {
-    DEBUG && log("PresentationControlService - onSocketAccepted: "
-                 + aClientSocket.host + ":" + aClientSocket.port);
+    DEBUG && log("PresentationControlService - onSocketAccepted: " +
+                 aClientSocket.host + ":" + aClientSocket.port); // jshint ignore:line
     let deviceInfo = new TCPDeviceInfo(aClientSocket.host, aClientSocket.port);
     this.holdControlChannel(this.responseSession(deviceInfo, aClientSocket));
   },
 
   holdControlChannel: function(aControlChannel) {
     this._controlChannels.push(aControlChannel);
   },
 
@@ -172,77 +189,77 @@ PresentationControlService.prototype = {
     let index = this._controlChannels.indexOf(aControlChannel);
     if (index !== -1) {
       delete this._controlChannels[index];
     }
   },
 
   // nsIServerSocketListener (Triggered by nsIServerSocket.init)
   onStopListening: function(aServerSocket, aStatus) {
-    DEBUG && log("PresentationControlService - onStopListening: " + aStatus);
+    DEBUG && log("PresentationControlService - onStopListening: " + aStatus); // jshint ignore:line
   },
 
   close: function() {
-    DEBUG && log("PresentationControlService - close");
+    DEBUG && log("PresentationControlService - close"); // jshint ignore:line
     if (this._isServiceInit()) {
-      DEBUG && log("PresentationControlService - close server socket");
+      DEBUG && log("PresentationControlService - close server socket"); // jshint ignore:line
       this._serverSocket.close();
       this._serverSocket = null;
 
       Services.obs.removeObserver(this, "network-active-changed");
       Services.obs.removeObserver(this, "network:offline-status-changed");
     }
     this._port = 0;
   },
 
   // nsIObserver
   observe: function(aSubject, aTopic, aData) {
-    DEBUG && log("PresentationControlService - observe: " + aTopic);
+    DEBUG && log("PresentationControlService - observe: " + aTopic); // jshint ignore:line
     switch (aTopic) {
       case "network-active-changed": {
         if (!aSubject) {
-          DEBUG && log("No active network");
+          DEBUG && log("No active network"); // jshint ignore:line
           return;
         }
 
         /**
          * Restart service only when original status is online because other
          * cases will be handled by "network:offline-status-changed".
          */
         if (!Services.io.offline) {
           this._restartServer();
         }
         break;
       }
       case "network:offline-status-changed": {
         if (aData == "offline") {
-          DEBUG && log("network offline");
+          DEBUG && log("network offline"); // jshint ignore:line
           return;
         }
         this._restartServer();
         break;
       }
     }
   },
 
   _restartServer: function() {
-    DEBUG && log("PresentationControlService - restart service");
+    DEBUG && log("PresentationControlService - restart service"); // jshint ignore:line
 
     // restart server socket
     if (this._isServiceInit()) {
       let port = this._port;
       this.close();
 
       try {
         this.startServer();
         if (this._listener && this._port !== port) {
            this._listener.onPortChange(this._port);
         }
       } catch (e) {
-        DEBUG && log("PresentationControlService - restart service fail: " + e);
+        DEBUG && log("PresentationControlService - restart service fail: " + e); // jshint ignore:line
       }
     }
   },
 
   classID: Components.ID("{f4079b8b-ede5-4b90-a112-5b415a931deb}"),
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIServerSocketListener,
                                           Ci.nsIPresentationControlService,
                                           Ci.nsIObserver]),
@@ -314,387 +331,340 @@ function discriptionAsJson(aDescription)
       break;
   }
   return json;
 }
 
 function TCPControlChannel(presentationService,
                            transport,
                            deviceInfo,
-                           presentationId,
-                           direction,
-                           url) {
-  DEBUG && log("create TCPControlChannel: " + presentationId + " with role: "
-               + direction);
+                           direction) {
+  DEBUG && log("create TCPControlChannel for : " + direction); // jshint ignore:line
   this._deviceInfo = deviceInfo;
-  this._presentationId = presentationId;
   this._direction = direction;
   this._transport = transport;
-  this._url = url;
 
-  this._presentationService =  presentationService;
+  this._presentationService = presentationService;
 
   let currentThread = Services.tm.currentThread;
   transport.setEventSink(this, currentThread);
 
   this._input = this._transport.openInputStream(0, 0, 0)
                                .QueryInterface(Ci.nsIAsyncInputStream);
   this._input.asyncWait(this.QueryInterface(Ci.nsIStreamListener),
                         Ci.nsIAsyncInputStream.WAIT_CLOSURE_ONLY,
                         0,
                         currentThread);
 
   this._output = this._transport
                      .openOutputStream(Ci.nsITransport.OPEN_UNBUFFERED, 0, 0);
 
+  this._stateMachine =
+    (direction === "sender") ? new ControllerStateMachine(this, presentationService.id)
+                             : new ReceiverStateMachine(this);
   // Since the transport created by server socket is already CONNECTED_TO
   if (this._direction === "receiver") {
     this._createInputStreamPump();
   }
 }
 
 TCPControlChannel.prototype = {
   _connected: false,
   _pendingOpen: false,
   _pendingOffer: null,
   _pendingAnswer: null,
   _pendingClose: null,
   _pendingCloseReason: null,
-  _sendingMessageType: null,
-
-  _sendMessage: function(aType, aJSONData, aOnThrow) {
-    if (!aOnThrow) {
-      aOnThrow = function(e) {throw e.result;}
-    }
-
-    if (!aType || !aJSONData) {
-      aOnThrow();
-      return;
-    }
-
-    if (!this._connected) {
-      DEBUG && log("TCPControlChannel - send" + aType + " fails");
-      throw Cr.NS_ERROR_FAILURE;
-    }
-
-    DEBUG && log("TCPControlChannel - send" + aType + ": "
-                 + JSON.stringify(aJSONData));
-    try {
-      this._send(aJSONData);
-    } catch (e) {
-      aOnThrow(e);
-    }
-    this._sendingMessageType = aType;
-  },
-
-  _sendInit: function() {
-    let msg = {
-      type: "requestSession:Init",
-      presentationId: this._presentationId,
-      url: this._url,
-      id: this._presentationService.id,
-    };
-
-    this._sendMessage("init", msg, function(e) {
-      this.close();
-      this._notifyClosed(e.result);
-    });
-  },
 
   sendOffer: function(aOffer) {
-    let msg = {
-      type: "requestSession:Offer",
-      presentationId: this.presentationId,
-      offer: discriptionAsJson(aOffer),
-    };
-    this._sendMessage("offer", msg);
+    this._stateMachine.sendOffer(discriptionAsJson(aOffer));
   },
 
   sendAnswer: function(aAnswer) {
-    let msg = {
-      type: "requestSession:Answer",
-      presentationId: this.presentationId,
-      answer: discriptionAsJson(aAnswer),
-    };
-    this._sendMessage("answer", msg);
+    this._stateMachine.sendAnswer(discriptionAsJson(aAnswer));
   },
 
   sendIceCandidate: function(aCandidate) {
-    let msg = {
-      type: "requestSession:IceCandidate",
-      presentationId: this.presentationId,
-      iceCandidate: aCandidate,
-    };
-    this._sendMessage("iceCandidate", msg);
+    this._stateMachine.updateIceCandidate(aCandidate);
   },
+
+  launch: function(aPresentationId, aUrl) {
+    this._stateMachine.launch(aPresentationId, aUrl);
+  },
+
   // may throw an exception
   _send: function(aMsg) {
-    DEBUG && log("TCPControlChannel - Send: " + JSON.stringify(aMsg, null, 2));
+    DEBUG && log("TCPControlChannel - Send: " + JSON.stringify(aMsg, null, 2)); // jshint ignore:line
 
     /**
      * XXX In TCP streaming, it is possible that more than one message in one
      * TCP packet. We use line delimited JSON to identify where one JSON encoded
      * object ends and the next begins. Therefore, we do not allow newline
      * characters whithin the whole message, and add a newline at the end.
      * Please see the parser code in |onDataAvailable|.
      */
     let message = JSON.stringify(aMsg).replace(["\n"], "") + "\n";
     try {
       this._output.write(message, message.length);
     } catch(e) {
-      DEBUG && log("TCPControlChannel - Failed to send message: " + e.name);
+      DEBUG && log("TCPControlChannel - Failed to send message: " + e.name); // jshint ignore:line
       throw e;
     }
   },
 
   // nsIAsyncInputStream (Triggered by nsIInputStream.asyncWait)
   // Only used for detecting connection refused
   onInputStreamReady: function(aStream) {
     try {
       aStream.available();
     } catch (e) {
-      DEBUG && log("TCPControlChannel - onInputStreamReady error: " + e.name);
+      DEBUG && log("TCPControlChannel - onInputStreamReady error: " + e.name); // jshint ignore:line
       // NS_ERROR_CONNECTION_REFUSED
-      this._listener.notifyClosed(e.result);
+      this._listener.notifyDisconnected(e.result);
     }
   },
 
   // nsITransportEventSink (Triggered by nsISocketTransport.setEventSink)
-  onTransportStatus: function(aTransport, aStatus, aProg, aProgMax) {
-    DEBUG && log("TCPControlChannel - onTransportStatus: "
-                 + aStatus.toString(16) + " with role: " + this._direction);
+  onTransportStatus: function(aTransport, aStatus) {
+    DEBUG && log("TCPControlChannel - onTransportStatus: " + aStatus.toString(16) +
+                 " with role: " + this._direction); // jshint ignore:line
     if (aStatus === Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
       this._connected = true;
 
       if (!this._pump) {
         this._createInputStreamPump();
       }
-
-      if (this._direction === "sender") {
-        this._sendInit();
-      }
-    } else if (aStatus === Ci.nsISocketTransport.STATUS_SENDING_TO) {
-      if (this._sendingMessageType === "init") {
-        this._notifyOpened();
-      }
-      this._sendingMessageType = null;
     }
   },
 
   // nsIRequestObserver (Triggered by nsIInputStreamPump.asyncRead)
   onStartRequest: function() {
-    DEBUG && log("TCPControlChannel - onStartRequest with role: "
-                 + this._direction);
+    DEBUG && log("TCPControlChannel - onStartRequest with role: " +
+                 this._direction); // jshint ignore:line
   },
 
   // nsIRequestObserver (Triggered by nsIInputStreamPump.asyncRead)
   onStopRequest: function(aRequest, aContext, aStatus) {
-    DEBUG && log("TCPControlChannel - onStopRequest: " + aStatus
-                 + " with role: " + this._direction);
-    this.close(aStatus);
-    this._notifyClosed(aStatus);
+    DEBUG && log("TCPControlChannel - onStopRequest: " + aStatus +
+                 " with role: " + this._direction); // jshint ignore:line
+    this._stateMachine.onChannelClosed(aStatus, true);
   },
 
   // nsIStreamListener (Triggered by nsIInputStreamPump.asyncRead)
-  onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
+  onDataAvailable: function(aRequest, aContext, aInputStream) {
     let data = NetUtil.readInputStreamToString(aInputStream,
                                                aInputStream.available());
-    DEBUG && log("TCPControlChannel - onDataAvailable: " + data);
+    DEBUG && log("TCPControlChannel - onDataAvailable: " + data); // jshint ignore:line
 
     // Parser of line delimited JSON. Please see |_send| for more informaiton.
     let jsonArray = data.split("\n");
     jsonArray.pop();
     for (let json of jsonArray) {
       let msg;
       try {
         msg = JSON.parse(json);
       } catch (e) {
-        DEBUG && log("TCPSignalingChannel - error in parsing json: " + e);
+        DEBUG && log("TCPSignalingChannel - error in parsing json: " + e); // jshint ignore:line
       }
 
       this._handleMessage(msg);
     }
   },
 
   _createInputStreamPump: function() {
-    DEBUG && log("TCPControlChannel - create pump with role: "
-                 + this._direction);
+    DEBUG && log("TCPControlChannel - create pump with role: " +
+                 this._direction); // jshint ignore:line
     this._pump = Cc["@mozilla.org/network/input-stream-pump;1"].
                createInstance(Ci.nsIInputStreamPump);
     this._pump.init(this._input, -1, -1, 0, 0, false);
     this._pump.asyncRead(this, null);
+    this._stateMachine.onChannelReady();
   },
 
   // Handle command from remote side
   _handleMessage: function(aMsg) {
-    DEBUG && log("TCPControlChannel - handleMessage from "
-                 + JSON.stringify(this._deviceInfo) + ": " + JSON.stringify(aMsg));
-    switch (aMsg.type) {
-      case "requestSession:Init": {
-        this._deviceInfo.id = aMsg.id;
-        this._url = aMsg.url;
-        this._presentationId = aMsg.presentationId;
-        this._presentationService.onSessionRequest(this._deviceInfo,
-                                                   aMsg.url,
-                                                   aMsg.presentationId,
-                                                   this);
-        this._notifyOpened();
-        break;
-      }
-      case "requestSession:Offer": {
-        this._onOffer(aMsg.offer);
-        break;
-      }
-      case "requestSession:Answer": {
-        this._onAnswer(aMsg.answer);
-        break;
-      }
-      case "requestSession:IceCandidate": {
-        this._listener.onIceCandidate(aMsg.iceCandidate);
-        break;
-      }
-      case "requestSession:CloseReason": {
-        this._pendingCloseReason = aMsg.reason;
-        break;
-      }
-    }
+    DEBUG && log("TCPControlChannel - handleMessage from " +
+                 JSON.stringify(this._deviceInfo) + ": " + JSON.stringify(aMsg)); // jshint ignore:line
+    this._stateMachine.onCommand(aMsg);
   },
 
   get listener() {
     return this._listener;
   },
 
   set listener(aListener) {
-    DEBUG && log("TCPControlChannel - set listener: " + aListener);
+    DEBUG && log("TCPControlChannel - set listener: " + aListener); // jshint ignore:line
     if (!aListener) {
       this._listener = null;
       return;
     }
 
     this._listener = aListener;
     if (this._pendingOpen) {
       this._pendingOpen = false;
-      DEBUG && log("TCPControlChannel - notify pending opened");
-      this._listener.notifyOpened();
+      DEBUG && log("TCPControlChannel - notify pending opened"); // jshint ignore:line
+      this._listener.notifyConnected();
     }
 
     if (this._pendingOffer) {
       let offer = this._pendingOffer;
-      DEBUG && log("TCPControlChannel - notify pending offer: "
-                   + JSON.stringify(offer));
+      DEBUG && log("TCPControlChannel - notify pending offer: " +
+                   JSON.stringify(offer)); // jshint ignore:line
       this._listener.onOffer(new ChannelDescription(offer));
       this._pendingOffer = null;
     }
 
     if (this._pendingAnswer) {
       let answer = this._pendingAnswer;
-      DEBUG && log("TCPControlChannel - notify pending answer: "
-                   + JSON.stringify(answer));
+      DEBUG && log("TCPControlChannel - notify pending answer: " +
+                   JSON.stringify(answer)); // jshint ignore:line
       this._listener.onAnswer(new ChannelDescription(answer));
       this._pendingAnswer = null;
     }
 
     if (this._pendingClose) {
-      DEBUG && log("TCPControlChannel - notify pending closed");
-      this._notifyClosed(this._pendingCloseReason);
+      DEBUG && log("TCPControlChannel - notify pending closed"); // jshint ignore:line
+      this._notifyDisconnected(this._pendingCloseReason);
       this._pendingClose = null;
     }
   },
 
   /**
    * These functions are designed to handle the interaction with listener
    * appropriately. |_FUNC| is to handle |this._listener.FUNC|.
    */
   _onOffer: function(aOffer) {
     if (!this._connected) {
       return;
     }
     if (!this._listener) {
       this._pendingOffer = aOffer;
       return;
     }
-    DEBUG && log("TCPControlChannel - notify offer: "
-                 + JSON.stringify(aOffer));
+    DEBUG && log("TCPControlChannel - notify offer: " +
+                 JSON.stringify(aOffer)); // jshint ignore:line
     this._listener.onOffer(new ChannelDescription(aOffer));
   },
 
   _onAnswer: function(aAnswer) {
     if (!this._connected) {
       return;
     }
     if (!this._listener) {
       this._pendingAnswer = aAnswer;
       return;
     }
-    DEBUG && log("TCPControlChannel - notify answer: "
-                 + JSON.stringify(aAnswer));
+    DEBUG && log("TCPControlChannel - notify answer: " +
+                 JSON.stringify(aAnswer)); // jshint ignore:line
     this._listener.onAnswer(new ChannelDescription(aAnswer));
   },
 
-  _notifyOpened: function() {
+  _notifyConnected: function() {
     this._connected = true;
     this._pendingClose = false;
     this._pendingCloseReason = Cr.NS_OK;
 
     if (!this._listener) {
       this._pendingOpen = true;
       return;
     }
 
-    DEBUG && log("TCPControlChannel - notify opened with role: "
-                 + this._direction);
-    this._listener.notifyOpened();
+    DEBUG && log("TCPControlChannel - notify opened with role: " +
+                 this._direction); // jshint ignore:line
+    this._listener.notifyConnected();
   },
 
-  _notifyClosed: function(aReason) {
+  _notifyDisconnected: function(aReason) {
     this._connected = false;
     this._pendingOpen = false;
     this._pendingOffer = null;
     this._pendingAnswer = null;
 
     // Remote endpoint closes the control channel with abnormal reason.
     if (aReason == Cr.NS_OK && this._pendingCloseReason != Cr.NS_OK) {
       aReason = this._pendingCloseReason;
     }
 
     if (!this._listener) {
       this._pendingClose = true;
       this._pendingCloseReason = aReason;
       return;
     }
 
-    DEBUG && log("TCPControlChannel - notify closed with role: "
-                 + this._direction);
-    this._listener.notifyClosed(aReason);
+    DEBUG && log("TCPControlChannel - notify closed with role: " +
+                 this._direction); // jshint ignore:line
+    this._listener.notifyDisconnected(aReason);
   },
 
-  close: function(aReason) {
-    DEBUG && log("TCPControlChannel - close with reason: " + aReason);
-
+  _closeTransport: function() {
     if (this._connected) {
-      // default reason is NS_OK
-      if (typeof aReason !== "undefined" && aReason !== Cr.NS_OK) {
-        let msg = {
-          type: "requestSession:CloseReason",
-          presentationId: this.presentationId,
-          reason: aReason,
-        };
-        this._sendMessage("close", msg);
-        this._pendingCloseReason = aReason;
-      }
-
       this._transport.setEventSink(null, null);
       this._pump = null;
 
       this._input.close();
       this._output.close();
       this._presentationService.releaseControlChannel(this);
+    }
+  },
+
+  disconnect: function(aReason) {
+    DEBUG && log("TCPControlChannel - disconnect with reason: " + aReason); // jshint ignore:line
+
+    if (this._connected) {
+      // default reason is NS_OK
+      aReason = !aReason ? Cr.NS_OK : aReason;
+      this._stateMachine.onChannelClosed(aReason, false);
+
+      this._closeTransport();
 
       this._connected = false;
     }
   },
 
+  // callback from state machine
+  sendCommand: function(command) {
+    this._send(command);
+  },
+
+  notifyDeviceConnected: function(deviceId) {
+    switch (this._direction) {
+      case "receiver":
+        this._deviceInfo.id = deviceId;
+        break;
+    }
+    this._notifyConnected();
+  },
+
+  notifyDisconnected: function(reason) {
+    this._notifyDisconnected(reason);
+    this._closeTransport();
+    this._connected = false;
+  },
+
+  notifyLaunch: function(presentationId, url) {
+    switch (this._direction) {
+      case "receiver":
+        this._presentationService.onSessionRequest(this._deviceInfo,
+                                                   url,
+                                                   presentationId,
+                                                   this);
+      break;
+    }
+  },
+
+  notifyOffer: function(offer) {
+    this._onOffer(offer);
+  },
+
+  notifyAnswer: function(answer) {
+    this._onAnswer(answer);
+  },
+
+  notifyIceCandidate: function(candidate) {
+    this._listener.onIceCandidate(candidate);
+  },
+
   classID: Components.ID("{fefb8286-0bdc-488b-98bf-0c11b485c955}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel,
                                          Ci.nsIStreamListener]),
 };
 
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PresentationControlService]);
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PresentationControlService]); // jshint ignore:line
--- a/dom/presentation/provider/PresentationDeviceProviderModule.cpp
+++ b/dom/presentation/provider/PresentationDeviceProviderModule.cpp
@@ -2,55 +2,86 @@
 /* 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 "DisplayDeviceProvider.h"
 #include "MulticastDNSDeviceProvider.h"
 #include "mozilla/ModuleUtils.h"
 
+#ifdef MOZ_WIDGET_ANDROID
+#include "LegacyMDNSDeviceProvider.h"
+#endif //MOZ_WIDGET_ANDROID
+
 #define MULTICAST_DNS_PROVIDER_CID \
   {0x814f947a, 0x52f7, 0x41c9, \
     { 0x94, 0xa1, 0x36, 0x84, 0x79, 0x72, 0x84, 0xac }}
 #define DISPLAY_DEVICE_PROVIDER_CID \
   { 0x515d9879, 0xfe0b, 0x4d9f, \
     { 0x89, 0x49, 0x7f, 0xa7, 0x65, 0x6c, 0x01, 0x0e } }
 
+#ifdef MOZ_WIDGET_ANDROID
+#define LEGACY_MDNS_PROVIDER_CID \
+  { 0x6885ff39, 0xd98c, 0x4356, \
+    { 0x9e, 0xb3, 0x56, 0x56, 0x31, 0x63, 0x0a, 0xf6 } }
+#endif //MOZ_WIDGET_ANDROID
 
 #define DISPLAY_DEVICE_PROVIDER_CONTRACT_ID "@mozilla.org/presentation-device/displaydevice-provider;1"
 #define MULTICAST_DNS_PROVIDER_CONTRACT_ID "@mozilla.org/presentation-device/multicastdns-provider;1"
 
+#ifdef MOZ_WIDGET_ANDROID
+#define LEGACY_MDNS_PROVIDER_CONTRACT_ID "@mozilla.org/presentation-device/legacy-mdns-provider;1"
+#endif //MOZ_WIDGET_ANDROID
+
 using mozilla::dom::presentation::MulticastDNSDeviceProvider;
 using mozilla::dom::presentation::DisplayDeviceProvider;
 
+#ifdef MOZ_WIDGET_ANDROID
+using mozilla::dom::presentation::legacy::LegacyMDNSDeviceProvider;
+#endif //MOZ_WIDGET_ANDROID
+
 NS_GENERIC_FACTORY_CONSTRUCTOR(MulticastDNSDeviceProvider)
 NS_DEFINE_NAMED_CID(MULTICAST_DNS_PROVIDER_CID);
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(DisplayDeviceProvider)
 NS_DEFINE_NAMED_CID(DISPLAY_DEVICE_PROVIDER_CID);
 
+#ifdef MOZ_WIDGET_ANDROID
+NS_GENERIC_FACTORY_CONSTRUCTOR(LegacyMDNSDeviceProvider)
+NS_DEFINE_NAMED_CID(LEGACY_MDNS_PROVIDER_CID);
+#endif //MOZ_WIDGET_ANDROID
+
 static const mozilla::Module::CIDEntry kPresentationDeviceProviderCIDs[] = {
   { &kMULTICAST_DNS_PROVIDER_CID, false, nullptr, MulticastDNSDeviceProviderConstructor },
   { &kDISPLAY_DEVICE_PROVIDER_CID, false, nullptr, DisplayDeviceProviderConstructor },
+#ifdef MOZ_WIDGET_ANDROID
+  { &kLEGACY_MDNS_PROVIDER_CID, false, nullptr, LegacyMDNSDeviceProviderConstructor },
+#endif //MOZ_WIDGET_ANDROID
   { nullptr }
 };
 
 static const mozilla::Module::ContractIDEntry kPresentationDeviceProviderContracts[] = {
   { MULTICAST_DNS_PROVIDER_CONTRACT_ID, &kMULTICAST_DNS_PROVIDER_CID },
   { DISPLAY_DEVICE_PROVIDER_CONTRACT_ID, &kDISPLAY_DEVICE_PROVIDER_CID },
+#ifdef MOZ_WIDGET_ANDROID
+  { LEGACY_MDNS_PROVIDER_CONTRACT_ID, &kLEGACY_MDNS_PROVIDER_CID },
+#endif //MOZ_WIDGET_ANDROID
   { nullptr }
 };
 
 static const mozilla::Module::CategoryEntry kPresentationDeviceProviderCategories[] = {
 #if defined(MOZ_WIDGET_COCOA) || defined(MOZ_WIDGET_ANDROID) || (defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 16)
   { PRESENTATION_DEVICE_PROVIDER_CATEGORY, "MulticastDNSDeviceProvider", MULTICAST_DNS_PROVIDER_CONTRACT_ID },
 #endif
 #if defined(MOZ_WIDGET_GONK)
   { PRESENTATION_DEVICE_PROVIDER_CATEGORY, "DisplayDeviceProvider", DISPLAY_DEVICE_PROVIDER_CONTRACT_ID },
 #endif
+#ifdef MOZ_WIDGET_ANDROID
+  { PRESENTATION_DEVICE_PROVIDER_CATEGORY, "LegacyMDNSDeviceProvider", LEGACY_MDNS_PROVIDER_CONTRACT_ID },
+#endif //MOZ_WIDGET_ANDROID
   { nullptr }
 };
 
 static const mozilla::Module kPresentationDeviceProviderModule = {
   mozilla::Module::kVersion,
   kPresentationDeviceProviderCIDs,
   kPresentationDeviceProviderContracts,
   kPresentationDeviceProviderCategories
new file mode 100644
--- /dev/null
+++ b/dom/presentation/provider/ReceiverStateMachine.jsm
@@ -0,0 +1,186 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+/* jshint esnext:true, globalstrict:true, moz:true, undef:true, unused:true */
+/* globals Components, dump */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["ReceiverStateMachine"]; // jshint ignore:line
+
+const { utils: Cu } = Components;
+
+/* globals State, CommandType */
+Cu.import("resource://gre/modules/presentation/StateMachineHelper.jsm");
+
+const DEBUG = false;
+function debug(str) {
+  dump("-*- ReceiverStateMachine: " + str + "\n");
+}
+
+var handlers = [
+  function _initHandler(stateMachine, command) {
+    // shouldn't receive any command at init state
+    DEBUG && debug("unexpected command: " + JSON.stringify(command)); // jshint ignore:line
+  },
+  function _connectingHandler(stateMachine, command) {
+    switch (command.type) {
+      case CommandType.CONNECT:
+        stateMachine._sendCommand({
+          type: CommandType.CONNECT_ACK
+        });
+        stateMachine.state = State.CONNECTED;
+        stateMachine._notifyDeviceConnected(command.deviceId);
+        break;
+      case CommandType.DISCONNECT:
+        stateMachine.state = State.CLOSED;
+        stateMachine._notifyDisconnected(command.reason);
+        break;
+      default:
+        debug("unexpected command: " + JSON.stringify(command));
+        // ignore unexpected command
+        break;
+    }
+  },
+  function _connectedHandler(stateMachine, command) {
+    switch (command.type) {
+      case CommandType.DISCONNECT:
+        stateMachine.state = State.CLOSED;
+        stateMachine._notifyDisconnected(command.reason);
+        break;
+      case CommandType.LAUNCH:
+        stateMachine._notifyLaunch(command.presentationId,
+                                   command.url);
+        stateMachine._sendCommand({
+          type: CommandType.LAUNCH_ACK,
+          presentationId: command.presentationId
+        });
+        break;
+      case CommandType.OFFER:
+      case CommandType.ICE_CANDIDATE:
+        stateMachine._notifyChannelDescriptor(command);
+        break;
+      default:
+        debug("unexpected command: " + JSON.stringify(command));
+        // ignore unexpected command
+        break;
+    }
+  },
+  function _closingHandler(stateMachine, command) {
+    // ignore every command in closing state.
+    DEBUG && debug("unexpected command: " + JSON.stringify(command)); // jshint ignore:line
+  },
+  function _closedHandler(stateMachine, command) {
+    // ignore every command in closed state.
+    DEBUG && debug("unexpected command: " + JSON.stringify(command)); // jshint ignore:line
+  },
+];
+
+function ReceiverStateMachine(channel) {
+  this.state = State.INIT;
+  this._channel = channel;
+}
+
+ReceiverStateMachine.prototype = {
+  launch: function _launch() {
+    // presentation session can only be launched by controlling UA.
+    debug("receiver shouldn't trigger launch");
+  },
+
+  sendOffer: function _sendOffer() {
+    // offer can only be sent by controlling UA.
+    debug("receiver shouldn't generate offer");
+  },
+
+  sendAnswer: function _sendAnswer(answer) {
+    if (this.state === State.CONNECTED) {
+      this._sendCommand({
+        type: CommandType.ANSWER,
+        answer: answer,
+      });
+    }
+  },
+
+  updateIceCandidate: function _updateIceCandidate(candidate) {
+    if (this.state === State.CONNECTED) {
+      this._sendCommand({
+        type: CommandType.ICE_CANDIDATE,
+        candidate: candidate,
+      });
+    }
+  },
+
+  onCommand: function _onCommand(command) {
+    handlers[this.state](this, command);
+  },
+
+  onChannelReady: function _onChannelReady() {
+    if (this.state === State.INIT) {
+      this.state = State.CONNECTING;
+    }
+  },
+
+  onChannelClosed: function _onChannelClose(reason, isByRemote) {
+    switch (this.state) {
+      case State.CONNECTED:
+        if (isByRemote) {
+          this.state = State.CLOSED;
+          this._notifyDisconnected(reason);
+        } else {
+          this._sendCommand({
+            type: CommandType.DISCONNECT,
+            reason: reason
+          });
+          this.state = State.CLOSING;
+          this._closeReason = reason;
+        }
+        break;
+      case State.CLOSING:
+        if (isByRemote) {
+          this.state = State.CLOSED;
+          if (this._closeReason) {
+            reason = this._closeReason;
+            delete this._closeReason;
+          }
+          this._notifyDisconnected(reason);
+        } else {
+          // do nothing and wait for remote channel closed.
+        }
+        break;
+      default:
+        DEBUG && debug("unexpected channel close: " + reason + ", " + isByRemote); // jshint ignore:line
+        break;
+    }
+  },
+
+  _sendCommand: function _sendCommand(command) {
+    this._channel.sendCommand(command);
+  },
+
+  _notifyDeviceConnected: function _notifyDeviceConnected(deviceName) {
+    this._channel.notifyDeviceConnected(deviceName);
+  },
+
+  _notifyDisconnected: function _notifyDisconnected(reason) {
+    this._channel.notifyDisconnected(reason);
+  },
+
+  _notifyLaunch: function _notifyLaunch(presentationId, url) {
+    this._channel.notifyLaunch(presentationId, url);
+  },
+
+  _notifyChannelDescriptor: function _notifyChannelDescriptor(command) {
+    switch (command.type) {
+      case CommandType.OFFER:
+        this._channel.notifyOffer(command.offer);
+        break;
+      case CommandType.ICE_CANDIDATE:
+        this._channel.notifyIceCandidate(command.candidate);
+        break;
+    }
+  },
+};
+
+this.ReceiverStateMachine = ReceiverStateMachine; // jshint ignore:line
new file mode 100644
--- /dev/null
+++ b/dom/presentation/provider/StateMachineHelper.jsm
@@ -0,0 +1,35 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+/* jshint esnext:true, globalstrict:true, moz:true, undef:true, unused:true */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["State", "CommandType"]; // jshint ignore:line
+
+const State = Object.freeze({
+  INIT: 0,
+  CONNECTING: 1,
+  CONNECTED: 2,
+  CLOSING: 3,
+  CLOSED: 4,
+});
+
+const CommandType = Object.freeze({
+  // control channel life cycle
+  CONNECT: "connect", // { deviceId: <string> }
+  CONNECT_ACK: "connect-ack", // { presentationId: <string> }
+  DISCONNECT: "disconnect", // { reason: <int> }
+  // presentation session life cycle
+  LAUNCH: "launch", // { presentationId: <string>, url: <string> }
+  LAUNCH_ACK: "launch-ack", // { presentationId: <string> }
+  // session transport establishment
+  OFFER: "offer", // { offer: <json> }
+  ANSWER: "answer", // { answer: <json> }
+  ICE_CANDIDATE: "ice-candidate", // { candidate: <string> }
+});
+
+this.State = State; // jshint ignore:line
+this.CommandType = CommandType; // jshint ignore:line
--- a/dom/presentation/provider/moz.build
+++ b/dom/presentation/provider/moz.build
@@ -10,10 +10,27 @@ EXTRA_COMPONENTS += [
 ]
 
 UNIFIED_SOURCES += [
     'DisplayDeviceProvider.cpp',
     'MulticastDNSDeviceProvider.cpp',
     'PresentationDeviceProviderModule.cpp',
 ]
 
+EXTRA_JS_MODULES.presentation += [
+    'ControllerStateMachine.jsm',
+    'ReceiverStateMachine.jsm',
+    'StateMachineHelper.jsm',
+]
+
+# for TV 2.5 device backward capability
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
+    EXTRA_COMPONENTS += [
+        'LegacyPresentationControlService.js',
+        'LegacyProviders.manifest',
+    ]
+
+    UNIFIED_SOURCES += [
+        'LegacyMDNSDeviceProvider.cpp',
+    ]
+
 include('/ipc/chromium/chromium-config.mozbuild')
 FINAL_LIBRARY = 'xul'
--- a/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js
+++ b/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js
@@ -136,34 +136,36 @@ const mockedControlChannel = {
       } catch (e) {
         isValid = false;
       }
     } else if (aSDP.type == Ci.nsIPresentationChannelDescription.TYPE_DATACHANNEL) {
       isValid = (aSDP.dataChannelSDP == "test-sdp");
     }
     return isValid;
   },
-  close: function(reason) {
+  launch: function(presentationId, url) {
+  },
+  disconnect: function(reason) {
     sendAsyncMessage('control-channel-closed', reason);
-    this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).notifyClosed(reason);
+    this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).notifyDisconnected(reason);
   },
   simulateReceiverReady: function() {
     this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).notifyReceiverReady();
   },
   simulateOnOffer: function() {
     sendAsyncMessage('offer-received');
     this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).onOffer(mockedChannelDescription);
   },
   simulateOnAnswer: function() {
     sendAsyncMessage('answer-received');
     this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).onAnswer(mockedChannelDescription);
   },
-  simulateNotifyOpened: function() {
+  simulateNotifyConnected: function() {
     sendAsyncMessage('control-channel-opened');
-    this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).notifyOpened();
+    this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).notifyConnected();
   },
 };
 
 const mockedDevice = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice]),
   id: 'id',
   name: 'name',
   type: 'type',
@@ -398,21 +400,21 @@ addMessageListener('trigger-incoming-ans
   mockedControlChannel.simulateOnAnswer();
 });
 
 addMessageListener('trigger-incoming-transport', function() {
   mockedServerSocket.simulateOnSocketAccepted(mockedServerSocket, mockedSocketTransport);
 });
 
 addMessageListener('trigger-control-channel-open', function(reason) {
-  mockedControlChannel.simulateNotifyOpened();
+  mockedControlChannel.simulateNotifyConnected();
 });
 
 addMessageListener('trigger-control-channel-close', function(reason) {
-  mockedControlChannel.close(reason);
+  mockedControlChannel.disconnect(reason);
 });
 
 addMessageListener('trigger-data-transport-close', function(reason) {
   mockedSessionTransport.close(reason);
 });
 
 addMessageListener('trigger-incoming-message', function(message) {
   mockedSessionTransport.simulateIncomingMessage(message);
--- a/dom/presentation/tests/mochitest/PresentationSessionChromeScript1UA.js
+++ b/dom/presentation/tests/mochitest/PresentationSessionChromeScript1UA.js
@@ -164,74 +164,86 @@ const mockControlChannelOfSender = {
     } else {
       debug('set listener for mockControlChannelOfSender with null');
     }
     this._listener = listener;
   },
   get listener() {
     return this._listener;
   },
-  notifyOpened: function() {
-    // send offer after notifyOpened immediately
+  notifyConnected: function() {
+    // send offer after notifyConnected immediately
     this._listener
         .QueryInterface(Ci.nsIPresentationControlChannelListener)
-        .notifyOpened();
+        .notifyConnected();
   },
   sendOffer: function(offer) {
     sendAsyncMessage('offer-sent');
   },
   onAnswer: function(answer) {
     this._listener
         .QueryInterface(Ci.nsIPresentationControlChannelListener)
         .onAnswer(answer);
   },
-  close: function(reason) {
+  launch: function(presentationId, url) {
+    sendAsyncMessage('sender-launch', url);
+  },
+  disconnect: function(reason) {
     this._listener
         .QueryInterface(Ci.nsIPresentationControlChannelListener)
-        .notifyClosed(reason);
-    mockControlChannelOfReceiver.close();
+        .notifyDisconnected(reason);
+    mockControlChannelOfReceiver.disconnect();
   }
 };
 
 // control channel of receiver
 const mockControlChannelOfReceiver = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel]),
   set listener(listener) {
     // PresentationPresentingInfo::SetControlChannel
     if (listener) {
       debug('set listener for mockControlChannelOfReceiver without null');
     } else {
       debug('set listener for mockControlChannelOfReceiver with null');
     }
     this._listener = listener;
+
+    if (this._pendingOpened) {
+      this._pendingOpened = false;
+      this.notifyConnected();
+    }
   },
   get listener() {
     return this._listener;
   },
-  notifyOpened: function() {
+  notifyConnected: function() {
     // do nothing
+    if (!this._listener) {
+      this._pendingOpened = true;
+      return;
+    }
     this._listener
         .QueryInterface(Ci.nsIPresentationControlChannelListener)
-        .notifyOpened();
+        .notifyConnected();
   },
   onOffer: function(offer) {
     this._listener
         .QueryInterface(Ci.nsIPresentationControlChannelListener)
         .onOffer(offer);
   },
   sendAnswer: function(answer) {
     this._listener
         .QueryInterface(Ci.nsIPresentationSessionTransportCallback)
         .notifyTransportReady();
     sendAsyncMessage('answer-sent');
   },
-  close: function(reason) {
+  disconnect: function(reason) {
     this._listener
         .QueryInterface(Ci.nsIPresentationControlChannelListener)
-        .notifyClosed(reason);
+        .notifyDisconnected(reason);
     sendAsyncMessage('control-channel-receiver-closed', reason);
   }
 };
 
 const mockDevice = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice]),
   id:   'id',
   name: 'name',
@@ -352,18 +364,18 @@ function initMockAndListener() {
                  .onSessionRequest(mockDevice,
                                    url,
                                    sessionId,
                                    mockControlChannelOfReceiver);
   });
 
   addMessageListener('trigger-control-channel-open', function(reason) {
     debug('Got message: trigger-control-channel-open');
-    mockControlChannelOfSender.notifyOpened();
-    mockControlChannelOfReceiver.notifyOpened();
+    mockControlChannelOfSender.notifyConnected();
+    mockControlChannelOfReceiver.notifyConnected();
   });
 
   addMessageListener('trigger-on-offer', function() {
     debug('Got message: trigger-on-offer');
     mockControlChannelOfReceiver.onOffer(mockChannelDescriptionOfSender);
     mockServerSocket.onSocketAccepted(mockServerSocket, mockSocketTransport);
   });
 
--- a/dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway.js
+++ b/dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway.js
@@ -21,30 +21,35 @@ function setup() {
 
   gScript.addMessageListener('device-prompt', function devicePromptHandler() {
     debug('Got message: device-prompt');
     gScript.removeMessageListener('device-prompt', devicePromptHandler);
     gScript.sendAsyncMessage('trigger-device-prompt-select');
   });
 
   gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
-    debug('Got message: control-channel-established');
     gScript.removeMessageListener('control-channel-established',
                                   controlChannelEstablishedHandler);
+    gScript.sendAsyncMessage("trigger-control-channel-open");
+  });
+
+  gScript.addMessageListener('sender-launch', function senderLaunchHandler(url) {
+    debug('Got message: sender-launch');
+    gScript.removeMessageListener('sender-launch', senderLaunchHandler);
+    is(url, receiverUrl, 'Receiver: should receive the same url');
     receiverIframe = document.createElement('iframe');
     receiverIframe.setAttribute("mozbrowser", "true");
     receiverIframe.setAttribute("mozpresentation", receiverUrl);
     var oop = location.pathname.indexOf('_inproc') == -1;
     receiverIframe.setAttribute("remote", oop);
 
     receiverIframe.setAttribute('src', receiverUrl);
     receiverIframe.addEventListener("mozbrowserloadend", function mozbrowserloadendHander() {
       receiverIframe.removeEventListener("mozbrowserloadend", mozbrowserloadendHander);
       info("Receiver loaded.");
-      gScript.sendAsyncMessage("trigger-control-channel-open");
     });
 
     // This event is triggered when the iframe calls "alert".
     receiverIframe.addEventListener("mozbrowsershowmodalprompt", function receiverListener(evt) {
       var message = evt.detail.message;
       if (/^OK /.exec(message)) {
         ok(true, message.replace(/^OK /, ""));
       } else if (/^KO /.exec(message)) {
@@ -91,17 +96,17 @@ function setup() {
   });
 
   return Promise.resolve();
 }
 
 function testCreateRequest() {
   return new Promise(function(aResolve, aReject) {
     info('Sender: --- testCreateRequest ---');
-    request = new PresentationRequest("http://example.com");
+    request = new PresentationRequest(receiverUrl);
     request.getAvailability().then((aAvailability) => {
       aAvailability.onchange = function() {
         aAvailability.onchange = null;
         ok(aAvailability.value, "Sender: Device should be available.");
         aResolve();
       }
     }).catch((aError) => {
       ok(false, "Sender: Error occurred when getting availability: " + aError);
--- a/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver.html
+++ b/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver.html
@@ -35,26 +35,29 @@ function setup() {
 
   gScript.addMessageListener('device-prompt', function devicePromptHandler() {
     debug('Got message: device-prompt');
     gScript.removeMessageListener('device-prompt', devicePromptHandler);
     gScript.sendAsyncMessage('trigger-device-prompt-select');
   });
 
   gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
-    debug('Got message: control-channel-established');
-    gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+    gScript.removeMessageListener('control-channel-established',
+                                  controlChannelEstablishedHandler);
+    gScript.sendAsyncMessage("trigger-control-channel-open");
+  });
+
+  gScript.addMessageListener('sender-launch', function senderLaunchHandler(url) {
+    debug('Got message: sender-launch');
+    gScript.removeMessageListener('sender-launch', senderLaunchHandler);
+    is(url, receiverUrl, 'Receiver: should receive the same url');
     receiverIframe = document.createElement('iframe');
     receiverIframe.setAttribute('src', receiverUrl);
     receiverIframe.setAttribute("mozbrowser", "true");
     receiverIframe.setAttribute("mozpresentation", receiverUrl);
-    receiverIframe.onload = function() {
-      info('Receiver loaded.');
-      gScript.sendAsyncMessage('trigger-control-channel-open');
-    };
 
     // This event is triggered when the iframe calls "alert".
     receiverIframe.addEventListener("mozbrowsershowmodalprompt", function receiverListener(evt) {
       var message = evt.detail.message;
       debug('Got iframe message: ' + message);
       if (/^OK /.exec(message)) {
         ok(true, message.replace(/^OK /, ""));
       } else if (/^KO /.exec(message)) {
@@ -100,17 +103,17 @@ function setup() {
   });
 
   return Promise.resolve();
 }
 
 function testCreateRequest() {
   return new Promise(function(aResolve, aReject) {
     info('Sender: --- testCreateRequest ---');
-    request = new PresentationRequest("http://example.com");
+    request = new PresentationRequest(receiverUrl);
     request.getAvailability().then((aAvailability) => {
       aAvailability.onchange = function() {
         aAvailability.onchange = null;
         ok(aAvailability.value, "Sender: Device should be available.");
         aResolve();
       }
     }).catch((aError) => {
       ok(false, "Sender: Error occurred when getting availability: " + aError);
--- a/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver_oop.html
+++ b/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver_oop.html
@@ -35,28 +35,33 @@ function setup() {
 
   gScript.addMessageListener('device-prompt', function devicePromptHandler() {
     debug('Got message: device-prompt');
     gScript.removeMessageListener('device-prompt', devicePromptHandler);
     gScript.sendAsyncMessage('trigger-device-prompt-select');
   });
 
   gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
-    debug('Got message: control-channel-established');
     gScript.removeMessageListener('control-channel-established',
                                   controlChannelEstablishedHandler);
+    gScript.sendAsyncMessage("trigger-control-channel-open");
+  });
+
+  gScript.addMessageListener('sender-launch', function senderLaunchHandler(url) {
+    debug('Got message: sender-launch');
+    gScript.removeMessageListener('sender-launch', senderLaunchHandler);
+    is(url, receiverUrl, 'Receiver: should receive the same url');
     receiverIframe = document.createElement('iframe');
     receiverIframe.setAttribute("remote", "true");
     receiverIframe.setAttribute("mozbrowser", "true");
     receiverIframe.setAttribute("mozpresentation", receiverUrl);
     receiverIframe.setAttribute('src', receiverUrl);
     receiverIframe.addEventListener("mozbrowserloadend", function mozbrowserloadendHander() {
       receiverIframe.removeEventListener("mozbrowserloadend", mozbrowserloadendHander);
       info("Receiver loaded.");
-      gScript.sendAsyncMessage("trigger-control-channel-open");
     });
 
     // This event is triggered when the iframe calls "alert".
     receiverIframe.addEventListener("mozbrowsershowmodalprompt", function receiverListener(evt) {
       var message = evt.detail.message;
       if (/^OK /.exec(message)) {
         ok(true, message.replace(/^OK /, ""));
       } else if (/^KO /.exec(message)) {
@@ -103,17 +108,17 @@ function setup() {
   });
 
   return Promise.resolve();
 }
 
 function testCreateRequest() {
   return new Promise(function(aResolve, aReject) {
     info('Sender: --- testCreateRequest ---');
-    request = new PresentationRequest("http://example.com");
+    request = new PresentationRequest(receiverUrl);
     request.getAvailability()
       .then((aAvailability) => {
         aAvailability.onchange = function() {
           aAvailability.onchange = null;
           ok(aAvailability.value, "Sender: Device should be available.");
           aResolve();
         }
       })
--- a/dom/presentation/tests/mochitest/test_presentation_datachannel_sessiontransport.html
+++ b/dom/presentation/tests/mochitest/test_presentation_datachannel_sessiontransport.html
@@ -104,19 +104,19 @@ const clientListener = {
     setTimeout(()=>this._remoteBuilder.onOffer(aOffer), 0);
   },
   sendAnswer: function(aAnswer) {
     setTimeout(()=>this._remoteBuilder.onAnswer(aAnswer), 0);
   },
   sendIceCandidate: function(aCandidate) {
     setTimeout(()=>this._remoteBuilder.onIceCandidate(aCandidate), 0);
   },
-  close: function(aReason) {
-    setTimeout(()=>this._localBuilder.notifyClosed(aReason), 0);
-    setTimeout(()=>this._remoteBuilder.notifyClosed(aReason), 0);
+  disconnect: function(aReason) {
+    setTimeout(()=>this._localBuilder.notifyDisconnected(aReason), 0);
+    setTimeout(()=>this._remoteBuilder.notifyDisconnected(aReason), 0);
   },
   set remoteBuilder(aRemoteBuilder) {
     this._remoteBuilder = aRemoteBuilder;
   },
   set localBuilder(aLocalBuilder) {
     this._localBuilder = aLocalBuilder;
   },
 }
@@ -136,19 +136,19 @@ const serverListener = {
     setTimeout(()=>this._remoteBuilder.onOffer(aOffer), 0);
   },
   sendAnswer: function(aAnswer) {
     setTimeout(()=>this._remoteBuilder.onAnswer(aAnswer), 0);
   },
   sendIceCandidate: function(aCandidate) {
     setTimeout(()=>this._remoteBuilder.onIceCandidate(aCandidate), 0);
   },
-  close: function(aReason) {
-    setTimeout(()=>this._localBuilder.notifyClosed(aReason), 0);
-    setTimeout(()=>this._remoteBuilder.notifyClosed(aReason), 0);
+  disconnect: function(aReason) {
+    setTimeout(()=>this._localBuilder.notifyDisconnected(aReason), 0);
+    setTimeout(()=>this._remoteBuilder.notifyDisconnected(aReason), 0);
   },
   set remoteBuilder(aRemoteBuilder) {
     this._remoteBuilder = aRemoteBuilder;
   },
   set localBuilder(aLocalBuilder) {
     this._localBuilder = aLocalBuilder;
   },
 }
--- a/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js
+++ b/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js
@@ -15,16 +15,22 @@ const PROVIDER_CONTRACT_ID = "@mozilla.o
 const SD_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-sd;1";
 const UUID_CONTRACT_ID = "@mozilla.org/uuid-generator;1";
 const SERVER_CONTRACT_ID = "@mozilla.org/presentation/control-service;1";
 
 const PREF_DISCOVERY = "dom.presentation.discovery.enabled";
 const PREF_DISCOVERABLE = "dom.presentation.discoverable";
 const PREF_DEVICENAME= "dom.presentation.device.name";
 
+const LATEST_VERSION = 1;
+const SERVICE_TYPE = "_presentation-ctrl._tcp";
+const versionAttr = Cc["@mozilla.org/hash-property-bag;1"]
+                      .createInstance(Ci.nsIWritablePropertyBag2);
+versionAttr.setPropertyAsUint32("version", LATEST_VERSION);
+
 var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
 
 function MockFactory(aClass) {
   this._cls = aClass;
 }
 MockFactory.prototype = {
   createInstance: function(aOuter, aIID) {
     if (aOuter) {
@@ -195,17 +201,17 @@ TestPresentationDeviceListener.prototype
 function createDevice(host, port, serviceName, serviceType, domainName, attributes) {
   let device = new MockDNSServiceInfo();
   device.host = host || "";
   device.port = port || 0;
   device.address = host || "";
   device.serviceName = serviceName || "";
   device.serviceType = serviceType || "";
   device.domainName = domainName || "";
-  device.attributes = attributes || null;
+  device.attributes = attributes || versionAttr;
   return device;
 }
 
 function registerService() {
   Services.prefs.setBoolPref(PREF_DISCOVERABLE, true);
 
   let mockObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
@@ -331,17 +337,17 @@ function registerServiceDynamically() {
 }
 
 function addDevice() {
   Services.prefs.setBoolPref(PREF_DISCOVERY, true);
 
   let mockDevice = createDevice("device.local",
                                 12345,
                                 "service.name",
-                                "_mozilla_papi._tcp");
+                                SERVICE_TYPE);
   let mockObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
     startDiscovery: function(serviceType, listener) {
       listener.onDiscoveryStarted(serviceType);
       listener.onServiceFound(createDevice("",
                                            0,
                                            mockDevice.serviceName,
                                            mockDevice.serviceType));
@@ -388,20 +394,21 @@ function handleSessionRequest() {
   const testPresentationId = "test-presentation-id";
   const testDeviceName = "test-device-name";
 
   Services.prefs.setCharPref(PREF_DEVICENAME, testDeviceName);
 
   let mockDevice = createDevice("device.local",
                                 12345,
                                 "service.name",
-                                "_mozilla_papi._tcp");
+                                SERVICE_TYPE);
   let mockSDObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
     startDiscovery: function(serviceType, listener) {
+      do_print('xxx start discovery');
       listener.onDiscoveryStarted(serviceType);
       listener.onServiceFound(createDevice("",
                                            0,
                                            mockDevice.serviceName,
                                            mockDevice.serviceType));
       return {
         QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
         cancel: function() {}
@@ -413,64 +420,64 @@ function handleSessionRequest() {
                                               mockDevice.port,
                                               mockDevice.serviceName,
                                               mockDevice.serviceType));
     }
   };
 
   let mockServerObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlService]),
-    requestSession: function(deviceInfo, url, presentationId) {
+    connect: function(deviceInfo) {
       this.request = {
         deviceInfo: deviceInfo,
-        url: url,
-        presentationId: presentationId,
       };
       return {
         QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel]),
       };
     },
     id: "",
+    version: LATEST_VERSION,
+    isCompatibleServer: function(version) {
+      return this.version === version;
+    }
   };
 
   let contractHookSD = new ContractHook(SD_CONTRACT_ID, mockSDObj);
   let contractHookServer = new ContractHook(SERVER_CONTRACT_ID, mockServerObj);
   let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
   let listener = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener,
                                            Ci.nsISupportsWeakReference]),
     addDevice: function(device) {
       this.device = device;
     },
   };
 
   provider.listener = listener;
 
-  let controlChannel = listener.device.establishControlChannel(testUrl, testPresentationId);
+  let controlChannel = listener.device.establishControlChannel();
 
   Assert.equal(mockServerObj.request.deviceInfo.id, mockDevice.host);
   Assert.equal(mockServerObj.request.deviceInfo.address, mockDevice.host);
   Assert.equal(mockServerObj.request.deviceInfo.port, mockDevice.port);
-  Assert.equal(mockServerObj.request.url, testUrl);
-  Assert.equal(mockServerObj.request.presentationId, testPresentationId);
   Assert.equal(mockServerObj.id, testDeviceName);
 
   provider.listener = null;
 
   run_next_test();
 }
 
 function handleOnSessionRequest() {
   Services.prefs.setBoolPref(PREF_DISCOVERY, true);
   Services.prefs.setBoolPref(PREF_DISCOVERABLE, true);
 
   let mockDevice = createDevice("device.local",
                                 12345,
                                 "service.name",
-                                "_mozilla_papi._tcp");
+                                SERVICE_TYPE);
   let mockSDObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
     startDiscovery: function(serviceType, listener) {
       listener.onDiscoveryStarted(serviceType);
       listener.onServiceFound(createDevice("",
                                            0,
                                            mockDevice.serviceName,
                                            mockDevice.serviceType));
@@ -489,16 +496,17 @@ function handleOnSessionRequest() {
   };
 
   let mockServerObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlService]),
     startServer: function() {},
     sessionRequest: function() {},
     close: function() {},
     id: '',
+    version: LATEST_VERSION,
     port: 0,
     listener: null,
   };
 
   let contractHookSD = new ContractHook(SD_CONTRACT_ID, mockSDObj);
   let contractHookServer = new ContractHook(SERVER_CONTRACT_ID, mockServerObj);
   let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
   let listener = {
@@ -555,16 +563,17 @@ function handleOnSessionRequestFromUnkno
   };
 
   let mockServerObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlService]),
     startServer: function() {},
     sessionRequest: function() {},
     close: function() {},
     id: '',
+    version: LATEST_VERSION,
     port: 0,
     listener: null,
   };
 
   let contractHookSD = new ContractHook(SD_CONTRACT_ID, mockSDObj);
   let contractHookServer = new ContractHook(SERVER_CONTRACT_ID, mockServerObj);
   let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
   let listener = {
@@ -613,17 +622,17 @@ function handleOnSessionRequestFromUnkno
   provider.listener = null;
 
   run_next_test();
 }
 
 function noAddDevice() {
   Services.prefs.setBoolPref(PREF_DISCOVERY, false);
 
-  let mockDevice = createDevice("device.local", 12345, "service.name", "_mozilla_papi._tcp");
+  let mockDevice = createDevice("device.local", 12345, "service.name", SERVICE_TYPE);
   let mockObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
     startDiscovery: function(serviceType, listener) {
       Assert.ok(false, "shouldn't perform any device discovery");
     },
     registerService: function(serviceInfo, listener) {},
     resolveService: function(serviceInfo, listener) {
     }
@@ -640,24 +649,24 @@ function noAddDevice() {
   };
   provider.listener = listener;
   provider.forceDiscovery();
   provider.listener = null;
 
   run_next_test();
 }
 
-function ignoreSelfDevice() {
+function ignoreIncompatibleDevice() {
   Services.prefs.setBoolPref(PREF_DISCOVERY, false);
   Services.prefs.setBoolPref(PREF_DISCOVERABLE, true);
 
   let mockDevice = createDevice("device.local",
                                 12345,
                                 "service.name",
-                                "_mozilla_papi._tcp");
+                                SERVICE_TYPE);
   let mockSDObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
     startDiscovery: function(serviceType, listener) {
       listener.onDiscoveryStarted(serviceType);
       listener.onServiceFound(createDevice("",
                                            0,
                                            mockDevice.serviceName,
                                            mockDevice.serviceType));
@@ -687,16 +696,20 @@ function ignoreSelfDevice() {
   };
 
   let mockServerObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlService]),
     startServer: function() {},
     sessionRequest: function() {},
     close: function() {},
     id: '',
+    version: LATEST_VERSION,
+    isCompatibleServer: function(version) {
+      return false;
+    },
     port: 0,
     listener: null,
   };
 
   let contractHookSD = new ContractHook(SD_CONTRACT_ID, mockSDObj);
   let contractHookServer = new ContractHook(SERVER_CONTRACT_ID, mockServerObj);
   let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
   let listener = new TestPresentationDeviceListener();
@@ -708,23 +721,97 @@ function ignoreSelfDevice() {
   // Start discovery
   Services.prefs.setBoolPref(PREF_DISCOVERY, true);
   Assert.equal(listener.count(), 0);
 
   provider.listener = null;
 
   run_next_test();
 }
+
+function ignoreSelfDevice() {
+  Services.prefs.setBoolPref(PREF_DISCOVERY, false);
+  Services.prefs.setBoolPref(PREF_DISCOVERABLE, true);
+
+  let mockDevice = createDevice("device.local",
+                                12345,
+                                "service.name",
+                                SERVICE_TYPE);
+  let mockSDObj = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
+    startDiscovery: function(serviceType, listener) {
+      listener.onDiscoveryStarted(serviceType);
+      listener.onServiceFound(createDevice("",
+                                           0,
+                                           mockDevice.serviceName,
+                                           mockDevice.serviceType));
+      return {
+        QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
+        cancel: function() {}
+      };
+    },
+    registerService: function(serviceInfo, listener) {
+      listener.onServiceRegistered(createDevice("",
+                                                0,
+                                                mockDevice.serviceName,
+                                                mockDevice.serviceType));
+      return {
+        QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
+        cancel: function() {}
+      };
+    },
+    resolveService: function(serviceInfo, listener) {
+      Assert.equal(serviceInfo.serviceName, mockDevice.serviceName);
+      Assert.equal(serviceInfo.serviceType, mockDevice.serviceType);
+      listener.onServiceResolved(createDevice(mockDevice.host,
+                                              mockDevice.port,
+                                              mockDevice.serviceName,
+                                              mockDevice.serviceType));
+    }
+  };
+
+  let mockServerObj = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlService]),
+    startServer: function() {},
+    sessionRequest: function() {},
+    close: function() {},
+    id: '',
+    version: LATEST_VERSION,
+    isCompatibleServer: function(version) {
+      return this.version === version;
+    },
+    port: 0,
+    listener: null,
+  };
+
+  let contractHookSD = new ContractHook(SD_CONTRACT_ID, mockSDObj);
+  let contractHookServer = new ContractHook(SERVER_CONTRACT_ID, mockServerObj);
+  let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
+  let listener = new TestPresentationDeviceListener();
+
+  // Register service
+  provider.listener = listener;
+  Assert.equal(mockServerObj.id, mockDevice.host);
+
+  // Start discovery
+  Services.prefs.setBoolPref(PREF_DISCOVERY, true);
+  Assert.equal(listener.count(), 0);
+
+  provider.listener = null;
+
+  run_next_test();
+}
+
 function addDeviceDynamically() {
   Services.prefs.setBoolPref(PREF_DISCOVERY, false);
 
   let mockDevice = createDevice("device.local",
                                 12345,
                                 "service.name",
-                                "_mozilla_papi._tcp");
+                                SERVICE_TYPE);
   let mockObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
     startDiscovery: function(serviceType, listener) {
       listener.onDiscoveryStarted(serviceType);
       listener.onServiceFound(createDevice("",
                                            0,
                                            mockDevice.serviceName,
                                            mockDevice.serviceType));
@@ -767,18 +854,18 @@ function addDeviceDynamically() {
   provider.listener = null;
 
   run_next_test();
 }
 
 function updateDevice() {
   Services.prefs.setBoolPref(PREF_DISCOVERY, true);
 
-  let mockDevice1 = createDevice("A.local", 12345, "N1", "_mozilla_papi._tcp");
-  let mockDevice2 = createDevice("A.local", 23456, "N2", "_mozilla_papi._tcp");
+  let mockDevice1 = createDevice("A.local", 12345, "N1", SERVICE_TYPE);
+  let mockDevice2 = createDevice("A.local", 23456, "N2", SERVICE_TYPE);
 
   let mockObj = {
     discovered: false,
 
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
     startDiscovery: function(serviceType, listener) {
       listener.onDiscoveryStarted(serviceType);
 
@@ -793,17 +880,17 @@ function updateDevice() {
         QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
         cancel: function() {
           listener.onDiscoveryStopped(serviceType);
         }
       };
     },
     registerService: function(serviceInfo, listener) {},
     resolveService: function(serviceInfo, listener) {
-      Assert.equal(serviceInfo.serviceType, "_mozilla_papi._tcp");
+      Assert.equal(serviceInfo.serviceType, SERVICE_TYPE);
       if (serviceInfo.serviceName == "N1") {
         listener.onServiceResolved(mockDevice1);
       } else if (serviceInfo.serviceName == "N2") {
         listener.onServiceResolved(mockDevice2);
       } else {
         Assert.ok(false);
       }
     }
@@ -853,19 +940,19 @@ function updateDevice() {
   provider.listener = null;
 
   run_next_test();
 }
 
 function diffDiscovery() {
   Services.prefs.setBoolPref(PREF_DISCOVERY, true);
 
-  let mockDevice1 = createDevice("A.local", 12345, "N1", "_mozilla_papi._tcp");
-  let mockDevice2 = createDevice("B.local", 23456, "N2", "_mozilla_papi._tcp");
-  let mockDevice3 = createDevice("C.local", 45678, "N3", "_mozilla_papi._tcp");
+  let mockDevice1 = createDevice("A.local", 12345, "N1", SERVICE_TYPE);
+  let mockDevice2 = createDevice("B.local", 23456, "N2", SERVICE_TYPE);
+  let mockDevice3 = createDevice("C.local", 45678, "N3", SERVICE_TYPE);
 
   let mockObj = {
     discovered: false,
 
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
     startDiscovery: function(serviceType, listener) {
       listener.onDiscoveryStarted(serviceType);
 
@@ -882,17 +969,17 @@ function diffDiscovery() {
         QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
         cancel: function() {
           listener.onDiscoveryStopped(serviceType);
         }
       };
     },
     registerService: function(serviceInfo, listener) {},
     resolveService: function(serviceInfo, listener) {
-      Assert.equal(serviceInfo.serviceType, "_mozilla_papi._tcp");
+      Assert.equal(serviceInfo.serviceType, SERVICE_TYPE);
       if (serviceInfo.serviceName == "N1") {
         listener.onServiceResolved(mockDevice1);
       } else if (serviceInfo.serviceName == "N2") {
         listener.onServiceResolved(mockDevice2);
       } else if (serviceInfo.serviceName == "N3") {
         listener.onServiceResolved(mockDevice3);
       } else {
         Assert.ok(false);
@@ -939,17 +1026,17 @@ function diffDiscovery() {
 
 function serverClosed() {
   Services.prefs.setBoolPref(PREF_DISCOVERABLE, true);
   Services.prefs.setBoolPref(PREF_DISCOVERY, true);
 
   let mockDevice = createDevice("device.local",
                                 12345,
                                 "service.name",
-                                "_mozilla_papi._tcp");
+                                SERVICE_TYPE);
 
   let mockObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
     startDiscovery: function(serviceType, listener) {
       listener.onDiscoveryStarted(serviceType);
       listener.onServiceFound(createDevice("",
                                            0,
                                            mockDevice.serviceName,
@@ -1029,16 +1116,17 @@ function run_test() {
   add_test(registerService);
   add_test(noRegisterService);
   add_test(registerServiceDynamically);
   add_test(addDevice);
   add_test(handleSessionRequest);
   add_test(handleOnSessionRequest);
   add_test(handleOnSessionRequestFromUnknownDevice);
   add_test(noAddDevice);
+  add_test(ignoreIncompatibleDevice);
   add_test(ignoreSelfDevice);
   add_test(addDeviceDynamically);
   add_test(updateDevice);
   add_test(diffDiscovery);
   add_test(serverClosed);
 
   run_next_test();
 }
--- a/dom/presentation/tests/xpcshell/test_presentation_device_manager.js
+++ b/dom/presentation/tests/xpcshell/test_presentation_device_manager.js
@@ -16,17 +16,18 @@ function TestPresentationDevice() {}
 
 
 function TestPresentationControlChannel() {}
 
 TestPresentationControlChannel.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel]),
   sendOffer: function(offer) {},
   sendAnswer: function(answer) {},
-  close: function() {},
+  disconnect: function() {},
+  launch: function() {},
   set listener(listener) {},
   get listener() {},
 };
 
 var testProvider = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceProvider]),
 
   forceDiscovery: function() {
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/xpcshell/test_presentation_state_machine.js
@@ -0,0 +1,195 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+/* jshint esnext:true, globalstrict:true, moz:true, undef:true, unused:true */
+/* globals Components,Assert,run_next_test,add_test,do_execute_soon */
+
+'use strict';
+
+const { utils: Cu, results: Cr } = Components;
+
+/* globals ControllerStateMachine */
+Cu.import('resource://gre/modules/presentation/ControllerStateMachine.jsm');
+/* globals ReceiverStateMachine */
+Cu.import('resource://gre/modules/presentation/ReceiverStateMachine.jsm');
+/* globals State */
+Cu.import('resource://gre/modules/presentation/StateMachineHelper.jsm');
+
+const testControllerId = 'test-controller-id';
+const testPresentationId = 'test-presentation-id';
+const testUrl = 'http://example.org';
+
+let mockControllerChannel = {};
+let mockReceiverChannel = {};
+
+let controllerState = new ControllerStateMachine(mockControllerChannel, testControllerId);
+let receiverState = new ReceiverStateMachine(mockReceiverChannel);
+
+mockControllerChannel.sendCommand = function(command) {
+  do_execute_soon(function() {
+    receiverState.onCommand(command);
+  });
+};
+
+mockReceiverChannel.sendCommand = function(command) {
+  do_execute_soon(function() {
+    controllerState.onCommand(command);
+  });
+};
+
+function connect() {
+  Assert.equal(controllerState.state, State.INIT, 'controller in init state');
+  Assert.equal(receiverState.state, State.INIT, 'receiver in init state');
+  // step 1: underlying connection is ready
+  controllerState.onChannelReady();
+  Assert.equal(controllerState.state, State.CONNECTING, 'controller in connecting state');
+  receiverState.onChannelReady();
+  Assert.equal(receiverState.state, State.CONNECTING, 'receiver in connecting state');
+
+  // step 2: receiver reply to connect command
+  mockReceiverChannel.notifyDeviceConnected = function(deviceId) {
+    Assert.equal(deviceId, testControllerId, 'receiver connect to mock controller');
+    Assert.equal(receiverState.state, State.CONNECTED, 'receiver in connected state');
+
+    // step 3: controller receive connect-ack command
+    mockControllerChannel.notifyDeviceConnected = function() {
+      Assert.equal(controllerState.state, State.CONNECTED, 'controller in connected state');
+      run_next_test();
+    };
+  };
+}
+
+function launch() {
+  Assert.equal(controllerState.state, State.CONNECTED, 'controller in connected state');
+  Assert.equal(receiverState.state, State.CONNECTED, 'receiver in connected state');
+
+  controllerState.launch(testPresentationId, testUrl);
+  mockReceiverChannel.notifyLaunch = function(presentationId, url) {
+    Assert.equal(receiverState.state, State.CONNECTED, 'receiver in connected state');
+    Assert.equal(presentationId, testPresentationId, 'expected presentationId received');
+    Assert.equal(url, testUrl, 'expected url received');
+
+    mockControllerChannel.notifyLaunch = function(presentationId) {
+      Assert.equal(controllerState.state, State.CONNECTED, 'controller in connected state');
+      Assert.equal(presentationId, testPresentationId, 'expected presentationId received from ack');
+
+      run_next_test();
+    };
+  };
+}
+
+function exchangeSDP() {
+  Assert.equal(controllerState.state, State.CONNECTED, 'controller in connected state');
+  Assert.equal(receiverState.state, State.CONNECTED, 'receiver in connected state');
+
+  const testOffer = 'test-offer';
+  const testAnswer = 'test-answer';
+  const testIceCandidate = 'test-ice-candidate';
+  controllerState.sendOffer(testOffer);
+  mockReceiverChannel.notifyOffer = function(offer) {
+    Assert.equal(offer, testOffer, 'expected offer received');
+
+    receiverState.sendAnswer(testAnswer);
+    mockControllerChannel.notifyAnswer = function(answer) {
+      Assert.equal(answer, testAnswer, 'expected answer received');
+
+      controllerState.updateIceCandidate(testIceCandidate);
+      mockReceiverChannel.notifyIceCandidate = function(candidate) {
+        Assert.equal(candidate, testIceCandidate, 'expected ice candidate received in receiver');
+
+        receiverState.updateIceCandidate(testIceCandidate);
+        mockControllerChannel.notifyIceCandidate = function(candidate) {
+          Assert.equal(candidate, testIceCandidate, 'expected ice candidate received in controller');
+
+          run_next_test();
+        };
+      };
+    };
+  };
+}
+
+function disconnect() {
+  // step 1: controller send disconnect command
+  controllerState.onChannelClosed(Cr.NS_OK, false);
+  Assert.equal(controllerState.state, State.CLOSING, 'controller in closing state');
+
+  mockReceiverChannel.notifyDisconnected = function(reason) {
+    Assert.equal(reason, Cr.NS_OK, 'receive close reason');
+    Assert.equal(receiverState.state, State.CLOSED, 'receiver in closed state');
+
+    receiverState.onChannelClosed(Cr.NS_OK, true);
+    Assert.equal(receiverState.state, State.CLOSED, 'receiver in closed state');
+
+    mockControllerChannel.notifyDisconnected = function(reason) {
+      Assert.equal(reason, Cr.NS_OK, 'receive close reason');
+      Assert.equal(controllerState.state, State.CLOSED, 'controller in closed state');
+
+      run_next_test();
+    };
+    controllerState.onChannelClosed(Cr.NS_OK, true);
+  };
+}
+
+function receiverDisconnect() {
+  // initial state: controller and receiver are connected
+  controllerState.state = State.CONNECTED;
+  receiverState.state = State.CONNECTED;
+
+  // step 1: controller send disconnect command
+  receiverState.onChannelClosed(Cr.NS_OK, false);
+  Assert.equal(receiverState.state, State.CLOSING, 'receiver in closing state');
+
+  mockControllerChannel.notifyDisconnected = function(reason) {
+    Assert.equal(reason, Cr.NS_OK, 'receive close reason');
+    Assert.equal(controllerState.state, State.CLOSED, 'controller in closed state');
+
+    controllerState.onChannelClosed(Cr.NS_OK, true);
+    Assert.equal(controllerState.state, State.CLOSED, 'controller in closed state');
+
+    mockReceiverChannel.notifyDisconnected = function(reason) {
+      Assert.equal(reason, Cr.NS_OK, 'receive close reason');
+      Assert.equal(receiverState.state, State.CLOSED, 'receiver in closed state');
+
+      run_next_test();
+    };
+    receiverState.onChannelClosed(Cr.NS_OK, true);
+  };
+}
+
+function abnormalDisconnect() {
+  // initial state: controller and receiver are connected
+  controllerState.state = State.CONNECTED;
+  receiverState.state = State.CONNECTED;
+
+  const testErrorReason = Cr.NS_ERROR_FAILURE;
+  // step 1: controller send disconnect command
+  controllerState.onChannelClosed(testErrorReason, false);
+  Assert.equal(controllerState.state, State.CLOSING, 'controller in closing state');
+
+  mockReceiverChannel.notifyDisconnected = function(reason) {
+    Assert.equal(reason, testErrorReason, 'receive abnormal close reason');
+    Assert.equal(receiverState.state, State.CLOSED, 'receiver in closed state');
+
+    receiverState.onChannelClosed(Cr.NS_OK, true);
+    Assert.equal(receiverState.state, State.CLOSED, 'receiver in closed state');
+
+    mockControllerChannel.notifyDisconnected = function(reason) {
+      Assert.equal(reason, testErrorReason, 'receive abnormal close reason');
+      Assert.equal(controllerState.state, State.CLOSED, 'controller in closed state');
+
+      run_next_test();
+    };
+    controllerState.onChannelClosed(Cr.NS_OK, true);
+  };
+}
+
+add_test(connect);
+add_test(launch);
+add_test(exchangeSDP);
+add_test(disconnect);
+add_test(receiverDisconnect);
+add_test(abnormalDisconnect);
+
+function run_test() { // jshint ignore:line
+  run_next_test();
+}
--- a/dom/presentation/tests/xpcshell/test_tcp_control_channel.js
+++ b/dom/presentation/tests/xpcshell/test_tcp_control_channel.js
@@ -4,17 +4,17 @@
 
 'use strict';
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
 
-var tps;
+var pcs;
 
 // Call |run_next_test| if all functions in |names| are called
 function makeJointSuccess(names) {
   let funcs = {}, successCount = 0;
   names.forEach(function(name) {
     funcs[name] = function() {
       do_print('got expected: ' + name);
       if (++successCount === names.length)
@@ -51,35 +51,35 @@ var candidate;
 const OFFER_ADDRESS = '192.168.123.123';
 const OFFER_PORT = 123;
 
 // controller's presentation channel description
 const ANSWER_ADDRESS = '192.168.321.321';
 const ANSWER_PORT = 321;
 
 function loopOfferAnser() {
-  tps = Cc["@mozilla.org/presentation/control-service;1"]
+  pcs = Cc["@mozilla.org/presentation/control-service;1"]
         .createInstance(Ci.nsIPresentationControlService);
-  tps.id = 'controllerID';
-  tps.startServer(PRESENTER_CONTROL_CHANNEL_PORT);
+  pcs.id = 'controllerID';
+  pcs.startServer(PRESENTER_CONTROL_CHANNEL_PORT);
 
   testPresentationServer();
 }
 
 
 function testPresentationServer() {
   let yayFuncs = makeJointSuccess(['controllerControlChannelClose',
                                    'presenterControlChannelClose']);
   let controllerControlChannel;
 
-  tps.listener = {
+  pcs.listener = {
 
     onSessionRequest: function(deviceInfo, url, presentationId, controlChannel) {
       controllerControlChannel = controlChannel;
-      Assert.equal(deviceInfo.id, tps.id, 'expected device id');
+      Assert.equal(deviceInfo.id, pcs.id, 'expected device id');
       Assert.equal(deviceInfo.address, '127.0.0.1', 'expected device address');
       Assert.equal(url, 'http://example.com', 'expected url');
       Assert.equal(presentationId, 'testPresentationId', 'expected presentation id');
 
       controllerControlChannel.listener = {
         status: 'created',
         onOffer: function(aOffer) {
           Assert.equal(this.status, 'opened', '1. controllerControlChannel: get offer, send answer');
@@ -104,25 +104,25 @@ function testPresentationServer() {
         onIceCandidate: function(aCandidate) {
           Assert.ok(true, '3. controllerControlChannel: get ice candidate, close channel');
           let recvCandidate = JSON.parse(aCandidate);
           for (let key in recvCandidate) {
             if (typeof(recvCandidate[key]) !== "function") {
               Assert.equal(recvCandidate[key], candidate[key], "key " + key + " should match.");
             }
           }
-          controllerControlChannel.close(CLOSE_CONTROL_CHANNEL_REASON);
+          controllerControlChannel.disconnect(CLOSE_CONTROL_CHANNEL_REASON);
         },
-        notifyOpened: function() {
+        notifyConnected: function() {
           Assert.equal(this.status, 'created', '0. controllerControlChannel: opened');
           this.status = 'opened';
         },
-        notifyClosed: function(aReason) {
+        notifyDisconnected: function(aReason) {
           Assert.equal(this.status, 'onOffer', '4. controllerControlChannel: closed');
-          Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, 'presenterControlChannel notify closed');
+          Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, 'controllerControlChannel notify closed');
           this.status = 'closed';
           yayFuncs.controllerControlChannelClose();
         },
         QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
       };
     },
 
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlServerListener]),
@@ -130,19 +130,17 @@ function testPresentationServer() {
 
   let presenterDeviceInfo = {
     id: 'presentatorID',
     address: '127.0.0.1',
     port: PRESENTER_CONTROL_CHANNEL_PORT,
     QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPDeviceInfo]),
   };
 
-  let presenterControlChannel = tps.requestSession(presenterDeviceInfo,
-                                                   'http://example.com',
-                                                   'testPresentationId');
+  let presenterControlChannel = pcs.connect(presenterDeviceInfo);
 
   presenterControlChannel.listener = {
     status: 'created',
     onOffer: function(offer) {
       Assert.ok(false, 'get offer');
     },
     onAnswer: function(aAnswer) {
       Assert.equal(this.status, 'opened', '2. presenterControlChannel: get answer, send ICE candidate');
@@ -157,70 +155,71 @@ function testPresentationServer() {
         sdpMid: "helloworld",
         sdpMLineIndex: 1
       };
       presenterControlChannel.sendIceCandidate(JSON.stringify(candidate));
     },
     onIceCandidate: function(aCandidate) {
       Assert.ok(false, 'get ICE candidate');
     },
-    notifyOpened: function() {
+    notifyConnected: function() {
       Assert.equal(this.status, 'created', '0. presenterControlChannel: opened, send offer');
+      presenterControlChannel.launch('testPresentationId', 'http://example.com');
       this.status = 'opened';
       try {
         let tcpType = Ci.nsIPresentationChannelDescription.TYPE_TCP;
         let offer = new TestDescription(tcpType, [OFFER_ADDRESS], OFFER_PORT)
         presenterControlChannel.sendOffer(offer);
       } catch (e) {
         Assert.ok(false, 'sending offer fails:' + e);
       }
     },
-    notifyClosed: function(aReason) {
+    notifyDisconnected: function(aReason) {
       this.status = 'closed';
       Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, '4. presenterControlChannel notify closed');
       yayFuncs.presenterControlChannelClose();
     },
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
   };
 }
 
 function setOffline() {
-  tps.listener = {
+  pcs.listener = {
     onPortChange: function(aPort) {
       Assert.notEqual(aPort, 0, 'TCPPresentationServer port changed and the port should be valid');
-      tps.close();
+      pcs.close();
       run_next_test();
     },
   };
 
   // Let the server socket restart automatically.
   Services.io.offline = true;
   Services.io.offline = false;
 }
 
 function oneMoreLoop() {
   try {
-    tps.startServer(PRESENTER_CONTROL_CHANNEL_PORT);
+    pcs.startServer(PRESENTER_CONTROL_CHANNEL_PORT);
     testPresentationServer();
   } catch (e) {
     Assert.ok(false, 'TCP presentation init fail:' + e);
     run_next_test();
   }
 }
 
 
 function shutdown()
 {
-  tps.listener = {
+  pcs.listener = {
     onPortChange: function(aPort) {
       Assert.ok(false, 'TCPPresentationServer port changed');
     },
   };
-  tps.close();
-  Assert.equal(tps.port, 0, "TCPPresentationServer closed");
+  pcs.close();
+  Assert.equal(pcs.port, 0, "TCPPresentationServer closed");
   run_next_test();
 }
 
 // Test manually close control channel with NS_ERROR_FAILURE
 function changeCloseReason() {
   CLOSE_CONTROL_CHANNEL_REASON = Cr.NS_ERROR_FAILURE;
   run_next_test();
 }
--- a/dom/presentation/tests/xpcshell/xpcshell.ini
+++ b/dom/presentation/tests/xpcshell/xpcshell.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 head =
 tail =
 
 [test_multicast_dns_device_provider.js]
 [test_presentation_device_manager.js]
 [test_presentation_session_transport.js]
 [test_tcp_control_channel.js]
+[test_presentation_state_machine.js]
--- a/dom/push/test/xpcshell/xpcshell.ini
+++ b/dom/push/test/xpcshell/xpcshell.ini
@@ -1,12 +1,11 @@
 [DEFAULT]
 head = head.js head-http2.js
 tail =
-firefox-appdir = browser
 # Push notifications and alarms are currently disabled on Android.
 skip-if = toolkit == 'android'
 
 [test_clear_forgetAboutSite.js]
 [test_clear_origin_data.js]
 [test_crypto.js]
 [test_drop_expired.js]
 [test_handler_service.js]
--- a/dom/security/test/csp/file_main.js
+++ b/dom/security/test/csp/file_main.js
@@ -10,19 +10,17 @@ function doXHR(uri, callback) {
   } catch(ex) {}
 }
 
 doXHR("http://mochi.test:8888/tests/dom/security/test/csp/file_CSP.sjs?testid=xhr_good");
 doXHR("http://example.com/tests/dom/security/test/csp/file_CSP.sjs?testid=xhr_bad");
 fetch("http://mochi.test:8888/tests/dom/security/test/csp/file_CSP.sjs?testid=fetch_good");
 fetch("http://example.com/tests/dom/security/test/csp/file_CSP.sjs?testid=fetch_bad");
 navigator.sendBeacon("http://mochi.test:8888/tests/dom/security/test/csp/file_CSP.sjs?testid=beacon_good");
-try {
-  navigator.sendBeacon("http://example.com/tests/dom/security/test/csp/file_CSP.sjs?testid=beacon_bad");
-} catch(ex) {}
+navigator.sendBeacon("http://example.com/tests/dom/security/test/csp/file_CSP.sjs?testid=beacon_bad");
 
 var topWorkerBlob;
 var nestedWorkerBlob;
 
 doXHR("file_main_worker.js", function (topResponse) {
   topWorkerBlob = URL.createObjectURL(topResponse);
   doXHR("file_child_worker.js", function (response) {
     nestedWorkerBlob = URL.createObjectURL(response);
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_sendbeacon.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <meta http-equiv="Content-Security-Policy" content= "connect-src 'none'">
+  <title>Bug 1234813 - sendBeacon should not throw if blocked by Content Policy</title>
+</head>
+<body>
+
+<script type="application/javascript">
+try {
+  navigator.sendBeacon("http://example.com/sendbeaconintonirvana");
+  window.parent.postMessage({result: "blocked-beacon-does-not-throw"}, "*");
+  }
+  catch (e) {
+    window.parent.postMessage({result: "blocked-beacon-throws"}, "*");
+  }
+</script>
+
+</body>
+</html>
--- a/dom/security/test/csp/mochitest.ini
+++ b/dom/security/test/csp/mochitest.ini
@@ -179,16 +179,17 @@ support-files =
   file_sandbox_7.html
   file_sandbox_8.html
   file_sandbox_9.html
   file_sandbox_10.html
   file_sandbox_11.html
   file_sandbox_12.html
   file_require_sri_meta.sjs
   file_require_sri_meta.js
+  file_sendbeacon.html
 
 [test_base-uri.html]
 [test_blob_data_schemes.html]
 [test_connect-src.html]
 [test_CSP.html]
 [test_allow_https_schemes.html]
 skip-if = buildapp == 'b2g' #no ssl support
 [test_bug663567.html]
@@ -270,8 +271,9 @@ tags = mcb
 tags = mcb
 [test_form_action_blocks_url.html]
 [test_meta_whitespace_skipping.html]
 [test_iframe_sandbox.html]
 [test_iframe_sandbox_top_1.html]
 [test_sandbox.html]
 [test_ping.html]
 [test_require_sri_meta.html]
+[test_sendbeacon.html]
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/test_sendbeacon.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Bug 1234813 - sendBeacon should not throw if blocked by Content Policy</title>
+  <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<iframe style="width:100%;" id="testframe" src="file_sendbeacon.html"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * Let's try to fire a sendBeacon which gets blocked by CSP. Let's make sure
+ * sendBeacon does not throw an exception.
+ */
+SimpleTest.waitForExplicitFinish();
+
+// a postMessage handler used to bubble up the
+// result from within the iframe.
+window.addEventListener("message", receiveMessage, false);
+function receiveMessage(event) {
+  var result = event.data.result;
+  is(result, "blocked-beacon-does-not-throw", "sendBeacon should not throw");
+  window.removeEventListener("message", receiveMessage, false);
+  SimpleTest.finish();
+}
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/general/file_contentpolicytype_targeted_link_iframe.sjs
@@ -0,0 +1,46 @@
+// custom *.sjs for Bug 1255240
+
+const TEST_FRAME = `
+  <!DOCTYPE HTML>
+  <html>
+  <head><meta charset='utf-8'></head>
+  <body>
+  <a id='testlink' target='innerframe' href='file_contentpolicytype_targeted_link_iframe.sjs?innerframe'>click me</a>
+  <iframe name='innerframe'></iframe>
+  <script type='text/javascript'>
+    var link = document.getElementById('testlink');
+    testlink.click();
+  </script>
+  </body>
+  </html> `;
+
+const INNER_FRAME = `
+  <!DOCTYPE HTML>
+  <html>
+  <head><meta charset='utf-8'></head>
+  hello world!
+  </body>
+  </html>`;
+
+function handleRequest(request, response)
+{
+  // avoid confusing cache behaviors
+  response.setHeader("Cache-Control", "no-cache", false);
+  response.setHeader("Content-Type", "text/html", false);
+
+  var queryString = request.queryString;
+
+  if (queryString === "testframe") {
+    response.write(TEST_FRAME);
+    return;
+  }
+
+  if (queryString === "innerframe") {
+    response.write(INNER_FRAME);
+    return;
+  }
+
+  // we should never get here, but just in case
+  // return something unexpected
+  response.write("do'h");
+}
new file mode 100644
--- /dev/null
+++ b/dom/security/test/general/mochitest.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files =
+  file_contentpolicytype_targeted_link_iframe.sjs
+
+[test_contentpolicytype_targeted_link_iframe.html]
new file mode 100644
--- /dev/null
+++ b/dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Bug 1255240 - Test content policy types within content policies for targeted links in iframes</title>
+  <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * Let's load a link into a targeted iframe and make sure the content policy
+ * type used for content policy checks is of TYPE_SUBDOCUMENT both times.
+ */
+
+const Cc = SpecialPowers.Cc;
+const Ci = SpecialPowers.Ci;
+
+const EXPECTED_CONTENT_TYPE = Ci.nsIContentPolicy.TYPE_SUBDOCUMENT;
+const EXPECTED_URL = 
+  "http://mochi.test:8888/tests/dom/security/test/general/file_contentpolicytype_targeted_link_iframe.sjs?innerframe";
+const TEST_FRAME_URL =
+  "file_contentpolicytype_targeted_link_iframe.sjs?testframe";
+
+// we should get two content policy chcks with the same content policy type
+const EXPECTED_RESULTS = 2;
+var testCounter = 0;
+
+// -----  START Content Policy implementation for the test
+var categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
+
+const POLICYNAME = "@mozilla.org/iframetestpolicy;1";
+const POLICYID = SpecialPowers.wrap(SpecialPowers.Components)
+                              .ID("{6cc95ef3-40e1-4d59-87f0-86f100373227}");
+
+var policy = {
+  // nsISupports implementation
+  QueryInterface: function(iid) {
+    iid = SpecialPowers.wrap(iid);
+    if (iid.equals(Ci.nsISupports) ||
+        iid.equals(Ci.nsIFactory) ||
+        iid.equals(Ci.nsIContentPolicy))
+      return this;
+
+    throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE;
+  },
+
+  // nsIFactory implementation
+  createInstance: function(outer, iid) {
+    return this.QueryInterface(iid);
+  },
+
+  // nsIContentPolicy implementation
+  shouldLoad: function(contentType, contentLocation, requestOrigin,
+                       context, mimeTypeGuess, extra) {
+
+    // make sure we get the right amount of content policy calls
+    // e.g. about:blank also gets chrcked by content policies
+    if (contentLocation.asciiSpec === EXPECTED_URL) {
+      is(contentType, EXPECTED_CONTENT_TYPE,
+         "content policy type should TYPESUBDOCUMENT");
+      testCounter++;
+    }
+
+    if (testCounter === EXPECTED_RESULTS) {
+      categoryManager.deleteCategoryEntry("content-policy", POLICYNAME, false);
+      SimpleTest.finish();
+    }
+    return Ci.nsIContentPolicy.ACCEPT;
+  },
+
+  shouldProcess: function(contentType, contentLocation, requestOrigin,
+                          context, mimeTypeGuess, extra) {
+    return Ci.nsIContentPolicy.ACCEPT;
+  }
+}
+policy = SpecialPowers.wrapCallbackObject(policy);
+
+// Register content policy
+var componentManager = SpecialPowers.wrap(SpecialPowers.Components).manager
+                                    .QueryInterface(Ci.nsIComponentRegistrar);
+
+componentManager.registerFactory(POLICYID, "Test content policy", POLICYNAME, policy);
+categoryManager.addCategoryEntry("content-policy", POLICYNAME, POLICYNAME, false, true);
+
+// ----- END Content Policy implementation for the test
+
+
+// start the test
+SimpleTest.waitForExplicitFinish();
+var testframe = document.getElementById("testframe");
+testframe.src = TEST_FRAME_URL;
+
+</script>
+</body>
+</html>
--- a/dom/security/test/moz.build
+++ b/dom/security/test/moz.build
@@ -10,16 +10,17 @@ XPCSHELL_TESTS_MANIFESTS += [
 
 GeckoCppUnitTests([
      'TestCSPParser',
 ])
 
 MOCHITEST_MANIFESTS += [
     'cors/mochitest.ini',
     'csp/mochitest.ini',
+    'general/mochitest.ini',
     'mixedcontentblocker/mochitest.ini',
     'sri/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += [
     'csp/chrome.ini',
 ]
 
--- a/image/SourceBuffer.cpp
+++ b/image/SourceBuffer.cpp
@@ -98,16 +98,17 @@ SourceBuffer::CreateChunk(size_t aCapaci
   size_t finalCapacity = aRoundUp ? RoundedUpCapacity(aCapacity)
                                   : aCapacity;
 
   // Use the size of the SurfaceCache as an additional heuristic to avoid
   // allocating huge buffers. Generally images do not get smaller when decoded,
   // so if we could store the source data in the SurfaceCache, we assume that
   // there's no way we'll be able to store the decoded version.
   if (MOZ_UNLIKELY(!SurfaceCache::CanHold(finalCapacity))) {
+    NS_WARNING("SourceBuffer refused to create chunk too large for SurfaceCache");
     return Nothing();
   }
 
   return Some(Chunk(finalCapacity));
 }
 
 nsresult
 SourceBuffer::Compact()
--- a/js/public/UbiNode.h
+++ b/js/public/UbiNode.h
@@ -676,16 +676,18 @@ class Node {
     mozilla::AlignedStorage2<Base> storage;
     Base* base() { return storage.addr(); }
     const Base* base() const { return storage.addr(); }
 
     template<typename T>
     void construct(T* ptr) {
         static_assert(sizeof(Concrete<T>) == sizeof(*base()),
                       "ubi::Base specializations must be the same size as ubi::Base");
+        static_assert(mozilla::IsBaseOf<Base, Concrete<T>>::value,
+                      "ubi::Concrete<T> must inherit from ubi::Base");
         Concrete<T>::construct(base(), ptr);
     }
     struct ConstructFunctor;
 
   public:
     Node() { construct<void>(nullptr); }
 
     template<typename T>
--- a/js/src/builtin/Object.js
+++ b/js/src/builtin/Object.js
@@ -83,21 +83,16 @@ function Object_toLocaleString() {
     var O = this;
 
     // Step 2.
     return callContentFunction(O.toString, O);
 }
 
 // ES7 draft (2016 March 8) B.2.2.3
 function ObjectDefineSetter(name, setter) {
-    if (this === null || this === undefined)
-        AddContentTelemetry(TELEMETRY_DEFINE_GETTER_SETTER_THIS_NULL_UNDEFINED, 1);
-    else
-        AddContentTelemetry(TELEMETRY_DEFINE_GETTER_SETTER_THIS_NULL_UNDEFINED, 0);
-
     // Step 1.
     var object = ToObject(this);
 
     // Step 2.
     if (!IsCallable(setter))
         ThrowTypeError(JSMSG_BAD_GETTER_OR_SETTER, "setter");
 
     // Step 3.
@@ -114,21 +109,16 @@ function ObjectDefineSetter(name, setter
     // Step 5.
     std_Object_defineProperty(object, key, desc);
 
     // Step 6. (implicit)
 }
 
 // ES7 draft (2016 March 8) B.2.2.2
 function ObjectDefineGetter(name, getter) {
-    if (this === null || this === undefined)
-        AddContentTelemetry(TELEMETRY_DEFINE_GETTER_SETTER_THIS_NULL_UNDEFINED, 1);
-    else
-        AddContentTelemetry(TELEMETRY_DEFINE_GETTER_SETTER_THIS_NULL_UNDEFINED, 0);
-
     // Step 1.
     var object = ToObject(this);
 
     // Step 2.
     if (!IsCallable(getter))
         ThrowTypeError(JSMSG_BAD_GETTER_OR_SETTER, "getter");
 
     // Step 3.
--- a/js/src/builtin/SelfHostingDefines.h
+++ b/js/src/builtin/SelfHostingDefines.h
@@ -85,17 +85,16 @@
 #define PROMISE_REJECTION_TRACKER_OPERATION_HANDLE true
 
 // NB: keep these in sync with the copy in jsfriendapi.h.
 #define JSITER_OWNONLY    0x8   /* iterate over obj's own properties only */
 #define JSITER_HIDDEN     0x10  /* also enumerate non-enumerable properties */
 #define JSITER_SYMBOLS    0x20  /* also include symbol property keys */
 #define JSITER_SYMBOLSONLY 0x40 /* exclude string property keys */
 
-#define TELEMETRY_DEFINE_GETTER_SETTER_THIS_NULL_UNDEFINED 25
 
 #define REGEXP_FLAGS_SLOT 2
 
 #define REGEXP_IGNORECASE_FLAG  0x01
 #define REGEXP_GLOBAL_FLAG      0x02
 #define REGEXP_MULTILINE_FLAG   0x04
 #define REGEXP_STICKY_FLAG      0x08
 #define REGEXP_UNICODE_FLAG     0x10
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -129,23 +129,19 @@ enum {
     JS_TELEMETRY_GC_SCC_SWEEP_TOTAL_MS,
     JS_TELEMETRY_GC_SCC_SWEEP_MAX_PAUSE_MS,
     JS_TELEMETRY_GC_MINOR_REASON,
     JS_TELEMETRY_GC_MINOR_REASON_LONG,
     JS_TELEMETRY_GC_MINOR_US,
     JS_TELEMETRY_DEPRECATED_LANGUAGE_EXTENSIONS_IN_CONTENT,
     JS_TELEMETRY_DEPRECATED_LANGUAGE_EXTENSIONS_IN_ADDONS,
     JS_TELEMETRY_ADDON_EXCEPTIONS,
-    JS_TELEMETRY_DEFINE_GETTER_SETTER_THIS_NULL_UNDEFINED,
     JS_TELEMETRY_END
 };
 
-static_assert(JS_TELEMETRY_DEFINE_GETTER_SETTER_THIS_NULL_UNDEFINED == 25,
-              "This value needs to be kept in sync with SelfHostingDefines.h");
-
 typedef void
 (*JSAccumulateTelemetryDataCallback)(int id, uint32_t sample, const char* key);
 
 extern JS_FRIEND_API(void)
 JS_SetAccumulateTelemetryCallback(JSContext* cx, JSAccumulateTelemetryDataCallback callback);
 
 extern JS_FRIEND_API(bool)
 JS_GetIsSecureContext(JSCompartment* compartment);
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -3227,20 +3227,16 @@ AccumulateTelemetryCallback(int id, uint
         Telemetry::Accumulate(Telemetry::JS_DEPRECATED_LANGUAGE_EXTENSIONS_IN_CONTENT, sample);
         break;
       case JS_TELEMETRY_DEPRECATED_LANGUAGE_EXTENSIONS_IN_ADDONS:
         Telemetry::Accumulate(Telemetry::JS_DEPRECATED_LANGUAGE_EXTENSIONS_IN_ADDONS, sample);
         break;
       case JS_TELEMETRY_ADDON_EXCEPTIONS:
         Telemetry::Accumulate(Telemetry::JS_TELEMETRY_ADDON_EXCEPTIONS, nsDependentCString(key), sample);
         break;
-      case JS_TELEMETRY_DEFINE_GETTER_SETTER_THIS_NULL_UNDEFINED:
-        MOZ_ASSERT(sample == 0 || sample == 1);
-        Telemetry::Accumulate(Telemetry::JS_DEFINE_GETTER_SETTER_THIS_NULL_UNDEFINED, sample);
-        break;
       default:
         MOZ_ASSERT_UNREACHABLE("Unexpected JS_TELEMETRY id");
     }
 }
 
 static void
 CompartmentNameCallback(JSRuntime* rt, JSCompartment* comp,
                         char* buf, size_t bufsize)
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -78,25 +78,22 @@ ElementTagToString(dom::Element* aElemen
   nsCString result;
   nsDependentAtomString buf(aElement->NodeInfo()->NameAtom());
   result.AppendPrintf("(%s@%p)", NS_ConvertUTF16toUTF8(buf).get(), aElement);
   return result;
 }
 #endif
 
 RestyleManager::RestyleManager(nsPresContext* aPresContext)
-  : mPresContext(aPresContext)
+  : RestyleManagerBase(aPresContext)
   , mDoRebuildAllStyleData(false)
   , mInRebuildAllStyleData(false)
-  , mObservingRefreshDriver(false)
   , mInStyleRefresh(false)
   , mSkipAnimationRules(false)
   , mHavePendingNonAnimationRestyles(false)
-  , mRestyleGeneration(1)
-  , mHoverGeneration(0)
   , mRebuildAllExtraHint(nsChangeHint(0))
   , mRebuildAllRestyleHint(nsRestyleHint(0))
   , mAnimationGeneration(0)
   , mReframingStyleContexts(nullptr)
   , mAnimationsWithDestroyedFrame(nullptr)
   , mPendingRestyles(ELEMENT_HAS_PENDING_RESTYLE |
                      ELEMENT_IS_POTENTIAL_RESTYLE_ROOT |
                      ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR)
@@ -215,17 +212,17 @@ SyncViewsAndInvalidateDescendants(nsIFra
  * the restyled element's principle frame to one of its ancestor frames based
  * on what nsCSSRendering::FindBackground returns, since the background style
  * may have been propagated up to an ancestor frame. Processing hints using an
  * ancestor frame is fine in general, but nsChangeHint_ChildrenOnlyTransform is
  * a special case since it is intended to update the children of a specific
  * frame.
  */
 static nsIFrame*
-GetFrameForChildrenOnlyTransformHint(nsIFrame *aFrame)
+GetFrameForChildrenOnlyTransformHint(nsIFrame* aFrame)
 {
   if (aFrame->GetType() == nsGkAtoms::viewportFrame) {
     // This happens if the root-<svg> is fixed positioned, in which case we
     // can't use aFrame->GetContent() to find the primary frame, since
     // GetContent() returns nullptr for ViewportFrame.
     aFrame = aFrame->PrincipalChildList().FirstChild();
   }
   // For an nsHTMLScrollFrame, this will get the SVG frame that has the
@@ -352,34 +349,34 @@ ApplyRenderingChangeToTree(nsPresContext
   // We check StyleDisplay()->HasTransformStyle() in addition to checking
   // IsTransformed() since we can get here for some frames that don't support
   // CSS transforms.
   NS_ASSERTION(!(aChange & nsChangeHint_UpdateTransformLayer) ||
                aFrame->IsTransformed() ||
                aFrame->StyleDisplay()->HasTransformStyle(),
                "Unexpected UpdateTransformLayer hint");
 
-  nsIPresShell *shell = aPresContext->PresShell();
+  nsIPresShell* shell = aPresContext->PresShell();
   if (shell->IsPaintingSuppressed()) {
     // Don't allow synchronous rendering changes when painting is turned off.
     aChange &= ~nsChangeHint_RepaintFrame;
     if (!aChange) {
       return;
     }
   }
 
   // Trigger rendering updates by damaging this frame and any
   // continuations of this frame.
 #ifdef DEBUG
   gInApplyRenderingChangeToTree = true;
 #endif
   if (aChange & nsChangeHint_RepaintFrame) {
     // If the frame's background is propagated to an ancestor, walk up to
     // that ancestor and apply the RepaintFrame change hint to it.
-    nsStyleContext *bgSC;
+    nsStyleContext* bgSC;
     nsIFrame* propagatedFrame = aFrame;
     while (!nsCSSRendering::FindBackground(propagatedFrame, &bgSC)) {
       propagatedFrame = propagatedFrame->GetParent();
       NS_ASSERTION(aFrame, "root frame must paint");
     }
 
     if (propagatedFrame != aFrame) {
       DoApplyRenderingChangeToTree(propagatedFrame, nsChangeHint_RepaintFrame);
@@ -437,29 +434,29 @@ RestyleManager::RecomputePosition(nsIFra
       }
 
       // Update sticky positioning for an entire element at once, starting with
       // the first continuation or ib-split sibling.
       // It's rare that the frame we already have isn't already the first
       // continuation or ib-split sibling, but it can happen when styles differ
       // across continuations such as ::first-line or ::first-letter, and in
       // those cases we will generally (but maybe not always) do the work twice.
-      nsIFrame *firstContinuation =
+      nsIFrame* firstContinuation =
         nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
 
       StickyScrollContainer::ComputeStickyOffsets(firstContinuation);
       StickyScrollContainer* ssc =
         StickyScrollContainer::GetStickyScrollContainerForFrame(firstContinuation);
       if (ssc) {
         ssc->PositionContinuations(firstContinuation);
       }
     } else {
       MOZ_ASSERT(NS_STYLE_POSITION_RELATIVE == display->mPosition,
                  "Unexpected type of positioning");
-      for (nsIFrame *cont = aFrame; cont;
+      for (nsIFrame* cont = aFrame; cont;
            cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
         nsIFrame* cb = cont->GetContainingBlock();
         nsMargin newOffsets;
         WritingMode wm = cb->GetWritingMode();
         const LogicalSize size(wm, cb->GetContentRectRelativeToSelf().Size());
 
         nsHTMLReflowState::ComputeRelativeOffsets(wm, cont, size, newOffsets);
         NS_ASSERTION(newOffsets.left == -newOffsets.right &&
@@ -637,24 +634,24 @@ RestyleManager::StyleChangeReflow(nsIFra
   nsIPresShell::ReflowRootHandling rootHandling;
   if (aHint & nsChangeHint_ReflowChangesSizeOrPosition) {
     rootHandling = nsIPresShell::ePositionOrSizeChange;
   } else {
     rootHandling = nsIPresShell::eNoPositionOrSizeChange;
   }
 
   do {
-    mPresContext->PresShell()->FrameNeedsReflow(aFrame, dirtyType, dirtyBits,
+    PresContext()->PresShell()->FrameNeedsReflow(aFrame, dirtyType, dirtyBits,
                                                 rootHandling);
     aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
   } while (aFrame);
 }
 
 void
-RestyleManager::AddSubtreeToOverflowTracker(nsIFrame* aFrame) 
+RestyleManager::AddSubtreeToOverflowTracker(nsIFrame* aFrame)
 {
   if (aFrame->FrameMaintainsOverflow()) {
     mOverflowChangedTracker.AddFrame(aFrame,
                                      OverflowChangedTracker::CHILDREN_CHANGED);
   }
   nsIFrame::ChildListIterator lists(aFrame);
   for (; !lists.IsDone(); lists.Next()) {
     for (nsIFrame* child : lists.CurrentList()) {
@@ -740,17 +737,17 @@ RestyleManager::ProcessRestyledFrames(ns
 
   PROFILER_LABEL("RestyleManager", "ProcessRestyledFrames",
     js::ProfileEntry::Category::CSS);
 
   // Make sure to not rebuild quote or counter lists while we're
   // processing restyles
   FrameConstructor()->BeginUpdate();
 
-  FramePropertyTable* propTable = mPresContext->PropertyTable();
+  FramePropertyTable* propTable = PresContext()->PropertyTable();
 
   // Mark frames so that we skip frames that die along the way, bug 123049.
   // A frame can be in the list multiple times with different hints. Further
   // optmization is possible if nsStyleChangeList::AppendChange could coalesce
   int32_t index = count;
 
   while (0 <= --index) {
     const nsStyleChangeData* changeData;
@@ -793,17 +790,17 @@ RestyleManager::ProcessRestyledFrames(ns
         !(hint & nsChangeHint_ReconstructFrame)) {
       if (NeedToReframeForAddingOrRemovingTransform(frame) ||
           frame->GetType() == nsGkAtoms::fieldSetFrame ||
           frame->GetContentInsertionFrame() != frame) {
         // The frame has positioned children that need to be reparented, or
         // it can't easily be converted to/from being an abs-pos container correctly.
         hint |= nsChangeHint_ReconstructFrame;
       } else {
-        for (nsIFrame *cont = frame; cont;
+        for (nsIFrame* cont = frame; cont;
              cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
           // Normally frame construction would set state bits as needed,
           // but we're not going to reconstruct the frame so we need to set them.
           // It's because we need to set this state on each affected frame
           // that we can't coalesce nsChangeHint_UpdateContainingBlock hints up
           // to ancestors (i.e. it can't be an inherited change hint).
           if (cont->IsAbsPosContaininingBlock()) {
             if (cont->StyleDisplay()->HasTransform(cont)) {
@@ -851,17 +848,17 @@ RestyleManager::ProcessRestyledFrames(ns
       if (!(frame->GetStateBits() & NS_FRAME_MAY_BE_TRANSFORMED)) {
         // Frame can not be transformed, and thus a change in transform will
         // have no effect and we should not use the
         // nsChangeHint_UpdatePostTransformOverflow hint.
         hint &= ~nsChangeHint_UpdatePostTransformOverflow;
       }
 
       if (hint & nsChangeHint_UpdateEffects) {
-        for (nsIFrame *cont = frame; cont;
+        for (nsIFrame* cont = frame; cont;
              cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
           nsSVGEffects::UpdateEffects(cont);
         }
       }
       if ((hint & nsChangeHint_InvalidateRenderingObservers) ||
           ((hint & nsChangeHint_UpdateOpacityLayer) &&
            frame->IsFrameOfType(nsIFrame::eSVG) &&
            !(frame->GetStateBits() & NS_STATE_IS_OUTER_SVG))) {
@@ -898,17 +895,17 @@ RestyleManager::ProcessRestyledFrames(ns
           // these frames. Repaint the whole frame.
           hint |= nsChangeHint_RepaintFrame;
         }
       }
 
       if (hint & (nsChangeHint_RepaintFrame | nsChangeHint_SyncFrameView |
                   nsChangeHint_UpdateOpacityLayer | nsChangeHint_UpdateTransformLayer |
                   nsChangeHint_ChildrenOnlyTransform | nsChangeHint_SchedulePaint)) {
-        ApplyRenderingChangeToTree(mPresContext, frame, hint);
+        ApplyRenderingChangeToTree(PresContext(), frame, hint);
       }
       if ((hint & nsChangeHint_RecomputePosition) && !didReflowThisFrame) {
         ActiveLayerTracker::NotifyOffsetRestyle(frame);
         // It is possible for this to fall back to a reflow
         if (!RecomputePosition(frame)) {
           didReflowThisFrame = true;
         }
       }
@@ -916,17 +913,17 @@ RestyleManager::ProcessRestyledFrames(ns
                    (hint & nsChangeHint_UpdateOverflow),
                    "nsChangeHint_UpdateOverflow should be passed too");
       if (!didReflowThisFrame &&
           (hint & (nsChangeHint_UpdateOverflow |
                    nsChangeHint_UpdatePostTransformOverflow |
                    nsChangeHint_UpdateParentOverflow |
                    nsChangeHint_UpdateSubtreeOverflow))) {
         if (hint & nsChangeHint_UpdateSubtreeOverflow) {
-          for (nsIFrame *cont = frame; cont; cont =
+          for (nsIFrame* cont = frame; cont; cont =
                  nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
             AddSubtreeToOverflowTracker(cont);
           }
           // The work we just did in AddSubtreeToOverflowTracker
           // subsumes some of the other hints:
           hint &= ~(nsChangeHint_UpdateOverflow |
                     nsChangeHint_UpdatePostTransformOverflow);
         }
@@ -968,38 +965,38 @@ RestyleManager::ProcessRestyledFrames(ns
             // nsChangeHint_UpdatePostTransformOverflow,
             // CHILDREN_CHANGED is selected as it is
             // strictly stronger.
             if (hint & nsChangeHint_UpdateOverflow) {
               changeKind = OverflowChangedTracker::CHILDREN_CHANGED;
             } else {
               changeKind = OverflowChangedTracker::TRANSFORM_CHANGED;
             }
-            for (nsIFrame *cont = frame; cont; cont =
+            for (nsIFrame* cont = frame; cont; cont =
                    nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
               mOverflowChangedTracker.AddFrame(cont, changeKind);
             }
           }
           // UpdateParentOverflow hints need to be processed in addition
           // to the above, since if the processing of the above hints
           // yields no change, the update will not propagate to the
           // parent.
           if (hint & nsChangeHint_UpdateParentOverflow) {
             MOZ_ASSERT(frame->GetParent(),
                        "shouldn't get style hints for the root frame");
-            for (nsIFrame *cont = frame; cont; cont =
+            for (nsIFrame* cont = frame; cont; cont =
                    nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
               mOverflowChangedTracker.AddFrame(cont->GetParent(),
                                    OverflowChangedTracker::CHILDREN_CHANGED);
             }
           }
         }
       }
       if ((hint & nsChangeHint_UpdateCursor) && !didUpdateCursor) {
-        mPresContext->PresShell()->SynthesizeMouseMove(false);
+        PresContext()->PresShell()->SynthesizeMouseMove(false);
         didUpdateCursor = true;
       }
     }
   }
 
   FrameConstructor()->EndUpdate();
 
   // cleanup references and verify the style tree.  Note that the latter needs
@@ -1048,19 +1045,19 @@ RestyleManager::RestyleElement(Element* 
     // of <area>s.  See bug 135040.  We can remove this block once that's fixed.
     aPrimaryFrame = nullptr;
   }
   NS_ASSERTION(!aPrimaryFrame || aPrimaryFrame->GetContent() == aElement,
                "frame/content mismatch");
 
   // If we're restyling the root element and there are 'rem' units in
   // use, handle dynamic changes to the definition of a 'rem' here.
-  if (mPresContext->UsesRootEMUnits() && aPrimaryFrame &&
+  if (PresContext()->UsesRootEMUnits() && aPrimaryFrame &&
       !mInRebuildAllStyleData) {
-    nsStyleContext *oldContext = aPrimaryFrame->StyleContext();
+    nsStyleContext* oldContext = aPrimaryFrame->StyleContext();
     if (!oldContext->GetParent()) { // check that we're the root element
       RefPtr<nsStyleContext> newContext = StyleSet()->
         ResolveStyleFor(aElement, nullptr /* == oldContext->GetParent() */);
       if (oldContext->StyleFont()->mFont.size !=
           newContext->StyleFont()->mFont.size) {
         // The basis for 'rem' units has changed.
         mRebuildAllRestyleHint |= aRestyleHint;
         if (aRestyleHint & eRestyle_SomeDescendants) {
@@ -1112,17 +1109,17 @@ RestyleManager::ReframingStyleContexts::
 
 RestyleManager::ReframingStyleContexts::~ReframingStyleContexts()
 {
   // Before we go away, we need to flush out any frame construction that
   // was enqueued, so that we start transitions.
   // Note that this is a little bit evil in that we're calling into code
   // that calls our member functions from our destructor, but it's at
   // the beginning of our destructor, so it shouldn't be too bad.
-  mRestyleManager->mPresContext->FrameConstructor()->CreateNeededFrames();
+  mRestyleManager->PresContext()->FrameConstructor()->CreateNeededFrames();
 }
 
 RestyleManager::AnimationsWithDestroyedFrame::AnimationsWithDestroyedFrame(
                                           RestyleManager* aRestyleManager)
   : mRestyleManager(aRestyleManager)
   , mRestorePointer(mRestyleManager->mAnimationsWithDestroyedFrame)
 {
   MOZ_ASSERT(!mRestyleManager->mAnimationsWithDestroyedFrame,
@@ -1181,85 +1178,21 @@ RestyleManager::ContentStateChanged(nsIC
   // XXXbz it would be good if this function only took Elements, but
   // we'd have to make ESM guarantee that usefully.
   if (!aContent->IsElement()) {
     return NS_OK;
   }
 
   Element* aElement = aContent->AsElement();
 
-  nsStyleSet* styleSet = StyleSet();
-  NS_ASSERTION(styleSet, "couldn't get style set");
-
-  nsChangeHint hint = NS_STYLE_HINT_NONE;
-  // Any change to a content state that affects which frames we construct
-  // must lead to a frame reconstruct here if we already have a frame.
-  // Note that we never decide through non-CSS means to not create frames
-  // based on content states, so if we already don't have a frame we don't
-  // need to force a reframe -- if it's needed, the HasStateDependentStyle
-  // call will handle things.
-  nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
-  CSSPseudoElementType pseudoType = CSSPseudoElementType::NotPseudo;
-  if (primaryFrame) {
-    // If it's generated content, ignore LOADING/etc state changes on it.
-    if (!primaryFrame->IsGeneratedContentFrame() &&
-        aStateMask.HasAtLeastOneOfStates(NS_EVENT_STATE_BROKEN |
-                                         NS_EVENT_STATE_USERDISABLED |
-                                         NS_EVENT_STATE_SUPPRESSED |
-                                         NS_EVENT_STATE_LOADING)) {
-      hint = nsChangeHint_ReconstructFrame;
-    } else {
-      uint8_t app = primaryFrame->StyleDisplay()->mAppearance;
-      if (app) {
-        nsITheme *theme = mPresContext->GetTheme();
-        if (theme && theme->ThemeSupportsWidget(mPresContext,
-                                                primaryFrame, app)) {
-          bool repaint = false;
-          theme->WidgetStateChanged(primaryFrame, app, nullptr, &repaint, nullptr);
-          if (repaint) {
-            hint |= nsChangeHint_RepaintFrame;
-          }
-        }
-      }
-    }
-
-    pseudoType = primaryFrame->StyleContext()->GetPseudoType();
-
-    primaryFrame->ContentStatesChanged(aStateMask);
-  }
-
-
-  nsRestyleHint rshint;
-
-  if (pseudoType >= CSSPseudoElementType::Count) {
-    rshint = styleSet->HasStateDependentStyle(aElement, aStateMask);
-  } else if (nsCSSPseudoElements::PseudoElementSupportsUserActionState(
-                                                                  pseudoType)) {
-    // If aElement is a pseudo-element, we want to check to see whether there
-    // are any state-dependent rules applying to that pseudo.
-    Element* ancestor = ElementForStyleContext(nullptr, primaryFrame,
-                                               pseudoType);
-    rshint = styleSet->HasStateDependentStyle(ancestor, pseudoType, aElement,
-                                              aStateMask);
-  } else {
-    rshint = nsRestyleHint(0);
-  }
-
-  if (aStateMask.HasState(NS_EVENT_STATE_HOVER) && rshint != 0) {
-    ++mHoverGeneration;
-  }
-
-  if (aStateMask.HasState(NS_EVENT_STATE_VISITED)) {
-    // Exposing information to the page about whether the link is
-    // visited or not isn't really something we can worry about here.
-    // FIXME: We could probably do this a bit better.
-    hint |= nsChangeHint_RepaintFrame;
-  }
-
-  PostRestyleEvent(aElement, rshint, hint);
+  nsChangeHint changeHint;
+  nsRestyleHint restyleHint;
+  ContentStateChangedInternal(aElement, aStateMask, &changeHint, &restyleHint);
+
+  PostRestyleEvent(aElement, restyleHint, changeHint);
   return NS_OK;
 }
 
 // Forwarded nsIMutationObserver method, to handle restyling.
 void
 RestyleManager::AttributeWillChange(Element* aElement,
                                     int32_t aNameSpaceID,
                                     nsIAtom* aAttribute,
@@ -1285,17 +1218,17 @@ RestyleManager::AttributeChanged(Element
                                  int32_t aNameSpaceID,
                                  nsIAtom* aAttribute,
                                  int32_t aModType,
                                  const nsAttrValue* aOldValue)
 {
   // Hold onto the PresShell to prevent ourselves from being destroyed.
   // XXXbz how, exactly, would this attribute change cause us to be
   // destroyed from inside this function?
-  nsCOMPtr<nsIPresShell> shell = mPresContext->GetPresShell();
+  nsCOMPtr<nsIPresShell> shell = PresContext()->GetPresShell();
 
   // Get the frame associated with the content which is the highest in the frame tree
   nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
 
 #if 0
   NS_FRAME_LOG(NS_FRAME_TRACE_CALLS,
      ("RestyleManager::AttributeChanged: content=%p[%s] frame=%p",
       aContent, ContentTag(aElement, 0), frame));
@@ -1307,45 +1240,45 @@ RestyleManager::AttributeChanged(Element
   bool reframe = (hint & nsChangeHint_ReconstructFrame) != 0;
 
 #ifdef MOZ_XUL
   // The following listbox widget trap prevents offscreen listbox widget
   // content from being removed and re-inserted (which is what would
   // happen otherwise).
   if (!primaryFrame && !reframe) {
     int32_t namespaceID;
-    nsIAtom* tag = mPresContext->Document()->BindingManager()->
+    nsIAtom* tag = PresContext()->Document()->BindingManager()->
                      ResolveTag(aElement, &namespaceID);
 
     if (namespaceID == kNameSpaceID_XUL &&
         (tag == nsGkAtoms::listitem ||
          tag == nsGkAtoms::listcell))
       return;
   }
 
   if (aAttribute == nsGkAtoms::tooltiptext ||
       aAttribute == nsGkAtoms::tooltip)
   {
-    nsIRootBox* rootBox = nsIRootBox::GetRootBox(mPresContext->GetPresShell());
+    nsIRootBox* rootBox = nsIRootBox::GetRootBox(PresContext()->GetPresShell());
     if (rootBox) {
       if (aModType == nsIDOMMutationEvent::REMOVAL)
         rootBox->RemoveTooltipSupport(aElement);
       if (aModType == nsIDOMMutationEvent::ADDITION)
         rootBox->AddTooltipSupport(aElement);
     }
   }
 
 #endif // MOZ_XUL
 
   if (primaryFrame) {
     // See if we have appearance information for a theme.
     const nsStyleDisplay* disp = primaryFrame->StyleDisplay();
     if (disp->mAppearance) {
-      nsITheme *theme = mPresContext->GetTheme();
-      if (theme && theme->ThemeSupportsWidget(mPresContext, primaryFrame, disp->mAppearance)) {
+      nsITheme* theme = PresContext()->GetTheme();
+      if (theme && theme->ThemeSupportsWidget(PresContext(), primaryFrame, disp->mAppearance)) {
         bool repaint = false;
         theme->WidgetStateChanged(primaryFrame, disp->mAppearance, aAttribute,
             &repaint, aOldValue);
         if (repaint)
           hint |= nsChangeHint_RepaintFrame;
       }
     }
 
@@ -1453,17 +1386,17 @@ RestyleManager::RestyleForAppend(Element
 
 // Needed since we can't use PostRestyleEvent on non-elements (with
 // eRestyle_LaterSiblings or nsRestyleHint(eRestyle_Subtree |
 // eRestyle_LaterSiblings) as appropriate).
 static void
 RestyleSiblingsStartingWith(RestyleManager* aRestyleManager,
                             nsIContent* aStartingSibling /* may be null */)
 {
-  for (nsIContent *sibling = aStartingSibling; sibling;
+  for (nsIContent* sibling = aStartingSibling; sibling;
        sibling = sibling->GetNextSibling()) {
     if (sibling->IsElement()) {
       aRestyleManager->
         PostRestyleEvent(sibling->AsElement(),
                          nsRestyleHint(eRestyle_Subtree | eRestyle_LaterSiblings),
                          NS_STYLE_HINT_NONE);
       break;
     }
@@ -1656,17 +1589,17 @@ RestyleManager::RebuildAllStyleData(nsCh
              "eRestyle_ForceDescendants");
 
   mRebuildAllExtraHint |= aExtraHint;
   mRebuildAllRestyleHint |= aRestyleHint;
 
   // Processing the style changes could cause a flush that propagates to
   // the parent frame and thus destroys the pres shell, so we must hold
   // a reference.
-  nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell();
+  nsCOMPtr<nsIPresShell> presShell = PresContext()->GetPresShell();
   if (!presShell || !presShell->GetRootFrame()) {
     mDoRebuildAllStyleData = false;
     return;
   }
 
   // Make sure that the viewmanager will outlive the presshell
   RefPtr<nsViewManager> vm = presShell->GetViewManager();
 
@@ -1681,17 +1614,17 @@ RestyleManager::RebuildAllStyleData(nsCh
   ProcessPendingRestyles();
 }
 
 void
 RestyleManager::StartRebuildAllStyleData(RestyleTracker& aRestyleTracker)
 {
   MOZ_ASSERT(mIsProcessingRestyles);
 
-  nsIFrame* rootFrame = mPresContext->PresShell()->GetRootFrame();
+  nsIFrame* rootFrame = PresContext()->PresShell()->GetRootFrame();
   if (!rootFrame) {
     // No need to do anything.
     return;
   }
 
   mInRebuildAllStyleData = true;
 
   // Tell the style set to get the old rule tree out of the way
@@ -1712,17 +1645,17 @@ RestyleManager::StartRebuildAllStyleData
       (restyleHint & ~(eRestyle_Force | eRestyle_ForceDescendants))) {
     // We want this hint to apply to the root node's primary frame
     // rather than the root frame, since it's the primary frame that has
     // the styles for the root element (rather than the ancestors of the
     // primary frame whose mContent is the root node but which have
     // different styles).  If we use up the hint for one of the
     // ancestors that we hit first, then we'll fail to do the restyling
     // we need to do.
-    Element* root = mPresContext->Document()->GetRootElement();
+    Element* root = PresContext()->Document()->GetRootElement();
     if (root) {
       // If the root element is gone, dropping the hint on the floor
       // should be fine.
       aRestyleTracker.AddPendingRestyle(root, restyleHint, nsChangeHint(0));
     }
     restyleHint = nsRestyleHint(0);
   }
 
@@ -1752,23 +1685,23 @@ RestyleManager::FinishRebuildAllStyleDat
   StyleSet()->EndReconstruct();
 
   mInRebuildAllStyleData = false;
 }
 
 void
 RestyleManager::ProcessPendingRestyles()
 {
-  NS_PRECONDITION(mPresContext->Document(), "No document?  Pshaw!");
+  NS_PRECONDITION(PresContext()->Document(), "No document?  Pshaw!");
   NS_PRECONDITION(!nsContentUtils::IsSafeToRunScript(),
                   "Missing a script blocker!");
 
   // First do any queued-up frame creation.  (We should really
   // merge this into the rest of the process, though; see bug 827239.)
-  mPresContext->FrameConstructor()->CreateNeededFrames();
+  PresContext()->FrameConstructor()->CreateNeededFrames();
 
   // Process non-animation restyles...
   MOZ_ASSERT(!mIsProcessingRestyles,
              "Nesting calls to ProcessPendingRestyles?");
   mIsProcessingRestyles = true;
 
   // Before we process any restyles, we need to ensure that style
   // resulting from any animations is up-to-date, so that if any style
@@ -1796,23 +1729,23 @@ RestyleManager::ProcessPendingRestyles()
     // want to tell the transition manager to act as though we're in
     // UpdateOnlyAnimationStyles.
     //
     // FIXME: In the future, we might want to refactor the way the
     // animation and transition manager do their refresh driver ticks so
     // that we can use UpdateOnlyAnimationStyles, with a different
     // boolean argument, for this update as well, instead of having them
     // post style updates in their WillRefresh methods.
-    mPresContext->TransitionManager()->SetInAnimationOnlyStyleUpdate(true);
+    PresContext()->TransitionManager()->SetInAnimationOnlyStyleUpdate(true);
   }
 
   ProcessRestyles(mPendingRestyles);
 
   if (!haveNonAnimation) {
-    mPresContext->TransitionManager()->SetInAnimationOnlyStyleUpdate(false);
+    PresContext()->TransitionManager()->SetInAnimationOnlyStyleUpdate(false);
   }
 
   mIsProcessingRestyles = false;
 
   NS_ASSERTION(haveNonAnimation || !mHavePendingNonAnimationRestyles,
                "should not have added restyles");
   mHavePendingNonAnimationRestyles = false;
 
@@ -1830,17 +1763,17 @@ RestyleManager::ProcessPendingRestyles()
              "should have called FinishRebuildAllStyleData");
 }
 
 void
 RestyleManager::BeginProcessingRestyles(RestyleTracker& aRestyleTracker)
 {
   // Make sure to not rebuild quote or counter lists while we're
   // processing restyles
-  mPresContext->FrameConstructor()->BeginUpdate();
+  PresContext()->FrameConstructor()->BeginUpdate();
 
   mInStyleRefresh = true;
 
   if (ShouldStartRebuildAllFor(aRestyleTracker)) {
     mDoRebuildAllStyleData = false;
     StartRebuildAllStyleData(aRestyleTracker);
   }
 }
@@ -1857,54 +1790,54 @@ RestyleManager::EndProcessingRestyles()
   // Set mInStyleRefresh to false now, since the EndUpdate call might
   // add more restyles.
   mInStyleRefresh = false;
 
   if (mInRebuildAllStyleData) {
     FinishRebuildAllStyleData();
   }
 
-  mPresContext->FrameConstructor()->EndUpdate();
+  PresContext()->FrameConstructor()->EndUpdate();
 
 #ifdef DEBUG
-  mPresContext->PresShell()->VerifyStyleTree();
+  PresContext()->PresShell()->VerifyStyleTree();
 #endif
 }
 
 void
 RestyleManager::UpdateOnlyAnimationStyles()
 {
-  bool doCSS = mPresContext->EffectCompositor()->HasPendingStyleUpdates();
-
-  nsIDocument* document = mPresContext->Document();
+  bool doCSS = PresContext()->EffectCompositor()->HasPendingStyleUpdates();
+
+  nsIDocument* document = PresContext()->Document();
   nsSMILAnimationController* animationController =
     document->HasAnimationController() ?
     document->GetAnimationController() :
     nullptr;
   bool doSMIL = animationController &&
                 animationController->MightHavePendingStyleUpdates();
 
   if (!doCSS && !doSMIL) {
     return;
   }
 
-  nsTransitionManager* transitionManager = mPresContext->TransitionManager();
+  nsTransitionManager* transitionManager = PresContext()->TransitionManager();
 
   transitionManager->SetInAnimationOnlyStyleUpdate(true);
 
   RestyleTracker tracker(ELEMENT_HAS_PENDING_ANIMATION_ONLY_RESTYLE |
                          ELEMENT_IS_POTENTIAL_ANIMATION_ONLY_RESTYLE_ROOT);
   tracker.Init(this);
 
   if (doCSS) {
     // FIXME:  We should have the transition manager and animation manager
     // add only the elements for which animations are currently throttled
     // (i.e., animating on the compositor with main-thread style updates
     // suppressed).
-    mPresContext->EffectCompositor()->AddStyleUpdatesTo(tracker);
+    PresContext()->EffectCompositor()->AddStyleUpdatesTo(tracker);
   }
 
   if (doSMIL) {
     animationController->AddStyleUpdatesTo(tracker);
   }
 
   ProcessRestyles(tracker);
 
@@ -1912,18 +1845,18 @@ RestyleManager::UpdateOnlyAnimationStyle
 }
 
 void
 RestyleManager::PostRestyleEvent(Element* aElement,
                                  nsRestyleHint aRestyleHint,
                                  nsChangeHint aMinChangeHint,
                                  const RestyleHintData* aRestyleHintData)
 {
-  if (MOZ_UNLIKELY(!mPresContext) ||
-      MOZ_UNLIKELY(mPresContext->PresShell()->IsDestroying())) {
+  if (MOZ_UNLIKELY(IsDisconnected()) ||
+      MOZ_UNLIKELY(PresContext()->PresShell()->IsDestroying())) {
     return;
   }
 
   if (aRestyleHint == 0 && !aMinChangeHint) {
     // Nothing to do here
     return;
   }
 
@@ -1943,20 +1876,20 @@ RestyleManager::PostRestyleEvent(Element
 
 void
 RestyleManager::PostRestyleEventInternal(bool aForLazyConstruction)
 {
   // Make sure we're not in a style refresh; if we are, we still have
   // a call to ProcessPendingRestyles coming and there's no need to
   // add ourselves as a refresh observer until then.
   bool inRefresh = !aForLazyConstruction && mInStyleRefresh;
-  nsIPresShell* presShell = mPresContext->PresShell();
-  if (!mObservingRefreshDriver && !inRefresh) {
-    mObservingRefreshDriver = mPresContext->RefreshDriver()->
-      AddStyleFlushObserver(presShell);
+  nsIPresShell* presShell = PresContext()->PresShell();
+  if (!ObservingRefreshDriver() && !inRefresh) {
+    SetObservingRefreshDriver(PresContext()->RefreshDriver()->
+        AddStyleFlushObserver(presShell));
   }
 
   // Unconditionally flag our document as needing a flush.  The other
   // option here would be a dedicated boolean to track whether we need
   // to do so (set here and unset in ProcessPendingRestyles).
   presShell->GetDocument()->SetNeedStyleFlush();
 }
 
@@ -2410,33 +2343,33 @@ RestyleManager::ReparentStyleContext(nsI
     // Check that our assumption that continuations of the same
     // pseudo-type and with the same style context parent have the
     // same style context is valid before the reresolution.  (We need
     // to check the pseudo-type and style context parent because of
     // :first-letter and :first-line, where we create styled and
     // unstyled letter/line frames distinguished by pseudo-type, and
     // then need to distinguish their descendants based on having
     // different parents.)
-    nsIFrame *nextContinuation = aFrame->GetNextContinuation();
+    nsIFrame* nextContinuation = aFrame->GetNextContinuation();
     if (nextContinuation) {
-      nsStyleContext *nextContinuationContext =
+      nsStyleContext* nextContinuationContext =
         nextContinuation->StyleContext();
       NS_ASSERTION(oldContext == nextContinuationContext ||
                    oldContext->GetPseudo() !=
                      nextContinuationContext->GetPseudo() ||
                    oldContext->GetParent() !=
                      nextContinuationContext->GetParent(),
                    "continuations should have the same style context");
     }
   }
 #endif
 
-  nsIFrame *prevContinuation =
+  nsIFrame* prevContinuation =
     GetPrevContinuationWithPossiblySameStyle(aFrame);
-  nsStyleContext *prevContinuationContext;
+  nsStyleContext* prevContinuationContext;
   bool copyFromContinuation =
     prevContinuation &&
     (prevContinuationContext = prevContinuation->StyleContext())
       ->GetPseudo() == oldContext->GetPseudo() &&
      prevContinuationContext->GetParent() == newParentContext;
   if (copyFromContinuation) {
     // Just use the style context from the frame's previous
     // continuation (see assertion about aFrame->GetNextContinuation()
@@ -2600,16 +2533,17 @@ ElementRestyler::ElementRestyler(nsPresC
   , mKidsDesiredA11yNotifications(mDesiredA11yNotifications)
   , mOurA11yNotification(eDontNotify)
   , mVisibleKidsOfHiddenElement(aVisibleKidsOfHiddenElement)
 #endif
 #ifdef RESTYLE_LOGGING
   , mLoggingDepth(aRestyleTracker.LoggingDepth() + 1)
 #endif
 {
+  MOZ_ASSERT_IF(mContent, !mContent->IsStyledByServo());
 }
 
 ElementRestyler::ElementRestyler(const ElementRestyler& aParentRestyler,
                                  nsIFrame* aFrame,
                                  uint32_t aConstructorFlags)
   : mPresContext(aParentRestyler.mPresContext)
   , mFrame(aFrame)
   , mParentContent(aParentRestyler.mContent)
@@ -2634,16 +2568,17 @@ ElementRestyler::ElementRestyler(const E
   , mKidsDesiredA11yNotifications(mDesiredA11yNotifications)
   , mOurA11yNotification(eDontNotify)
   , mVisibleKidsOfHiddenElement(aParentRestyler.mVisibleKidsOfHiddenElement)
 #endif
 #ifdef RESTYLE_LOGGING
   , mLoggingDepth(aParentRestyler.mLoggingDepth + 1)
 #endif
 {
+  MOZ_ASSERT_IF(mContent, !mContent->IsStyledByServo());
   if (aConstructorFlags & FOR_OUT_OF_FLOW_CHILD) {
     // Note that the out-of-flow may not be a geometric descendant of
     // the frame where we started the reresolve.  Therefore, even if
     // mHintsHandled already includes nsChangeHint_AllReflowHints we
     // don't want to pass that on to the out-of-flow reresolve, since
     // that can lead to the out-of-flow not getting reflowed when it
     // should be (eg a reresolve starting at <body> that involves
     // reflowing the <body> would miss reflowing fixed-pos nodes that
@@ -2682,16 +2617,17 @@ ElementRestyler::ElementRestyler(ParentC
   , mKidsDesiredA11yNotifications(mDesiredA11yNotifications)
   , mOurA11yNotification(eDontNotify)
   , mVisibleKidsOfHiddenElement(aParentRestyler.mVisibleKidsOfHiddenElement)
 #endif
 #ifdef RESTYLE_LOGGING
   , mLoggingDepth(aParentRestyler.mLoggingDepth + 1)
 #endif
 {
+  MOZ_ASSERT_IF(mContent, !mContent->IsStyledByServo());
 }
 
 ElementRestyler::ElementRestyler(nsPresContext* aPresContext,
                                  nsIContent* aContent,
                                  nsStyleChangeList* aChangeList,
                                  nsChangeHint aHintsHandledByAncestors,
                                  RestyleTracker& aRestyleTracker,
                                  nsTArray<nsCSSSelector*>& aSelectorsForDescendants,
@@ -2899,29 +2835,31 @@ ElementRestyler::ConditionallyRestyleChi
 }
 
 void
 ElementRestyler::ConditionallyRestyleChildren(nsIFrame* aFrame,
                                               Element* aRestyleRoot)
 {
   MOZ_ASSERT(aFrame->GetContent());
   MOZ_ASSERT(aFrame->GetContent()->IsElement());
+  MOZ_ASSERT(!aFrame->GetContent()->IsStyledByServo());
 
   ConditionallyRestyleUndisplayedDescendants(aFrame, aRestyleRoot);
   ConditionallyRestyleContentChildren(aFrame, aRestyleRoot);
 }
 
 // The structure of this method parallels RestyleContentChildren.
 // If you update this method, you probably want to update that one too.
 void
 ElementRestyler::ConditionallyRestyleContentChildren(nsIFrame* aFrame,
                                                      Element* aRestyleRoot)
 {
   MOZ_ASSERT(aFrame->GetContent());
   MOZ_ASSERT(aFrame->GetContent()->IsElement());
+  MOZ_ASSERT(!aFrame->GetContent()->IsStyledByServo());
 
   if (aFrame->GetContent()->HasFlag(mRestyleTracker.RootBit())) {
     aRestyleRoot = aFrame->GetContent()->AsElement();
   }
 
   for (nsIFrame* f = aFrame; f;
        f = GetNextContinuationWithSameStyle(f, f->StyleContext())) {
     nsIFrame::ChildListIterator lists(f);
@@ -2996,24 +2934,24 @@ void
 ElementRestyler::ConditionallyRestyleUndisplayedNodes(
     UndisplayedNode* aUndisplayed,
     nsIContent* aUndisplayedParent,
     const uint8_t aDisplay,
     Element* aRestyleRoot)
 {
   MOZ_ASSERT(aDisplay == NS_STYLE_DISPLAY_NONE ||
              aDisplay == NS_STYLE_DISPLAY_CONTENTS);
-
   if (!aUndisplayed) {
     return;
   }
 
   if (aUndisplayedParent &&
       aUndisplayedParent->IsElement() &&
       aUndisplayedParent->HasFlag(mRestyleTracker.RootBit())) {
+    MOZ_ASSERT(!aUndisplayedParent->IsStyledByServo());
     aRestyleRoot = aUndisplayedParent->AsElement();
   }
 
   for (UndisplayedNode* undisplayed = aUndisplayed; undisplayed;
        undisplayed = undisplayed->mNext) {
 
     if (!undisplayed->mContent->IsElement()) {
       continue;
@@ -3030,16 +2968,17 @@ ElementRestyler::ConditionallyRestyleUnd
     }
   }
 }
 
 void
 ElementRestyler::ConditionallyRestyleContentDescendants(Element* aElement,
                                                         Element* aRestyleRoot)
 {
+  MOZ_ASSERT(!aElement->IsStyledByServo());
   if (aElement->HasFlag(mRestyleTracker.RootBit())) {
     aRestyleRoot = aElement;
   }
 
   FlattenedChildIterator it(aElement);
   for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
     if (n->IsElement()) {
       Element* e = n->AsElement();
@@ -3060,16 +2999,17 @@ ElementRestyler::ConditionallyRestyle(ns
   }
 
   return ConditionallyRestyle(aFrame->GetContent()->AsElement(), aRestyleRoot);
 }
 
 bool
 ElementRestyler::ConditionallyRestyle(Element* aElement, Element* aRestyleRoot)
 {
+  MOZ_ASSERT(!aElement->IsStyledByServo());
   LOG_RESTYLE("considering element %s for eRestyle_SomeDescendants",
               ElementTagToString(aElement).get());
   LOG_RESTYLE_INDENT();
 
   if (aElement->HasFlag(mRestyleTracker.RootBit())) {
     aRestyleRoot = aElement;
   }
 
@@ -3961,19 +3901,19 @@ ElementRestyler::RestyleSelf(nsIFrame* a
     mParentFrameHintsNotHandledForDescendants =
       nsChangeHint_Hints_NotHandledForDescendants;
   }
 
   LOG_RESTYLE("parentContext = %p", parentContext);
 
   // do primary context
   RefPtr<nsStyleContext> newContext;
-  nsIFrame *prevContinuation =
+  nsIFrame* prevContinuation =
     GetPrevContinuationWithPossiblySameStyle(aSelf);
-  nsStyleContext *prevContinuationContext;
+  nsStyleContext* prevContinuationContext;
   bool copyFromContinuation =
     prevContinuation &&
     (prevContinuationContext = prevContinuation->StyleContext())
       ->GetPseudo() == oldContext->GetPseudo() &&
      prevContinuationContext->GetParent() == parentContext;
   if (copyFromContinuation) {
     // Just use the style context from the frame's previous
     // continuation.
@@ -4428,17 +4368,17 @@ ElementRestyler::RestyleChildren(nsResty
   // on a frame change.  The act of reconstructing frames will force
   // new style contexts to be resolved on all of this frame's
   // descendants anyway, so we want to avoid wasting time processing
   // style contexts that we're just going to throw away anyway. - dwh
   // It's also important to check mHintsHandled since reresolving the
   // kids would use mFrame->StyleContext(), which is out of date if
   // mHintsHandled has a ReconstructFrame hint; doing this could trigger
   // assertions about mismatched rule trees.
-  nsIFrame *lastContinuation;
+  nsIFrame* lastContinuation;
   if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
     InitializeAccessibilityNotifications(mFrame->StyleContext());
 
     for (nsIFrame* f = mFrame; f;
          f = GetNextContinuationWithSameStyle(f, f->StyleContext())) {
       lastContinuation = f;
       RestyleContentChildren(f, aChildRestyleHint);
     }
@@ -5037,25 +4977,16 @@ RestyleManager::ComputeAndProcessStyleCh
   r.RestyleChildrenOfDisplayContentsElement(frame, aNewContext, aMinChange,
                                             aRestyleTracker,
                                             aRestyleHint, aRestyleHintData);
   ProcessRestyledFrames(changeList);
   ClearCachedInheritedStyleDataOnDescendants(contextsToClear);
 }
 
 nsStyleSet*
-RestyleManager::StyleSet() const
-{
-  MOZ_ASSERT(mPresContext->StyleSet()->IsGecko(),
-             "RestyleManager should only be used with a Gecko-flavored "
-             "style backend");
-  return mPresContext->StyleSet()->AsGecko();
-}
-
-nsStyleSet*
 ElementRestyler::StyleSet() const
 {
   MOZ_ASSERT(mPresContext->StyleSet()->IsGecko(),
              "ElementRestyler should only be used with a Gecko-flavored "
              "style backend");
   return mPresContext->StyleSet()->AsGecko();
 }
 
--- a/layout/base/RestyleManager.h
+++ b/layout/base/RestyleManager.h
@@ -7,16 +7,17 @@
  * Code responsible for managing style changes: tracking what style
  * changes need to happen, scheduling them, and doing them.
  */
 
 #ifndef mozilla_RestyleManager_h
 #define mozilla_RestyleManager_h
 
 #include "mozilla/RestyleLogging.h"
+#include "mozilla/RestyleManagerBase.h"
 #include "nsISupportsImpl.h"
 #include "nsChangeHint.h"
 #include "RestyleTracker.h"
 #include "nsPresContext.h"
 #include "nsRefreshDriver.h"
 #include "nsRefPtrHashtable.h"
 #include "nsTransitionManager.h"
 
@@ -28,48 +29,36 @@ namespace mozilla {
   enum class CSSPseudoElementType : uint8_t;
   class EventStates;
   struct UndisplayedNode;
 
 namespace dom {
   class Element;
 } // namespace dom
 
-class RestyleManager final
+class RestyleManager final : public RestyleManagerBase
 {
 public:
-  friend class ::nsRefreshDriver;
   friend class RestyleTracker;
 
-  typedef mozilla::dom::Element Element;
-
   explicit RestyleManager(nsPresContext* aPresContext);
 
 private:
   // Private destructor, to discourage deletion outside of Release():
   ~RestyleManager()
   {
     MOZ_ASSERT(!mReframingStyleContexts,
                "temporary member should be nulled out before destruction");
     MOZ_ASSERT(!mAnimationsWithDestroyedFrame,
                "leaving dangling pointers from AnimationsWithDestroyedFrame");
   }
 
 public:
   NS_INLINE_DECL_REFCOUNTING(mozilla::RestyleManager)
 
-  void Disconnect() {
-    mPresContext = nullptr;
-  }
-
-  nsPresContext* PresContext() const {
-    MOZ_ASSERT(mPresContext);
-    return mPresContext;
-  }
-
   // Should be called when a frame is going to be destroyed and
   // WillDestroyFrameTree hasn't been called yet.
   void NotifyDestroyingFrame(nsIFrame* aFrame);
 
   // Forwarded nsIDocumentObserver method, to handle restyling (and
   // passing the notification to the frame).
   nsresult ContentStateChanged(nsIContent*   aContent,
                                EventStates aStateMask);
@@ -83,24 +72,16 @@ public:
   // Forwarded nsIMutationObserver method, to handle restyling (and
   // passing the notification to the frame).
   void AttributeChanged(Element* aElement,
                         int32_t  aNameSpaceID,
                         nsIAtom* aAttribute,
                         int32_t  aModType,
                         const nsAttrValue* aOldValue);
 
-  // Get an integer that increments every time we process pending restyles.
-  // The value is never 0.
-  uint32_t GetRestyleGeneration() const { return mRestyleGeneration; }
-
-  // Get an integer that increments every time there is a style change
-  // as a result of a change to the :hover content state.
-  uint32_t GetHoverGeneration() const { return mHoverGeneration; }
-
   // Get a counter that increments on every style change, that we use to
   // track whether off-main-thread animations are up-to-date.
   uint64_t GetAnimationGeneration() const { return mAnimationGeneration; }
 
   static uint64_t GetAnimationGenerationForFrame(nsIFrame* aFrame);
 
   // Update the animation generation count to mark that animation state
   // has changed.
@@ -134,25 +115,23 @@ public:
    */
   nsresult ReparentStyleContext(nsIFrame* aFrame);
 
   void ClearSelectors() {
     mPendingRestyles.ClearSelectors();
   }
 
 private:
-  nsCSSFrameConstructor* FrameConstructor() const
-    { return PresContext()->FrameConstructor(); }
-
   // Used when restyling an element with a frame.
   void ComputeAndProcessStyleChange(nsIFrame*              aFrame,
                                     nsChangeHint           aMinChange,
                                     RestyleTracker&        aRestyleTracker,
                                     nsRestyleHint          aRestyleHint,
                                     const RestyleHintData& aRestyleHintData);
+
   // Used when restyling a display:contents element.
   void ComputeAndProcessStyleChange(nsStyleContext*        aNewContext,
                                     Element*               aElement,
                                     nsChangeHint           aMinChange,
                                     RestyleTracker&        aRestyleTracker,
                                     nsRestyleHint          aRestyleHint,
                                     const RestyleHintData& aRestyleHintData);
 
@@ -451,17 +430,17 @@ public:
 #endif
 
 #ifdef RESTYLE_LOGGING
   /**
    * Returns whether a restyle event currently being processed by this
    * RestyleManager should be logged.
    */
   bool ShouldLogRestyle() {
-    return ShouldLogRestyle(mPresContext);
+    return ShouldLogRestyle(PresContext());
   }
 
   /**
    * Returns whether a restyle event currently being processed for the
    * document with the specified nsPresContext should be logged.
    */
   static bool ShouldLogRestyle(nsPresContext* aPresContext) {
     return aPresContext->RestyleLoggingEnabled() &&
@@ -487,17 +466,22 @@ public:
   // environment variable.
   static uint32_t StructsToLog();
 
   static nsCString StructNamesToString(uint32_t aSIDs);
   int32_t& LoggingDepth() { return mLoggingDepth; }
 #endif
 
 private:
-  inline nsStyleSet* StyleSet() const;
+  inline nsStyleSet* StyleSet() const {
+    MOZ_ASSERT(PresContext()->StyleSet()->IsGecko(),
+               "RestyleManager should only be used with a Gecko-flavored "
+               "style backend");
+    return PresContext()->StyleSet()->AsGecko();
+  }
 
   /* aMinHint is the minimal change that should be made to the element */
   // XXXbz do we really need the aPrimaryFrame argument here?
   void RestyleElement(Element*        aElement,
                       nsIFrame*       aPrimaryFrame,
                       nsChangeHint    aMinHint,
                       RestyleTracker& aRestyleTracker,
                       nsRestyleHint   aRestyleHint,
@@ -522,44 +506,33 @@ private:
     return mDoRebuildAllStyleData &&
            &aRestyleTracker == &mPendingRestyles;
   }
 
   void ProcessRestyles(RestyleTracker& aRestyleTracker) {
     // Fast-path the common case (esp. for the animation restyle
     // tracker) of not having anything to do.
     if (aRestyleTracker.Count() || ShouldStartRebuildAllFor(aRestyleTracker)) {
-      if (++mRestyleGeneration == 0) {
-        // Keep mRestyleGeneration from being 0, since that's what
-        // nsPresContext::GetRestyleGeneration returns when it no
-        // longer has a RestyleManager.
-        ++mRestyleGeneration;
-      }
+      IncrementRestyleGeneration();
       aRestyleTracker.DoProcessRestyles();
     }
   }
 
 private:
-  nsPresContext* mPresContext; // weak, disconnected in Disconnect
-
   // True if we need to reconstruct the rule tree the next time we
   // process restyles.
   bool mDoRebuildAllStyleData : 1;
   // True if we're currently in the process of reconstructing the rule tree.
   bool mInRebuildAllStyleData : 1;
-  // True if we're already waiting for a refresh notification
-  bool mObservingRefreshDriver : 1;
   // True if we're in the middle of a nsRefreshDriver refresh
   bool mInStyleRefresh : 1;
   // Whether rule matching should skip styles associated with animation
   bool mSkipAnimationRules : 1;
   bool mHavePendingNonAnimationRestyles : 1;
 
-  uint32_t mRestyleGeneration;
-  uint32_t mHoverGeneration;
   nsChangeHint mRebuildAllExtraHint;
   nsRestyleHint mRebuildAllRestyleHint;
 
   OverflowChangedTracker mOverflowChangedTracker;
 
   // The total number of animation flushes by this frame constructor.
   // Used to keep the layer and animation manager in sync.
   uint64_t mAnimationGeneration;
new file mode 100644
--- /dev/null
+++ b/layout/base/RestyleManagerBase.cpp
@@ -0,0 +1,103 @@
+/* -*- 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 "mozilla/RestyleManagerBase.h"
+#include "mozilla/StyleSetHandle.h"
+#include "nsIFrame.h"
+
+namespace mozilla {
+
+RestyleManagerBase::RestyleManagerBase(nsPresContext* aPresContext)
+  : mPresContext(aPresContext)
+  , mRestyleGeneration(1)
+  , mHoverGeneration(0)
+  , mObservingRefreshDriver(false)
+{
+  MOZ_ASSERT(mPresContext);
+}
+
+/**
+ * Calculates the change hint and the restyle hint for a given content state
+ * change.
+ *
+ * This is called from both Restyle managers.
+ */
+void
+RestyleManagerBase::ContentStateChangedInternal(Element* aElement,
+                                                EventStates aStateMask,
+                                                nsChangeHint* aOutChangeHint,
+                                                nsRestyleHint* aOutRestyleHint)
+{
+  MOZ_ASSERT(aOutChangeHint);
+  MOZ_ASSERT(aOutRestyleHint);
+
+  StyleSetHandle styleSet = PresContext()->StyleSet();
+  NS_ASSERTION(styleSet, "couldn't get style set");
+
+  *aOutChangeHint = NS_STYLE_HINT_NONE;
+  // Any change to a content state that affects which frames we construct
+  // must lead to a frame reconstruct here if we already have a frame.
+  // Note that we never decide through non-CSS means to not create frames
+  // based on content states, so if we already don't have a frame we don't
+  // need to force a reframe -- if it's needed, the HasStateDependentStyle
+  // call will handle things.
+  nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
+  CSSPseudoElementType pseudoType = CSSPseudoElementType::NotPseudo;
+  if (primaryFrame) {
+    // If it's generated content, ignore LOADING/etc state changes on it.
+    if (!primaryFrame->IsGeneratedContentFrame() &&
+        aStateMask.HasAtLeastOneOfStates(NS_EVENT_STATE_BROKEN |
+                                         NS_EVENT_STATE_USERDISABLED |
+                                         NS_EVENT_STATE_SUPPRESSED |
+                                         NS_EVENT_STATE_LOADING)) {
+      *aOutChangeHint = nsChangeHint_ReconstructFrame;
+    } else {
+      uint8_t app = primaryFrame->StyleDisplay()->mAppearance;
+      if (app) {
+        nsITheme *theme = PresContext()->GetTheme();
+        if (theme && theme->ThemeSupportsWidget(PresContext(),
+                                                primaryFrame, app)) {
+          bool repaint = false;
+          theme->WidgetStateChanged(primaryFrame, app, nullptr, &repaint, nullptr);
+          if (repaint) {
+            *aOutChangeHint |= nsChangeHint_RepaintFrame;
+          }
+        }
+      }
+    }
+
+    pseudoType = primaryFrame->StyleContext()->GetPseudoType();
+
+    primaryFrame->ContentStatesChanged(aStateMask);
+  }
+
+  if (pseudoType >= CSSPseudoElementType::Count) {
+    *aOutRestyleHint = styleSet->HasStateDependentStyle(aElement, aStateMask);
+  } else if (nsCSSPseudoElements::PseudoElementSupportsUserActionState(
+                                                                  pseudoType)) {
+    // If aElement is a pseudo-element, we want to check to see whether there
+    // are any state-dependent rules applying to that pseudo.
+    Element* ancestor = ElementForStyleContext(nullptr, primaryFrame,
+                                               pseudoType);
+    *aOutRestyleHint = styleSet->HasStateDependentStyle(ancestor, pseudoType, aElement,
+                                                       aStateMask);
+  } else {
+    *aOutRestyleHint = nsRestyleHint(0);
+  }
+
+  if (aStateMask.HasState(NS_EVENT_STATE_HOVER) && *aOutRestyleHint != 0) {
+    IncrementHoverGeneration();
+  }
+
+  if (aStateMask.HasState(NS_EVENT_STATE_VISITED)) {
+    // Exposing information to the page about whether the link is
+    // visited or not isn't really something we can worry about here.
+    // FIXME: We could probably do this a bit better.
+    *aOutChangeHint |= nsChangeHint_RepaintFrame;
+  }
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/layout/base/RestyleManagerBase.h
@@ -0,0 +1,97 @@
+/* -*- 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 mozilla_RestyleManagerBase_h
+#define mozilla_RestyleManagerBase_h
+
+#include "nsChangeHint.h"
+
+namespace mozilla {
+
+class ServoRestyleManager;
+class RestyleManager;
+
+/**
+ * Class for sharing data and logic common to both RestyleManager and
+ * ServoRestyleManager.
+ */
+class RestyleManagerBase
+{
+protected:
+  explicit RestyleManagerBase(nsPresContext* aPresContext);
+
+public:
+  typedef mozilla::dom::Element Element;
+
+  // Get an integer that increments every time we process pending restyles.
+  // The value is never 0.
+  uint32_t GetRestyleGeneration() const { return mRestyleGeneration; }
+
+  // Get an integer that increments every time there is a style change
+  // as a result of a change to the :hover content state.
+  uint32_t GetHoverGeneration() const { return mHoverGeneration; }
+
+  bool ObservingRefreshDriver() const { return mObservingRefreshDriver; }
+
+  void SetObservingRefreshDriver(bool aObserving) {
+      mObservingRefreshDriver = aObserving;
+  }
+
+  void Disconnect() { mPresContext = nullptr; }
+
+protected:
+  void ContentStateChangedInternal(Element* aElement,
+                                   EventStates aStateMask,
+                                   nsChangeHint* aOutChangeHint,
+                                   nsRestyleHint* aOutRestyleHint);
+
+  bool IsDisconnected() { return mPresContext == nullptr; }
+
+  void IncrementHoverGeneration() {
+    ++mHoverGeneration;
+  }
+
+  void IncrementRestyleGeneration() {
+    if (++mRestyleGeneration == 0) {
+      // Keep mRestyleGeneration from being 0, since that's what
+      // nsPresContext::GetRestyleGeneration returns when it no
+      // longer has a RestyleManager.
+      ++mRestyleGeneration;
+    }
+  }
+
+  nsPresContext* PresContext() const {
+    MOZ_ASSERT(mPresContext);
+    return mPresContext;
+  }
+
+  nsCSSFrameConstructor* FrameConstructor() const {
+    return PresContext()->FrameConstructor();
+  }
+
+  inline bool IsGecko() const {
+    return !IsServo();
+  }
+
+  inline bool IsServo() const {
+#ifdef MOZ_STYLO
+    return PresContext()->StyleSet()->IsServo();
+#else
+    return false;
+#endif
+  }
+
+private:
+  nsPresContext* mPresContext; // weak, can be null after Disconnect().
+  uint32_t mRestyleGeneration;
+  uint32_t mHoverGeneration;
+  // True if we're already waiting for a refresh notification.
+  bool mObservingRefreshDriver;
+};
+
+} // namespace mozilla
+
+#endif
--- a/layout/base/RestyleManagerHandle.h
+++ b/layout/base/RestyleManagerHandle.h
@@ -130,16 +130,18 @@ public:
     inline void AttributeChanged(dom::Element* aElement,
                                  int32_t aNameSpaceID,
                                  nsIAtom* aAttribute,
                                  int32_t aModType,
                                  const nsAttrValue* aOldValue);
     inline nsresult ReparentStyleContext(nsIFrame* aFrame);
     inline bool HasPendingRestyles();
     inline uint64_t GetRestyleGeneration() const;
+    inline uint32_t GetHoverGeneration() const;
+    inline void SetObservingRefreshDriver(bool aObserving);
 
   private:
     // Stores a pointer to an RestyleManager or a ServoRestyleManager.  The least
     // significant bit is 0 for the former, 1 for the latter.  This is
     // valid as the least significant bit will never be used for a pointer
     // value on platforms we care about.
     uintptr_t mValue;
   };
--- a/layout/base/RestyleManagerHandleInlines.h
+++ b/layout/base/RestyleManagerHandleInlines.h
@@ -138,14 +138,27 @@ RestyleManagerHandle::Ptr::HasPendingRes
 }
 
 uint64_t
 RestyleManagerHandle::Ptr::GetRestyleGeneration() const
 {
   FORWARD(GetRestyleGeneration, ());
 }
 
+uint32_t
+RestyleManagerHandle::Ptr::GetHoverGeneration() const
+{
+  FORWARD(GetHoverGeneration, ());
+}
+
+void
+RestyleManagerHandle::Ptr::SetObservingRefreshDriver(bool aObserving)
+{
+  FORWARD(SetObservingRefreshDriver, (aObserving));
+}
+
+
 } // namespace mozilla
 
 #undef FORWARD
 #undef FORWARD_CONCRETE
 
 #endif // mozilla_RestyleManagerHandleInlines_h
--- a/layout/base/RestyleTracker.cpp
+++ b/layout/base/RestyleTracker.cpp
@@ -163,28 +163,30 @@ RestyleTracker::DoProcessRestyles()
 
     // loop so that we process any restyle events generated by processing
     while (mPendingRestyles.Count()) {
       if (mHaveLaterSiblingRestyles) {
         // Convert them to individual restyles on all the later siblings
         AutoTArray<RefPtr<Element>, RESTYLE_ARRAY_STACKSIZE> laterSiblingArr;
         for (auto iter = mPendingRestyles.Iter(); !iter.Done(); iter.Next()) {
           auto element = static_cast<dom::Element*>(iter.Key());
+          MOZ_ASSERT(!element->IsStyledByServo(), "Should not have Servo-styled elements here");
           // Only collect the entries that actually need restyling by us (and
           // haven't, for example, already been restyled).
           // It's important to not mess with the flags on entries not in our
           // document.
           if (element->GetComposedDoc() == Document() &&
               element->HasFlag(RestyleBit()) &&
               (iter.Data()->mRestyleHint & eRestyle_LaterSiblings)) {
             laterSiblingArr.AppendElement(element);
           }
         }
         for (uint32_t i = 0; i < laterSiblingArr.Length(); ++i) {
           Element* element = laterSiblingArr[i];
+          MOZ_ASSERT(!element->IsStyledByServo());
           for (nsIContent* sibling = element->GetNextSibling();
                sibling;
                sibling = sibling->GetNextSibling()) {
             if (sibling->IsElement()) {
               LOG_RESTYLE("adding pending restyle for %s due to "
                           "eRestyle_LaterSiblings hint on %s",
                           FrameTagToString(sibling->AsElement()).get(),
                           FrameTagToString(element->AsElement()).get());
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -1,37 +1,57 @@
 /* -*- 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 "mozilla/ServoRestyleManager.h"
+#include "mozilla/ServoStyleSet.h"
 
 using namespace mozilla::dom;
 
 namespace mozilla {
 
-ServoRestyleManager::ServoRestyleManager()
-  : mRestyleGeneration(1)
+ServoRestyleManager::ServoRestyleManager(nsPresContext* aPresContext)
+  : RestyleManagerBase(aPresContext)
 {
 }
 
 void
 ServoRestyleManager::Disconnect()
 {
   NS_ERROR("stylo: ServoRestyleManager::Disconnect not implemented");
 }
 
 void
 ServoRestyleManager::PostRestyleEvent(Element* aElement,
                                       nsRestyleHint aRestyleHint,
                                       nsChangeHint aMinChangeHint)
 {
-  NS_ERROR("stylo: ServoRestyleManager::PostRestyleEvent not implemented");
+  if (MOZ_UNLIKELY(IsDisconnected()) ||
+      MOZ_UNLIKELY(PresContext()->PresShell()->IsDestroying())) {
+    return;
+  }
+
+  nsIPresShell* presShell = PresContext()->PresShell();
+  if (!ObservingRefreshDriver()) {
+    SetObservingRefreshDriver(PresContext()->RefreshDriver()->
+        AddStyleFlushObserver(presShell));
+  }
+
+  aElement->SetIsDirtyForServo();
+  nsINode* cur = aElement;
+  while ((cur = cur->GetParentNode())) {
+    if (cur->HasDirtyDescendantsForServo())
+      break;
+    cur->SetHasDirtyDescendantsForServo();
+  }
+
+  presShell->GetDocument()->SetNeedStyleFlush();
 }
 
 void
 ServoRestyleManager::PostRestyleEventForLazyConstruction()
 {
   NS_ERROR("stylo: ServoRestyleManager::PostRestyleEventForLazyConstruction not implemented");
 }
 
@@ -48,17 +68,17 @@ ServoRestyleManager::PostRebuildAllStyle
 {
   MOZ_CRASH("stylo: ServoRestyleManager::PostRebuildAllStyleDataEvent not implemented");
 }
 
 void
 ServoRestyleManager::ProcessPendingRestyles()
 {
   // XXXheycam Do nothing for now.
-  mRestyleGeneration++;
+  IncrementRestyleGeneration();
 }
 
 void
 ServoRestyleManager::RestyleForInsertOrChange(Element* aContainer,
                                               nsIContent* aChild)
 {
   NS_ERROR("stylo: ServoRestyleManager::RestyleForInsertOrChange not implemented");
 }
@@ -77,17 +97,27 @@ ServoRestyleManager::RestyleForRemove(El
 {
   NS_ERROR("stylo: ServoRestyleManager::RestyleForRemove not implemented");
 }
 
 nsresult
 ServoRestyleManager::ContentStateChanged(nsIContent* aContent,
                                          EventStates aStateMask)
 {
-  NS_ERROR("stylo: ServoRestyleManager::ContentStateChanged not implemented");
+  if (!aContent->IsElement()) {
+    return NS_OK;
+  }
+
+  Element* aElement = aContent->AsElement();
+  nsChangeHint changeHint;
+  nsRestyleHint restyleHint;
+  ContentStateChangedInternal(aElement, aStateMask, &changeHint, &restyleHint);
+
+  // TODO(emilio): Post a restyle here, and make it effective.
+  // PostRestyleEvent(aElement, restyleHint, changeHint);
   return NS_OK;
 }
 
 void
 ServoRestyleManager::AttributeWillChange(Element* aElement,
                                          int32_t aNameSpaceID,
                                          nsIAtom* aAttribute,
                                          int32_t aModType,
@@ -114,15 +144,9 @@ ServoRestyleManager::ReparentStyleContex
 
 bool
 ServoRestyleManager::HasPendingRestyles()
 {
   NS_ERROR("stylo: ServoRestyleManager::HasPendingRestyles not implemented");
   return false;
 }
 
-uint64_t
-ServoRestyleManager::GetRestyleGeneration() const
-{
-  return mRestyleGeneration;
-}
-
 } // namespace mozilla
--- a/layout/base/ServoRestyleManager.h
+++ b/layout/base/ServoRestyleManager.h
@@ -3,40 +3,43 @@
 /* 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 mozilla_ServoRestyleManager_h
 #define mozilla_ServoRestyleManager_h
 
 #include "mozilla/EventStates.h"
+#include "mozilla/RestyleManagerBase.h"
 #include "nsChangeHint.h"
 #include "nsISupportsImpl.h"
+#include "nsPresContext.h"
+#include "nsINode.h"
 
 namespace mozilla {
 namespace dom {
 class Element;
 } // namespace dom
 } // namespace mozilla
 class nsAttrValue;
 class nsIAtom;
 class nsIContent;
 class nsIFrame;
 
 namespace mozilla {
 
 /**
  * Restyle manager for a Servo-backed style system.
  */
-class ServoRestyleManager
+class ServoRestyleManager : public RestyleManagerBase
 {
 public:
   NS_INLINE_DECL_REFCOUNTING(ServoRestyleManager)
 
-  ServoRestyleManager();
+  explicit ServoRestyleManager(nsPresContext* aPresContext);
 
   void Disconnect();
   void PostRestyleEvent(dom::Element* aElement,
                         nsRestyleHint aRestyleHint,
                         nsChangeHint aMinChangeHint);
   void PostRestyleEventForLazyConstruction();
   void RebuildAllStyleData(nsChangeHint aExtraHint,
                            nsRestyleHint aRestyleHint);
@@ -59,19 +62,24 @@ public:
                            const nsAttrValue* aNewValue);
   void AttributeChanged(dom::Element* aElement,
                         int32_t aNameSpaceID,
                         nsIAtom* aAttribute,
                         int32_t aModType,
                         const nsAttrValue* aOldValue);
   nsresult ReparentStyleContext(nsIFrame* aFrame);
   bool HasPendingRestyles();
-  uint64_t GetRestyleGeneration() const;
 
 protected:
   ~ServoRestyleManager() {}
 
-  uint64_t mRestyleGeneration;
+private:
+  inline ServoStyleSet* StyleSet() const {
+    MOZ_ASSERT(PresContext()->StyleSet()->IsServo(),
+               "ServoRestyleManager should only be used with a Servo-flavored "
+               "style backend");
+    return PresContext()->StyleSet()->AsServo();
+  }
 };
 
 } // namespace mozilla
 
 #endif // mozilla_ServoRestyleManager_h
--- a/layout/base/moz.build
+++ b/layout/base/moz.build
@@ -102,16 +102,17 @@ EXPORTS += [
 EXPORTS.mozilla += [
     'ArenaObjectID.h',
     'ArenaRefPtr.h',
     'ArenaRefPtrInlines.h',
     'GeometryUtils.h',
     'PaintTracker.h',
     'RestyleLogging.h',
     'RestyleManager.h',
+    'RestyleManagerBase.h',
     'RestyleManagerHandle.h',
     'RestyleManagerHandleInlines.h',
     'ServoRestyleManager.h',
     'StaticPresData.h',
 ]
 
 UNIFIED_SOURCES += [
     'AccessibleCaret.cpp',
@@ -149,16 +150,17 @@ UNIFIED_SOURCES += [
     'nsPresContext.cpp',
     'nsPresShell.cpp',
     'nsQuoteList.cpp',
     'nsStyleChangeList.cpp',
     'nsStyleSheetService.cpp',
     'PaintTracker.cpp',
     'PositionedEventTargeting.cpp',
     'RestyleManager.cpp',
+    'RestyleManagerBase.cpp',
     'RestyleTracker.cpp',
     'ScrollbarStyles.cpp',
     'ServoRestyleManager.cpp',
     'StackArena.cpp',
     'StaticPresData.cpp',
     'TouchManager.cpp',
     'ZoomConstraintsClient.cpp',
 ]
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -1975,17 +1975,17 @@ nsCSSFrameConstructor::GetParentType(nsI
   if (aFrameType == nsGkAtoms::tableColGroupFrame) {
     return eTypeColGroup;
   }
   if (aFrameType == nsGkAtoms::rubyBaseContainerFrame) {
     return eTypeRubyBaseContainer;
   }
   if (aFrameType == nsGkAtoms::rubyTextContainerFrame) {
     return eTypeRubyTextContainer;
-  } 
+  }
   if (aFrameType == nsGkAtoms::rubyFrame) {
     return eTypeRuby;
   }
 
   return eTypeBlock;
 }
 
 static nsContainerFrame*
@@ -2425,17 +2425,17 @@ nsCSSFrameConstructor::ConstructDocEleme
     state.mPresShell->CaptureHistoryState(getter_AddRefs(mTempFrameTreeState));
 
   // Make sure that we'll handle restyles for this document element in
   // the future.  We need this, because the document element might
   // have stale restyle bits from a previous frame constructor for
   // this document.  Unlike in AddFrameConstructionItems, it's safe to
   // unset all element restyle flags, since we don't have any
   // siblings.
-  aDocElement->UnsetFlags(ELEMENT_ALL_RESTYLE_FLAGS);
+  aDocElement->UnsetRestyleFlagsIfGecko();
 
   // --------- CREATE AREA OR BOX FRAME -------
   // FIXME: Should this use ResolveStyleContext?  (The calls in this
   // function are the only case in nsCSSFrameConstructor where we don't
   // do so for the construction of a style context for an element.)
   RefPtr<nsStyleContext> styleContext;
   styleContext = mPresShell->StyleSet()->ResolveStyleFor(aDocElement,
                                                          nullptr);
@@ -4135,17 +4135,17 @@ nsCSSFrameConstructor::CreateAnonymousFr
     if (newFrame) {
       NS_ASSERTION(content->GetPrimaryFrame(),
                    "Content must have a primary frame now");
       newFrame->AddStateBits(NS_FRAME_ANONYMOUSCONTENTCREATOR_CONTENT);
       aChildItems.AddChild(newFrame);
     } else {
       FrameConstructionItemList items;
       {
-        // Skip parent display based style-fixup during our 
+        // Skip parent display based style-fixup during our
         // AddFrameConstructionItems() call:
         TreeMatchContext::AutoParentDisplayBasedStyleFixupSkipper
           parentDisplayBasedStyleFixupSkipper(aState.mTreeMatchContext);
 
         AddFrameConstructionItems(aState, content, true, insertion, items);
       }
       ConstructFramesFromItemList(aState, items, aParentFrame, aChildItems);
     }
@@ -5508,17 +5508,17 @@ nsCSSFrameConstructor::AddPageBreakItem(
 }
 
 bool
 nsCSSFrameConstructor::ShouldCreateItemsForChild(nsFrameConstructorState& aState,
                                                  nsIContent* aContent,
                                                  nsContainerFrame* aParentFrame)
 {
   aContent->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME);
-  if (aContent->IsElement()) {
+  if (aContent->IsElement() && !aContent->IsStyledByServo()) {
     // We can't just remove our pending restyle flags, since we may
     // have restyle-later-siblings set on us.  But we _can_ remove the
     // "is possible restyle root" flags, and need to.  Otherwise we can
     // end up with stale such flags (e.g. if we used to have a
     // display:none parent when our last restyle was posted and
     // processed and now no longer do).
     aContent->UnsetFlags(ELEMENT_ALL_RESTYLE_FLAGS &
                          ~ELEMENT_PENDING_RESTYLE_FLAGS);
@@ -9751,17 +9751,17 @@ nsCSSFrameConstructor::sPseudoParentData
   { // Ruby
     FCDATA_DECL(FCDATA_IS_LINE_PARTICIPANT |
                 FCDATA_USE_CHILD_ITEMS |
                 FCDATA_SKIP_FRAMESET,
                 NS_NewRubyFrame),
     &nsCSSAnonBoxes::ruby
   },
   { // Ruby Base
-    FCDATA_DECL(FCDATA_USE_CHILD_ITEMS | 
+    FCDATA_DECL(FCDATA_USE_CHILD_ITEMS |
                 FCDATA_IS_LINE_PARTICIPANT |
                 FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRubyBaseContainer) |
                 FCDATA_SKIP_FRAMESET,
                 NS_NewRubyBaseFrame),
     &nsCSSAnonBoxes::rubyBase
   },
   { // Ruby Base Container
     FCDATA_DECL(FCDATA_USE_CHILD_ITEMS |
@@ -10321,17 +10321,17 @@ nsCSSFrameConstructor::CreateNeededPseud
         break;
       case eTypeTable:
         // Either colgroup or rowgroup, depending on what we're grouping.
         wrapperType = groupingParentType == eTypeColGroup ?
           eTypeColGroup : eTypeRowGroup;
         break;
       case eTypeColGroup:
         MOZ_CRASH("Colgroups should be suppresing non-col child items");
-      default: 
+      default:
         NS_ASSERTION(ourParentType == eTypeBlock, "Unrecognized parent type");
         if (IsRubyParentType(groupingParentType)) {
           wrapperType = eTypeRuby;
         } else {
           NS_ASSERTION(IsTableParentType(groupingParentType),
                        "groupingParentType should be either Ruby or table");
           wrapperType = eTypeTable;
         }
@@ -10537,23 +10537,26 @@ nsCSSFrameConstructor::AddFCItemsForAnon
     nsIContent* content = aAnonymousItems[i].mContent;
 #ifdef DEBUG
     nsIAnonymousContentCreator* creator = do_QueryFrame(aFrame);
     NS_ASSERTION(!creator || !creator->CreateFrameFor(content),
                  "If you need to use CreateFrameFor, you need to call "
                  "CreateAnonymousFrames manually and not follow the standard "
                  "ProcessChildren() codepath for this frame");
 #endif
+    // Anything restyled by servo should already have the style data.
+    MOZ_ASSERT_IF(content->IsStyledByServo(), !!content->GetServoNodeData());
+    // Gecko-styled nodes should have no pending restyle flags.
+    MOZ_ASSERT_IF(!content->IsStyledByServo(),
+                  !content->IsElement() ||
+                  !(content->GetFlags() & ELEMENT_ALL_RESTYLE_FLAGS));
     // Assert some things about this content
     MOZ_ASSERT(!(content->GetFlags() &
                  (NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME)),
                "Should not be marked as needing frames");
-    MOZ_ASSERT(!content->IsElement() ||
-               !(content->GetFlags() & ELEMENT_ALL_RESTYLE_FLAGS),
-               "Should have no pending restyle flags");
     MOZ_ASSERT(!content->GetPrimaryFrame(),
                "Should have no existing frame");
     MOZ_ASSERT(!content->IsNodeOfType(nsINode::eCOMMENT) &&
                !content->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION),
                "Why is someone creating garbage anonymous content");
 
     RefPtr<nsStyleContext> styleContext;
     TreeMatchContext::AutoParentDisplayBasedStyleFixupSkipper
@@ -10691,19 +10694,17 @@ nsCSSFrameConstructor::ProcessChildren(n
           ancestorPusher.PushAncestorAndStyleScope(parent->AsElement());
         } else {
           ancestorPusher.PushStyleScope(parent->AsElement());
         }
       }
 
       // Frame construction item construction should not post
       // restyles, so removing restyle flags here is safe.
-      if (child->IsElement()) {
-        child->UnsetFlags(ELEMENT_ALL_RESTYLE_FLAGS);
-      }
+      child->UnsetRestyleFlagsIfGecko();
       if (addChildItems) {
         AddFrameConstructionItems(aState, child, iter.XBLInvolved(), insertion,
                                   itemsToConstruct);
       } else {
         ClearLazyBits(child, child->GetNextSibling());
       }
     }
     itemsToConstruct.SetParentHasNoXBLChildren(!iter.XBLInvolved());
@@ -12003,23 +12004,22 @@ nsCSSFrameConstructor::BuildInlineChildI
       // Manually check for comments/PIs, since we don't have a frame to pass to
       // AddFrameConstructionItems.  We know our parent is a non-replaced inline,
       // so there is no need to do the NeedFrameFor check.
       content->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME);
       if (content->IsNodeOfType(nsINode::eCOMMENT) ||
           content->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION)) {
         continue;
       }
-      if (content->IsElement()) {
-        // See comment explaining why we need to remove the "is possible
-        // restyle root" flags in AddFrameConstructionItems.  But note
-        // that we can remove all restyle flags, just like in
-        // ProcessChildren and for the same reason.
-        content->UnsetFlags(ELEMENT_ALL_RESTYLE_FLAGS);
-      }
+
+      // See comment explaining why we need to remove the "is possible
+      // restyle root" flags in AddFrameConstructionItems.  But note
+      // that we can remove all restyle flags, just like in
+      // ProcessChildren and for the same reason.
+      content->UnsetRestyleFlagsIfGecko();
 
       RefPtr<nsStyleContext> childContext =
         ResolveStyleContext(parentStyleContext, content, &aState);
 
       AddFrameConstructionItemsInternal(aState, content, nullptr,
                                         content->NodeInfo()->NameAtom(),
                                         content->GetNameSpaceID(),
                                         iter.XBLInvolved(), childContext,
--- a/layout/base/nsCSSRenderingBorders.cpp
+++ b/layout/base/nsCSSRenderingBorders.cpp
@@ -16,16 +16,17 @@
 #include "DottedCornerFinder.h"
 #include "nsLayoutUtils.h"
 #include "nsStyleConsts.h"
 #include "nsContentUtils.h"
 #include "nsCSSColorUtils.h"
 #include "GeckoProfiler.h"
 #include "nsExpirationTracker.h"
 #include "RoundedRect.h"
+#include "nsIScriptError.h"
 #include "nsClassHashtable.h"
 #include "nsPresContext.h"
 #include "nsStyleStruct.h"
 #include "mozilla/gfx/2D.h"
 #include "gfx2DGlue.h"
 #include "gfxGradientCache.h"
 #include <algorithm>
 
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -739,17 +739,18 @@ nsDisplayListBuilder::nsDisplayListBuild
       mIsPaintingToWindow(false),
       mIsCompositingCheap(false),
       mContainsPluginItem(false),
       mAncestorHasApzAwareEventHandler(false),
       mHaveScrollableDisplayPort(false),
       mWindowDraggingAllowed(false),
       mIsBuildingForPopup(nsLayoutUtils::IsPopup(aReferenceFrame)),
       mForceLayerForScrollParent(false),
-      mAsyncPanZoomEnabled(nsLayoutUtils::AsyncPanZoomEnabled(aReferenceFrame))
+      mAsyncPanZoomEnabled(nsLayoutUtils::AsyncPanZoomEnabled(aReferenceFrame)),
+      mBuildingInvisibleItems(false)
 {
   MOZ_COUNT_CTOR(nsDisplayListBuilder);
   PL_InitArenaPool(&mPool, "displayListArena", 4096,
                    std::max(NS_ALIGNMENT_OF(void*),NS_ALIGNMENT_OF(double))-1);
 
   nsPresContext* pc = aReferenceFrame->PresContext();
   nsIPresShell *shell = pc->PresShell();
   if (pc->IsRenderingOnlySelection()) {
@@ -1639,21 +1640,27 @@ nsDisplayList::ComputeVisibilityForSubli
 
   bool anyVisible = false;
 
   AutoTArray<nsDisplayItem*, 512> elements;
   MoveListTo(this, &elements);
 
   for (int32_t i = elements.Length() - 1; i >= 0; --i) {
     nsDisplayItem* item = elements[i];
-    nsRect bounds = item->GetClippedBounds(aBuilder);
-
-    nsRegion itemVisible;
-    itemVisible.And(*aVisibleRegion, bounds);
-    item->mVisibleRect = itemVisible.GetBounds();
+
+    if (item->mForceNotVisible) {
+      NS_ASSERTION(item->mVisibleRect.IsEmpty(),
+        "invisible items should have empty vis rect");
+    } else {
+      nsRect bounds = item->GetClippedBounds(aBuilder);
+
+      nsRegion itemVisible;
+      itemVisible.And(*aVisibleRegion, bounds);
+      item->mVisibleRect = itemVisible.GetBounds();
+    }
 
     if (item->ComputeVisibility(aBuilder, aVisibleRegion)) {
       anyVisible = true;
 
       nsRegion opaque = TreatAsOpaque(item, aBuilder);
       // Subtract opaque item from the visible region
       aBuilder->SubtractFromVisibleRegion(aVisibleRegion, opaque);
     }
@@ -2239,16 +2246,17 @@ nsDisplayItem::nsDisplayItem(nsDisplayLi
 {}
 
 nsDisplayItem::nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                              const DisplayItemScrollClip* aScrollClip)
   : mFrame(aFrame)
   , mClip(aBuilder->ClipState().GetCurrentCombinedClip(aBuilder))
   , mScrollClip(aScrollClip)
   , mAnimatedGeometryRoot(nullptr)
+  , mForceNotVisible(aBuilder->IsBuildingInvisibleItems())
 #ifdef MOZ_DUMP_PAINTING
   , mPainted(false)
 #endif
 {
   mReferenceFrame = aBuilder->FindReferenceFrameFor(aFrame, &mToReferenceFrame);
   // This can return the wrong result if the item override ShouldFixToViewport(),
   // the item needs to set it again in its constructor.
   mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor(aFrame);
@@ -2301,21 +2309,26 @@ nsDisplayItem::ComputeVisibility(nsDispl
 {
   return !mVisibleRect.IsEmpty() &&
     !IsInvisibleInRect(aVisibleRegion->GetBounds());
 }
 
 bool
 nsDisplayItem::RecomputeVisibility(nsDisplayListBuilder* aBuilder,
                                    nsRegion* aVisibleRegion) {
-  nsRect bounds = GetClippedBounds(aBuilder);
-
-  nsRegion itemVisible;
-  itemVisible.And(*aVisibleRegion, bounds);
-  mVisibleRect = itemVisible.GetBounds();
+  if (mForceNotVisible) {
+    NS_ASSERTION(mVisibleRect.IsEmpty(),
+      "invisible items should have empty vis rect");
+  } else {
+    nsRect bounds = GetClippedBounds(aBuilder);
+
+    nsRegion itemVisible;
+    itemVisible.And(*aVisibleRegion, bounds);
+    mVisibleRect = itemVisible.GetBounds();
+  }
 
   // When we recompute visibility within layers we don't need to
   // expand the visible region for content behind plugins (the plugin
   // is not in the layer).
   if (!ComputeVisibility(aBuilder, aVisibleRegion)) {
     mVisibleRect = nsRect();
     return false;
   }
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -714,17 +714,18 @@ public:
       : mBuilder(aBuilder),
         mPrevFrame(aBuilder->mCurrentFrame),
         mPrevReferenceFrame(aBuilder->mCurrentReferenceFrame),
         mPrevLayerEventRegions(aBuilder->mLayerEventRegions),
         mPrevOffset(aBuilder->mCurrentOffsetToReferenceFrame),
         mPrevDirtyRect(aBuilder->mDirtyRect),
         mPrevAGR(aBuilder->mCurrentAGR),
         mPrevIsAtRootOfPseudoStackingContext(aBuilder->mIsAtRootOfPseudoStackingContext),
-        mPrevAncestorHasApzAwareEventHandler(aBuilder->mAncestorHasApzAwareEventHandler)
+        mPrevAncestorHasApzAwareEventHandler(aBuilder->mAncestorHasApzAwareEventHandler),
+        mPrevBuildingInvisibleItems(aBuilder->mBuildingInvisibleItems)
     {
       if (aForChild->IsTransformed()) {
         aBuilder->mCurrentOffsetToReferenceFrame = nsPoint();
         aBuilder->mCurrentReferenceFrame = aForChild;
       } else if (aBuilder->mCurrentFrame == aForChild->GetParent()) {
         aBuilder->mCurrentOffsetToReferenceFrame += aForChild->GetPosition();
       } else {
         aBuilder->mCurrentReferenceFrame =
@@ -754,37 +755,42 @@ public:
     // current frame is an immediate descendant.
     const nsIFrame* GetPrevAnimatedGeometryRoot() const {
       return mPrevAnimatedGeometryRoot;
     }
     bool IsAnimatedGeometryRoot() const {
       return *mBuilder->mCurrentAGR == mBuilder->mCurrentFrame;
 
     }
+    void RestoreBuildingInvisibleItemsValue() {
+      mBuilder->mBuildingInvisibleItems = mPrevBuildingInvisibleItems;
+    }
     ~AutoBuildingDisplayList() {
       mBuilder->mCurrentFrame = mPrevFrame;
       mBuilder->mCurrentReferenceFrame = mPrevReferenceFrame;
       mBuilder->mLayerEventRegions = mPrevLayerEventRegions;
       mBuilder->mCurrentOffsetToReferenceFrame = mPrevOffset;
       mBuilder->mDirtyRect = mPrevDirtyRect;
       mBuilder->mCurrentAGR = mPrevAGR;
       mBuilder->mIsAtRootOfPseudoStackingContext = mPrevIsAtRootOfPseudoStackingContext;
       mBuilder->mAncestorHasApzAwareEventHandler = mPrevAncestorHasApzAwareEventHandler;
+      mBuilder->mBuildingInvisibleItems = mPrevBuildingInvisibleItems;
     }
   private:
     nsDisplayListBuilder* mBuilder;
     const nsIFrame*       mPrevFrame;
     const nsIFrame*       mPrevReferenceFrame;
     nsIFrame*             mPrevAnimatedGeometryRoot;
     nsDisplayLayerEventRegions* mPrevLayerEventRegions;
     nsPoint               mPrevOffset;
     nsRect                mPrevDirtyRect;
     AnimatedGeometryRoot* mPrevAGR;
     bool                  mPrevIsAtRootOfPseudoStackingContext;
     bool                  mPrevAncestorHasApzAwareEventHandler;
+    bool                  mPrevBuildingInvisibleItems;
   };
 
   /**
    * A helper class to temporarily set the value of mInTransform.
    */
   class AutoInTransformSetter;
   friend class AutoInTransformSetter;
   class AutoInTransformSetter {
@@ -1124,16 +1130,21 @@ public:
 
   const nsRect GetPreserves3DDirtyRect(const nsIFrame *aFrame) const {
     return mPreserves3DCtx.mDirtyRect;
   }
   void SetPreserves3DDirtyRect(const nsRect &aDirtyRect) {
     mPreserves3DCtx.mDirtyRect = aDirtyRect;
   }
 
+  bool IsBuildingInvisibleItems() const { return mBuildingInvisibleItems; }
+  void SetBuildingInvisibleItems(bool aBuildingInvisibleItems) {
+    mBuildingInvisibleItems = aBuildingInvisibleItems;
+  }
+
 private:
   void MarkOutOfFlowFrameForDisplay(nsIFrame* aDirtyFrame, nsIFrame* aFrame,
                                     const nsRect& aDirtyRect);
 
   /**
    * Returns whether a frame acts as an animated geometry root, optionally
    * returning the next ancestor to check.
    */
@@ -1273,16 +1284,17 @@ private:
   // True when the first async-scrollable scroll frame for which we build a
   // display list has a display port. An async-scrollable scroll frame is one
   // which WantsAsyncScroll().
   bool                           mHaveScrollableDisplayPort;
   bool                           mWindowDraggingAllowed;
   bool                           mIsBuildingForPopup;
   bool                           mForceLayerForScrollParent;
   bool                           mAsyncPanZoomEnabled;
+  bool                           mBuildingInvisibleItems;
 };
 
 class nsDisplayItem;
 class nsDisplayList;
 /**
  * nsDisplayItems are put in singly-linked lists rooted in an nsDisplayList.
  * nsDisplayItemLink holds the link. The lists are linked from lowest to
  * highest in z-order.
@@ -1872,16 +1884,17 @@ protected:
   nsPoint   mToReferenceFrame;
   // This is the rectangle that needs to be painted.
   // Display item construction sets this to the dirty rect.
   // nsDisplayList::ComputeVisibility sets this to the visible region
   // of the item by intersecting the current visible region with the bounds
   // of the item. Paint implementations can use this to limit their drawing.
   // Guaranteed to be contained in GetBounds().
   nsRect    mVisibleRect;
+  bool      mForceNotVisible;
 #ifdef MOZ_DUMP_PAINTING
   // True if this frame has been painted.
   bool      mPainted;
 #endif
 };
 
 /**
  * Manages a singly-linked list of display list items.
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -2160,47 +2160,25 @@ nsDocumentViewer::RequestWindowClose(boo
     mDeferredWindowClose = true;
   } else
 #endif
     *aCanClose = true;
 
   return NS_OK;
 }
 
-static StyleBackendType
-StyleBackendTypeForDocument(nsIDocument* aDocument, nsIDocShell* aContainer)
-{
-  MOZ_ASSERT(aDocument);
-
-  // XXX For now we use a Servo-backed style set only for (X)HTML documents
-  // in content docshells.  This should let us avoid implementing XUL-specific
-  // CSS features.  And apart from not supporting SVG properties in Servo
-  // yet, the root SVG element likes to create a style sheet for an SVG
-  // document before we have a pres shell (i.e. before we make the decision
-  // here about whether to use a Gecko- or Servo-backed style system), so
-  // we avoid Servo-backed style sets for SVG documents.
-
-  return nsPresContext::StyloEnabled() &&
-         aDocument->IsHTMLOrXHTML() &&
-         aContainer &&
-         aContainer->ItemType() == nsIDocShell::typeContent ?
-           StyleBackendType::Servo :
-           StyleBackendType::Gecko;
-}
-
 StyleSetHandle
 nsDocumentViewer::CreateStyleSet(nsIDocument* aDocument)
 {
   // Make sure this does the same thing as PresShell::AddSheet wrt ordering.
 
   // this should eventually get expanded to allow for creating
   // different sets for different media
 
-  StyleBackendType backendType =
-    StyleBackendTypeForDocument(aDocument, mContainer);
+  StyleBackendType backendType = aDocument->GetStyleBackendType();
 
   StyleSetHandle styleSet;
   if (backendType == StyleBackendType::Gecko) {
     styleSet = new nsStyleSet();
   } else {
     styleSet = new ServoStyleSet();
   }
 
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -9360,8 +9360,16 @@ nsLayoutUtils::GetCumulativeApzCallbackT
       }
     }
     frame = GetCrossDocParentFrame(frame);
     lastContent = content;
     content = frame ? frame->GetContent() : nullptr;
   }
   return delta;
 }
+
+/* static */ bool
+nsLayoutUtils::SupportsServoStyleBackend(nsIDocument* aDocument)
+{
+  return nsPresContext::StyloEnabled() &&
+         aDocument->IsHTMLOrXHTML() &&
+         static_cast<nsDocument*>(aDocument)->IsContentDocument();
+}
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -2849,16 +2849,24 @@ public:
    * transforms on the content elements encountered along the way. Return the
    * accumulated value.
    * XXX: Note that this does not take into account CSS transforms, nor
    * differences in structure between the frame tree and the layer tree (which
    * is probably what we *want* to be computing).
    */
   static CSSPoint GetCumulativeApzCallbackTransform(nsIFrame* aFrame);
 
+  /*
+   * Returns whether the given document supports being rendered with a
+   * Servo-backed style system.  This checks whether Stylo is enabled
+   * globally, that the document is an HTML document, and that it is
+   * being presented in a content docshell.
+   */
+  static bool SupportsServoStyleBackend(nsIDocument* aDocument);
+
 private:
   static uint32_t sFontSizeInflationEmPerLine;
   static uint32_t sFontSizeInflationMinTwips;
   static uint32_t sFontSizeInflationLineThreshold;
   static int32_t  sFontSizeInflationMappingIntercept;
   static uint32_t sFontSizeInflationMaxRatio;
   static bool sFontSizeInflationForceEnabled;
   static bool sFontSizeInflationDisabledInMasterProcess;
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -3677,33 +3677,28 @@ FlushLayoutRecursive(nsIDocument* aDocum
   return true;
 }
 
 void
 PresShell::DispatchSynthMouseMove(WidgetGUIEvent* aEvent,
                                   bool aFlushOnHoverChange)
 {
   RestyleManagerHandle restyleManager = mPresContext->RestyleManager();
-  if (restyleManager->IsServo()) {
-    NS_ERROR("stylo: cannot dispatch synthetic mouse moves when using a "
-             "ServoRestyleManager yet");
-    return;
-  }
   uint32_t hoverGenerationBefore =
-    restyleManager->AsGecko()->GetHoverGeneration();
+    restyleManager->GetHoverGeneration();
   nsEventStatus status;
   nsView* targetView = nsView::GetViewFor(aEvent->mWidget);
   if (!targetView)
     return;
   targetView->GetViewManager()->DispatchEvent(aEvent, targetView, &status);
   if (MOZ_UNLIKELY(mIsDestroying)) {
     return;
   }
   if (aFlushOnHoverChange &&
-      hoverGenerationBefore != restyleManager->AsGecko()->GetHoverGeneration()) {
+      hoverGenerationBefore != restyleManager->GetHoverGeneration()) {
     // Flush so that the resulting reflow happens now so that our caller
     // can suppress any synthesized mouse moves caused by that reflow.
     // This code only ever runs for the root document, but :hover changes
     // can happen in descendant documents too, so make sure we flush
     // all of them.
     FlushLayoutRecursive(mDocument);
   }
 }
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -1741,20 +1741,17 @@ nsRefreshDriver::Tick(int64_t aNowEpoch,
             profiler_tracing("Paint", "Styles", mStyleCause, TRACING_INTERVAL_START);
             mStyleCause = nullptr;
           }
 
           NS_ADDREF(shell);
           mStyleFlushObservers.RemoveElement(shell);
           RestyleManagerHandle restyleManager =
             shell->GetPresContext()->RestyleManager();
-          // XXX stylo: ServoRestyleManager does not observer the refresh driver yet.
-          if (restyleManager->IsGecko()) {
-            restyleManager->AsGecko()->mObservingRefreshDriver = false;
-          }
+          restyleManager->SetObservingRefreshDriver(false);
           shell->FlushPendingNotifications(ChangesToFlush(Flush_Style, false));
           // Inform the FontFaceSet that we ticked, so that it can resolve its
           // ready promise if it needs to (though it might still be waiting on
           // a layout flush).
           nsPresContext* presContext = shell->GetPresContext();
           if (presContext) {
             presContext->NotifyFontFaceSetOnRefresh();
           }
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -2794,22 +2794,30 @@ nsIFrame::BuildDisplayListForChild(nsDis
                "Stacking contexts must also be pseudo-stacking-contexts");
 
   nsDisplayListBuilder::AutoBuildingDisplayList
     buildingForChild(aBuilder, child, dirty, pseudoStackingContext);
   DisplayListClipState::AutoClipMultiple clipState(aBuilder);
   CheckForApzAwareEventHandlers(aBuilder, child);
 
   if (savedOutOfFlowData) {
+    aBuilder->SetBuildingInvisibleItems(false);
+
     clipState.SetClipForContainingBlockDescendants(
       &savedOutOfFlowData->mContainingBlockClip);
     clipState.SetScrollClipForContainingBlockDescendants(
       savedOutOfFlowData->mContainingBlockScrollClip);
   } else if (GetStateBits() & NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO &&
              isPlaceholder) {
+    NS_ASSERTION(dirty.IsEmpty(), "should have empty dirty rect");
+    // Every item we build from now until we descent into an out of flow that
+    // does have saved out of flow data should be invisible. This state gets
+    // restored when AutoBuildingDisplayList gets out of scope.
+    aBuilder->SetBuildingInvisibleItems(true);
+
     // If we have nested out-of-flow frames and the outer one isn't visible
     // then we won't have stored clip data for it. We can just clear the clip
     // instead since we know we won't render anything, and the inner out-of-flow
     // frame will setup the correct clip for itself.
     clipState.SetClipForContainingBlockDescendants(nullptr);
     clipState.SetScrollClipForContainingBlockDescendants(nullptr);
   }
 
@@ -2893,17 +2901,19 @@ nsIFrame::BuildDisplayListForChild(nsDis
     list.AppendToTop(pseudoStack.Floats());
     list.AppendToTop(pseudoStack.Content());
     list.AppendToTop(pseudoStack.Outlines());
     extraPositionedDescendants.AppendToTop(pseudoStack.PositionedDescendants());
 #ifdef DEBUG
     DisplayDebugBorders(aBuilder, child, aLists);
 #endif
   }
-    
+
+  buildingForChild.RestoreBuildingInvisibleItemsValue();
+
   // Clear clip rect for the construction of the items below. Since we're
   // clipping all their contents, they themselves don't need to be clipped.
   clipState.Clear();
 
   if (isPositioned || isVisuallyAtomic ||
       (aFlags & DISPLAY_CHILD_FORCE_STACKING_CONTEXT)) {
     // Genuine stacking contexts, and positioned pseudo-stacking-contexts,
     // go in this level.
--- a/layout/generic/nsFrameSetFrame.cpp
+++ b/layout/generic/nsFrameSetFrame.cpp
@@ -274,29 +274,25 @@ nsHTMLFramesetFrame::Init(nsIContent*   
 
   for (uint32_t childX = 0; childX < numChildren; childX++) {
     if (mChildCount == numCells) { // we have more <frame> or <frameset> than cells
       // Clear the lazy bits in the remaining children.  Also clear
       // the restyle flags, like nsCSSFrameConstructor::ProcessChildren does.
       for (uint32_t i = childX; i < numChildren; i++) {
         nsIContent *child = mContent->GetChildAt(i);
         child->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME);
-        if (child->IsElement()) {
-          child->UnsetFlags(ELEMENT_ALL_RESTYLE_FLAGS);
-        }
+        child->UnsetRestyleFlagsIfGecko();
       }
       break;
     }
     nsIContent *child = mContent->GetChildAt(childX);
     child->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME);
     // Also clear the restyle flags in the child like
     // nsCSSFrameConstructor::ProcessChildren does.
-    if (child->IsElement()) {
-      child->UnsetFlags(ELEMENT_ALL_RESTYLE_FLAGS);
-    }
+    child->UnsetRestyleFlagsIfGecko();
 
     // IMPORTANT: This must match the conditions in
     // nsCSSFrameConstructor::ContentAppended/Inserted/Removed
     if (!child->IsHTMLElement())
       continue;
 
     if (child->IsAnyOfHTMLElements(nsGkAtoms::frameset, nsGkAtoms::frame)) {
       RefPtr<nsStyleContext> kidSC;
@@ -637,22 +633,22 @@ nsresult nsHTMLFramesetFrame::HandleEven
                                            nsEventStatus* aEventStatus)
 {
   NS_ENSURE_ARG_POINTER(aEventStatus);
   if (mDragger) {
     // the nsFramesetBorderFrame has captured NS_MOUSE_DOWN
     switch (aEvent->mMessage) {
       case eMouseMove:
         MouseDrag(aPresContext, aEvent);
-	      break;
+        break;
       case eMouseUp:
         if (aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) {
           EndMouseDrag(aPresContext);
         }
-	      break;
+        break;
       default:
         break;
     }
     *aEventStatus = nsEventStatus_eConsumeNoDefault;
   } else {
     *aEventStatus = nsEventStatus_eIgnore;
   }
   return NS_OK;
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -537,22 +537,34 @@ GlyphObserver::NotifyGlyphsChanged()
   nsIPresShell* shell = mFrame->PresContext()->PresShell();
   for (nsIFrame* f = mFrame; f;
        f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
     if (f != mFrame && f->HasAnyStateBits(TEXT_IN_TEXTRUN_USER_DATA)) {
       // f will have its own GlyphObserver (if needed) so we can stop here.
       break;
     }
     f->InvalidateFrame();
-    // Theoretically we could just update overflow areas, perhaps using
-    // OverflowChangedTracker, but that would do a bunch of work eagerly that
-    // we should probably do lazily here since there could be a lot
-    // of text frames affected and we'd like to coalesce the work. So that's
-    // not easy to do well.
-    shell->FrameNeedsReflow(f, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
+
+    // If this is a non-display text frame within SVG <text>, we need
+    // to reflow the SVGTextFrame. (This is similar to reflowing the
+    // SVGTextFrame in response to style changes, in
+    // SVGTextFrame::DidSetStyleContext.)
+    if (f->IsSVGText() && f->GetStateBits() & NS_FRAME_IS_NONDISPLAY) {
+      auto svgTextFrame = static_cast<SVGTextFrame*>(
+                            nsLayoutUtils::GetClosestFrameOfType(f,
+                            nsGkAtoms::svgTextFrame));
+      svgTextFrame->ScheduleReflowSVGNonDisplayText();
+    } else {
+      // Theoretically we could just update overflow areas, perhaps using
+      // OverflowChangedTracker, but that would do a bunch of work eagerly that
+      // we should probably do lazily here since there could be a lot
+      // of text frames affected and we'd like to coalesce the work. So that's
+      // not easy to do well.
+      shell->FrameNeedsReflow(f, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
+    }
   }
 }
 
 int32_t nsTextFrame::GetContentEnd() const {
   nsTextFrame* next = static_cast<nsTextFrame*>(GetNextContinuation());
   return next ? next->GetContentOffset() : mContent->GetText()->GetLength();
 }
 
--- a/layout/generic/nsVideoFrame.cpp
+++ b/layout/generic/nsVideoFrame.cpp
@@ -630,25 +630,29 @@ nsVideoFrame::GetVideoIntrinsicSize(nsRe
 }
 
 void
 nsVideoFrame::UpdatePosterSource(bool aNotify)
 {
   NS_ASSERTION(HasVideoElement(), "Only call this on <video> elements.");
   HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent());
 
-  if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::poster)) {
+  if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::poster) &&
+      !element->AttrValueIs(kNameSpaceID_None,
+                            nsGkAtoms::poster,
+                            nsGkAtoms::_empty,
+                            eIgnoreCase)) {
     nsAutoString posterStr;
     element->GetPoster(posterStr);
     mPosterImage->SetAttr(kNameSpaceID_None,
                           nsGkAtoms::src,
                           posterStr,
                           aNotify);
   } else {
-    mPosterImage->UnsetAttr(kNameSpaceID_None, nsGkAtoms::poster, aNotify);
+    mPosterImage->UnsetAttr(kNameSpaceID_None, nsGkAtoms::src, aNotify);
   }
 }
 
 nsresult
 nsVideoFrame::AttributeChanged(int32_t aNameSpaceID,
                                nsIAtom* aAttribute,
                                int32_t aModType)
 {
--- a/layout/reftests/text-svgglyphs/reftest.list
+++ b/layout/reftests/text-svgglyphs/reftest.list
@@ -12,12 +12,12 @@ pref(gfx.font_rendering.opentype_svg.ena
 pref(gfx.font_rendering.opentype_svg.enabled,true)    fuzzy-if(cocoaWidget,1,7028) fuzzy-if(gtkWidget&&/^Linux\x20x86_64/.test(http.oscpu),1,23) fuzzy-if(skiaContent,1,250) == svg-glyph-objectgradient-zoom.svg svg-glyph-objectgradient-zoom-ref.svg
 pref(gfx.font_rendering.opentype_svg.enabled,true)    fuzzy-if(gtkWidget,1,1438) fuzzy-if(winWidget,1,1954) fuzzy-if(Android||B2G,8,3795) == svg-glyph-objectpattern.svg svg-glyph-objectpattern-ref.svg
 pref(gfx.font_rendering.opentype_svg.enabled,true)    == clip.html clip-ref.html
 pref(gfx.font_rendering.opentype_svg.enabled,true)    fuzzy(1,13) fuzzy-if(gtkWidget&&/^Linux\x20x86_64/.test(http.oscpu),1,62) fuzzy-if(B2G,1,25) fuzzy-if(skiaContent,1,350) == svg-glyph-objectopacity.svg svg-glyph-objectopacity-ref.svg # see bug 871961#c5
 pref(gfx.font_rendering.opentype_svg.enabled,true)    fuzzy-if(gtkWidget,1,2268) fuzzy-if(winWidget,1,3074) fuzzy-if(Android||B2G,5,4715) == svg-glyph-objectopacity2.svg svg-glyph-objectopacity2-ref.svg
 pref(gfx.font_rendering.opentype_svg.enabled,true)    fuzzy-if(skiaContent,2,200) == svg-glyph-paintnone.svg svg-glyph-paintnone-ref.svg
 pref(gfx.font_rendering.opentype_svg.enabled,true)    fuzzy-if(skiaContent,2,200) == svg-glyph-cachedopacity.svg svg-glyph-cachedopacity-ref.svg
 pref(gfx.font_rendering.opentype_svg.enabled,true)    fuzzy-if(cocoaWidget,255,100) == svg-glyph-objectvalue.svg svg-glyph-objectvalue-ref.svg
-#pref(gfx.font_rendering.opentype_svg.enabled,true)    fails == svg-glyph-mask.svg svg-glyph-mask-ref.svg # bug 872483, 1135329
+pref(gfx.font_rendering.opentype_svg.enabled,true)    fails == svg-glyph-mask.svg svg-glyph-mask-ref.svg # bug 872483
 pref(gfx.font_rendering.opentype_svg.enabled,true)    == svg-glyph-paint-server.svg svg-glyph-paint-server-ref.svg
 pref(gfx.font_rendering.opentype_svg.enabled,true)    == svg-glyph-transform.svg svg-glyph-transform-ref.svg
 pref(gfx.font_rendering.opentype_svg.enabled,true)    == svg-glyph-extents.html svg-glyph-extents-ref.html
--- a/layout/style/Loader.h
+++ b/layout/style/Loader.h
@@ -196,29 +196,16 @@ public:
  private:
   // Private destructor, to discourage deletion outside of Release():
   ~Loader();
 
  public:
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(Loader)
   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(Loader)
 
-  /**
-   * Returns the StyleBackendType that will be used for style sheets created
-   * by this Loader.  If SetStyleBackendType has been called, that value
-   * will be returned by GetStyleBackendType.  For Loaders created with the
-   * nsIDocument* constructor and which haven't had SetStyleBackendType
-   * called on them, the document's StyleBackendType will be returned.
-   */
-  StyleBackendType GetStyleBackendType() const;
-
-  void SetStyleBackendType(StyleBackendType aType) {
-    mStyleBackendType = Some(aType);
-  }
-
   void DropDocumentReference(); // notification that doc is going away
 
   void SetCompatibilityMode(nsCompatibility aCompatMode)
   { mCompatMode = aCompatMode; }
   nsCompatibility GetCompatibilityMode() { return mCompatMode; }
   nsresult SetPreferredSheet(const nsAString& aTitle);
 
   // XXXbz sort out what the deal is with events!  When should they fire?
@@ -547,16 +534,18 @@ private:
   void SheetComplete(SheetLoadData* aLoadData, nsresult aStatus);
 
   // The guts of SheetComplete.  This may be called recursively on parent datas
   // or datas that had glommed on to a single load.  The array is there so load
   // datas whose observers need to be notified can be added to it.
   void DoSheetComplete(SheetLoadData* aLoadData, nsresult aStatus,
                        LoadDataArray& aDatasToNotify);
 
+  StyleBackendType GetStyleBackendType() const;
+
   struct Sheets {
     nsBaseHashtable<URIPrincipalReferrerPolicyAndCORSModeHashKey,
                     StyleSheetHandle::RefPtr,
                     StyleSheetHandle> mCompleteSheets;
     nsDataHashtable<URIPrincipalReferrerPolicyAndCORSModeHashKey, SheetLoadData*>
                       mLoadingDatas; // weak refs
     nsDataHashtable<URIPrincipalReferrerPolicyAndCORSModeHashKey, SheetLoadData*>
                       mPendingDatas; // weak refs
@@ -583,16 +572,18 @@ private:
   // whole bunch at once (e.g. in one of the stop methods).  This is used to
   // make sure that HasPendingLoads() won't return false until we're notifying
   // on the last data we're working with.
   uint32_t          mDatasToNotifyOn;
 
   nsCompatibility   mCompatMode;
   nsString          mPreferredSheet;  // title of preferred sheet
 
+  // Set explicitly when the Loader(StyleBackendType) constructor is used, or
+  // taken from the document when the Loader(nsIDocument*) constructor is used.
   mozilla::Maybe<StyleBackendType> mStyleBackendType;
 
   bool              mEnabled; // is enabled to load new styles
 
 #ifdef DEBUG
   bool              mSyncCallback;
 #endif
 };
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -159,16 +159,35 @@ Gecko_Namespace(RawGeckoElement* aElemen
 
 nsIAtom*
 Gecko_GetElementId(RawGeckoElement* aElement)
 {
   const nsAttrValue* attr = aElement->GetParsedAttr(nsGkAtoms::id);
   return attr ? attr->GetAtomValue() : nullptr;
 }
 
+// Dirtiness tracking.
+uint32_t
+Gecko_GetNodeFlags(RawGeckoNode* aNode)
+{
+  return aNode->GetFlags();
+}
+
+void
+Gecko_SetNodeFlags(RawGeckoNode* aNode, uint32_t aFlags)
+{
+  aNode->SetFlags(aFlags);
+}
+
+void
+Gecko_UnsetNodeFlags(RawGeckoNode* aNode, uint32_t aFlags)
+{
+  aNode->UnsetFlags(aFlags);
+}
+
 template<class MatchFn>
 bool
 DoMatch(RawGeckoElement* aElement, nsIAtom* aNS, nsIAtom* aName, MatchFn aMatch)
 {
   if (aNS) {
     int32_t ns = nsContentUtils::NameSpaceManager()->GetNameSpaceID(aNS);
     NS_ENSURE_TRUE(ns != kNameSpaceID_Unknown, false);
     const nsAttrValue* value = aElement->GetParsedAttr(aName, ns);
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -171,16 +171,21 @@ NS_DECL_HOLDER_FFI_REFCOUNTING(nsIURI, U
 // Display style.
 void Gecko_SetMozBinding(nsStyleDisplay* style_struct,
                          const uint8_t* string_bytes, uint32_t string_length,
                          ThreadSafeURIHolder* base_uri,
                          ThreadSafeURIHolder* referrer,
                          ThreadSafePrincipalHolder* principal);
 void Gecko_CopyMozBindingFrom(nsStyleDisplay* des, const nsStyleDisplay* src);
 
+// Dirtiness tracking.
+uint32_t Gecko_GetNodeFlags(RawGeckoNode* node);
+void Gecko_SetNodeFlags(RawGeckoNode* node, uint32_t flags);
+void Gecko_UnsetNodeFlags(RawGeckoNode* node, uint32_t flags);
+
 // Styleset and Stylesheet management.
 //
 // TODO: Make these return already_AddRefed and UniquePtr when the binding
 // generator is smart enough to handle them.
 RawServoStyleSheet* Servo_StylesheetFromUTF8Bytes(
     const uint8_t* bytes, uint32_t length,
     mozilla::css::SheetParsingMode parsing_mode,
     ThreadSafeURIHolder* base,
--- a/layout/style/StyleBackendType.h
+++ b/layout/style/StyleBackendType.h
@@ -9,15 +9,15 @@
 
 namespace mozilla {
 
 /**
  * Enumeration that represents one of the two supported style system backends.
  */
 enum class StyleBackendType : int
 {
-  Gecko,
+  Gecko = 1,
   Servo
 };
 
 } // namespace mozilla
 
 #endif // mozilla_StyleBackendType_h
--- a/layout/svg/SVGTextFrame.h
+++ b/layout/svg/SVGTextFrame.h
@@ -394,18 +394,18 @@ public:
    * but which will skip over any ancestor non-display container frames on the
    * way to the nsSVGOuterSVGFrame.  It exists for the situation where a
    * non-display <text> element has changed and needs to ensure ReflowSVG will
    * be called on its closest display container frame, so that
    * nsSVGDisplayContainerFrame::ReflowSVG will call ReflowSVGNonDisplayText on
    * it.
    *
    * The only case where we have to do this is in response to a style change on
-   * a non-display <text>; the only caller of ScheduleReflowSVGNonDisplayText
-   * currently is SVGTextFrame::DidSetStyleContext.
+   * a non-display <text>. It is done in response to glyphs changes on
+   * non-display <text> (i.e., animated SVG-in-OpenType glyphs).
    */
   void ScheduleReflowSVGNonDisplayText();
 
   /**
    * Updates the mFontSizeScaleFactor value by looking at the range of
    * font-sizes used within the <text>.
    *
    * @return Whether mFontSizeScaleFactor changed.
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -911,13 +911,14 @@ pref("identity.fxaccounts.remote.profile
 pref("identity.fxaccounts.remote.oauth.uri", "https://oauth.accounts.firefox.com/v1");
 
 // Token server used by Firefox Account-authenticated Sync.
 pref("identity.sync.tokenserver.uri", "https://token.services.mozilla.com/1.0/sync/1.5");
 
 // Enable Presentation API
 pref("dom.presentation.enabled", true);
 pref("dom.presentation.discovery.enabled", true);
+pref("dom.presentation.discovery.legacy.enabled", true); // for TV 2.5 backward capability
 
 pref("dom.audiochannel.audioCompeting", true);
 pref("dom.audiochannel.mediaControl", true);
 
 pref("media.openUnsupportedTypeWithExternalApp", true);
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
@@ -273,16 +273,21 @@ public class ToolbarDisplayLayout extend
         String strippedURL = stripAboutReaderURL(url);
 
         final boolean isHttpOrHttps = StringUtils.isHttpOrHttps(strippedURL);
 
         if (mPrefs.shouldTrimUrls()) {
             strippedURL = StringUtils.stripCommonSubdomains(StringUtils.stripScheme(strippedURL));
         }
 
+        // The URL bar does not support RTL currently (See bug 928688 and meta bug 702845).
+        // Displaying a URL using RTL (or mixed) characters can lead to an undesired reordering
+        // of elements of the URL. That's why we are forcing the URL to use LTR (bug 1284372).
+        strippedURL = StringUtils.forceLTR(strippedURL);
+
         // This value is not visible to screen readers but we rely on it when running UI tests. Screen
         // readers will instead focus BrowserToolbar and read the "base domain" from there. UI tests
         // will read the content description to obtain the full URL for performing assertions.
         setContentDescription(strippedURL);
 
         final SiteIdentity siteIdentity = tab.getSiteIdentity();
         if (siteIdentity.hasOwner() && SwitchBoard.isInExperiment(mActivity, Experiments.URLBAR_SHOW_EV_CERT_OWNER)) {
             // Show Owner of EV certificate as title
--- a/mobile/android/base/java/org/mozilla/gecko/util/StringUtils.java
+++ b/mobile/android/base/java/org/mozilla/gecko/util/StringUtils.java
@@ -270,9 +270,37 @@ public class StringUtils {
         return Collections.unmodifiableSet(names);
     }
 
     public static String safeSubstring(@NonNull final String str, final int start, final int end) {
         return str.substring(
                 Math.max(0, start),
                 Math.min(end, str.length()));
     }
+
+    /**
+     * Check if this might be a RTL (right-to-left) text by looking at the first character.
+     */
+    public static boolean isRTL(String text) {
+        if (TextUtils.isEmpty(text)) {
+            return false;
+        }
+
+        final char character = text.charAt(0);
+        final byte directionality = Character.getDirectionality(character);
+
+        return directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT
+                || directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC
+                || directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING
+                || directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE;
+    }
+
+    /**
+     * Force LTR (left-to-right) by prepending the text with the "left-to-right mark" (U+200E) if needed.
+     */
+    public static String forceLTR(String text) {
+        if (!isRTL(text)) {
+            return text;
+        }
+
+        return "\u200E" + text;
+    }
 }
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -410,16 +410,18 @@
 @BINPATH@/components/PresentationDeviceInfoManager.manifest
 @BINPATH@/components/PresentationDeviceInfoManager.js
 @BINPATH@/components/BuiltinProviders.manifest
 @BINPATH@/components/PresentationControlService.js
 @BINPATH@/components/PresentationNetworkHelper.js
 @BINPATH@/components/PresentationNetworkHelper.manifest
 @BINPATH@/components/PresentationDataChannelSessionTransport.js
 @BINPATH@/components/PresentationDataChannelSessionTransport.manifest
+@BINPATH@/components/LegacyProviders.manifest
+@BINPATH@/components/LegacyPresentationControlService.js
 
 @BINPATH@/components/PACGenerator.js
 @BINPATH@/components/PACGenerator.manifest
 
 @BINPATH@/components/TVSimulatorService.js
 @BINPATH@/components/TVSimulatorService.manifest
 
 ; Modules
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/util/TestStringUtils.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/util/TestStringUtils.java
@@ -7,36 +7,38 @@ package org.mozilla.gecko.util;
 
 import junit.framework.Assert;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 @RunWith(TestRunner.class)
 public class TestStringUtils {
     @Test
     public void testIsHttpOrHttps() {
         // No value
         Assert.assertFalse(StringUtils.isHttpOrHttps(null));
         Assert.assertFalse(StringUtils.isHttpOrHttps(""));
 
         // Garbage
         Assert.assertFalse(StringUtils.isHttpOrHttps("lksdjflasuf"));
 
         // URLs with http/https
-        Assert.assertTrue(StringUtils.isHttpOrHttps("https://www.google.com"));
-        Assert.assertTrue(StringUtils.isHttpOrHttps("http://www.facebook.com"));
-        Assert.assertTrue(StringUtils.isHttpOrHttps("https://mozilla.org/en-US/firefox/products/"));
+        assertTrue(StringUtils.isHttpOrHttps("https://www.google.com"));
+        assertTrue(StringUtils.isHttpOrHttps("http://www.facebook.com"));
+        assertTrue(StringUtils.isHttpOrHttps("https://mozilla.org/en-US/firefox/products/"));
 
         // IP addresses
-        Assert.assertTrue(StringUtils.isHttpOrHttps("https://192.168.0.1"));
-        Assert.assertTrue(StringUtils.isHttpOrHttps("http://63.245.215.20/en-US/firefox/products"));
+        assertTrue(StringUtils.isHttpOrHttps("https://192.168.0.1"));
+        assertTrue(StringUtils.isHttpOrHttps("http://63.245.215.20/en-US/firefox/products"));
 
         // Other protocols
         Assert.assertFalse(StringUtils.isHttpOrHttps("ftp://people.mozilla.org"));
         Assert.assertFalse(StringUtils.isHttpOrHttps("javascript:window.google.com"));
         Assert.assertFalse(StringUtils.isHttpOrHttps("tel://1234567890"));
 
         // No scheme
         Assert.assertFalse(StringUtils.isHttpOrHttps("google.com"));
@@ -48,9 +50,40 @@ public class TestStringUtils {
         assertEquals(StringUtils.stripRef(null), null);
         assertEquals(StringUtils.stripRef(""), "");
 
         assertEquals(StringUtils.stripRef("??AAABBBCCC"), "??AAABBBCCC");
         assertEquals(StringUtils.stripRef("https://mozilla.org"), "https://mozilla.org");
         assertEquals(StringUtils.stripRef("https://mozilla.org#BBBB"), "https://mozilla.org");
         assertEquals(StringUtils.stripRef("https://mozilla.org/#BBBB"), "https://mozilla.org/");
     }
+
+    @Test
+    public void testIsRTL() {
+        assertFalse(StringUtils.isRTL("mozilla.org"));
+        assertFalse(StringUtils.isRTL("something.عربي"));
+
+        assertTrue(StringUtils.isRTL("عربي"));
+        assertTrue(StringUtils.isRTL("عربي.org"));
+
+        // Text with LTR mark
+        assertFalse(StringUtils.isRTL("\u200EHello"));
+        assertFalse(StringUtils.isRTL("\u200Eعربي"));
+    }
+
+    @Test
+    public void testForceLTR() {
+        assertFalse(StringUtils.isRTL(StringUtils.forceLTR("عربي")));
+        assertFalse(StringUtils.isRTL(StringUtils.forceLTR("عربي.org")));
+
+        // Strings that are already LTR are not modified
+        final String someLtrString = "HelloWorld";
+        assertEquals(someLtrString, StringUtils.forceLTR(someLtrString));
+
+        // We add the LTR mark only once
+        final String someRtlString = "عربي";
+        assertEquals(4, someRtlString.length());
+        final String forcedLtrString = StringUtils.forceLTR(someRtlString);
+        assertEquals(5, forcedLtrString.length());
+        final String forcedAgainLtrString = StringUtils.forceLTR(forcedLtrString);
+        assertEquals(5, forcedAgainLtrString.length());
+    }
 }
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1551,17 +1551,17 @@ pref("network.http.enable-packaged-apps"
 // Enable this to bring in the signature verification if the signature exists.
 // Set to false if you don't need the signed packaged web app support (i.e. NSec).
 pref("network.http.signed-packages.enabled", false);
 
 // If it is set to false, headers with empty value will not appear in the header
 // array - behavior as it used to be. If it is true: empty headers coming from
 // the network will exist in header array as empty string. Call SetHeader with
 // an empty value will still delete the header.(Bug 6699259)
-pref("network.http.keep_empty_response_headers_as_empty_string", false);
+pref("network.http.keep_empty_response_headers_as_empty_string", true);
 
 // default values for FTP
 // in a DSCP environment this should be 40 (0x28, or AF11), per RFC-4594,
 // Section 4.8 "High-Throughput Data Service Class", and 80 (0x50, or AF22)
 // per Section 4.7 "Low-Latency Data Service Class".
 pref("network.ftp.data.qos", 0);
 pref("network.ftp.control.qos", 0);
 
@@ -5238,16 +5238,17 @@ pref("dom.udpsocket.enabled", false);
 
 // Disable before keyboard events and after keyboard events by default.
 pref("dom.beforeAfterKeyboardEvent.enabled", false);
 
 // Presentation API
 pref("dom.presentation.enabled", false);
 pref("dom.presentation.tcp_server.debug", false);
 pref("dom.presentation.discovery.enabled", false);
+pref("dom.presentation.discovery.legacy.enabled", false);
 pref("dom.presentation.discovery.timeout_ms", 10000);
 pref("dom.presentation.discoverable", false);
 pref("dom.presentation.session_transport.data_channel.enable", false);
 
 #ifdef XP_MACOSX
 #if !defined(RELEASE_BUILD) || defined(DEBUG)
 // In non-release builds we crash by default on insecure text input (when a
 // password editor has focus but secure event input isn't enabled).  The
--- a/security/sandbox/linux/SandboxFilter.cpp
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -182,16 +182,17 @@ public:
 
       // Metadata of opened files
     CASES_FOR_fstat:
       return Allow();
 
       // Simple I/O
     case __NR_write:
     case __NR_read:
+    case __NR_readv:
     case __NR_writev: // see SandboxLogging.cpp
     CASES_FOR_lseek:
       return Allow();
 
       // Memory mapping
     CASES_FOR_mmap:
     case __NR_munmap:
       return Allow();
@@ -433,16 +434,17 @@ public:
     case SYS_SOCKET:
       return Some(Error(EACCES));
 #else // #ifdef DESKTOP
     case SYS_RECV:
     case SYS_SEND:
     case SYS_SOCKET: // DANGEROUS
     case SYS_CONNECT: // DANGEROUS
     case SYS_ACCEPT:
+    case SYS_ACCEPT4:
     case SYS_BIND:
     case SYS_LISTEN:
     case SYS_GETSOCKOPT:
     case SYS_SETSOCKOPT:
     case SYS_GETSOCKNAME:
     case SYS_GETPEERNAME:
     case SYS_SHUTDOWN:
       return Some(Allow());
@@ -512,16 +514,17 @@ public:
     case __NR_getcwd:
     CASES_FOR_statfs:
     CASES_FOR_fstatfs:
     case __NR_chmod:
     case __NR_rename:
     case __NR_symlink:
     case __NR_quotactl:
     case __NR_utimes:
+    case __NR_link:
     case __NR_unlink:
     case __NR_fchown:
     case __NR_fchmod:
 #endif
       return Allow();
 
     case __NR_readlink:
     case __NR_readlinkat:
@@ -639,16 +642,19 @@ public:
     }
 #endif
 
 #ifdef __NR_semget
     case __NR_semget:
       return Allow();
 #endif
 
+    case __NR_mlock:
+      return Allow();
+
 #endif // DESKTOP
 
 #ifdef __NR_getrandom
     case __NR_getrandom:
       return Allow();
 #endif
 
       // nsSystemInfo uses uname (and we cache an instance, so
deleted file mode 100644
--- a/testing/web-platform/meta/XMLHttpRequest/getresponseheader-cookies-and-more.htm.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[getresponseheader-cookies-and-more.htm]
-  type: testharness
-  [XMLHttpRequest: getResponseHeader() custom/non-existent headers and cookies]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/cors/response-headers.htm.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[response-headers.htm]
-  type: testharness
-  [getResponseHeader: Combined testing of cors response headers]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/custom-elements/v0/creating-and-passing-registries/share-registry-create-document.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[share-registry-create-document.html]
-  type: testharness
-  [Document created by createDocument with HTML namespace should share an existing registry]
-    expected: FAIL
-
--- a/testing/web-platform/meta/dom/nodes/DOMImplementation-createDocument.html.ini
+++ b/testing/web-platform/meta/dom/nodes/DOMImplementation-createDocument.html.ini
@@ -4,20 +4,14 @@
     expected: FAIL
 
   [createDocument test 172: metadata for null,null,DocumentType node]
     expected: FAIL
 
   [createDocument test 172: null,null,DocumentType node,null]
     expected: FAIL
 
-  [createDocument test 179: metadata for "http://www.w3.org/1999/xhtml","",null]
-    expected: FAIL
-
-  [createDocument test 180: metadata for "http://www.w3.org/2000/svg","",null]
-    expected: FAIL
-
   [createDocument test 185: null,"",DocumentType node]
     expected: FAIL
 
   [createDocument test 186: null,"",DocumentType node]
     expected: FAIL
 
deleted file mode 100644
--- a/testing/web-platform/meta/dom/nodes/Document-contentType/contentType/createDocument.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[createDocument.html]
-  type: testharness
-  [document.implementation.createDocument: document.contentType === 'application/xhtml+xml']
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/dom/nodes/Document-createElement-namespace.html.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-[Document-createElement-namespace.html]
-  type: testharness
-  [Created element's namespace in created XHTML document]
-    expected: FAIL
-
-  [Created element's namespace in created SVG document]
-    expected: FAIL
-
--- a/testing/web-platform/meta/html/infrastructure/urls/resolving-urls/query-encoding/utf-16be.html.ini
+++ b/testing/web-platform/meta/html/infrastructure/urls/resolving-urls/query-encoding/utf-16be.html.ini
@@ -7,19 +7,16 @@
     expected: TIMEOUT
 
   [hyperlink auditing <area ping>]
     expected: TIMEOUT
 
   [loading image <video poster>]
     disabled: true
 
-  [loading webvtt <track>]
-    expected: TIMEOUT
-
   [history.pushState]
     expected: FAIL
 
   [history.replaceState]
     expected: FAIL
 
   [SharedWorker() in a dedicated worker]
     expected: FAIL
--- a/testing/web-platform/meta/html/infrastructure/urls/resolving-urls/query-encoding/utf-16le.html.ini
+++ b/testing/web-platform/meta/html/infrastructure/urls/resolving-urls/query-encoding/utf-16le.html.ini
@@ -7,19 +7,16 @@
     expected: TIMEOUT
 
   [hyperlink auditing <area ping>]
     expected: TIMEOUT
 
   [loading image <video poster>]
     disabled: true
 
-  [loading webvtt <track>]
-    expected: TIMEOUT
-
   [history.pushState]
     expected: FAIL
 
   [history.replaceState]
     expected: FAIL
 
   [SharedWorker() in a dedicated worker]
     expected: FAIL
--- a/testing/web-platform/meta/html/infrastructure/urls/resolving-urls/query-encoding/utf-8.html.ini
+++ b/testing/web-platform/meta/html/infrastructure/urls/resolving-urls/query-encoding/utf-8.html.ini
@@ -7,19 +7,16 @@
     expected: TIMEOUT
 
   [hyperlink auditing <area ping>]
     expected: TIMEOUT
 
   [loading image <video poster>]
     disabled: true
 
-  [loading webvtt <track>]
-    expected: TIMEOUT
-
   [history.pushState]
     expected: FAIL
 
   [history.replaceState]
     expected: FAIL
 
   [SharedWorker() in a dedicated worker]
     expected: FAIL
--- a/testing/web-platform/meta/html/infrastructure/urls/resolving-urls/query-encoding/windows-1251.html.ini
+++ b/testing/web-platform/meta/html/infrastructure/urls/resolving-urls/query-encoding/windows-1251.html.ini
@@ -134,17 +134,17 @@
 
   [loading video <audio>]
     expected: FAIL
 
   [loading video <audio><source>]
     expected: FAIL
 
   [loading webvtt <track>]
-    expected: TIMEOUT
+    expected: FAIL
 
   [submit form <form action>]
     expected: FAIL
 
   [submit form <input formaction>]
     expected: FAIL
 
   [submit form <button formaction>]
--- a/testing/web-platform/meta/html/infrastructure/urls/resolving-urls/query-encoding/windows-1252.html.ini
+++ b/testing/web-platform/meta/html/infrastructure/urls/resolving-urls/query-encoding/windows-1252.html.ini
@@ -4,19 +4,16 @@
   disabled:
     if os == "mac": https://bugzilla.mozilla.org/show_bug.cgi?id=1034063
   [hyperlink auditing <a ping>]
     expected: TIMEOUT
 
   [hyperlink auditing <area ping>]
     expected: TIMEOUT
 
-  [loading webvtt <track>]
-    expected: TIMEOUT
-
   [EventSource constructor]
     expected: FAIL
 
   [EventSource#url]
     expected: FAIL
 
   [window.open()]
     expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/interfaces/TextTrack/cues.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[cues.html]
-  type: testharness
-  [TextTrack.cues, default attribute]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/getter.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[getter.html]
-  type: testharness
-  [TextTrackList getter]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/length.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[length.html]
-  type: testharness
-  [TextTrackList.length]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/cloneNode.html.ini
+++ /dev/null
@@ -1,12 +0,0 @@
-[cloneNode.html]
-  type: testharness
-  expected: TIMEOUT
-  [track element cloneNode, loading]
-    expected: FAIL
-
-  [track element cloneNode, loaded]
-    expected: TIMEOUT
-
-  [track element cloneNode, failed to load]
-    expected: TIMEOUT
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-template-element/additions-to-serializing-xhtml-documents/outerhtml.html.ini
+++ /dev/null
@@ -1,11 +0,0 @@
-[outerhtml.html]
-  type: testharness
-  [Template contents should be serialized instead of template element if serializing template element]
-    expected: FAIL
-
-  [Template contents should be serialized instead of template element if serializing template element. Test nested template]
-    expected: FAIL
-
-  [Template contents should be serialized instead of template element if serializing template element. Test serializing whole document]
-    expected: FAIL
-
--- a/testing/web-platform/tests/content-security-policy/blink-contrib/connect-src-beacon-blocked.sub.html
+++ b/testing/web-platform/tests/content-security-policy/blink-contrib/connect-src-beacon-blocked.sub.html
@@ -17,19 +17,19 @@ connect-src 'self' http://{{host}}:{{por
     <script>
        if (typeof navigator.sendBeacon != 'function') {
             t_log.set_status(t_log.NOTRUN, "No navigator.sendBeacon, cannot run test.");
             t_log.phase = t_log.phases.HAS_RESULT;
             t_log.done();
         } else {
 	    try {
 	    var es = navigator.sendBeacon("http://www1.{{host}}:{{ports[http][0]}}/security/contentSecurityPolicy/echo-report.php");
-         	log("Fail");
+         	log("Pass");
             } catch (e) {
-            	log("Pass");
+            	log("Fail");
             }
             var report =  document.createElement("script");
             report.src = "../support/checkReport.sub.js?reportExists=true&amp;reportField=violated-directive&amp;reportValue=connect-src%20&apos;self&apos;";
             report.async = true;
             report.defer = true;
             document.body.appendChild(report);
 	}
 
rename from browser/components/contextualidentity/ContextualIdentityService.jsm
rename to toolkit/components/contextualidentity/ContextualIdentityService.jsm
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contextualidentity/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; c-basic-offset: 4; 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/.
+
+EXTRA_JS_MODULES += [
+    'ContextualIdentityService.jsm',
+]
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -14,16 +14,17 @@ DIRS += [
     'aboutmemory',
     'aboutperformance',
     'addoncompat',
     'alerts',
     'apppicker',
     'asyncshutdown',
     'commandlines',
     'contentprefs',
+    'contextualidentity',
     'cookie',
     'crashmonitor',
     'diskspacewatcher',
     'downloads',
     'extensions',
     'exthelper',
     'filepicker',
     'filewatcher',
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -613,24 +613,16 @@
   },
   "JS_DEPRECATED_LANGUAGE_EXTENSIONS_IN_ADDONS": {
     "alert_emails": ["jdemooij@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 10,
     "description": "Use of SpiderMonkey's deprecated language extensions in add-ons: ForEach=0, DestructuringForIn=1 (obsolete), LegacyGenerator=2, ExpressionClosure=3, LetBlock=4 (obsolete), LetExpression=5 (obsolete), NoSuchMethod=6 (obsolete), FlagsArgument=7 (obsolete), RegExpSourceProp=8 (obsolete), RestoredRegExpStatics=9 (obsolete), BlockScopeFunRedecl=10"
   },
-  "JS_DEFINE_GETTER_SETTER_THIS_NULL_UNDEFINED": {
-    "alert_emails": ["jdemooij@mozilla.com"],
-    "bug_numbers": [1249123],
-    "expires_in_version": "55",
-    "kind": "boolean",
-    "releaseChannelCollection": "opt-out",
-    "description": "__defineGetter__ or __defineSetter__ invoked with 1 = null/undefined or 0 = everything else as |this| value"
-  },
   "XUL_CACHE_DISABLED": {
     "expires_in_version": "default",
     "kind": "flag",
     "description": "XUL cache was disabled"
   },
   "MEMORY_RESIDENT_FAST": {
     "alert_emails": ["memshrink-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
--- a/toolkit/components/thumbnails/BackgroundPageThumbs.jsm
+++ b/toolkit/components/thumbnails/BackgroundPageThumbs.jsm
@@ -31,17 +31,17 @@ const TEL_CAPTURE_DONE_BAD_URI = 5;
 
 // These are looked up on the global as properties below.
 XPCOMUtils.defineConstant(this, "TEL_CAPTURE_DONE_OK", TEL_CAPTURE_DONE_OK);
 XPCOMUtils.defineConstant(this, "TEL_CAPTURE_DONE_TIMEOUT", TEL_CAPTURE_DONE_TIMEOUT);
 XPCOMUtils.defineConstant(this, "TEL_CAPTURE_DONE_CRASHED", TEL_CAPTURE_DONE_CRASHED);
 XPCOMUtils.defineConstant(this, "TEL_CAPTURE_DONE_BAD_URI", TEL_CAPTURE_DONE_BAD_URI);
 
 XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
-                                  "resource:///modules/ContextualIdentityService.jsm");
+                                  "resource://gre/modules/ContextualIdentityService.jsm");
 const global = this;
 
 const BackgroundPageThumbs = {
 
   /**
    * Asynchronously captures a thumbnail of the given URL.
    *
    * The page is loaded anonymously, and plug-ins are disabled.
--- a/toolkit/forgetaboutsite/ForgetAboutSite.jsm
+++ b/toolkit/forgetaboutsite/ForgetAboutSite.jsm
@@ -8,17 +8,17 @@ Components.utils.import("resource://gre/
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/NetUtil.jsm");
 Components.utils.import("resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                   "resource://gre/modules/Downloads.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
-                                  "resource:///modules/ContextualIdentityService.jsm");
+                                  "resource://gre/modules/ContextualIdentityService.jsm");
 
 this.EXPORTED_SYMBOLS = ["ForgetAboutSite"];
 
 /**
  * Returns true if the string passed in is part of the root domain of the
  * current string.  For example, if this is "www.mozilla.org", and we pass in
  * "mozilla.org", this will return true.  It would return false the other way
  * around.
--- a/toolkit/forgetaboutsite/test/unit/xpcshell.ini
+++ b/toolkit/forgetaboutsite/test/unit/xpcshell.ini
@@ -1,9 +1,8 @@
 [DEFAULT]
 head = head_forgetaboutsite.js ../../../../dom/push/test/xpcshell/head.js
 tail =
-firefox-appdir = browser
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 support-files =
   !/dom/push/test/xpcshell/head.js
 
 [test_removeDataFromDomain.js]
--- a/widget/gtk/nsNativeThemeGTK.cpp
+++ b/widget/gtk/nsNativeThemeGTK.cpp
@@ -302,16 +302,18 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u
           // the slider to the actual scrollbar object
           nsIFrame *tmpFrame = aFrame->GetParent()->GetParent();
 
           aState->curpos = CheckIntAttr(tmpFrame, nsGkAtoms::curpos, 0);
           aState->maxpos = CheckIntAttr(tmpFrame, nsGkAtoms::maxpos, 100);
 
           if (CheckBooleanAttr(aFrame, nsGkAtoms::active)) {
             aState->active = TRUE;
+            // Set hover state to emulate Gtk style of active scrollbar thumb 
+            aState->inHover = TRUE;
           }
         }
 
         if (aWidgetType == NS_THEME_SCROLLBARBUTTON_UP ||
             aWidgetType == NS_THEME_SCROLLBARBUTTON_DOWN ||
             aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT ||
             aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT) {
           // set the state to disabled when the scrollbar is scrolled to
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -1346,41 +1346,45 @@ SetUserTimeAndStartupIDForActivatedWindo
         if (timestamp) {
             gdk_window_focus(gtk_widget_get_window(aWindow), timestamp);
             GTKToolkit->SetFocusTimestamp(0);
         }
         return;
     }
 
 #if defined(MOZ_ENABLE_STARTUP_NOTIFICATION)
-    GdkWindow* gdkWindow = gtk_widget_get_window(aWindow);
-  
-    GdkScreen* screen = gdk_window_get_screen(gdkWindow);
-    SnDisplay* snd =
-        sn_display_new(gdk_x11_display_get_xdisplay(gdk_window_get_display(gdkWindow)), 
-                       nullptr, nullptr);
-    if (!snd)
-        return;
-    SnLauncheeContext* ctx =
-        sn_launchee_context_new(snd, gdk_screen_get_number(screen),
-                                desktopStartupID.get());
-    if (!ctx) {
+    // TODO - Implement for non-X11 Gtk backends (Bug 726479)
+    if (mIsX11Display) {
+        GdkWindow* gdkWindow = gtk_widget_get_window(aWindow);
+
+        GdkScreen* screen = gdk_window_get_screen(gdkWindow);
+        SnDisplay* snd =
+            sn_display_new(gdk_x11_display_get_xdisplay(gdk_window_get_display(gdkWindow)),
+                           nullptr, nullptr);
+        if (!snd)
+            return;
+        SnLauncheeContext* ctx =
+            sn_launchee_context_new(snd, gdk_screen_get_number(screen),
+                                    desktopStartupID.get());
+        if (!ctx) {
+            sn_display_unref(snd);
+            return;
+        }
+
+        if (sn_launchee_context_get_id_has_timestamp(ctx)) {
+            gdk_x11_window_set_user_time(gdkWindow,
+                sn_launchee_context_get_timestamp(ctx));
+        }
+
+        sn_launchee_context_setup_window(ctx, gdk_x11_window_get_xid(gdkWindow));
+        sn_launchee_context_complete(ctx);
+
+        sn_launchee_context_unref(ctx);
         sn_display_unref(snd);
-        return;
-    }
-
-    if (sn_launchee_context_get_id_has_timestamp(ctx)) {
-        gdk_x11_window_set_user_time(gdkWindow, sn_launchee_context_get_timestamp(ctx));
-    }
-
-    sn_launchee_context_setup_window(ctx, gdk_x11_window_get_xid(gdkWindow));
-    sn_launchee_context_complete(ctx);
-
-    sn_launchee_context_unref(ctx);
-    sn_display_unref(snd);
+    }
 #endif
 
     // If we used the startup ID, that already contains the focus timestamp;
     // we don't want to reuse the timestamp next time we raise the window
     GTKToolkit->SetFocusTimestamp(0);
     GTKToolkit->SetDesktopStartupID(EmptyCString());
 }