Merge m-c to b2g-inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 09 Jun 2015 13:27:49 -0400
changeset 278540 656411888ae67cd69efb48c21a48bfe0c53d9785
parent 278539 caef3592c5ad51592509c486d5b96376d05639c7 (current diff)
parent 278454 29d982aac2a2ed6fe4fac574a7634282ecd7801b (diff)
child 278541 d2dc5d77d315ce7cd4820bbf75021c00ab2022df
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to b2g-inbound. a=merge
dom/media/test/contentDuration1.sjs
dom/media/test/contentDuration2.sjs
dom/media/test/contentDuration3.sjs
dom/media/test/contentDuration4.sjs
dom/media/test/contentDuration5.sjs
dom/media/test/contentDuration6.sjs
dom/media/test/contentDuration7.sjs
dom/media/test/noContentLength.sjs
dom/media/test/test_contentDuration1.html
dom/media/test/test_contentDuration2.html
dom/media/test/test_contentDuration3.html
dom/media/test/test_contentDuration4.html
dom/media/test/test_contentDuration5.html
dom/media/test/test_contentDuration6.html
dom/media/test/test_contentDuration7.html
dom/media/test/test_seekable2.html
dom/media/test/test_seekable3.html
js/src/jit/BaselineHelpers.h
js/src/jit/BaselineRegisters.h
js/src/jit/SharedIC.cpp
js/src/jit/SharedIC.h
js/src/jit/SharedICHelpers.h
js/src/jit/SharedICRegisters.h
js/src/jit/arm/BaselineHelpers-arm.h
js/src/jit/arm/BaselineRegisters-arm.h
js/src/jit/arm/SharedICHelpers-arm.h
js/src/jit/arm/SharedICRegisters-arm.h
js/src/jit/mips/BaselineHelpers-mips.h
js/src/jit/mips/BaselineRegisters-mips.h
js/src/jit/mips/SharedICHelpers-mips.h
js/src/jit/mips/SharedICRegisters-mips.h
js/src/jit/none/BaselineHelpers-none.h
js/src/jit/none/BaselineRegisters-none.h
js/src/jit/none/SharedICHelpers-none.h
js/src/jit/none/SharedICRegisters-none.h
js/src/jit/x64/BaselineHelpers-x64.h
js/src/jit/x64/BaselineRegisters-x64.h
js/src/jit/x64/SharedICHelpers-x64.h
js/src/jit/x64/SharedICRegisters-x64.h
js/src/jit/x86/BaselineHelpers-x86.h
js/src/jit/x86/BaselineRegisters-x86.h
js/src/jit/x86/SharedICHelpers-x86.h
js/src/jit/x86/SharedICRegisters-x86.h
widget/moz.build
--- a/accessible/mac/AccessibleWrap.h
+++ b/accessible/mac/AccessibleWrap.h
@@ -1,14 +1,14 @@
 /* -*- Mode: Objective-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/. */
 
-/* For documentation of the accessibility architecture, 
+/* For documentation of the accessibility architecture,
  * see http://lxr.mozilla.org/seamonkey/source/accessible/accessible-docs.html
  */
 
 #ifndef _AccessibleWrap_H_
 #define _AccessibleWrap_H_
 
 #include <objc/objc.h>
 
@@ -82,32 +82,38 @@ protected:
 #else
   id GetNativeObject();
 #endif
 
 private:
 
   /**
    * Our native object. Private because its creation is done lazily.
-   * Don't access it directly. Ever. Unless you are GetNativeObject() or 
+   * Don't access it directly. Ever. Unless you are GetNativeObject() or
    * Shutdown()
    */
 #if defined(__OBJC__)
   // if we are in Objective-C, we use the actual Obj-C class.
   mozAccessible* mNativeObject;
 #else
   id mNativeObject;
 #endif
 
   /**
    * We have created our native. This does not mean there is one.
    * This can never go back to false.
    * We need it because checking whether we need a native object cost time.
    */
-  bool mNativeInited;  
+  bool mNativeInited;
 };
 
+#if defined(__OBJC__)
+  void FireNativeEvent(mozAccessible* aNativeAcc, uint32_t aEventType);
+#else
+  void FireNativeEvent(id aNativeAcc, uint32_t aEventType);
+#endif
+
 Class GetTypeFromRole(roles::Role aRole);
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/accessible/mac/AccessibleWrap.mm
+++ b/accessible/mac/AccessibleWrap.mm
@@ -15,52 +15,52 @@
 #import "mozHTMLAccessible.h"
 #import "mozTextAccessible.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 AccessibleWrap::
   AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) :
-  Accessible(aContent, aDoc), mNativeObject(nil),  
+  Accessible(aContent, aDoc), mNativeObject(nil),
   mNativeInited(false)
 {
 }
 
 AccessibleWrap::~AccessibleWrap()
 {
 }
 
-mozAccessible* 
+mozAccessible*
 AccessibleWrap::GetNativeObject()
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
-  
+
   if (!mNativeInited && !mNativeObject && !IsDefunct() && !AncestorIsFlat()) {
     uintptr_t accWrap = reinterpret_cast<uintptr_t>(this);
     mNativeObject = [[GetNativeType() alloc] initWithAccessible:accWrap];
   }
-  
+
   mNativeInited = true;
-  
+
   return mNativeObject;
-  
+
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 void
 AccessibleWrap::GetNativeInterface(void** aOutInterface)
 {
   *aOutInterface = static_cast<void*>(GetNativeObject());
 }
 
 // overridden in subclasses to create the right kind of object. by default we create a generic
 // 'mozAccessible' node.
 Class
-AccessibleWrap::GetNativeType () 
+AccessibleWrap::GetNativeType ()
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   if (IsXULTabpanels())
     return [mozPaneAccessible class];
 
   return GetTypeFromRole(Role());
 
@@ -107,28 +107,17 @@ AccessibleWrap::HandleAccEvent(AccEvent*
   Accessible* accessible = aEvent->GetAccessible();
   NS_ENSURE_STATE(accessible);
 
   mozAccessible *nativeAcc = nil;
   accessible->GetNativeInterface((void**)&nativeAcc);
   if (!nativeAcc)
     return NS_ERROR_FAILURE;
 
-  switch (eventType) {
-    case nsIAccessibleEvent::EVENT_FOCUS:
-      [nativeAcc didReceiveFocus];
-      break;
-    case nsIAccessibleEvent::EVENT_VALUE_CHANGE:
-      [nativeAcc valueDidChange];
-      break;
-    case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED:
-    case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED:
-      [nativeAcc selectedTextDidChange];
-      break;
-  }
+  FireNativeEvent(nativeAcc, eventType);
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 void
 AccessibleWrap::InvalidateChildren()
@@ -160,24 +149,24 @@ AccessibleWrap::RemoveChild(Accessible* 
   if (removed && mNativeObject)
     [mNativeObject invalidateChildren];
 
   return removed;
 }
 
 // if we for some reason have no native accessible, we should be skipped over (and traversed)
 // when fetching all unignored children, etc.  when counting unignored children, we will not be counted.
-bool 
-AccessibleWrap::IsIgnored() 
+bool
+AccessibleWrap::IsIgnored()
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
-  
+
   mozAccessible* nativeObject = GetNativeObject();
   return (!nativeObject) || [nativeObject accessibilityIsIgnored];
-  
+
   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
 }
 
 void
 AccessibleWrap::GetUnignoredChildren(nsTArray<Accessible*>* aChildrenArray)
 {
   // we're flat; there are no children.
   if (nsAccUtils::MustPrune(this))
@@ -198,67 +187,88 @@ AccessibleWrap::GetUnignoredChildren(nsT
   }
 }
 
 Accessible*
 AccessibleWrap::GetUnignoredParent() const
 {
   // Go up the chain to find a parent that is not ignored.
   AccessibleWrap* parentWrap = static_cast<AccessibleWrap*>(Parent());
-  while (parentWrap && parentWrap->IsIgnored()) 
+  while (parentWrap && parentWrap->IsIgnored())
     parentWrap = static_cast<AccessibleWrap*>(parentWrap->Parent());
-    
+
   return parentWrap;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // AccessibleWrap protected
 
 bool
 AccessibleWrap::AncestorIsFlat()
 {
   // We don't create a native object if we're child of a "flat" accessible;
   // for example, on OS X buttons shouldn't have any children, because that
-  // makes the OS confused. 
+  // makes the OS confused.
   //
   // To maintain a scripting environment where the XPCOM accessible hierarchy
   // look the same on all platforms, we still let the C++ objects be created
   // though.
 
   Accessible* parent = Parent();
   while (parent) {
     if (nsAccUtils::MustPrune(parent))
       return true;
 
     parent = parent->Parent();
   }
   // no parent was flat
   return false;
 }
 
+void
+a11y::FireNativeEvent(mozAccessible* aNativeAcc, uint32_t aEventType)
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+  switch (aEventType) {
+    case nsIAccessibleEvent::EVENT_FOCUS:
+      [aNativeAcc didReceiveFocus];
+      break;
+    case nsIAccessibleEvent::EVENT_VALUE_CHANGE:
+      [aNativeAcc valueDidChange];
+      break;
+    case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED:
+    case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED:
+      [aNativeAcc selectedTextDidChange];
+      break;
+  }
+
+  NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
 Class
-a11y::GetTypeFromRole(roles::Role aRole) 
+a11y::GetTypeFromRole(roles::Role aRole)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   switch (aRole) {
     case roles::COMBOBOX:
     case roles::PUSHBUTTON:
     case roles::SPLITBUTTON:
     case roles::TOGGLE_BUTTON:
     {
         return [mozButtonAccessible class];
     }
-    
+
     case roles::PAGETAB:
       return [mozButtonAccessible class];
 
     case roles::CHECKBUTTON:
       return [mozCheckboxAccessible class];
-      
+
     case roles::HEADING:
       return [mozHeadingAccessible class];
 
     case roles::PAGETABLIST:
       return [mozTabsAccessible class];
 
     case roles::ENTRY:
     case roles::STATICTEXT:
@@ -268,17 +278,17 @@ a11y::GetTypeFromRole(roles::Role aRole)
       // normal textfield (static or editable)
       return [mozTextAccessible class];
 
     case roles::TEXT_LEAF:
       return [mozTextLeafAccessible class];
 
     case roles::LINK:
       return [mozLinkAccessible class];
-      
+
     default:
       return [mozAccessible class];
   }
-  
+
   return nil;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
--- a/accessible/mac/MacUtils.mm
+++ b/accessible/mac/MacUtils.mm
@@ -13,21 +13,21 @@
 namespace mozilla {
 namespace a11y {
 namespace utils {
 
 /**
  * Get a localized string from the a11y string bundle.
  * Return nil if not found.
  */
-NSString* 
+NSString*
 LocalizedString(const nsString& aString)
 {
   nsString text;
-  
+
   Accessible::TranslateString(aString, text);
-  
+
   return text.IsEmpty() ? nil : nsCocoaUtils::ToNSString(text);
 }
 
 }
 }
 }
--- a/accessible/mac/Platform.mm
+++ b/accessible/mac/Platform.mm
@@ -42,39 +42,54 @@ ProxyCreated(ProxyAccessible* aProxy, ui
   uintptr_t accWrap = reinterpret_cast<uintptr_t>(aProxy) | IS_PROXY;
   mozAccessible* mozWrapper = [[type alloc] initWithAccessible:accWrap];
   aProxy->SetWrapper(reinterpret_cast<uintptr_t>(mozWrapper));
 }
 
 void
 ProxyDestroyed(ProxyAccessible* aProxy)
 {
-  mozAccessible* wrapper =
-    reinterpret_cast<mozAccessible*>(aProxy->GetWrapper());
+  mozAccessible* wrapper = GetNativeFromProxy(aProxy);
   [wrapper expire];
   [wrapper release];
   aProxy->SetWrapper(0);
 }
 
 void
-ProxyEvent(ProxyAccessible*, uint32_t)
+ProxyEvent(ProxyAccessible* aProxy, uint32_t aEventType)
 {
+  // ignore everything but focus-changed, value-changed, caret and selection
+  // events for now.
+  if (aEventType != nsIAccessibleEvent::EVENT_FOCUS &&
+      aEventType != nsIAccessibleEvent::EVENT_VALUE_CHANGE &&
+      aEventType != nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED &&
+      aEventType != nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED)
+    return;
+
+  mozAccessible* wrapper = GetNativeFromProxy(aProxy);
+  if (wrapper)
+    FireNativeEvent(wrapper, aEventType);
 }
 
 void
-ProxyStateChangeEvent(ProxyAccessible*, uint64_t, bool)
+ProxyStateChangeEvent(ProxyAccessible* aProxy, uint64_t, bool)
 {
+  // mac doesn't care about state change events
 }
 
 void
 ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset)
 {
-}
+  mozAccessible* wrapper = GetNativeFromProxy(aTarget);
+  if (wrapper)
+    [wrapper selectedTextDidChange];
 }
-}
+
+} // namespace a11y
+} // namespace mozilla
 
 @interface GeckoNSApplication(a11y)
 -(void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute;
 @end
 
 @implementation GeckoNSApplication(a11y)
 
 -(void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute
--- a/accessible/mac/RootAccessibleWrap.h
+++ b/accessible/mac/RootAccessibleWrap.h
@@ -1,14 +1,14 @@
 /* -*- 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/. */
 
-/* For documentation of the accessibility architecture, 
+/* For documentation of the accessibility architecture,
  * see http://lxr.mozilla.org/seamonkey/source/accessible/accessible-docs.html
  */
 
 #ifndef mozilla_a11y_RootAccessibleWrap_h__
 #define mozilla_a11y_RootAccessibleWrap_h__
 
 #include "RootAccessible.h"
 
@@ -18,17 +18,17 @@ namespace a11y {
 class RootAccessibleWrap : public RootAccessible
 {
 public:
   RootAccessibleWrap(nsIDocument* aDocument, nsIContent* aRootContent,
                      nsIPresShell* aPresShell);
   virtual ~RootAccessibleWrap();
 
     Class GetNativeType ();
-    
+
     // let's our native accessible get in touch with the
     // native cocoa view that is our accessible parent.
     void GetNativeWidget (void **aOutView);
 };
 
 } // namespace a11y
 } // namespace mozilla
 
--- a/accessible/mac/RootAccessibleWrap.mm
+++ b/accessible/mac/RootAccessibleWrap.mm
@@ -41,14 +41,14 @@ RootAccessibleWrap::GetNativeWidget(void
 {
   nsIFrame *frame = GetFrame();
   if (frame) {
     nsView *view = frame->GetViewExternal();
     if (view) {
       nsIWidget *widget = view->GetWidget();
       if (widget) {
         *aOutView = (void**)widget->GetNativeData (NS_NATIVE_WIDGET);
-        NS_ASSERTION (*aOutView, 
+        NS_ASSERTION (*aOutView,
                       "Couldn't get the native NSView parent we need to connect the accessibility hierarchy!");
       }
     }
   }
 }
--- a/accessible/mac/TextLeafAccessibleWrap.h
+++ b/accessible/mac/TextLeafAccessibleWrap.h
@@ -5,15 +5,15 @@
 
 #ifndef mozilla_a11y_TextLeafAccessibleWrap_h__
 #define mozilla_a11y_TextLeafAccessibleWrap_h__
 
 #include "TextLeafAccessible.h"
 
 namespace mozilla {
 namespace a11y {
- 
+
 typedef class TextLeafAccessible TextLeafAccessibleWrap;
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/accessible/mac/mozAccessible.h
+++ b/accessible/mac/mozAccessible.h
@@ -1,14 +1,15 @@
 /* -*- Mode: Objective-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 "AccessibleWrap.h"
+#include "ProxyAccessible.h"
 
 #import <Cocoa/Cocoa.h>
 
 #import "mozAccessibleProtocol.h"
 
 @class mozRootAccessible;
 
 /**
@@ -26,32 +27,38 @@ GetObjectOrRepresentedView(id <mozAccess
 inline mozAccessible*
 GetNativeFromGeckoAccessible(mozilla::a11y::Accessible* aAccessible)
 {
   mozAccessible* native = nil;
   aAccessible->GetNativeInterface((void**)&native);
   return native;
 }
 
+inline mozAccessible*
+GetNativeFromProxy(mozilla::a11y::ProxyAccessible* aProxy)
+{
+  return reinterpret_cast<mozAccessible*>(aProxy->GetWrapper());
+}
+
 // This is OR'd with the Accessible owner to indicate the wrap-ee is a proxy.
 static const uintptr_t IS_PROXY = 1;
 
 @interface mozAccessible : NSObject <mozAccessible>
 {
   /**
    * Weak reference; it owns us.
    */
   uintptr_t mGeckoAccessible;
-  
+
   /**
    * Strong ref to array of children
    */
   NSMutableArray* mChildren;
-  
-  /** 
+
+  /**
    * Weak reference to the parent
    */
   mozAccessible* mParent;
 
   /**
    * The role of our gecko accessible.
    */
   mozilla::a11y::role        mRole;
@@ -117,17 +124,17 @@ static const uintptr_t IS_PROXY = 1;
 - (void)valueDidChange;
 - (void)selectedTextDidChange;
 
 #pragma mark -
 
 // invalidates and removes all our children from our cached array.
 - (void)invalidateChildren;
 
-/** 
+/**
  * Append a child if they are already cached.
  */
 - (void)appendChild:(mozilla::a11y::Accessible*)aAccessible;
 
 // makes ourselves "expired". after this point, we might be around if someone
 // has retained us (e.g., a third-party), but we really contain no information.
 - (void)expire;
 - (BOOL)isExpired;
--- a/accessible/mac/mozAccessible.mm
+++ b/accessible/mac/mozAccessible.mm
@@ -1,13 +1,13 @@
 /* -*- Mode: Objective-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/. */
- 
+
 #import "mozAccessible.h"
 
 #import "MacUtils.h"
 #import "mozView.h"
 
 #include "Accessible-inl.h"
 #include "nsAccUtils.h"
 #include "nsIAccessibleRelation.h"
@@ -32,39 +32,39 @@ using namespace mozilla::a11y;
 static inline id
 GetClosestInterestingAccessible(id anObject)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   // this object is not ignored, so let's return it.
   if (![anObject accessibilityIsIgnored])
     return GetObjectOrRepresentedView(anObject);
-  
+
   // find the closest ancestor that is not ignored.
   id unignoredObject = anObject;
   while ((unignoredObject = [unignoredObject accessibilityAttributeValue:NSAccessibilityParentAttribute])) {
     if (![unignoredObject accessibilityIsIgnored])
       // object is not ignored, so let's stop the search.
       break;
   }
-  
+
   // if it's a mozAccessible, we need to take care to maybe return the view we
   // represent, to the AT.
   if ([unignoredObject respondsToSelector:@selector(hasRepresentedView)])
     return GetObjectOrRepresentedView(unignoredObject);
-  
+
   return unignoredObject;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 #pragma mark -
 
 @implementation mozAccessible
- 
+
 - (id)initWithAccessible:(uintptr_t)aGeckoAccessible
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   if ((self = [super init])) {
     mGeckoAccessible = aGeckoAccessible;
     if (aGeckoAccessible & IS_PROXY)
       mRole = [self getProxyAccessible]->Role();
@@ -99,17 +99,17 @@ GetClosestInterestingAccessible(id anObj
 - (mozilla::a11y::ProxyAccessible*)getProxyAccessible
 {
   // Check if mGeckoAccessible points at a proxy
   if (!(mGeckoAccessible & IS_PROXY))
     return nil;
 
   return reinterpret_cast<ProxyAccessible*>(mGeckoAccessible & ~IS_PROXY);
 }
- 
+
 #pragma mark -
 
 - (BOOL)accessibilityIsIgnored
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
 
   // unknown (either unimplemented, or irrelevant) elements are marked as ignored
   // as well as expired elements.
@@ -123,22 +123,22 @@ GetClosestInterestingAccessible(id anObj
 
 - (NSArray*)accessibilityAttributeNames
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   // if we're expired, we don't support any attributes.
   if (![self getGeckoAccessible])
     return [NSArray array];
-  
+
   static NSArray *generalAttributes = nil;
-  
+
   if (!generalAttributes) {
     // standard attributes that are shared and supported by all generic elements.
-    generalAttributes = [[NSArray alloc] initWithObjects:  NSAccessibilityChildrenAttribute, 
+    generalAttributes = [[NSArray alloc] initWithObjects:  NSAccessibilityChildrenAttribute,
                                                            NSAccessibilityParentAttribute,
                                                            NSAccessibilityRoleAttribute,
                                                            NSAccessibilityTitleAttribute,
                                                            NSAccessibilityValueAttribute,
                                                            NSAccessibilitySubroleAttribute,
                                                            NSAccessibilityRoleDescriptionAttribute,
                                                            NSAccessibilityPositionAttribute,
                                                            NSAccessibilityEnabledAttribute,
@@ -156,48 +156,48 @@ GetClosestInterestingAccessible(id anObj
   }
 
   return generalAttributes;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (id)accessibilityAttributeValue:(NSString*)attribute
-{  
+{
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   if (![self getGeckoAccessible])
     return nil;
 
 #if DEBUG
   if ([attribute isEqualToString:@"AXMozDescription"])
     return [NSString stringWithFormat:@"role = %u native = %@", mRole, [self class]];
 #endif
-  
+
   if ([attribute isEqualToString:NSAccessibilityChildrenAttribute])
     return [self children];
-  if ([attribute isEqualToString:NSAccessibilityParentAttribute]) 
+  if ([attribute isEqualToString:NSAccessibilityParentAttribute])
     return [self parent];
-  
+
 #ifdef DEBUG_hakan
   NSLog (@"(%@ responding to attr %@)", self, attribute);
 #endif
 
   if ([attribute isEqualToString:NSAccessibilityRoleAttribute])
     return [self role];
-  if ([attribute isEqualToString:NSAccessibilityPositionAttribute]) 
+  if ([attribute isEqualToString:NSAccessibilityPositionAttribute])
     return [self position];
   if ([attribute isEqualToString:NSAccessibilitySubroleAttribute])
     return [self subrole];
   if ([attribute isEqualToString:NSAccessibilityEnabledAttribute])
     return [NSNumber numberWithBool:[self isEnabled]];
   if ([attribute isEqualToString:NSAccessibilityValueAttribute])
     return [self value];
-  if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) 
-    return [self roleDescription];  
+  if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute])
+    return [self roleDescription];
   if ([attribute isEqualToString:NSAccessibilityDescriptionAttribute])
     return [self customDescription];
   if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
     return [NSNumber numberWithBool:[self isFocused]];
   if ([attribute isEqualToString:NSAccessibilitySizeAttribute])
     return [self size];
   if ([attribute isEqualToString:NSAccessibilityWindowAttribute])
     return [self window];
@@ -208,45 +208,45 @@ GetClosestInterestingAccessible(id anObj
   if ([attribute isEqualToString:NSAccessibilityTitleUIElementAttribute]) {
     Relation rel =
       [self getGeckoAccessible]->RelationByType(RelationType::LABELLED_BY);
     Accessible* tempAcc = rel.Next();
     return tempAcc ? GetNativeFromGeckoAccessible(tempAcc) : nil;
   }
   if ([attribute isEqualToString:NSAccessibilityHelpAttribute])
     return [self help];
-    
+
 #ifdef DEBUG
  NSLog (@"!!! %@ can't respond to attribute %@", self, attribute);
 #endif
   return nil;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
 
   if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
     return [self canBeFocused];
-  
+
   return NO;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
 }
 
 - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
 #ifdef DEBUG_hakan
   NSLog (@"[%@] %@='%@'", self, attribute, value);
 #endif
-  
+
   // we only support focusing elements so far.
   if ([attribute isEqualToString:NSAccessibilityFocusedAttribute] && [value boolValue])
     [self focus];
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (id)accessibilityHitTest:(NSPoint)point
@@ -270,48 +270,48 @@ GetClosestInterestingAccessible(id anObj
 
   if (child) {
     mozAccessible* nativeChild = GetNativeFromGeckoAccessible(child);
     if (nativeChild)
       return GetClosestInterestingAccessible(nativeChild);
   }
 
   // if we didn't find anything, return ourself (or the first unignored ancestor).
-  return GetClosestInterestingAccessible(self); 
+  return GetClosestInterestingAccessible(self);
 }
 
-- (NSArray*)accessibilityActionNames 
+- (NSArray*)accessibilityActionNames
 {
   return nil;
 }
 
-- (NSString*)accessibilityActionDescription:(NSString*)action 
+- (NSString*)accessibilityActionDescription:(NSString*)action
 {
   // by default we return whatever the MacOS API know about.
   // if you have custom actions, override.
   return NSAccessibilityActionDescription(action);
 }
 
-- (void)accessibilityPerformAction:(NSString*)action 
+- (void)accessibilityPerformAction:(NSString*)action
 {
 }
 
 - (id)accessibilityFocusedUIElement
 {
   AccessibleWrap* accWrap = [self getGeckoAccessible];
   if (!accWrap)
     return nil;
-  
+
   Accessible* focusedGeckoChild = accWrap->FocusedChild();
   if (focusedGeckoChild) {
     mozAccessible *focusedChild = GetNativeFromGeckoAccessible(focusedGeckoChild);
     if (focusedChild)
       return GetClosestInterestingAccessible(focusedChild);
   }
-  
+
   // return ourself if we can't get a native focused child.
   return GetClosestInterestingAccessible(self);
 }
 
 #pragma mark -
 
 - (id <mozAccessible>)parent
 {
@@ -319,19 +319,19 @@ GetClosestInterestingAccessible(id anObj
 
   AccessibleWrap* accWrap = [self getGeckoAccessible];
   Accessible* accessibleParent = accWrap->GetUnignoredParent();
   if (accessibleParent) {
     id nativeParent = GetNativeFromGeckoAccessible(accessibleParent);
     if (nativeParent)
       return GetClosestInterestingAccessible(nativeParent);
   }
-  
+
   // GetUnignoredParent() returns null when there is no unignored accessible all the way up to
-  // the root accessible. so we'll have to return whatever native accessible is above our root accessible 
+  // the root accessible. so we'll have to return whatever native accessible is above our root accessible
   // (which might be the owning NSWindow in the application, for example).
   //
   // get the native root accessible, and tell it to return its first parent unignored accessible.
   id nativeParent =
     GetNativeFromGeckoAccessible(accWrap->RootAccessible());
   NSAssert1 (nativeParent, @"!!! we can't find a parent for %@", self);
 
   return GetClosestInterestingAccessible(nativeParent);
@@ -375,26 +375,26 @@ GetClosestInterestingAccessible(id anObj
   for (uint32_t idx = 0; idx < totalCount; idx++) {
     Accessible* curAccessible = childrenArray.ElementAt(idx);
     if (curAccessible) {
       mozAccessible *curNative = GetNativeFromGeckoAccessible(curAccessible);
       if (curNative)
         [mChildren addObject:GetObjectOrRepresentedView(curNative)];
     }
   }
-  
+
 #ifdef DEBUG_hakan
   // make sure we're not returning any ignored accessibles.
   NSEnumerator *e = [mChildren objectEnumerator];
   mozAccessible *m = nil;
   while ((m = [e nextObject])) {
     NSAssert1(![m accessibilityIsIgnored], @"we should never return an ignored accessible! (%@)", m);
   }
 #endif
-  
+
   return mChildren;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (NSValue*)position
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
@@ -705,30 +705,30 @@ struct RoleDescrComparator
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (void)appendChild:(Accessible*)aAccessible
 {
   // if mChildren is nil, then we don't even need to bother
   if (!mChildren)
     return;
-    
+
   mozAccessible *curNative = GetNativeFromGeckoAccessible(aAccessible);
   if (curNative)
     [mChildren addObject:GetObjectOrRepresentedView(curNative)];
 }
 
 - (void)expire
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   [self invalidateChildren];
 
   mGeckoAccessible = 0;
-  
+
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (BOOL)isExpired
 {
   return ![self getGeckoAccessible];
 }
 
@@ -742,23 +742,23 @@ struct RoleDescrComparator
 // parent.
 - (void)sanityCheckChildren:(NSArray *)children
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   NSAssert(![self accessibilityIsIgnored], @"can't sanity check children of an ignored accessible!");
   NSEnumerator *iter = [children objectEnumerator];
   mozAccessible *curObj = nil;
-  
+
   NSLog(@"sanity checking %@", self);
-  
+
   while ((curObj = [iter nextObject])) {
     id realSelf = GetObjectOrRepresentedView(self);
     NSLog(@"checking %@", realSelf);
-    NSAssert2([curObj parent] == realSelf, 
+    NSAssert2([curObj parent] == realSelf,
               @"!!! %@ not returning %@ as AXParent, even though it is a AXChild of it!", curObj, realSelf);
   }
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (void)sanityCheckChildren
 {
@@ -778,36 +778,36 @@ struct RoleDescrComparator
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (void)printHierarchyWithLevel:(unsigned)level
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   NSAssert(![self isExpired], @"!!! trying to print hierarchy of expired object!");
-  
+
   // print this node
   NSMutableString *indent = [NSMutableString stringWithCapacity:level];
   unsigned i=0;
   for (;i<level;i++)
     [indent appendString:@" "];
-  
+
   NSLog (@"%@(#%i) %@", indent, level, self);
-  
+
   // use |children| method to make sure our children are lazily fetched first.
   NSArray *children = [self children];
   if (!children)
     return;
-    
+
   if (![self accessibilityIsIgnored])
     [self sanityCheckChildren];
-    
+
   NSEnumerator *iter = [children objectEnumerator];
   mozAccessible *object = nil;
-  
+
   while (iter && (object = [iter nextObject]))
     // print every child node's subtree, increasing the indenting
     // by two for every level.
     [object printHierarchyWithLevel:(level+1)];
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
--- a/accessible/mac/mozAccessibleProtocol.h
+++ b/accessible/mac/mozAccessibleProtocol.h
@@ -4,31 +4,31 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #import <Cocoa/Cocoa.h>
 
 #import "mozView.h"
 
 /* This protocol's primary use is so widget/cocoa can talk back to us
    properly.
-   
-   ChildView owns the topmost mozRootAccessible, and needs to take care of setting up 
+
+   ChildView owns the topmost mozRootAccessible, and needs to take care of setting up
    that parent/child relationship.
-   
+
    This protocol is thus used to make sure it knows it's talking to us, and not
    just some random |id|.
 */
 
 @protocol mozAccessible
 
 // returns whether this accessible is the root accessible. there is one
 // root accessible per window.
 - (BOOL)isRoot;
 
-// some mozAccessibles implement accessibility support in place of another object. for example, 
+// some mozAccessibles implement accessibility support in place of another object. for example,
 // ChildView gets its support from us.
 //
 // instead of returning a mozAccessible to the OS when it wants an object, we need to pass the view we represent, so the
 // OS doesn't get confused and think we return some random object.
 - (BOOL)hasRepresentedView;
 - (id)representedView;
 
 #ifdef DEBUG
--- a/accessible/mac/mozActionElements.mm
+++ b/accessible/mac/mozActionElements.mm
@@ -157,34 +157,34 @@ enum CheckboxValue {
 
 - (NSString*)accessibilityActionDescription:(NSString*)action 
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   if ([action isEqualToString:NSAccessibilityPressAction]) {
     if ([self isChecked] != kUnchecked)
       return @"uncheck checkbox"; // XXX: localize this later?
-    
+
     return @"check checkbox"; // XXX: localize this later?
   }
-  
+
   return nil;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (int)isChecked
 {
   uint64_t state = [self getGeckoAccessible]->NativeState();
 
   // check if we're checked or in a mixed state
   if (state & states::CHECKED) {
     return (state & states::MIXED) ? kMixed : kChecked;
   }
-  
+
   return kUnchecked;
 }
 
 - (id)value
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   return [NSNumber numberWithInt:[self isChecked]];
@@ -202,34 +202,34 @@ enum CheckboxValue {
 
   [super dealloc];
 }
 
 - (NSArray*)accessibilityAttributeNames
 {
   // standard attributes that are shared and supported by root accessible (AXMain) elements.
   static NSMutableArray* attributes = nil;
-  
+
   if (!attributes) {
     attributes = [[super accessibilityAttributeNames] mutableCopy];
     [attributes addObject:NSAccessibilityContentsAttribute];
     [attributes addObject:NSAccessibilityTabsAttribute];
   }
-  
-  return attributes;  
+
+  return attributes;
 }
 
 - (id)accessibilityAttributeValue:(NSString *)attribute
-{  
+{
   if ([attribute isEqualToString:NSAccessibilityContentsAttribute])
     return [super children];
   if ([attribute isEqualToString:NSAccessibilityTabsAttribute])
     return [self tabs];
-  
-  return [super accessibilityAttributeValue:attribute];  
+
+  return [super accessibilityAttributeValue:attribute];
 }
 
 /**
  * Returns the selected tab (the mozAccessible)
  */
 - (id)value
 {
   if (![self getGeckoAccessible])
@@ -251,17 +251,17 @@ enum CheckboxValue {
 - (id)tabs
 {
   if (mTabs)
     return mTabs;
 
   NSArray* children = [self children];
   NSEnumerator* enumerator = [children objectEnumerator];
   mTabs = [[NSMutableArray alloc] init];
-  
+
   id obj;
   while ((obj = [enumerator nextObject]))
     if ([obj isTab])
       [mTabs addObject:obj];
 
   return mTabs;
 }
 
--- a/accessible/mac/mozDocAccessible.h
+++ b/accessible/mac/mozDocAccessible.h
@@ -4,17 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #import <Cocoa/Cocoa.h>
 #import "mozAccessible.h"
 
 // our protocol that we implement (so cocoa widgets can talk to us)
 #import "mozAccessibleProtocol.h"
 
-/* 
+/*
   The root accessible. There is one per window.
   Created by the RootAccessibleWrap.
 */
 @interface mozRootAccessible : mozAccessible
 {
   // the mozView that we're representing.
   // all outside communication goes through the mozView.
   // in reality, it's just piping all calls to us, and we're
--- a/accessible/mac/mozDocAccessible.mm
+++ b/accessible/mac/mozDocAccessible.mm
@@ -9,78 +9,78 @@
 
 #import "mozView.h"
 
 // This must be included last:
 #include "nsObjCExceptions.h"
 
 using namespace mozilla::a11y;
 
-static id <mozAccessible, mozView> 
+static id <mozAccessible, mozView>
 getNativeViewFromRootAccessible(Accessible* aAccessible)
 {
   RootAccessibleWrap* root =
     static_cast<RootAccessibleWrap*>(aAccessible->AsRoot());
   id <mozAccessible, mozView> nativeView = nil;
   root->GetNativeWidget ((void**)&nativeView);
   return nativeView;
 }
 
 #pragma mark -
 
 @implementation mozRootAccessible
 
 - (NSArray*)accessibilityAttributeNames
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
-  
+
   // if we're expired, we don't support any attributes.
   if (![self getGeckoAccessible])
     return [NSArray array];
-  
+
   // standard attributes that are shared and supported by root accessible (AXMain) elements.
   static NSMutableArray* attributes = nil;
-  
+
   if (!attributes) {
     attributes = [[super accessibilityAttributeNames] mutableCopy];
     [attributes addObject:NSAccessibilityMainAttribute];
     [attributes addObject:NSAccessibilityMinimizedAttribute];
   }
 
   return attributes;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (id)accessibilityAttributeValue:(NSString *)attribute
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
-  
+
   if ([attribute isEqualToString:NSAccessibilityMainAttribute])
     return [NSNumber numberWithBool:[[self window] isMainWindow]];
   if ([attribute isEqualToString:NSAccessibilityMinimizedAttribute])
     return [NSNumber numberWithBool:[[self window] isMiniaturized]];
 
   return [super accessibilityAttributeValue:attribute];
-  
+
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 
 // return the AXParent that our parallell NSView tells us about.
 - (id)parent
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   if (!mParallelView)
     mParallelView = (id<mozView, mozAccessible>)[self representedView];
-  
+
   if (mParallelView)
     return [mParallelView accessibilityAttributeValue:NSAccessibilityParentAttribute];
-  
+
   NSAssert(mParallelView, @"we're a root accessible w/o native view?");
   return [super parent];
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (BOOL)hasRepresentedView
 {
@@ -89,19 +89,19 @@ getNativeViewFromRootAccessible(Accessib
 
 // this will return our parallell NSView. see mozDocAccessible.h
 - (id)representedView
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   if (mParallelView)
     return (id)mParallelView;
-  
+
   mParallelView = getNativeViewFromRootAccessible ([self getGeckoAccessible]);
-  
+
   NSAssert(mParallelView, @"can't return root accessible's native parallel view.");
   return mParallelView;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (BOOL)isRoot
 {
--- a/accessible/mac/mozTextAccessible.mm
+++ b/accessible/mac/mozTextAccessible.mm
@@ -14,17 +14,17 @@
 
 using namespace mozilla::a11y;
 
 inline bool
 ToNSRange(id aValue, NSRange* aRange)
 {
   NS_PRECONDITION(aRange, "aRange is nil");
 
-  if ([aValue isKindOfClass:[NSValue class]] && 
+  if ([aValue isKindOfClass:[NSValue class]] &&
       strcmp([(NSValue*)aValue objCType], @encode(NSRange)) == 0) {
     *aRange = [aValue rangeValue];
     return true;
   }
 
   return false;
 }
 
@@ -99,17 +99,17 @@ ToNSString(id aValue)
     return [self selectedText];
 
   if ([attribute isEqualToString:NSAccessibilityTitleAttribute])
     return @"";
 
   if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
     // Apple's SpeechSynthesisServer expects AXValue to return an AXStaticText
     // object's AXSelectedText attribute. See bug 674612 for details.
-    // Also if there is no selected text, we return the full text. 
+    // Also if there is no selected text, we return the full text.
     // See bug 369710 for details.
     if ([[self role] isEqualToString:NSAccessibilityStaticTextRole]) {
       NSString* selectedText = [self selectedText];
       return (selectedText && [selectedText length]) ? selectedText : [self text];
     }
 
     return [self text];
   }
@@ -219,17 +219,17 @@ ToNSString(id aValue)
 }
 
 - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
 
   if ([attribute isEqualToString:NSAccessibilityValueAttribute])
     return ![self isReadOnly];
-  
+
   if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute] ||
       [attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute] ||
       [attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute])
     return YES;
 
   return [super accessibilityIsAttributeSettable:attribute];
 
   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
@@ -241,17 +241,17 @@ ToNSString(id aValue)
 
   AccessibleWrap* accWrap = [self getGeckoAccessible];
   HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
   if (!textAcc)
     return;
 
   if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
     [self setText:ToNSString(value)];
-    
+
     return;
   }
 
   if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) {
     NSString* stringValue = ToNSString(value);
     if (!stringValue)
       return;
 
@@ -277,17 +277,17 @@ ToNSString(id aValue)
   if ([attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute]) {
     NSRange range;
     if (!ToNSRange(value, &range))
       return;
 
     textAcc->ScrollSubstringTo(range.location, range.location + range.length,
                                nsIAccessibleScrollType::SCROLL_TYPE_TOP_EDGE);
     return;
-  } 
+  }
 
   [super accessibilitySetValue:value forAttribute:attribute];
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (NSString*)subrole
 {
--- a/b2g/app/ua-update.json.in
+++ b/b2g/app/ua-update.json.in
@@ -3,81 +3,53 @@
 // Send these sites a custom user-agent. Bugs should be included with an entry.
 {
   // bug 826347, msn.com
   "msn.com": "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19",
   // bug 826353, itau.com.br
   "itau.com.br": "\\(Mobile#(Android; Mobile",
   // bug 826510, r7.com
   "r7.com": "\\(Mobile#(Android; Mobile",
-  // bug 826514, estadao.com.br
-  "estadao.com.br": "\\(Mobile#(Android; Mobile",
-  // bug 826711, bb.com.br
-  "bb.com.br": "\\(Mobile#(Android; Mobile",
   // bug 827622, bing.com
   "bing.com": "\\(Mobile#(Android; Mobile",
   // bug 827626, magazineluiza.com.br
   "magazineluiza.com.br": "\\(Mobile#(Android; Mobile",
   // bug 827633, hao123.com
   "hao123.com": "\\(Mobile#(Android; Mobile",
-  // bug 827573, webmotors.com.br
-  "webmotors.com.br": "\\(Mobile#(Android; Mobile",
   // bug 827670, elpais.com.co
   "elpais.com.co": "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19",
   // bug 827674, avianca.com
   "avianca.com": "\\(Mobile#(Android; Mobile",
-  // bug 827678, marca.com
-  "marca.com": "\\(Mobile#(Android; Mobile",
-  // bug 828371, ingbank.pl
-  "ingbank.pl": "\\(Mobile#(Android; Mobile",
   // bug 828416, loteriasyapuestas.es
   "loteriasyapuestas.es": "\\(Mobile#(Android; Mobile",
   // bug 828418, bbva.es
   "bbva.es": "\\(Mobile#(Android; Mobile",
-  // bug 828422, publico.es
-  "publico.es": "\\(Mobile#(Android; Mobile",
   // bug 828439, movistar.com.ve
   "movistar.com.ve": "\\(Mobile#(Android; Mobile",
-  // bug 843129, 11870.com
-  "iphonejuegosgratis.com": "\\(Mobile#(Android; Mobile",
   // bug 843132, comunio.es
   "comunio.es": "\\(Mobile#(Android; Mobile",
   // bug 843151, citibank.com
   "citibank.com": "\\(Mobile#(Android; Mobile",
   // bug 843153, games.com
   "games.com": "\\(Mobile#(Android; Mobile",
   // bug 843160, ehow.com
   "ehow.com": "\\(Mobile#(Android; Mobile",
   // bug 878228, blikk.hu
   "blikk.hu": "\\(Mobile#(Android; Mobile",
-  // bug 878232, hazipatika.com
-  "hazipatika.com": "\\(Mobile#(Android; Mobile",
   // bug 878238, koponyeg.hu
   "koponyeg.hu": "\\(Mobile#(Android; Mobile",
   // bug 878240, kuruc.info
   "kuruc.info": "\\(Mobile#(Android; Mobile",
   // bug 878242, nemzetisport.hu
   "nemzetisport.hu": "\\(Mobile#(Android; Mobile",
   // bug 878246, port.hu
   "port.hu": "\\(Mobile#(Android; Mobile",
   // bug 878249, portfolio.hu
   "portfolio.hu": "\\(Mobile#(Android; Mobile",
-  // bug 878253, vatera.hu
-  "vatera.hu": "\\(Mobile#(Android; Mobile",
   // bug 878260, cdm.me
   "cdm.me": "\\(Mobile#(Android; Mobile",
   // bug 878262, download.com
   "download.com": "\\(Mobile#(Android; Mobile",
-  // bug 878264, haber.ba
-  "haber.ba": "\\(Mobile#(Android; Mobile",
-  // bug 878271, kurir-info.rs
-  "kurir-info.rs": "\\(Mobile#(Android; Mobile",
   // bug 878273, livescore.com
   "livescore.com": "\\(Mobile#(Android; Mobile",
-  // bug 878277, naslovi.net
-  "naslovi.net": "\\(Mobile#(Android; Mobile",
-  // bug 878649, univision.com
-  "univision.com": "\\(Mobile#(Android; Mobile",
   // bug 878653, redstarbelgrade.info
-  "redstarbelgrade.info": "\\(Mobile#(Android; Mobile",
-  // bug 878655, vesti-online.com
-  "vesti-online.com": "\\(Mobile#(Android; Mobile"
+  "redstarbelgrade.info": "\\(Mobile#(Android; Mobile"
 }
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -118,36 +118,38 @@ const gXPInstallObserver = {
             Services.telemetry
                     .getHistogramById("SECURITY_UI")
                     .add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL_CLICK_THROUGH);
           };
           break;
       }
     };
 
-    options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") +
-                           "find-and-install-add-ons";
+    options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
 
     let messageString;
     let notification = document.getElementById("addon-install-confirmation-notification");
     if (unsigned.length == installInfo.installs.length) {
       // None of the add-ons are verified
       messageString = gNavigatorBundle.getString("addonConfirmInstallUnsigned.message");
       notification.setAttribute("warning", "true");
+      options.learnMoreURL += "unsigned-addons";
     }
     else if (unsigned.length == 0) {
       // All add-ons are verified or don't need to be verified
       messageString = gNavigatorBundle.getString("addonConfirmInstall.message");
       notification.removeAttribute("warning");
+      options.learnMoreURL += "find-and-install-add-ons";
     }
     else {
       // Some of the add-ons are unverified, the list of names will indicate
       // which
       messageString = gNavigatorBundle.getString("addonConfirmInstallSomeUnsigned.message");
       notification.setAttribute("warning", "true");
+      options.learnMoreURL += "unsigned-addons";
     }
 
     let brandBundle = document.getElementById("bundle_brand");
     let brandShortName = brandBundle.getString("brandShortName");
 
     messageString = PluralForm.get(installInfo.installs.length, messageString);
     messageString = messageString.replace("#1", brandShortName);
     messageString = messageString.replace("#2", installInfo.installs.length);
@@ -305,18 +307,17 @@ const gXPInstallObserver = {
           args = [install.name];
         } else {
           error += "Incompatible";
           args = [brandShortName, Services.appinfo.version, install.name];
         }
 
         // Add Learn More link when refusing to install an unsigned add-on
         if (install.error == AddonManager.ERROR_SIGNEDSTATE_REQUIRED) {
-          options.learnMoreURL =
-            Services.prefs.getCharPref("xpinstall.signatures.infoURL");
+          options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons";
         }
 
         messageString = gNavigatorBundle.getFormattedString(error, args);
 
         PopupNotifications.show(browser, notificationID, messageString, anchorID,
                                 action, null, options);
 
         // Can't have multiple notifications with the same ID, so stop here.
--- a/browser/base/content/test/general/browser_parsable_script.js
+++ b/browser/base/content/test/general/browser_parsable_script.js
@@ -8,17 +8,27 @@
 const kWhitelist = new Set([
   /defaults\/profile\/prefs.js$/,
   /browser\/content\/browser\/places\/controller.js$/,
 ]);
 
 
 let moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");
 let {generateURIsFromDirTree} = Cu.import(moduleLocation, {});
-let {Reflect} = Cu.import("resource://gre/modules/reflect.jsm", {});
+
+// Normally we would use reflect.jsm to get Reflect.parse. However, if
+// we do that, then all the AST data is allocated in reflect.jsm's
+// zone. That exposes a bug in our GC. The GC collects reflect.jsm's
+// zone but not the zone in which our test code lives (since no new
+// data is being allocated in it). The cross-compartment wrappers in
+// our zone that point to the AST data never get collected, and so the
+// AST data itself is never collected. We need to GC both zones at
+// once to fix the problem.
+const init = Components.classes["@mozilla.org/jsreflect;1"].createInstance();
+init();
 
 /**
  * Check if an error should be ignored due to matching one of the whitelist
  * objects defined in kWhitelist
  *
  * @param uri the uri to check against the whitelist
  * @return true if the uri should be skipped, false otherwise.
  */
--- a/browser/components/places/tests/browser/browser_bookmarkProperties_addKeywordForThisSearch.js
+++ b/browser/components/places/tests/browser/browser_bookmarkProperties_addKeywordForThisSearch.js
@@ -5,19 +5,17 @@ const TEST_URL = "http://mochi.test:8888
 add_task(function* () {
   yield BrowserTestUtils.withNewTab({
     gBrowser,
     url: TEST_URL,
   }, function* (browser) {
     // We must wait for the context menu code to build metadata.
     yield openContextMenuForContentSelector(browser, 'form > input[name="search"]');
 
-    yield withBookmarksDialog(function*() {
-      AddKeywordForSearchField();
-    }, function* (dialogWin) {
+    yield withBookmarksDialog(AddKeywordForSearchField, function* (dialogWin) {
       let acceptBtn = dialogWin.document.documentElement.getButton("accept");
       ok(acceptBtn.disabled, "Accept button is disabled");
 
       let promiseKeywordNotification = promiseBookmarksNotification(
         "onItemChanged", (itemId, prop, isAnno, val) => prop == "keyword" && val =="kw");
 
       fillBookmarkTextField("editBMPanel_keywordField", "kw", dialogWin);
 
--- a/browser/components/places/tests/browser/head.js
+++ b/browser/components/places/tests/browser/head.js
@@ -305,17 +305,19 @@ let withBookmarksDialog = Task.async(fun
         waitForFocus(() => {
           resolve(win);
         }, win);
       });
     });
   });
 
   info("withBookmarksDialog: opening the dialog");
-  yield openFn();
+  // The dialog might be modal and could block our events loop, so executeSoon.
+  executeSoon(openFn);
+
   info("withBookmarksDialog: waiting for the dialog");
   let dialogWin = yield dialogPromise;
 
   // Ensure overlay is loaded
   ok(dialogWin.gEditItemOverlay.initialized, "EditItemOverlay is initialized");
 
   info("withBookmarksDialog: executing the task");
   try {
--- a/browser/devtools/framework/test/browser.ini
+++ b/browser/devtools/framework/test/browser.ini
@@ -27,16 +27,17 @@ support-files =
 [browser_target_remote.js]
 [browser_target_support.js]
 [browser_two_tabs.js]
 [browser_toolbox_dynamic_registration.js]
 [browser_toolbox_getpanelwhenready.js]
 [browser_toolbox_highlight.js]
 [browser_toolbox_hosts.js]
 [browser_toolbox_hosts_size.js]
+[browser_toolbox_minimize.js]
 [browser_toolbox_options.js]
 [browser_toolbox_options_disable_buttons.js]
 [browser_toolbox_options_disable_cache-01.js]
 skip-if = e10s # Bug 1030318
 [browser_toolbox_options_disable_cache-02.js]
 skip-if = e10s # Bug 1030318
 [browser_toolbox_options_disable_js.js]
 skip-if = e10s # Bug 1030318
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/test/browser_toolbox_minimize.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that when the toolbox is displayed in a bottom host, that host can be
+// minimized to just the tabbar height, and maximized again.
+// Also test that while minimized, switching to a tool, clicking on the
+// settings, or clicking on the selected tool's tab maximizes the toolbox again.
+// Finally test that the minimize button doesn't exist in other host types.
+
+const URL = "data:text/html;charset=utf8,test page";
+
+add_task(function*() {
+  info("Create a test tab and open the toolbox");
+  let tab = yield addTab(URL);
+  let target = TargetFactory.forTab(tab);
+  let toolbox = yield gDevTools.showToolbox(target, "webconsole");
+
+  let button = toolbox.doc.querySelector("#toolbox-dock-bottom-minimize");
+  ok(button, "The minimize button exists in the default bottom host");
+
+  info("Try to minimize the toolbox");
+  yield minimize(toolbox);
+  ok(parseInt(toolbox._host.frame.style.marginBottom, 10) < 0,
+     "The toolbox host has been hidden away with a negative-margin");
+
+  info("Try to maximize again the toolbox");
+  yield maximize(toolbox);
+  ok(parseInt(toolbox._host.frame.style.marginBottom, 10) == 0,
+     "The toolbox host is shown again");
+
+  info("Minimize again and switch to another tool");
+  yield minimize(toolbox);
+  let onMaximized = toolbox._host.once("maximized");
+  yield toolbox.selectTool("inspector");
+  yield onMaximized;
+
+  info("Minimize again and click on the tab of the current tool");
+  yield minimize(toolbox);
+  onMaximized = toolbox._host.once("maximized");
+  let tabButton = toolbox.doc.querySelector("#toolbox-tab-inspector");
+  EventUtils.synthesizeMouseAtCenter(tabButton, {}, toolbox.doc.defaultView);
+  yield onMaximized;
+
+  info("Minimize again and click on the settings tab");
+  yield minimize(toolbox);
+  onMaximized = toolbox._host.once("maximized");
+  let settingsButton = toolbox.doc.querySelector("#toolbox-tab-options");
+  EventUtils.synthesizeMouseAtCenter(settingsButton, {}, toolbox.doc.defaultView);
+  yield onMaximized;
+
+  info("Switch to a different host");
+  yield toolbox.switchHost(devtools.Toolbox.HostType.SIDE);
+  button = toolbox.doc.querySelector("#toolbox-dock-bottom-minimize");
+  ok(!button, "The minimize button doesn't exist in the side host");
+
+  Services.prefs.clearUserPref("devtools.toolbox.host");
+  yield toolbox.destroy();
+  gBrowser.removeCurrentTab();
+});
+
+function* minimize(toolbox) {
+  let button = toolbox.doc.querySelector("#toolbox-dock-bottom-minimize");
+  let onMinimized = toolbox._host.once("minimized");
+  EventUtils.synthesizeMouseAtCenter(button, {}, toolbox.doc.defaultView);
+  yield onMinimized;
+}
+
+function* maximize(toolbox) {
+  let button = toolbox.doc.querySelector("#toolbox-dock-bottom-minimize");
+  let onMaximized = toolbox._host.once("maximized");
+  EventUtils.synthesizeMouseAtCenter(button, {}, toolbox.doc.defaultView);
+  yield onMaximized;
+}
--- a/browser/devtools/framework/toolbox-hosts.js
+++ b/browser/devtools/framework/toolbox-hosts.js
@@ -1,11 +1,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/. */
+/* globals DOMHelpers, Services */
 
 "use strict";
 
 const {Cu} = require("chrome");
 const EventEmitter = require("devtools/toolkit/event-emitter");
 const {Promise: promise} = require("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource:///modules/devtools/DOMHelpers.jsm");
@@ -44,17 +45,17 @@ function BottomHost(hostTab) {
 BottomHost.prototype = {
   type: "bottom",
 
   heightPref: "devtools.toolbox.footer.height",
 
   /**
    * Create a box at the bottom of the host tab.
    */
-  create: function BH_create() {
+  create: function() {
     let deferred = promise.defer();
 
     let gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser;
     let ownerDocument = gBrowser.ownerDocument;
     this._nbox = gBrowser.getNotificationBox(this.hostTab.linkedBrowser);
 
     this._splitter = ownerDocument.createElement("splitter");
     this._splitter.setAttribute("class", "devtools-horizontal-splitter");
@@ -85,43 +86,90 @@ BottomHost.prototype = {
     focusTab(this.hostTab);
 
     return deferred.promise;
   },
 
   /**
    * Raise the host.
    */
-  raise: function BH_raise() {
+  raise: function() {
     focusTab(this.hostTab);
   },
 
   /**
+   * Minimize this host so that only the toolbox tabbar remains visible.
+   * @param {Number} height The height to minimize to. Defaults to 0, which
+   * means that the toolbox won't be visible at all once minimized.
+   */
+  minimize: function(height=0) {
+    if (this.isMinimized) {
+      return;
+    }
+    this.isMinimized = true;
+
+    this.frame.style.marginBottom = -this.frame.height + height + "px";
+    this._splitter.classList.add("disabled");
+
+    let onTransitionEnd = () => {
+      this.frame.removeEventListener("transitionend", onTransitionEnd);
+      this.emit("minimized");
+    };
+    this.frame.addEventListener("transitionend", onTransitionEnd);
+  },
+
+  /**
+   * If the host was minimized before, maximize it again (the host will be
+   * maximized to the height it previously had).
+   */
+  maximize: function() {
+    if (!this.isMinimized) {
+      return;
+    }
+    this.isMinimized = false;
+
+    this.frame.style.marginBottom = "0";
+    this._splitter.classList.remove("disabled");
+
+    let onTransitionEnd = () => {
+      this.frame.removeEventListener("transitionend", onTransitionEnd);
+      this.emit("maximized");
+    };
+    this.frame.addEventListener("transitionend", onTransitionEnd);
+  },
+
+  /**
+   * Toggle the minimize mode.
+   * @param {Number} minHeight The height to minimize to.
+   */
+  toggleMinimizeMode: function(minHeight) {
+    this.isMinimized ? this.maximize() : this.minimize(minHeight);
+  },
+
+  /**
    * Set the toolbox title.
+   * Nothing to do for this host type.
    */
-  setTitle: function BH_setTitle(title) {
-    // Nothing to do for this host type.
-  },
+  setTitle: function() {},
 
   /**
    * Destroy the bottom dock.
    */
-  destroy: function BH_destroy() {
+  destroy: function() {
     if (!this._destroyed) {
       this._destroyed = true;
 
       Services.prefs.setIntPref(this.heightPref, this.frame.height);
       this._nbox.removeChild(this._splitter);
       this._nbox.removeChild(this.frame);
     }
 
     return promise.resolve(null);
   }
-}
-
+};
 
 /**
  * Host object for the in-browser sidebar
  */
 function SidebarHost(hostTab) {
   this.hostTab = hostTab;
 
   EventEmitter.decorate(this);
@@ -130,17 +178,17 @@ function SidebarHost(hostTab) {
 SidebarHost.prototype = {
   type: "side",
 
   widthPref: "devtools.toolbox.sidebar.width",
 
   /**
    * Create a box in the sidebar of the host tab.
    */
-  create: function SH_create() {
+  create: function() {
     let deferred = promise.defer();
 
     let gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser;
     let ownerDocument = gBrowser.ownerDocument;
     this._sidebar = gBrowser.getSidebarContainer(this.hostTab.linkedBrowser);
 
     this._splitter = ownerDocument.createElement("splitter");
     this._splitter.setAttribute("class", "devtools-side-splitter");
@@ -170,42 +218,41 @@ SidebarHost.prototype = {
     focusTab(this.hostTab);
 
     return deferred.promise;
   },
 
   /**
    * Raise the host.
    */
-  raise: function SH_raise() {
+  raise: function() {
     focusTab(this.hostTab);
   },
 
   /**
    * Set the toolbox title.
+   * Nothing to do for this host type.
    */
-  setTitle: function SH_setTitle(title) {
-    // Nothing to do for this host type.
-  },
+  setTitle: function() {},
 
   /**
    * Destroy the sidebar.
    */
-  destroy: function SH_destroy() {
+  destroy: function() {
     if (!this._destroyed) {
       this._destroyed = true;
 
       Services.prefs.setIntPref(this.widthPref, this.frame.width);
       this._sidebar.removeChild(this._splitter);
       this._sidebar.removeChild(this.frame);
     }
 
     return promise.resolve(null);
   }
-}
+};
 
 /**
  * Host object for the toolbox in a separate window
  */
 function WindowHost() {
   this._boundUnload = this._boundUnload.bind(this);
 
   EventEmitter.decorate(this);
@@ -214,24 +261,24 @@ function WindowHost() {
 WindowHost.prototype = {
   type: "window",
 
   WINDOW_URL: "chrome://browser/content/devtools/framework/toolbox-window.xul",
 
   /**
    * Create a new xul window to contain the toolbox.
    */
-  create: function WH_create() {
+  create: function() {
     let deferred = promise.defer();
 
     let flags = "chrome,centerscreen,resizable,dialog=no";
     let win = Services.ww.openWindow(null, this.WINDOW_URL, "_blank",
                                      flags, null);
 
-    let frameLoad = (event) => {
+    let frameLoad = () => {
       win.removeEventListener("load", frameLoad, true);
       win.focus();
       this.frame = win.document.getElementById("toolbox-iframe");
       this.emit("ready", this.frame);
 
       deferred.resolve(this.frame);
     };
 
@@ -253,31 +300,31 @@ WindowHost.prototype = {
     this._window.removeEventListener("unload", this._boundUnload);
 
     this.emit("window-closed");
   },
 
   /**
    * Raise the host.
    */
-  raise: function RH_raise() {
+  raise: function() {
     this._window.focus();
   },
 
   /**
    * Set the toolbox title.
    */
-  setTitle: function WH_setTitle(title) {
+  setTitle: function(title) {
     this._window.document.title = title;
   },
 
   /**
    * Destroy the window.
    */
-  destroy: function WH_destroy() {
+  destroy: function() {
     if (!this._destroyed) {
       this._destroyed = true;
 
       this._window.removeEventListener("unload", this._boundUnload);
       this._window.close();
     }
 
     return promise.resolve(null);
@@ -291,62 +338,62 @@ function CustomHost(hostTab, options) {
   this.frame = options.customIframe;
   this.uid = options.uid;
   EventEmitter.decorate(this);
 }
 
 CustomHost.prototype = {
   type: "custom",
 
-  _sendMessageToTopWindow: function CH__sendMessageToTopWindow(msg, data) {
+  _sendMessageToTopWindow: function(msg, data) {
     // It's up to the custom frame owner (parent window) to honor
     // "close" or "raise" instructions.
     let topWindow = this.frame.ownerDocument.defaultView;
     if (!topWindow) {
       return;
     }
-    let json = {name:"toolbox-" + msg, uid: this.uid};
+    let json = {name: "toolbox-" + msg, uid: this.uid};
     if (data) {
       json.data = data;
     }
     topWindow.postMessage(JSON.stringify(json), "*");
   },
 
   /**
    * Create a new xul window to contain the toolbox.
    */
-  create: function CH_create() {
+  create: function() {
     return promise.resolve(this.frame);
   },
 
   /**
    * Raise the host.
    */
-  raise: function CH_raise() {
+  raise: function() {
     this._sendMessageToTopWindow("raise");
   },
 
   /**
    * Set the toolbox title.
    */
-  setTitle: function CH_setTitle(title) {
+  setTitle: function(title) {
     this._sendMessageToTopWindow("title", { value: title });
   },
 
   /**
    * Destroy the window.
    */
-  destroy: function WH_destroy() {
+  destroy: function() {
     if (!this._destroyed) {
       this._destroyed = true;
       this._sendMessageToTopWindow("close");
     }
     return promise.resolve(null);
   }
-}
+};
 
 /**
  *  Switch to the given tab in a browser and focus the browser window
  */
 function focusTab(tab) {
   let browserWindow = tab.ownerDocument.defaultView;
   browserWindow.focus();
   browserWindow.gBrowser.selectedTab = tab;
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -1,11 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* globals gDevTools, DOMHelpers, toolboxStrings, InspectorFront, Selection,
+   getPerformanceActorsConnection, CommandUtils, DevToolsUtils, screenManager,
+   oscpu, Hosts, is64Bit */
 
 "use strict";
 
 const MAX_ORDINAL = 99;
 const ZOOM_PREF = "devtools.toolbox.zoomValue";
 const SPLITCONSOLE_ENABLED_PREF = "devtools.toolbox.splitconsoleEnabled";
 const SPLITCONSOLE_HEIGHT_PREF = "devtools.toolbox.splitconsoleHeight";
 const MIN_ZOOM = 0.5;
@@ -123,16 +126,20 @@ function Toolbox(target, selectedTool, h
   this.highlighterUtils = getHighlighterUtils(this);
   this._highlighterReady = this._highlighterReady.bind(this);
   this._highlighterHidden = this._highlighterHidden.bind(this);
   this._prefChanged = this._prefChanged.bind(this);
   this._saveSplitConsoleHeight = this._saveSplitConsoleHeight.bind(this);
   this._onFocus = this._onFocus.bind(this);
   this._showDevEditionPromo = this._showDevEditionPromo.bind(this);
   this._updateTextboxMenuItems = this._updateTextboxMenuItems.bind(this);
+  this._onBottomHostMinimized = this._onBottomHostMinimized.bind(this);
+  this._onBottomHostMaximized = this._onBottomHostMaximized.bind(this);
+  this._onToolSelectWhileMinimized = this._onToolSelectWhileMinimized.bind(this);
+  this._onBottomHostWillChange = this._onBottomHostWillChange.bind(this);
 
   this._target.on("close", this.destroy);
 
   if (!hostType) {
     hostType = Services.prefs.getCharPref(this._prefs.LAST_HOST);
   }
   if (!selectedTool) {
     selectedTool = Services.prefs.getCharPref(this._prefs.LAST_TOOL);
@@ -309,17 +316,17 @@ Toolbox.prototype = {
    */
   get splitConsole() {
     return this._splitConsole;
   },
 
   /**
    * Open the toolbox
    */
-  open: function () {
+  open: function() {
     return Task.spawn(function*() {
       let iframe = yield this._host.create();
       let domReady = promise.defer();
 
       // Load the toolbox-level actor fronts and utilities now
       yield this._target.makeRemote();
       iframe.setAttribute("src", this._URL);
       iframe.setAttribute("aria-label", toolboxStrings("toolbox.label"));
@@ -386,23 +393,25 @@ Toolbox.prototype = {
 
       yield promise.all([
         splitConsolePromise,
         buttonsPromise,
         framesPromise
       ]);
 
       // Lazily connect to the profiler here and don't wait for it to complete,
-      // used to intercept console.profile calls before the performance tools are open.
+      // used to intercept console.profile calls before the performance tools
+      // are open.
       let profilerReady = this._connectProfiler();
 
-      // However, while testing, we must wait for the performance connection to finish,
-      // as most tests shut down without waiting for a toolbox destruction event,
-      // resulting in the shared profiler connection being opened and closed
-      // outside of the test that originally opened the toolbox.
+      // However, while testing, we must wait for the performance connection to
+      // finish, as most tests shut down without waiting for a toolbox
+      // destruction event, resulting in the shared profiler connection being
+      // opened and closed outside of the test that originally opened the
+      // toolbox.
       if (gDevTools.testing) {
         yield profilerReady;
       }
 
       this.emit("ready");
     }.bind(this)).then(null, console.error.bind(console));
   },
 
@@ -424,23 +433,23 @@ Toolbox.prototype = {
    * @param  {Object} data
    *         {
    *           newValue: The new value
    *           oldValue:  The old value
    *           pref: The name of the preference that has changed
    *         }
    */
   _prefChanged: function(event, data) {
-    switch(data.pref) {
-    case "devtools.cache.disabled":
-      this._applyCacheSettings();
-      break;
-    case "devtools.serviceWorkers.testing.enabled":
-      this._applyServiceWorkersTestingSettings();
-      break;
+    switch (data.pref) {
+      case "devtools.cache.disabled":
+        this._applyCacheSettings();
+        break;
+      case "devtools.serviceWorkers.testing.enabled":
+        this._applyServiceWorkersTestingSettings();
+        break;
     }
   },
 
   _buildOptions: function() {
     let key = this.doc.getElementById("toolbox-options-key");
     key.addEventListener("command", () => {
       this.selectTool("options");
     }, true);
@@ -623,42 +632,45 @@ Toolbox.prototype = {
 
       if (toolDefinition.key.startsWith("VK_")) {
         key.setAttribute("keycode", toolDefinition.key);
       } else {
         key.setAttribute("key", toolDefinition.key);
       }
 
       key.setAttribute("modifiers", toolDefinition.modifiers);
-      key.setAttribute("oncommand", "void(0);"); // needed. See bug 371900
+      // needed. See bug 371900
+      key.setAttribute("oncommand", "void(0);");
       key.addEventListener("command", () => {
         this.selectTool(toolId).then(() => this.fireCustomKey(toolId));
       }, true);
       doc.getElementById("toolbox-keyset").appendChild(key);
     }
 
     // Add key for toggling the browser console from the detached window
     if (!doc.getElementById("key_browserconsole")) {
       let key = doc.createElement("key");
       key.id = "key_browserconsole";
 
       key.setAttribute("key", toolboxStrings("browserConsoleCmd.commandkey"));
       key.setAttribute("modifiers", "accel,shift");
-      key.setAttribute("oncommand", "void(0)"); // needed. See bug 371900
+      // needed. See bug 371900
+      key.setAttribute("oncommand", "void(0)");
       key.addEventListener("command", () => {
         HUDService.toggleBrowserConsole();
       }, true);
       doc.getElementById("toolbox-keyset").appendChild(key);
     }
   },
 
   /**
-   * Handle any custom key events.  Returns true if there was a custom key binding run
-   * @param {string} toolId
-   *        Which tool to run the command on (skip if not current)
+   * Handle any custom key events.  Returns true if there was a custom key
+   * binding run.
+   * @param {string} toolId Which tool to run the command on (skip if not
+   * current)
    */
   fireCustomKey: function(toolId) {
     let toolDefinition = gDevTools.getToolDefinition(toolId);
 
     if (toolDefinition.onkey &&
         ((this.currentToolId === toolId) ||
           (toolId == "webconsole" && this.splitConsole))) {
       toolDefinition.onkey(this.getCurrentPanel(), this);
@@ -675,16 +687,42 @@ Toolbox.prototype = {
     while (dockBox.firstChild) {
       dockBox.removeChild(dockBox.firstChild);
     }
 
     if (!this._target.isLocalTab) {
       return;
     }
 
+    // Bottom-type host can be minimized, add a button for this.
+    if (this.hostType == Toolbox.HostType.BOTTOM) {
+      let minimizeBtn = this.doc.createElement("toolbarbutton");
+      minimizeBtn.id = "toolbox-dock-bottom-minimize";
+      minimizeBtn.className = "maximized";
+      minimizeBtn.setAttribute("tooltiptext",
+        toolboxStrings("toolboxDockButtons.bottom.minimize"));
+      // Calculate the height to which the host should be minimized so the
+      // tabbar is still visible.
+      let toolbarHeight = this.doc.querySelector(".devtools-tabbar")
+                                  .getBoxQuads({box: "content"})[0]
+                                  .bounds.height;
+      minimizeBtn.addEventListener("command", () => {
+        this._host.toggleMinimizeMode(toolbarHeight);
+      });
+      dockBox.appendChild(minimizeBtn);
+
+      // Update the label and icon when the state changes.
+      this._host.on("minimized", this._onBottomHostMinimized);
+      this._host.on("maximized", this._onBottomHostMaximized);
+      // Maximize again when a tool gets selected.
+      this.on("before-select", this._onToolSelectWhileMinimized);
+      // Maximize and stop listening before the host type changes.
+      this.once("host-will-change", this._onBottomHostWillChange);
+    }
+
     if (this.hostType == Toolbox.HostType.WINDOW) {
       this.closeButton.setAttribute("hidden", "true");
     } else {
       this.closeButton.removeAttribute("hidden");
     }
 
     let sideEnabled = Services.prefs.getBoolPref(this._prefs.SIDE_ENABLED);
 
@@ -704,16 +742,42 @@ Toolbox.prototype = {
       button.addEventListener("command", () => {
         this.switchHost(position);
       });
 
       dockBox.appendChild(button);
     }
   },
 
+  _onBottomHostMinimized: function() {
+    let btn = this.doc.querySelector("#toolbox-dock-bottom-minimize");
+    btn.className = "minimized";
+    btn.setAttribute("tooltiptext",
+      toolboxStrings("toolboxDockButtons.bottom.maximize"));
+  },
+
+  _onBottomHostMaximized: function() {
+    let btn = this.doc.querySelector("#toolbox-dock-bottom-minimize");
+    btn.className = "maximized";
+    btn.setAttribute("tooltiptext",
+      toolboxStrings("toolboxDockButtons.bottom.minimize"));
+  },
+
+  _onToolSelectWhileMinimized: function() {
+    this._host.maximize();
+  },
+
+  _onBottomHostWillChange: function() {
+    this._host.maximize();
+
+    this._host.off("minimized", this._onBottomHostMinimized);
+    this._host.off("maximized", this._onBottomHostMaximized);
+    this.off("before-select", this._onToolSelectWhileMinimized);
+  },
+
   /**
    * Add tabs to the toolbox UI for registered tools
    */
   _buildTabs: function() {
     for (let definition of gDevTools.getToolDefinitionArray()) {
       this._buildTabForTool(definition);
     }
   },
@@ -830,29 +894,30 @@ Toolbox.prototype = {
         return false;
       }
 
       return {
         id: options.id,
         button: button,
         label: button.getAttribute("tooltiptext"),
         visibilityswitch: "devtools." + options.id + ".enabled",
-        isTargetSupported: options.isTargetSupported ? options.isTargetSupported
-                                                     : target => target.isLocalTab
+        isTargetSupported: options.isTargetSupported
+                           ? options.isTargetSupported
+                           : target => target.isLocalTab
       };
     }).filter(button=>button);
   },
 
   /**
    * Ensure the visibility of each toolbox button matches the
    * preference value.  Simply hide buttons that are preffed off.
    */
   setToolboxButtonsVisibility: function() {
     this.toolboxButtons.forEach(buttonSpec => {
-      let { visibilityswitch, id, button, isTargetSupported } = buttonSpec;
+      let { visibilityswitch, button, isTargetSupported } = buttonSpec;
       let on = true;
       try {
         on = Services.prefs.getBoolPref(visibilityswitch);
       } catch (ex) { }
 
       on = on && isTargetSupported(this.target);
 
       if (button) {
@@ -934,17 +999,17 @@ Toolbox.prototype = {
       image.className = "highlighted-icon";
       image.setAttribute("src",
                          toolDefinition.highlightedicon || toolDefinition.icon);
       radio.appendChild(image);
     }
 
     if (toolDefinition.label && !toolDefinition.iconOnly) {
       let label = this.doc.createElement("label");
-      label.setAttribute("value", toolDefinition.label)
+      label.setAttribute("value", toolDefinition.label);
       label.setAttribute("crop", "end");
       label.setAttribute("flex", "1");
       radio.appendChild(label);
       radio.setAttribute("flex", "1");
     }
 
     if (!toolDefinition.bgTheme) {
       toolDefinition.bgTheme = "theme-toolbar";
@@ -1013,17 +1078,17 @@ Toolbox.prototype = {
           deferred.resolve(panel);
         });
       }
       return deferred.promise;
     }
 
     let definition = gDevTools.getToolDefinition(id);
     if (!definition) {
-      deferred.reject(new Error("no such tool id "+id));
+      deferred.reject(new Error("no such tool id " + id));
       return deferred.promise;
     }
 
     iframe = this.doc.createElement("iframe");
     iframe.className = "toolbox-panel-iframe";
     iframe.id = "toolbox-panel-iframe-" + id;
     iframe.setAttribute("flex", 1);
     iframe.setAttribute("forceOwnRefreshDriver", "");
@@ -1107,30 +1172,32 @@ Toolbox.prototype = {
     // if the (xul chrome) iframe is loaded in a content docshell.
     if (iframe.contentWindow) {
       let domHelper = new DOMHelpers(iframe.contentWindow);
       domHelper.onceDOMReady(onLoad);
     } else {
       let callback = () => {
         iframe.removeEventListener("DOMContentLoaded", callback);
         onLoad();
-      }
+      };
       iframe.addEventListener("DOMContentLoaded", callback);
     }
 
     return deferred.promise;
   },
 
   /**
    * Switch to the tool with the given id
    *
    * @param {string} id
    *        The id of the tool to switch to
    */
   selectTool: function(id) {
+    this.emit("before-select", id);
+
     let selected = this.doc.querySelector(".devtools-tab[selected]");
     if (selected) {
       selected.removeAttribute("selected");
       selected.setAttribute("aria-selected", "false");
     }
 
     let tab = this.doc.getElementById("toolbox-tab-" + id);
     tab.setAttribute("selected", "true");
@@ -1359,43 +1426,43 @@ Toolbox.prototype = {
     let title = toolboxStrings("toolbox.titleTemplate",
                                toolName,
                                this.target.isAddon ?
                                this.target.name :
                                this.target.url || this.target.name);
     this._host.setTitle(title);
   },
 
-  _listFrames: function (event) {
+  _listFrames: function(event) {
     if (!this._target.activeTab || !this._target.activeTab.traits.frames) {
       // We are not targetting a regular TabActor
       // it can be either an addon or browser toolbox actor
       return promise.resolve();
     }
     let packet = {
       to: this._target.form.actor,
       type: "listFrames"
     };
     return this._target.client.request(packet, resp => {
       this._updateFrames(null, { frames: resp.frames });
     });
   },
 
-  selectFrame: function (event) {
+  selectFrame: function(event) {
     let windowId = event.target.getAttribute("data-window-id");
     let packet = {
       to: this._target.form.actor,
       type: "switchToFrame",
       windowId: windowId
     };
     this._target.client.request(packet);
     // Wait for frameUpdate event to update the UI
   },
 
-  _updateFrames: function (event, data) {
+  _updateFrames: function(event, data) {
     if (!Services.prefs.getBoolPref("devtools.command-button-frames.enabled")) {
       return;
     }
 
     // We may receive this event before the toolbox is ready.
     if (!this.isReady) {
       return;
     }
@@ -1415,17 +1482,17 @@ Toolbox.prototype = {
       }
       // Toggle the toolbarbutton if we selected a non top-level frame
       if (item.hasAttribute("data-parent-id")) {
         menu.setAttribute("checked", "true");
       } else {
         menu.removeAttribute("checked");
       }
       // Uncheck the previously selected frame
-      let selected = menu.querySelector("menuitem[checked=true]")
+      let selected = menu.querySelector("menuitem[checked=true]");
       if (selected) {
         selected.removeAttribute("checked");
       }
       // Check the new one
       item.setAttribute("checked", "true");
     } else if (data.frames) {
       data.frames.forEach(win => {
         let item = menu.querySelector("menuitem[data-window-id=\"" + win.id + "\"]");
@@ -1453,18 +1520,18 @@ Toolbox.prototype = {
       });
     }
   },
 
   /**
    * Create a host object based on the given host type.
    *
    * Warning: some hosts require that the toolbox target provides a reference to
-   * the attached tab. Not all Targets have a tab property - make sure you correctly
-   * mix and match hosts and targets.
+   * the attached tab. Not all Targets have a tab property - make sure you
+   * correctly mix and match hosts and targets.
    *
    * @param {string} hostType
    *        The host type of the new host object
    *
    * @return {Host} host
    *        The created host object
    */
   _createHost: function(hostType, options) {
@@ -1485,16 +1552,18 @@ Toolbox.prototype = {
    * @param {string} hostType
    *        The host type of the new host object
    */
   switchHost: function(hostType) {
     if (hostType == this._host.type || !this._target.isLocalTab) {
       return null;
     }
 
+    this.emit("host-will-change", hostType);
+
     let newHost = this._createHost(hostType);
     return newHost.create().then(iframe => {
       // change toolbox document's parent to the new host
       iframe.QueryInterface(Ci.nsIFrameLoaderOwner);
       iframe.swapFrameLoaders(this.frame);
 
       // See bug 1022726, most probably because of swapFrameLoaders we need to
       // first focus the window here, and then once again further below to make
@@ -1665,42 +1734,82 @@ Toolbox.prototype = {
 
   _getScreenDimensions: function() {
     let width = {};
     let height = {};
 
     screenManager.primaryScreen.GetRect({}, {}, width, height);
     let dims = width.value + "x" + height.value;
 
-    if (width.value < 800 || height.value < 600) return 0;
-    if (dims === "800x600")   return 1;
-    if (dims === "1024x768")  return 2;
-    if (dims === "1280x800")  return 3;
-    if (dims === "1280x1024") return 4;
-    if (dims === "1366x768")  return 5;
-    if (dims === "1440x900")  return 6;
-    if (dims === "1920x1080") return 7;
-    if (dims === "2560×1440") return 8;
-    if (dims === "2560×1600") return 9;
-    if (dims === "2880x1800") return 10;
-    if (width.value > 2880 || height.value > 1800) return 12;
+    if (width.value < 800 || height.value < 600) {
+      return 0;
+    }
+    if (dims === "800x600") {
+      return 1;
+    }
+    if (dims === "1024x768") {
+      return 2;
+    }
+    if (dims === "1280x800") {
+      return 3;
+    }
+    if (dims === "1280x1024") {
+      return 4;
+    }
+    if (dims === "1366x768") {
+      return 5;
+    }
+    if (dims === "1440x900") {
+      return 6;
+    }
+    if (dims === "1920x1080") {
+      return 7;
+    }
+    if (dims === "2560×1440") {
+      return 8;
+    }
+    if (dims === "2560×1600") {
+      return 9;
+    }
+    if (dims === "2880x1800") {
+      return 10;
+    }
+    if (width.value > 2880 || height.value > 1800) {
+      return 12;
+    }
 
-    return 11; // Other dimension such as a VM.
+    // Other dimension such as a VM.
+    return 11;
   },
 
   _getOsCpu: function() {
-    if (oscpu.includes("NT 5.1") || oscpu.includes("NT 5.2")) return 0;
-    if (oscpu.includes("NT 6.0")) return 1;
-    if (oscpu.includes("NT 6.1")) return 2;
-    if (oscpu.includes("NT 6.2")) return 3;
-    if (oscpu.includes("NT 6.3")) return 4;
-    if (oscpu.includes("OS X"))   return 5;
-    if (oscpu.includes("Linux"))  return 6;
+    if (oscpu.includes("NT 5.1") || oscpu.includes("NT 5.2")) {
+      return 0;
+    }
+    if (oscpu.includes("NT 6.0")) {
+      return 1;
+    }
+    if (oscpu.includes("NT 6.1")) {
+      return 2;
+    }
+    if (oscpu.includes("NT 6.2")) {
+      return 3;
+    }
+    if (oscpu.includes("NT 6.3")) {
+      return 4;
+    }
+    if (oscpu.includes("OS X")) {
+      return 5;
+    }
+    if (oscpu.includes("Linux")) {
+      return 6;
+    }
 
-    return 12; // Other OS.
+    // Other OS.
+    return 12;
   },
 
   /**
    * Destroy the current host, and remove event listeners from its frame.
    *
    * @return {promise} to be resolved when the host is destroyed.
    */
   destroyHost: function() {
@@ -1864,18 +1973,18 @@ Toolbox.prototype = {
     showDoorhanger({ window, type: "deveditionpromo" });
   },
 
   /**
    * Enable / disable necessary textbox menu items using globalOverlay.js.
    */
   _updateTextboxMenuItems: function() {
     let window = this.doc.defaultView;
-    ['cmd_undo', 'cmd_delete', 'cmd_cut',
-     'cmd_copy', 'cmd_paste','cmd_selectAll'].forEach(window.goUpdateCommand);
+    ["cmd_undo", "cmd_delete", "cmd_cut",
+     "cmd_copy", "cmd_paste", "cmd_selectAll"].forEach(window.goUpdateCommand);
   },
 
   getPerformanceActorsConnection: function() {
     if (!this._performanceConnection) {
       this._performanceConnection = getPerformanceActorsConnection(this.target);
     }
     return this._performanceConnection;
   },
@@ -1915,41 +2024,41 @@ Toolbox.prototype = {
   get gViewSourceUtils() {
     return this.frame.contentWindow.gViewSourceUtils;
   },
 
   /**
    * Opens source in style editor. Falls back to plain "view-source:".
    * @see browser/devtools/shared/source-utils.js
    */
-  viewSourceInStyleEditor: function (sourceURL, sourceLine) {
+  viewSourceInStyleEditor: function(sourceURL, sourceLine) {
     return sourceUtils.viewSourceInStyleEditor(this, sourceURL, sourceLine);
   },
 
   /**
    * Opens source in debugger. Falls back to plain "view-source:".
    * @see browser/devtools/shared/source-utils.js
    */
-  viewSourceInDebugger: function (sourceURL, sourceLine) {
+  viewSourceInDebugger: function(sourceURL, sourceLine) {
     return sourceUtils.viewSourceInDebugger(this, sourceURL, sourceLine);
   },
 
   /**
    * Opens source in scratchpad. Falls back to plain "view-source:".
    * TODO The `sourceURL` for scratchpad instances are like `Scratchpad/1`.
    * If instances are scoped one-per-browser-window, then we should be able
    * to infer the URL from this toolbox, or use the built in scratchpad IN
    * the toolbox.
    *
    * @see browser/devtools/shared/source-utils.js
    */
-  viewSourceInScratchpad: function (sourceURL, sourceLine) {
+  viewSourceInScratchpad: function(sourceURL, sourceLine) {
     return sourceUtils.viewSourceInScratchpad(sourceURL, sourceLine);
   },
 
   /**
    * Opens source in plain "view-source:".
    * @see browser/devtools/shared/source-utils.js
    */
-  viewSource: function (sourceURL, sourceLine) {
+  viewSource: function(sourceURL, sourceLine) {
     return sourceUtils.viewSource(this, sourceURL, sourceLine);
   },
 };
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -432,16 +432,17 @@
 @RESPATH@/components/nsUpdateService.manifest
 @RESPATH@/components/nsUpdateService.js
 @RESPATH@/components/nsUpdateServiceStub.js
 #endif
 @RESPATH@/components/nsUpdateTimerManager.manifest
 @RESPATH@/components/nsUpdateTimerManager.js
 @RESPATH@/components/addoncompat.manifest
 @RESPATH@/components/multiprocessShims.js
+@RESPATH@/components/defaultShims.js
 @RESPATH@/components/remoteTagService.js
 @RESPATH@/components/pluginGlue.manifest
 @RESPATH@/components/ProcessSingleton.manifest
 @RESPATH@/components/MainProcessSingleton.js
 @RESPATH@/components/ContentProcessSingleton.js
 @RESPATH@/browser/components/nsSessionStore.manifest
 @RESPATH@/browser/components/nsSessionStartup.js
 @RESPATH@/browser/components/nsSessionStore.js
--- a/browser/locales/en-US/chrome/browser/devtools/toolbox.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/toolbox.properties
@@ -1,16 +1,30 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 toolboxDockButtons.bottom.tooltip=Dock to bottom of browser window
 toolboxDockButtons.side.tooltip=Dock to side of browser window
 toolboxDockButtons.window.tooltip=Show in separate window
 
+# LOCALIZATION NOTE (toolboxDockButtons.bottom.minimize): This string is shown
+# as a tooltip that appears in the toolbox when it is in "bottom host" mode and
+# when hovering over the minimize button in the toolbar. When clicked, the
+# button minimizes the toolbox so that just the toolbar is visible at the
+# bottom.
+toolboxDockButtons.bottom.minimize=Minimize the toolbox
+
+# LOCALIZATION NOTE (toolboxDockButtons.bottom.maximize): This string is shown
+# as a tooltip that appears in the toolbox when it is in "bottom host" mode and
+# when hovering over the maximize button in the toolbar. When clicked, the
+# button maximizes the toolbox again (if it had been minimized before) so that
+# the whole toolbox is visible again.
+toolboxDockButtons.bottom.maximize=Maximize the toolbox
+
 # LOCALIZATION NOTE (toolboxToggleButton.errors): Semi-colon list of plural
 # forms.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 number of errors in the current web page
 toolboxToggleButton.errors=#1 error;#1 errors
 
 # LOCALIZATION NOTE (toolboxToggleButton.warnings): Semi-colon list of plural
 # forms.
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -372,16 +372,18 @@ browser.jar:
   skin/classic/browser/devtools/responsiveui-rotate@2x.png       (../shared/devtools/images/responsivemode/responsiveui-rotate@2x.png)
   skin/classic/browser/devtools/responsiveui-touch.png           (../shared/devtools/images/responsivemode/responsiveui-touch.png)
   skin/classic/browser/devtools/responsiveui-touch@2x.png        (../shared/devtools/images/responsivemode/responsiveui-touch@2x.png)
   skin/classic/browser/devtools/responsiveui-screenshot.png      (../shared/devtools/images/responsivemode/responsiveui-screenshot.png)
   skin/classic/browser/devtools/responsiveui-screenshot@2x.png   (../shared/devtools/images/responsivemode/responsiveui-screenshot@2x.png)
   skin/classic/browser/devtools/toggle-tools.png          (../shared/devtools/images/toggle-tools.png)
   skin/classic/browser/devtools/toggle-tools@2x.png       (../shared/devtools/images/toggle-tools@2x.png)
   skin/classic/browser/devtools/dock-bottom@2x.png        (../shared/devtools/images/dock-bottom@2x.png)
+  skin/classic/browser/devtools/dock-bottom-minimize@2x.png (../shared/devtools/images/dock-bottom-minimize@2x.png)
+  skin/classic/browser/devtools/dock-bottom-maximize@2x.png (../shared/devtools/images/dock-bottom-maximize@2x.png)
   skin/classic/browser/devtools/dock-side@2x.png          (../shared/devtools/images/dock-side@2x.png)
   skin/classic/browser/devtools/floating-scrollbars.css   (devtools/floating-scrollbars.css)
   skin/classic/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.css)
 * skin/classic/browser/devtools/inspector.css               (../shared/devtools/inspector.css)
   skin/classic/browser/devtools/profiler-stopwatch.svg      (../shared/devtools/images/profiler-stopwatch.svg)
   skin/classic/browser/devtools/profiler-stopwatch-checked.svg      (../shared/devtools/images/profiler-stopwatch-checked.svg)
   skin/classic/browser/devtools/tool-options.svg            (../shared/devtools/images/tool-options.svg)
   skin/classic/browser/devtools/tool-webconsole.svg         (../shared/devtools/images/tool-webconsole.svg)
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -490,16 +490,18 @@ browser.jar:
   skin/classic/browser/devtools/responsiveui-rotate@2x.png       (../shared/devtools/images/responsivemode/responsiveui-rotate@2x.png)
   skin/classic/browser/devtools/responsiveui-touch.png           (../shared/devtools/images/responsivemode/responsiveui-touch.png)
   skin/classic/browser/devtools/responsiveui-touch@2x.png        (../shared/devtools/images/responsivemode/responsiveui-touch@2x.png)
   skin/classic/browser/devtools/responsiveui-screenshot.png      (../shared/devtools/images/responsivemode/responsiveui-screenshot.png)
   skin/classic/browser/devtools/responsiveui-screenshot@2x.png   (../shared/devtools/images/responsivemode/responsiveui-screenshot@2x.png)
   skin/classic/browser/devtools/toggle-tools.png            (../shared/devtools/images/toggle-tools.png)
   skin/classic/browser/devtools/toggle-tools@2x.png         (../shared/devtools/images/toggle-tools@2x.png)
   skin/classic/browser/devtools/dock-bottom@2x.png          (../shared/devtools/images/dock-bottom@2x.png)
+  skin/classic/browser/devtools/dock-bottom-minimize@2x.png (../shared/devtools/images/dock-bottom-minimize@2x.png)
+  skin/classic/browser/devtools/dock-bottom-maximize@2x.png (../shared/devtools/images/dock-bottom-maximize@2x.png)
   skin/classic/browser/devtools/dock-side@2x.png          (../shared/devtools/images/dock-side@2x.png)
 * skin/classic/browser/devtools/inspector.css               (../shared/devtools/inspector.css)
   skin/classic/browser/devtools/profiler-stopwatch.svg      (../shared/devtools/images/profiler-stopwatch.svg)
   skin/classic/browser/devtools/profiler-stopwatch-checked.svg      (../shared/devtools/images/profiler-stopwatch-checked.svg)
   skin/classic/browser/devtools/tool-options.svg            (../shared/devtools/images/tool-options.svg)
   skin/classic/browser/devtools/tool-webconsole.svg         (../shared/devtools/images/tool-webconsole.svg)
   skin/classic/browser/devtools/tool-debugger.svg           (../shared/devtools/images/tool-debugger.svg)
   skin/classic/browser/devtools/tool-debugger-paused.svg    (../shared/devtools/images/tool-debugger-paused.svg)
--- a/browser/themes/shared/devtools/common.css
+++ b/browser/themes/shared/devtools/common.css
@@ -17,16 +17,21 @@
 
 .devtools-monospace {
   font-family: var(--monospace-font-family);
 %if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_QT)
   font-size: 80%;
 %endif
 }
 
+/* Bottom-docked toolbox minimize transition */
+.devtools-toolbox-bottom-iframe {
+  transition: margin-bottom .1s;
+}
+
 /* Splitters */
 .devtools-horizontal-splitter {
   -moz-appearance: none;
   background-image: none;
   background-color: transparent;
   border: 0;
   border-bottom: 1px solid rgba(118, 121, 125, .5);
   min-height: 3px;
@@ -43,16 +48,21 @@
   -moz-border-end: 1px solid rgba(118, 121, 125, .5);
   min-width: 3px;
   width: 3px;
   -moz-margin-start: -3px;
   position: relative;
   cursor: e-resize;
 }
 
+.devtools-horizontal-splitter.disabled,
+.devtools-side-splitter.disabled {
+  pointer-events: none;
+}
+
 .devtools-toolbox-side-iframe {
   min-width: 465px;
 }
 
 /* Autocomplete Popup */
 /* Dark and light theme */
 
 .devtools-autocomplete-popup {
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..feb4d78eeca6e16909332bd9d80316a3059b6e79
GIT binary patch
literal 1096
zc$|GxT}Tu`9A7iTu7o~`lx$dgF!1){yj#81EADo0yMtDa^XQHsXx$xkvHfUw?9QXW
zB&5P#dI&7C=&`^ciLei{UdkS#pdbnZAA$nGf*|Or+0(ls)M0mK{(pYI-~4ChHy2V}
zhw6OwK7t_Xk_j=5N8tUn8*uLyi$C$O8^v>|+v-Jm#eqarvw9#%8cH8bLq!`pISV@p
zqI#d6$)TKlm{%=>QalV*Fl@qWod*lHq7Fa=dSIV!M##lSPf4I_5i%Q+8QB(Kzn&;M
zu)ElmQHuj=SR)S}0G$OM3m6b7pkSOZUA_<@OS(Lsduf^kB?uabkV{47<P;Dr2Z9jQ
z>Q@<-1sq4Q!4Su>Eg-<K0h+-d=Vya_h~oooV7ZZaHAm~^)1tJz7LFohKSDN7)A@X!
z%C}OM(?_%6aM;rb1pFA`cZW=*6#S;!R8|n7t2(-kbjt)@tn^reC_<9uEf{u1)^wMT
z2_G0;P;8o|82TTeEUyhUj0)OCY1q3q+RY5vkWNF_8gx{AalK6*+2#cYD#&s&mUW_h
zim84JS#G~&1EHG(&9b8ErkB`XV#qR|G+m^aDolzI5@#q~*Z3IM9%tL7D8ouD%f>_P
zTv!qU!5AA32@E5&mbs#(4jRxzWv=#@8(Wp@nPG_y9sLy4B*!v9Y16#ELM;;8CJIqW
z!g|cgwJdS96>2GQV+^w@m&V1=|6O%Ch5h4AE5^k^#rn|1es{339rNoE{3sqK#b~DR
zapB5a-!Rx5f8Y3fy7A(Zjc0oH=C(-3nSJUcTHlZeXxESBqYW|U^8A?HJhNB@sIA1y
zg=@sjrFCz7MB|R(dLr`UP)qHvd+y#{4I{T_j?IUs=#iVm=JD4Z!E<}E8+|iZznvDR
zx1JrpyED;|XCE}xoI;bex3t>3&$mAtojCe#+vn;tk2k!Uqh_;4Zf-Pt{=;`Yb?4Lf
tvAQp9)cO~rHzw}?ZpjigDMB9>hO3B{O;mkxVJhLh?PR=5oQxeG`2%p#VS)ev
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a82983c9fe706ff4291e66608ffc29641fb7750f
GIT binary patch
literal 1125
zc$|GxOK1~87+xFD(wh1LsV^AUM-`fVq|Mf>jkVoPY{V@k4O(+@vpZ?lc6YYBQ<Fx;
zcTo|+gCctIQL&&1ijRW_3u?itU{8V~B6zST5v25>=xp0m5#lhf|L6Pu`RAW+PPMmf
z^H(gdpeV{8Z<P~d1>Pv1OJ=uF_)eB(INFIjPzL8z2U5*CN<$Dg)h?KTs@~uG0ya{V
zZ>^E+#GT3xQ9~x9dKe~e+LYHCoAS1*bwdo&u*<L_^w-BvXkh3OdRIu{6kCE>qqX3`
zjzU{fD|BmNoo?C!8uKCvU_z{dyxC*9Vm?Ao#TCikbF(y<g5d55J)Kmi(hel#KoDZ;
z0vgBjKoA(dJ|qZyEeLXakmX1h0(`v~62xEwm}xXQ&CxSrLXORxMXU&&#n={EHkZpW
zxjF_pT`V6ChrJlVV1OV3Zoh@qe86(o6eGyc)f~gd2C{%>t7+7SBQ#ySf@#l&wcMF9
zk%F;#)n<8yWB&pw%KxFJIg5620%rb?c9Z=!WE0RueU3&RF0;lX+oI$^6(c8!P*1Un
z?OBA8n?*K|Is~vmQ8mNz92=(?iXz4>7ps;A<8p*19>y?qF~&E@Qgck^_!y~sG_*+w
z$E09=3m*<i94FNkxiZrFOlaXESO3F}mgIV3m}W!A*a!8PgG?}WY0)U1OH1iorn!3Q
zT%?j*mL$Xe^VCHb(T}&C6)rJmr4KElcZUf3^kC`*`4n&Ca&t2OF8$-zucoJ4%hnIf
znR~sex^H;MH*nl{p_%6opHChfJU$#u4XqoQ)7@MBYT(4r1<LEx-V?(q`rxB#Wx?i<
zM15Fv=-lL?-pK=%52QVTs*lXb)rp$b$Ckx!pFK8~ZK;AapUcxLsmga7%Fa@|N9RAg
zWq-Mz5}qwx#Z43nP`ZfTUb{Pf$=?7@nV@0C;>+cCpYN|t?9@ljE!pS$c5|$!RhhSN
zbU<F$xc$9v^4_OZ!mkZp(H5Vnx%uWs^!|m!%Z>gcW3lle-%uG<HlJcw?|hm$S$ODu
N{CKoYzSFYj=x<~ka8&>R
--- a/browser/themes/shared/devtools/images/pseudo-class.svg
+++ b/browser/themes/shared/devtools/images/pseudo-class.svg
@@ -19,11 +19,11 @@
       <use xlink:href="#class-block" mask="url(#mask-block-solid)" fill="currentColor" fill-opacity=".4"/>
       <use xlink:href="#class-block" mask="url(#mask-block-solid)" fill="currentColor" transform="translate(4 4)"/>
       <g transform="translate(8 8)" fill="currentColor">
         <path d="M2.5,0C2.2,0,2,0.2,2,0.5C2,0.8,2.2,1,2.5,1C2.8,1,3,0.8,3,0.5 C3,0.2,2.8,0,2.5,0z M4.5,0C4.2,0,4,0.2,4,0.5C4,0.8,4.2,1,4.5,1C4.8,1,5,0.8,5,0.5C5,0.2,4.8,0,4.5,0z M0.5,6C0.8,6,1,5.8,1,5.5 C1,5.2,0.8,5,0.5,5C0.2,5,0,5.2,0,5.5C0,5.8,0.2,6,0.5,6z M0.5,4C0.8,4,1,3.8,1,3.5C1,3.2,0.8,3,0.5,3C0.2,3,0,3.2,0,3.5 C0,3.8,0.2,4,0.5,4z M7.5,2C7.2,2,7,2.2,7,2.5C7,2.8,7.2,3,7.5,3C7.8,3,8,2.8,8,2.5C8,2.2,7.8,2,7.5,2z M7.5,4C7.2,4,7,4.2,7,4.5 C7,4.8,7.2,5,7.5,5C7.8,5,8,4.8,8,4.5C8,4.2,7.8,4,7.5,4z M5.5,7C5.2,7,5,7.2,5,7.5C5,7.8,5.2,8,5.5,8C5.8,8,6,7.8,6,7.5 C6,7.2,5.8,7,5.5,7z M3.5,7C3.2,7,3,7.2,3,7.5C3,7.8,3.2,8,3.5,8C3.8,8,4,7.8,4,7.5C4,7.2,3.8,7,3.5,7z M0.5,2C0.8,2,1,1.8,1,1.5v-1 C1,0.2,0.8,0,0.5,0C0.2,0,0,0.2,0,0.5v1C0,1.8,0.2,2,0.5,2z M8,0.5C8,0.2,7.8,0,7.5,0h-1C6.2,0,6,0.2,6,0.5C6,0.8,6.2,1,6.5,1h1 C7.8,1,8,0.8,8,0.5z M7.5,6C7.2,6,7,6.2,7,6.5v1C7,7.8,7.2,8,7.5,8C7.8,8,8,7.8,8,7.5v-1C8,6.2,7.8,6,7.5,6z M1.5,7h-1 C0.2,7,0,7.2,0,7.5C0,7.8,0.2,8,0.5,8h1C1.8,8,2,7.8,2,7.5C2,7.2,1.8,7,1.5,7z"/>
         <use xlink:href="#class-block" fill-opacity=".2"/>
       </g>
     </g>
   </defs>
-  <use xlink:href="#pseudo-class-shape" id="pseudo-class" color="#edf0f1"/>
+  <use xlink:href="#pseudo-class-shape" id="pseudo-class" color="#babec3"/>
   <use xlink:href="#pseudo-class-shape" id="pseudo-class-checked" color="#3089C9"/>
 </svg>
--- a/browser/themes/shared/devtools/toolbars.inc.css
+++ b/browser/themes/shared/devtools/toolbars.inc.css
@@ -652,16 +652,24 @@
 #toolbox-dock-side  > image {
   background-image: url("chrome://browser/skin/devtools/dock-side@2x.png");
 }
 
 #toolbox-dock-window > image {
   background-image: url("chrome://browser/skin/devtools/undock@2x.png");
 }
 
+#toolbox-dock-bottom-minimize > image {
+  background-image: url("chrome://browser/skin/devtools/dock-bottom-minimize@2x.png");
+}
+
+#toolbox-dock-bottom-minimize.minimized > image {
+  background-image: url("chrome://browser/skin/devtools/dock-bottom-maximize@2x.png");
+}
+
 #toolbox-dock-window,
 #toolbox-dock-bottom,
 #toolbox-dock-side {
   opacity: 0.8;
 }
 
 #toolbox-dock-window:hover,
 #toolbox-dock-bottom:hover,
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -462,16 +462,18 @@ browser.jar:
         skin/classic/browser/devtools/responsiveui-rotate@2x.png       (../shared/devtools/images/responsivemode/responsiveui-rotate@2x.png)
         skin/classic/browser/devtools/responsiveui-touch.png        (../shared/devtools/images/responsivemode/responsiveui-touch.png)
         skin/classic/browser/devtools/responsiveui-touch@2x.png        (../shared/devtools/images/responsivemode/responsiveui-touch@2x.png)
         skin/classic/browser/devtools/responsiveui-screenshot.png   (../shared/devtools/images/responsivemode/responsiveui-screenshot.png)
         skin/classic/browser/devtools/responsiveui-screenshot@2x.png   (../shared/devtools/images/responsivemode/responsiveui-screenshot@2x.png)
         skin/classic/browser/devtools/toggle-tools.png              (../shared/devtools/images/toggle-tools.png)
         skin/classic/browser/devtools/toggle-tools@2x.png              (../shared/devtools/images/toggle-tools@2x.png)
         skin/classic/browser/devtools/dock-bottom@2x.png            (../shared/devtools/images/dock-bottom@2x.png)
+        skin/classic/browser/devtools/dock-bottom-minimize@2x.png   (../shared/devtools/images/dock-bottom-minimize@2x.png)
+        skin/classic/browser/devtools/dock-bottom-maximize@2x.png   (../shared/devtools/images/dock-bottom-maximize@2x.png)
         skin/classic/browser/devtools/dock-side@2x.png              (../shared/devtools/images/dock-side@2x.png)
         skin/classic/browser/devtools/floating-scrollbars.css       (devtools/floating-scrollbars.css)
         skin/classic/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.css)
 *       skin/classic/browser/devtools/inspector.css                 (../shared/devtools/inspector.css)
         skin/classic/browser/devtools/profiler-stopwatch.svg        (../shared/devtools/images/profiler-stopwatch.svg)
         skin/classic/browser/devtools/profiler-stopwatch-checked.svg  (../shared/devtools/images/profiler-stopwatch-checked.svg)
         skin/classic/browser/devtools/tool-options.svg              (../shared/devtools/images/tool-options.svg)
         skin/classic/browser/devtools/tool-webconsole.svg           (../shared/devtools/images/tool-webconsole.svg)
--- a/dom/apps/AppsUtils.jsm
+++ b/dom/apps/AppsUtils.jsm
@@ -44,38 +44,60 @@ this.isAbsoluteURI = function(aURI) {
 }
 
 this.mozIApplication = function(aApp) {
   _setAppProperties(this, aApp);
 }
 
 mozIApplication.prototype = {
   hasPermission: function(aPermission) {
-    let uri = Services.io.newURI(this.origin, null, null);
-    let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
-                   .getService(Ci.nsIScriptSecurityManager);
     // This helper checks an URI inside |aApp|'s origin and part of |aApp| has a
     // specific permission. It is not checking if browsers inside |aApp| have such
     // permission.
-    let principal = secMan.getAppCodebasePrincipal(uri, this.localId,
-                                                   /*mozbrowser*/false);
+    let principal = this.principal;
+    if (this.installerIsBrowser) {
+      let uri = Services.io.newURI(this.origin, null, null);
+      principal =
+        Services.scriptSecurityManager.getAppCodebasePrincipal(uri, this.localId,
+                                                               /*mozbrowser*/false);
+    }
     let perm = Services.perms.testExactPermissionFromPrincipal(principal,
                                                                aPermission);
     return (perm === Ci.nsIPermissionManager.ALLOW_ACTION);
   },
 
   hasWidgetPage: function(aPageURL) {
     let uri = Services.io.newURI(aPageURL, null, null);
     let filepath = AppsUtils.getFilePath(uri.path);
     let eliminatedUri = Services.io.newURI(uri.prePath + filepath, null, null);
     let equalCriterion = aUrl => Services.io.newURI(aUrl, null, null)
                                             .equals(eliminatedUri);
     return this.widgetPages.find(equalCriterion) !== undefined;
   },
 
+  get principal() {
+    if (this._principal) {
+      return this._principal;
+    }
+
+    this._principal = null;
+
+    try {
+      this._principal = Services.scriptSecurityManager.getAppCodebasePrincipal(
+        Services.io.newURI(this.origin, null, null),
+        this.localId,
+        this.installerIsBrowser
+      );
+    } catch(e) {
+      dump("Could not create app principal " + e + "\n");
+    }
+
+    return this._principal;
+  },
+
   QueryInterface: function(aIID) {
     if (aIID.equals(Ci.mozIApplication) ||
         aIID.equals(Ci.nsISupports))
       return this;
     throw Cr.NS_ERROR_NO_INTERFACE;
   }
 }
 
new file mode 100644
--- /dev/null
+++ b/dom/apps/tests/unit/test_moziapplication.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const {interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource:///modules/AppsUtils.jsm");
+
+add_test(() => {
+  let app = {
+    name: "TestApp",
+    csp: "aCsp",
+    installOrigin: "http://installorigin.com",
+    origin: "http://www.example.com",
+    installTime: Date.now(),
+    manifestURL: "http://www.example.com/manifest.webapp",
+    appStatus: Ci.nsIPrincipal.APP_STATUS_NOT_INSTALLED,
+    removable: false,
+    id: 123,
+    localId: 123,
+    basePath: "/",
+    progress: 1.0,
+    installState: "installed",
+    downloadAvailable: false,
+    downloading: false,
+    lastUpdateCheck: Date.now(),
+    updateTime: Date.now(),
+    etag: "aEtag",
+    packageEtag: "aPackageEtag",
+    manifestHash: "aManifestHash",
+    packageHash: "aPackageHash",
+    staged: false,
+    installerAppId: 345,
+    installerIsBrowser: false,
+    storeId: "aStoreId",
+    storeVersion: 1,
+    role: "aRole",
+    redirects: "aRedirects",
+    kind: "aKind",
+    enabled: true,
+    sideloaded: false
+  };
+
+  let mozapp = new mozIApplication(app);
+
+  Object.keys(app).forEach((key) => {
+    if (key == "principal") {
+      return;
+    }
+    Assert.equal(app[key], mozapp[key],
+                 "app[" + key + "] should be equal to mozapp[" + key + "]");
+  });
+
+  Assert.ok(mozapp.principal, "app principal should exist");
+  let expectedPrincipalOrigin = app.origin + "!appId=" + app.localId;
+  Assert.equal(mozapp.principal.origin, expectedPrincipalOrigin,
+               "app principal origin ok");
+  Assert.equal(mozapp.principal.appId, app.localId, "app principal appId ok");
+  Assert.equal(mozapp.principal.isInBrowserElement, app.installerIsBrowser,
+               "app principal isInBrowserElement ok");
+  run_next_test();
+});
+
+function run_test() {
+  run_next_test();
+}
--- a/dom/apps/tests/unit/xpcshell.ini
+++ b/dom/apps/tests/unit/xpcshell.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 head =
 tail =
 
 [test_has_widget_criterion.js]
 [test_inter_app_comm_service.js]
 [test_manifestSanitizer.js]
 [test_manifestHelper.js]
+[test_moziapplication.js]
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -248,16 +248,17 @@ bool nsContentUtils::sInitialized = fals
 bool nsContentUtils::sIsFullScreenApiEnabled = false;
 bool nsContentUtils::sTrustedFullScreenOnly = true;
 bool nsContentUtils::sIsCutCopyAllowed = true;
 bool nsContentUtils::sIsPerformanceTimingEnabled = false;
 bool nsContentUtils::sIsResourceTimingEnabled = false;
 bool nsContentUtils::sIsUserTimingLoggingEnabled = false;
 bool nsContentUtils::sIsExperimentalAutocompleteEnabled = false;
 bool nsContentUtils::sEncodeDecodeURLHash = false;
+bool nsContentUtils::sPrivacyResistFingerprinting = false;
 
 uint32_t nsContentUtils::sHandlingInputTimeout = 1000;
 
 nsHtml5StringParser* nsContentUtils::sHTMLFragmentParser = nullptr;
 nsIParser* nsContentUtils::sXMLFragmentParser = nullptr;
 nsIFragmentContentSink* nsContentUtils::sXMLFragmentSink = nullptr;
 bool nsContentUtils::sFragmentParsingActive = false;
 
@@ -528,16 +529,19 @@ nsContentUtils::Init()
                                "dom.performance.enable_user_timing_logging", false);
 
   Preferences::AddBoolVarCache(&sIsExperimentalAutocompleteEnabled,
                                "dom.forms.autocomplete.experimental", false);
 
   Preferences::AddBoolVarCache(&sEncodeDecodeURLHash,
                                "dom.url.encode_decode_hash", false);
 
+  Preferences::AddBoolVarCache(&sPrivacyResistFingerprinting,
+                               "privacy.resistFingerprinting", false);
+
   Preferences::AddUintVarCache(&sHandlingInputTimeout,
                                "dom.event.handling-user-input-time-limit",
                                1000);
 
 #if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP))
   Preferences::AddBoolVarCache(&sDOMWindowDumpEnabled,
                                "browser.dom.window.dump.enabled");
 #endif
@@ -1984,16 +1988,26 @@ nsContentUtils::IsCallerChrome()
   if (SubjectPrincipal() == sSystemPrincipal) {
     return true;
   }
 
   // If the check failed, look for UniversalXPConnect on the cx compartment.
   return xpc::IsUniversalXPConnectEnabled(GetCurrentJSContext());
 }
 
+bool
+nsContentUtils::ShouldResistFingerprinting(nsIDocShell* aDocShell)
+{
+  if (!aDocShell) {
+    return false;
+  }
+  bool isChrome = nsContentUtils::IsChromeDoc(aDocShell->GetDocument());
+  return !isChrome && sPrivacyResistFingerprinting;
+}
+
 namespace mozilla {
 namespace dom {
 namespace workers {
 extern bool IsCurrentThreadRunningChromeWorker();
 extern JSContext* GetCurrentThreadJSContext();
 }
 }
 }
@@ -7768,8 +7782,21 @@ nsContentUtils::FirePageShowEvent(nsIDoc
   }
 
   nsCOMPtr<nsIDocument> doc = aItem->GetDocument();
   NS_ASSERTION(doc, "What happened here?");
   if (doc->IsShowing() == aFireIfShowing) {
     doc->OnPageShow(true, aChromeEventHandler);
   }
 }
+
+/* static */
+already_AddRefed<nsPIWindowRoot>
+nsContentUtils::GetWindowRoot(nsIDocument* aDoc)
+{
+  if (aDoc) {
+    nsPIDOMWindow* win = aDoc->GetWindow();
+    if (win) {
+      return win->GetTopWindowRoot();
+    }
+  }
+  return nullptr;
+}
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -94,16 +94,17 @@ class nsPresContext;
 class nsStringBuffer;
 class nsStringHashKey;
 class nsTextFragment;
 class nsView;
 class nsViewportInfo;
 class nsWrapperCache;
 class nsAttrValue;
 class nsITransferable;
+class nsPIWindowRoot;
 
 struct JSPropertyDescriptor;
 struct JSRuntime;
 struct nsIntMargin;
 
 template<class E> class nsCOMArray;
 template<class K, class V> class nsDataHashtable;
 template<class K, class V> class nsRefPtrHashtable;
@@ -193,16 +194,19 @@ public:
   static bool     IsCallerContentXBL();
 
   static bool     IsImageSrcSetDisabled();
 
   static bool LookupBindingMember(JSContext* aCx, nsIContent *aContent,
                                   JS::Handle<jsid> aId,
                                   JS::MutableHandle<JSPropertyDescriptor> aDesc);
 
+  // Check whether we should avoid leaking distinguishing information to JS/CSS.
+  static bool ShouldResistFingerprinting(nsIDocShell* aDocShell);
+
   /**
    * Returns the parent node of aChild crossing document boundaries.
    * Uses the parent node in the composed document.
    */
   static nsINode* GetCrossDocParentNode(nsINode* aChild);
 
   /**
    * Do not ever pass null pointers to this method.  If one of your
@@ -1907,16 +1911,26 @@ public:
    * Returns true if URL setters should percent encode the Hash/Ref segment
    * and getters should return the percent decoded value of the segment
    */
   static bool EncodeDecodeURLHash()
   {
     return sEncodeDecodeURLHash;
   }
 
+  /*
+   * Returns true if the browser should attempt to prevent content scripts
+   * from collecting distinctive information about the browser that could
+   * be used to "fingerprint" and track the user across websites.
+   */
+  static bool ResistFingerprinting()
+  {
+    return sPrivacyResistFingerprinting;
+  }
+
   /**
    * Returns true if the doc tree branch which contains aDoc contains any
    * plugins which we don't control event dispatch for, i.e. do any plugins
    * in the same tab as this document receive key events outside of our
    * control? This always returns false on MacOSX.
    */
   static bool HasPluginWithUncontrolledEventDispatch(nsIDocument* aDoc);
 
@@ -2345,16 +2359,18 @@ public:
 
   static void FirePageShowEvent(nsIDocShellTreeItem* aItem,
                                 mozilla::dom::EventTarget* aChromeEventHandler,
                                 bool aFireIfShowing);
 
   static void FirePageHideEvent(nsIDocShellTreeItem* aItem,
                                 mozilla::dom::EventTarget* aChromeEventHandler);
 
+  static already_AddRefed<nsPIWindowRoot> GetWindowRoot(nsIDocument* aDoc);
+
 private:
   static bool InitializeEventTable();
 
   static nsresult EnsureStringBundle(PropertiesFile aFile);
 
   static bool CanCallerAccess(nsIPrincipal* aSubjectPrincipal,
                                 nsIPrincipal* aPrincipal);
 
@@ -2445,16 +2461,17 @@ private:
   static bool sTrustedFullScreenOnly;
   static bool sIsCutCopyAllowed;
   static uint32_t sHandlingInputTimeout;
   static bool sIsPerformanceTimingEnabled;
   static bool sIsResourceTimingEnabled;
   static bool sIsUserTimingLoggingEnabled;
   static bool sIsExperimentalAutocompleteEnabled;
   static bool sEncodeDecodeURLHash;
+  static bool sPrivacyResistFingerprinting;
 
   static nsHtml5StringParser* sHTMLFragmentParser;
   static nsIParser* sXMLFragmentParser;
   static nsIFragmentContentSink* sXMLFragmentSink;
 
   /**
    * True if there's a fragment parser activation on the stack.
    */
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -65,28 +65,29 @@
 #include <algorithm>
 
 #if defined(MOZ_X11) && defined(MOZ_WIDGET_GTK)
 #include <gdk/gdk.h>
 #include <gdk/gdkx.h>
 #endif
 
 #include "Layers.h"
-#include "mozilla/layers/ShadowLayers.h"
 #include "gfxPrefs.h"
 
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/IDBFactoryBinding.h"
 #include "mozilla/dom/IDBMutableFileBinding.h"
 #include "mozilla/dom/indexedDB/IDBMutableFile.h"
 #include "mozilla/dom/indexedDB/IndexedDatabaseManager.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
 #include "mozilla/dom/quota/PersistenceType.h"
 #include "mozilla/dom/quota/QuotaManager.h"
+#include "mozilla/layers/FrameUniformityData.h"
+#include "mozilla/layers/ShadowLayers.h"
 #include "nsPrintfCString.h"
 #include "nsViewportInfo.h"
 #include "nsIFormControl.h"
 #include "nsIScriptError.h"
 #include "nsIAppShell.h"
 #include "nsWidgetsCID.h"
 #include "FrameLayerBuilder.h"
 #include "nsDisplayList.h"
@@ -3712,16 +3713,37 @@ nsDOMWindowUtils::SetChromeMargin(int32_
       }
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMWindowUtils::GetFrameUniformityTestData(JSContext* aContext,
+                                             JS::MutableHandleValue aOutFrameUniformity)
+{
+  MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+  nsIWidget* widget = GetWidget();
+  if (!widget) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsRefPtr<LayerManager> manager = widget->GetLayerManager();
+  if (!manager) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  FrameUniformityData outData;
+  manager->GetFrameUniformity(&outData);
+  outData.ToJS(aOutFrameUniformity, aContext);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMWindowUtils::XpconnectArgument(nsIDOMWindowUtils* aThis)
 {
   // Do nothing.
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::AskPermission(nsIContentPermissionRequest* aRequest)
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -4998,16 +4998,22 @@ nsGlobalWindow::SetInnerHeight(int32_t a
   return rv.StealNSResult();
 }
 
 nsIntSize
 nsGlobalWindow::GetOuterSize(ErrorResult& aError)
 {
   MOZ_ASSERT(IsOuterWindow());
 
+  if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
+    CSSIntSize size;
+    aError = GetInnerSize(size);
+    return nsIntSize(size.width, size.height);
+  }
+
   nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
   if (!treeOwnerAsWin) {
     aError.Throw(NS_ERROR_FAILURE);
     return nsIntSize(0, 0);
   }
 
   nsGlobalWindow* rootWindow =
     static_cast<nsGlobalWindow *>(GetPrivateRoot());
@@ -5162,16 +5168,21 @@ nsGlobalWindow::SetOuterHeight(int32_t a
   return rv.StealNSResult();
 }
 
 nsIntPoint
 nsGlobalWindow::GetScreenXY(ErrorResult& aError)
 {
   MOZ_ASSERT(IsOuterWindow());
 
+  // When resisting fingerprinting, always return (0,0)
+  if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
+    return nsIntPoint(0, 0);
+  }
+
   nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
   if (!treeOwnerAsWin) {
     aError.Throw(NS_ERROR_FAILURE);
     return nsIntPoint(0, 0);
   }
 
   int32_t x = 0, y = 0;
   aError = treeOwnerAsWin->GetPosition(&x, &y);
@@ -5235,16 +5246,21 @@ nsGlobalWindow::GetInnerScreenRect()
   return rootFrame->GetScreenRectInAppUnits();
 }
 
 float
 nsGlobalWindow::GetMozInnerScreenX(ErrorResult& aError)
 {
   FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenX, (aError), aError, 0);
 
+  // When resisting fingerprinting, always return 0.
+  if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
+    return 0.0;
+  }
+
   nsRect r = GetInnerScreenRect();
   return nsPresContext::AppUnitsToFloatCSSPixels(r.x);
 }
 
 NS_IMETHODIMP
 nsGlobalWindow::GetMozInnerScreenX(float* aScreenX)
 {
   ErrorResult rv;
@@ -5253,16 +5269,21 @@ nsGlobalWindow::GetMozInnerScreenX(float
   return rv.StealNSResult();
 }
 
 float
 nsGlobalWindow::GetMozInnerScreenY(ErrorResult& aError)
 {
   FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenY, (aError), aError, 0);
 
+  // Return 0 to prevent fingerprinting.
+  if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
+    return 0.0;
+  }
+
   nsRect r = GetInnerScreenRect();
   return nsPresContext::AppUnitsToFloatCSSPixels(r.y);
 }
 
 NS_IMETHODIMP
 nsGlobalWindow::GetMozInnerScreenY(float* aScreenY)
 {
   ErrorResult rv;
@@ -5281,16 +5302,20 @@ nsGlobalWindow::GetDevicePixelRatio(Erro
   }
 
   nsRefPtr<nsPresContext> presContext;
   mDocShell->GetPresContext(getter_AddRefs(presContext));
   if (!presContext) {
     return 1.0;
   }
 
+  if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
+    return 1.0;
+  }
+
   return float(nsPresContext::AppUnitsPerCSSPixel())/
       presContext->AppUnitsPerDevPixel();
 }
 
 NS_IMETHODIMP
 nsGlobalWindow::GetDevicePixelRatio(float* aRatio)
 {
   ErrorResult rv;
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -1054,16 +1054,18 @@ public:
   void Back(mozilla::ErrorResult& aError);
   void Forward(mozilla::ErrorResult& aError);
   void Home(mozilla::ErrorResult& aError);
   bool Find(const nsAString& aString, bool aCaseSensitive, bool aBackwards,
             bool aWrapAround, bool aWholeWord, bool aSearchInFrames,
             bool aShowDialog, mozilla::ErrorResult& aError);
   uint64_t GetMozPaintCount(mozilla::ErrorResult& aError);
 
+  bool ShouldResistFingerprinting();
+
   mozilla::dom::MozSelfSupport* GetMozSelfSupport(mozilla::ErrorResult& aError);
 
   already_AddRefed<nsIDOMWindow> OpenDialog(JSContext* aCx,
                                             const nsAString& aUrl,
                                             const nsAString& aName,
                                             const nsAString& aOptions,
                                             const mozilla::dom::Sequence<JS::Value>& aExtraArgument,
                                             mozilla::ErrorResult& aError);
--- a/dom/base/nsPIWindowRoot.h
+++ b/dom/base/nsPIWindowRoot.h
@@ -4,24 +4,31 @@
  * 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 nsPIWindowRoot_h__
 #define nsPIWindowRoot_h__
 
 #include "nsISupports.h"
 #include "mozilla/dom/EventTarget.h"
+#include "nsWeakReference.h"
 
 class nsPIDOMWindow;
 class nsIControllers;
 class nsIController;
 
+namespace mozilla {
+namespace dom {
+class TabParent;
+}
+}
+
 #define NS_IWINDOWROOT_IID \
-{ 0x728a2682, 0x55c0, 0x4860, \
- { 0x82, 0x6b, 0x0c, 0x30, 0x0a, 0xac, 0xaa, 0x60 } }
+{ 0x238edca0, 0xb30d, 0x46d3, \
+ { 0xb2, 0x6a, 0x17, 0xb6, 0x21, 0x28, 0x89, 0x7e } }
 
 class nsPIWindowRoot : public mozilla::dom::EventTarget
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IWINDOWROOT_IID)
 
   virtual nsPIDOMWindow* GetWindow()=0;
 
@@ -33,13 +40,22 @@ public:
                                            nsIController** aResult) = 0;
   virtual nsresult GetControllers(nsIControllers** aResult) = 0;
 
   virtual void GetEnabledDisabledCommands(nsTArray<nsCString>& aEnabledCommands,
                                           nsTArray<nsCString>& aDisabledCommands) = 0;
 
   virtual void SetParentTarget(mozilla::dom::EventTarget* aTarget) = 0;
   virtual mozilla::dom::EventTarget* GetParentTarget() = 0;
+
+  // Stores a weak reference to the browser.
+  virtual void AddBrowser(mozilla::dom::TabParent* aBrowser) = 0;
+  virtual void RemoveBrowser(mozilla::dom::TabParent* aBrowser) = 0;
+
+  typedef void (*BrowserEnumerator)(mozilla::dom::TabParent* aTab, void* aArg);
+
+  // Enumerate all stored browsers that for which the weak reference is valid.
+  virtual void EnumerateBrowsers(BrowserEnumerator aEnumFunc, void* aArg) = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsPIWindowRoot, NS_IWINDOWROOT_IID)
 
 #endif // nsPIWindowRoot_h__
--- a/dom/base/nsScreen.cpp
+++ b/dom/base/nsScreen.cpp
@@ -63,16 +63,21 @@ NS_INTERFACE_MAP_BEGIN(nsScreen)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(nsScreen, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(nsScreen, DOMEventTargetHelper)
 
 int32_t
 nsScreen::GetPixelDepth(ErrorResult& aRv)
 {
+  // Return 24 to prevent fingerprinting.
+  if (ShouldResistFingerprinting()) {
+    return 24;
+  }
+
   nsDeviceContext* context = GetDeviceContext();
 
   if (!context) {
     aRv.Throw(NS_ERROR_FAILURE);
     return -1;
   }
 
   uint32_t depth;
@@ -106,16 +111,21 @@ nsDeviceContext*
 nsScreen::GetDeviceContext()
 {
   return nsLayoutUtils::GetDeviceContextForScreenInfo(GetOwner());
 }
 
 nsresult
 nsScreen::GetRect(nsRect& aRect)
 {
+  // Return window inner rect to prevent fingerprinting.
+  if (ShouldResistFingerprinting()) {
+    return GetWindowInnerRect(aRect);
+  }
+
   nsDeviceContext *context = GetDeviceContext();
 
   if (!context) {
     return NS_ERROR_FAILURE;
   }
 
   context->GetRect(aRect);
 
@@ -125,16 +135,21 @@ nsScreen::GetRect(nsRect& aRect)
   aRect.width = nsPresContext::AppUnitsToIntCSSPixels(aRect.width);
 
   return NS_OK;
 }
 
 nsresult
 nsScreen::GetAvailRect(nsRect& aRect)
 {
+  // Return window inner rect to prevent fingerprinting.
+  if (ShouldResistFingerprinting()) {
+    return GetWindowInnerRect(aRect);
+  }
+
   nsDeviceContext *context = GetDeviceContext();
 
   if (!context) {
     return NS_ERROR_FAILURE;
   }
 
   context->GetClientRect(aRect);
 
@@ -161,32 +176,36 @@ nsScreen::Notify(const hal::ScreenConfig
   if (mOrientation != previousOrientation) {
     DispatchTrustedEvent(NS_LITERAL_STRING("mozorientationchange"));
   }
 }
 
 void
 nsScreen::GetMozOrientation(nsString& aOrientation)
 {
-  switch (mOrientation) {
-  case eScreenOrientation_PortraitPrimary:
-    aOrientation.AssignLiteral("portrait-primary");
-    break;
-  case eScreenOrientation_PortraitSecondary:
-    aOrientation.AssignLiteral("portrait-secondary");
-    break;
-  case eScreenOrientation_LandscapePrimary:
+  if (ShouldResistFingerprinting()) {
     aOrientation.AssignLiteral("landscape-primary");
-    break;
-  case eScreenOrientation_LandscapeSecondary:
-    aOrientation.AssignLiteral("landscape-secondary");
-    break;
-  case eScreenOrientation_None:
-  default:
-    MOZ_CRASH("Unacceptable mOrientation value");
+  } else {
+    switch (mOrientation) {
+    case eScreenOrientation_PortraitPrimary:
+      aOrientation.AssignLiteral("portrait-primary");
+      break;
+    case eScreenOrientation_PortraitSecondary:
+      aOrientation.AssignLiteral("portrait-secondary");
+      break;
+    case eScreenOrientation_LandscapePrimary:
+      aOrientation.AssignLiteral("landscape-primary");
+      break;
+    case eScreenOrientation_LandscapeSecondary:
+      aOrientation.AssignLiteral("landscape-secondary");
+      break;
+    case eScreenOrientation_None:
+    default:
+      MOZ_CRASH("Unacceptable mOrientation value");
+    }
   }
 }
 
 NS_IMETHODIMP
 nsScreen::GetSlowMozOrientation(nsAString& aOrientation)
 {
   nsString orientation;
   GetMozOrientation(orientation);
@@ -368,8 +387,32 @@ nsScreen::FullScreenEventListener::Handl
 
   target->RemoveSystemEventListener(NS_LITERAL_STRING("mozfullscreenchange"),
                                     this, true);
 
   hal::UnlockScreenOrientation();
 
   return NS_OK;
 }
+
+nsresult
+nsScreen::GetWindowInnerRect(nsRect& aRect)
+{
+  aRect.x = 0;
+  aRect.y = 0;
+  nsCOMPtr<nsIDOMWindow> win = GetOwner();
+  if (!win) {
+    return NS_ERROR_FAILURE;
+  }
+  nsresult rv = win->GetInnerWidth(&aRect.width);
+  NS_ENSURE_SUCCESS(rv, rv);
+  return win->GetInnerHeight(&aRect.height);
+}
+
+bool nsScreen::ShouldResistFingerprinting() const
+{
+  bool resist = false;
+  nsCOMPtr<nsPIDOMWindow> owner = GetOwner();
+  if (owner) {
+    resist = nsContentUtils::ShouldResistFingerprinting(owner->GetDocShell());
+  }
+  return resist;
+}
--- a/dom/base/nsScreen.h
+++ b/dom/base/nsScreen.h
@@ -126,16 +126,17 @@ public:
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   void Notify(const mozilla::hal::ScreenConfiguration& aConfiguration) override;
 
 protected:
   nsDeviceContext* GetDeviceContext();
   nsresult GetRect(nsRect& aRect);
   nsresult GetAvailRect(nsRect& aRect);
+  nsresult GetWindowInnerRect(nsRect& aRect);
 
   mozilla::dom::ScreenOrientation mOrientation;
 
 private:
   class FullScreenEventListener final : public nsIDOMEventListener
   {
     ~FullScreenEventListener() {}
   public:
@@ -153,12 +154,14 @@ private:
     FULLSCREEN_LOCK_ALLOWED,
     LOCK_ALLOWED
   };
 
   LockPermission GetLockOrientationPermission() const;
 
   bool IsDeviceSizePageSize();
 
+  bool ShouldResistFingerprinting() const;
+
   nsRefPtr<FullScreenEventListener> mEventListener;
 };
 
 #endif /* nsScreen_h___ */
--- a/dom/base/nsWindowRoot.cpp
+++ b/dom/base/nsWindowRoot.cpp
@@ -19,16 +19,17 @@
 #include "nsFocusManager.h"
 #include "nsIContent.h"
 #include "nsIDOMHTMLInputElement.h"
 #include "nsIDOMHTMLTextAreaElement.h"
 #include "nsIControllers.h"
 #include "nsIController.h"
 #include "xpcpublic.h"
 #include "nsCycleCollectionParticipant.h"
+#include "mozilla/dom/TabParent.h"
 
 #ifdef MOZ_XUL
 #include "nsIDOMXULElement.h"
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
@@ -380,16 +381,56 @@ nsWindowRoot::GetParentObject()
 }
 
 JSObject*
 nsWindowRoot::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return mozilla::dom::WindowRootBinding::Wrap(aCx, this, aGivenProto);
 }
 
+void
+nsWindowRoot::AddBrowser(mozilla::dom::TabParent* aBrowser)
+{
+  nsWeakPtr weakBrowser = do_GetWeakReference(static_cast<nsITabParent*>(aBrowser));
+  mWeakBrowsers.PutEntry(weakBrowser);
+}
+
+void
+nsWindowRoot::RemoveBrowser(mozilla::dom::TabParent* aBrowser)
+{
+  nsWeakPtr weakBrowser = do_GetWeakReference(static_cast<nsITabParent*>(aBrowser));
+  mWeakBrowsers.RemoveEntry(weakBrowser);
+}
+
+static PLDHashOperator
+WeakBrowserEnumFunc(nsRefPtrHashKey<nsIWeakReference>* aKey, void* aArg)
+{
+  nsTArray<nsRefPtr<TabParent>>* tabParents =
+    static_cast<nsTArray<nsRefPtr<TabParent>>*>(aArg);
+  nsCOMPtr<nsITabParent> tabParent(do_QueryReferent((*aKey).GetKey()));
+  TabParent* tab = TabParent::GetFrom(tabParent);
+  if (tab) {
+    tabParents->AppendElement(tab);
+  }
+  return PL_DHASH_NEXT;
+}
+
+void
+nsWindowRoot::EnumerateBrowsers(BrowserEnumerator aEnumFunc, void* aArg)
+{
+  // Collect strong references to all browsers in a separate array in
+  // case aEnumFunc alters mWeakBrowsers.
+  nsTArray<nsRefPtr<TabParent>> tabParents;
+  mWeakBrowsers.EnumerateEntries(WeakBrowserEnumFunc, &tabParents);
+
+  for (uint32_t i = 0; i < tabParents.Length(); ++i) {
+    aEnumFunc(tabParents[i], aArg);
+  }
+}
+
 ///////////////////////////////////////////////////////////////////////////////////
 
 already_AddRefed<EventTarget>
 NS_NewWindowRoot(nsPIDOMWindow* aWindow)
 {
   nsCOMPtr<EventTarget> result = new nsWindowRoot(aWindow);
   return result.forget();
 }
--- a/dom/base/nsWindowRoot.h
+++ b/dom/base/nsWindowRoot.h
@@ -64,29 +64,37 @@ public:
 
   nsIGlobalObject* GetParentObject();
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsWindowRoot,
                                                          nsIDOMEventTarget)
 
+  virtual void AddBrowser(mozilla::dom::TabParent* aBrowser) override;
+  virtual void RemoveBrowser(mozilla::dom::TabParent* aBrowser) override;
+  virtual void EnumerateBrowsers(BrowserEnumerator aEnumFunc, void *aArg) override;
+
 protected:
   virtual ~nsWindowRoot();
 
   void GetEnabledDisabledCommandsForControllers(nsIControllers* aControllers,
                                                 nsTHashtable<nsCharPtrHashKey>& aCommandsHandled,
                                                 nsTArray<nsCString>& aEnabledCommands,
                                                 nsTArray<nsCString>& aDisabledCommands);
 
   // Members
   nsCOMPtr<nsPIDOMWindow> mWindow;
   // We own the manager, which owns event listeners attached to us.
   nsRefPtr<mozilla::EventListenerManager> mListenerManager; // [Strong]
   nsCOMPtr<nsIDOMNode> mPopupNode; // [OWNER]
 
   nsCOMPtr<mozilla::dom::EventTarget> mParent;
+
+  // The TabParents that are currently registered with this top-level window.
+  typedef nsTHashtable<nsRefPtrHashKey<nsIWeakReference>> WeakBrowserTable;
+  WeakBrowserTable mWeakBrowsers;
 };
 
 extern already_AddRefed<mozilla::dom::EventTarget>
 NS_NewWindowRoot(nsPIDOMWindow* aWindow);
 
 #endif
new file mode 100644
--- /dev/null
+++ b/dom/base/test/chrome/bug418986-1.js
@@ -0,0 +1,71 @@
+// The main test function.
+let test = function (isContent) {
+  SimpleTest.waitForExplicitFinish();
+
+  let { ww } = SpecialPowers.Services;
+  window.chromeWindow = ww.activeWindow;
+
+  // The pairs of values expected to be the same when
+  // fingerprinting resistance is enabled.
+  let pairs = [
+    ["screenX", 0],
+    ["screenY", 0],
+    ["mozInnerScreenX", 0],
+    ["mozInnerScreenY", 0],
+    ["screen.pixelDepth", 24],
+    ["screen.colorDepth", 24],
+    ["screen.availWidth", "innerWidth"],
+    ["screen.availHeight", "innerHeight"],
+    ["screen.left", 0],
+    ["screen.top", 0],
+    ["screen.availLeft", 0],
+    ["screen.availTop", 0],
+    ["screen.width", "innerWidth"],
+    ["screen.height", "innerHeight"],
+    ["screen.mozOrientation", "'landscape-primary'"],
+    ["devicePixelRatio", 1]
+  ];
+
+  // checkPair: tests if members of pair [a, b] are equal when evaluated.
+  let checkPair = function (a, b) {
+    is(eval(a), eval(b), a + " should be equal to " + b);
+  };
+
+  // Returns generator object that iterates through pref values.
+  let prefVals = (for (prefVal of [false, true]) prefVal);
+
+  // The main test function, runs until all pref values are exhausted.
+  let nextTest = function () {
+    let {value : prefValue, done} = prefVals.next();
+    if (done) {
+      SimpleTest.finish();
+      return;
+    }
+    SpecialPowers.pushPrefEnv({set : [["privacy.resistFingerprinting", prefValue]]},
+      function () {
+        // We will be resisting fingerprinting if the pref is enabled,
+        // and we are in a content script (not chrome).
+        let resisting = prefValue && isContent;
+        // Check each of the pairs.
+        pairs.map(function ([item, onVal]) {
+          if (resisting) {
+            checkPair("window." + item, onVal);
+          } else {
+            if (!item.startsWith("moz")) {
+              checkPair("window." + item, "chromeWindow." + item);
+            }
+          }
+        });
+        if (!resisting) {
+          // Hard to predict these values, but we can enforce constraints:
+          ok(window.mozInnerScreenX >= chromeWindow.mozInnerScreenX,
+             "mozInnerScreenX");
+          ok(window.mozInnerScreenY >= chromeWindow.mozInnerScreenY,
+             "mozInnerScreenY");
+        }
+      nextTest();
+    });
+  }
+
+  nextTest();
+}
--- a/dom/base/test/chrome/chrome.ini
+++ b/dom/base/test/chrome/chrome.ini
@@ -1,13 +1,14 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g'
 support-files =
   blockNoPlugins.xml
   blockPluginHard.xml
+  bug418986-1.js
   cpows_child.js
   cpows_parent.xul
   file_bug391728.html
   file_bug391728_2.html
   file_bug549682.xul
   file_bug616841.xul
   file_bug816340.xul
   file_bug990812-1.xul
@@ -26,16 +27,17 @@ support-files =
 
 [test_bug206691.xul]
 [test_bug339494.xul]
 [test_bug357450.xul]
 [test_bug380418.html]
 [test_bug380418.html^headers^]
 [test_bug383430.html]
 [test_bug391728.html]
+[test_bug418986-1.xul]
 [test_bug421622.xul]
 [test_bug429785.xul]
 [test_bug430050.xul]
 [test_bug467123.xul]
 [test_bug549682.xul]
 [test_bug571390.xul]
 [test_bug574596.html]
 [test_bug1098074_throw_from_ReceiveMessage.xul]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/chrome/test_bug418986-1.xul
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=418986-1
+-->
+<window title="Mozilla Bug 418986 (Part 1)"
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986-1"
+     target="_blank">Mozilla Bug 418986 (Part 1)</a>
+
+  <script type="application/javascript;version=1.7" src="bug418986-1.js"></script>
+  <!-- test code goes here -->
+  <script type="application/javascript"><![CDATA[
+    window.onload = function() {
+      test(false);
+    };
+  ]]></script>
+  </body>
+</window>
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -51,16 +51,17 @@ support-files =
   bug682592-subframe-ref.html
   bug682592-subframe.html
   bug696301-script-1.js
   bug696301-script-1.js^headers^
   bug696301-script-2.js
   bug704320.sjs
   bug704320_counter.sjs
   bug819051.sjs
+  chrome/bug418986-1.js
   copypaste.js
   delayedServerEvents.sjs
   echo.sjs
   eventsource.resource
   eventsource.resource^headers^
   eventsource_redirect.resource
   eventsource_redirect.resource^headers^
   eventsource_redirect_to.resource
@@ -448,16 +449,17 @@ support-files = test_bug402150.html^head
 [test_bug414190.html]
 [test_bug415860.html]
 [test_bug416317-1.html]
 [test_bug416317-2.html]
 [test_bug416383.html]
 [test_bug417255.html]
 [test_bug417384.html]
 [test_bug418214.html]
+[test_bug418986-1.html]
 [test_bug419132.html]
 [test_bug419527.xhtml]
 [test_bug420609.xhtml]
 [test_bug420700.html]
 [test_bug421602.html]
 [test_bug422403-1.html]
 skip-if = buildapp == 'b2g' # b2g(bug 901343, specialpowers.wrap issue [nsIChannel.open]) b2g-debug(bug 901343, specialpowers.wrap issue [nsIChannel.open]) b2g-desktop(bug 901343, specialpowers.wrap issue [nsIChannel.open])
 [test_bug422403-2.xhtml]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_bug418986-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=418986
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test 1/3 for Bug 418986 - Resist fingerprinting by preventing exposure of screen and system info</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript;version=1.7" src="chrome/bug418986-1.js"></script>
+</head>
+<body>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986">Bug 418986</a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test"></pre>
+  <script>
+    window.onload = function() {
+      test(true);
+    };
+  </script>
+</body>
+</html>
--- a/dom/events/Event.cpp
+++ b/dom/events/Event.cpp
@@ -885,16 +885,23 @@ Event::Shutdown()
   }
 }
 
 LayoutDeviceIntPoint
 Event::GetScreenCoords(nsPresContext* aPresContext,
                        WidgetEvent* aEvent,
                        LayoutDeviceIntPoint aPoint)
 {
+  if (!nsContentUtils::IsCallerChrome() &&
+      nsContentUtils::ResistFingerprinting()) {
+    // When resisting fingerprinting, return client coordinates instead.
+    CSSIntPoint clientCoords = GetClientCoords(aPresContext, aEvent, aPoint, CSSIntPoint(0, 0));
+    return LayoutDeviceIntPoint(clientCoords.x, clientCoords.y);
+  }
+
   if (EventStateManager::sIsPointerLocked) {
     return EventStateManager::sLastScreenPoint;
   }
 
   if (!aEvent ||
        (aEvent->mClass != eMouseEventClass &&
         aEvent->mClass != eMouseScrollEventClass &&
         aEvent->mClass != eWheelEventClass &&
new file mode 100644
--- /dev/null
+++ b/dom/events/test/bug418986-3.js
@@ -0,0 +1,69 @@
+SimpleTest.waitForExplicitFinish();
+
+// The main testing function.
+let test = function (isContent) {
+  // Each definition is [eventType, prefSetting]
+  // Where we are setting the "privacy.resistFingerprinting" pref.
+  let eventDefs = [["mousedown", true],
+                   ["mouseup", true],
+                   ["mousedown", false],
+                   ["mouseup", false]];
+
+  let testCounter = 0;
+
+  // Declare ahead of time.
+  let setup;
+
+  // This function is called when the event handler fires.
+  let handleEvent = function (event, prefVal) {
+    let resisting = prefVal && isContent;
+    if (resisting) {
+      is(event.screenX, event.clientX, "event.screenX and event.clientX should be the same");
+      is(event.screenY, event.clientY, "event.screenY and event.clientY should be the same");
+    } else {
+      // We can't be sure about X coordinates not being equal, but we can test Y.
+      isnot(event.screenY, event.clientY, "event.screenY !== event.clientY");
+    }
+    ++testCounter;
+    if (testCounter < eventDefs.length) {
+      nextTest();
+    } else {
+      SimpleTest.finish();
+    }
+  };
+
+  // In this function, we set up the nth div and event handler,
+  // and then synthesize a mouse event in the div, to test
+  // whether the resulting events resist fingerprinting by
+  // suppressing absolute screen coordinates.
+  nextTest = function () {
+    let [eventType, prefVal] = eventDefs[testCounter];
+    SpecialPowers.pushPrefEnv({set:[["privacy.resistFingerprinting", prefVal]]},
+      function () {
+        // The following code creates a new div for each event in eventDefs,
+        // attaches a listener to listen for the event, and then generates
+        // a fake event at the center of the div.
+        let div = document.createElement("div");
+        div.style.width = "10px";
+        div.style.height = "10px";
+        div.style.backgroundColor = "red";
+        // Name the div after the event we're listening for.
+        div.id = eventType;
+        document.getElementById("body").appendChild(div);
+        // Seems we can't add an event listener in chrome unless we run
+        // it in a later task.
+        window.setTimeout(function() {
+          div.addEventListener(eventType, event => handleEvent(event, prefVal), false);
+          // For some reason, the following synthesizeMouseAtCenter call only seems to run if we
+          // wrap it in a window.setTimeout(..., 0).
+          window.setTimeout(function () {
+            synthesizeMouseAtCenter(div, {type : eventType});
+          }, 0);
+        }, 0);
+      });
+  };
+
+  // Now run by starting with the 0th event.
+  nextTest();
+
+};
--- a/dom/events/test/chrome.ini
+++ b/dom/events/test/chrome.ini
@@ -1,22 +1,24 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g'
 support-files =
   bug415498-doc1.html
   bug415498-doc2.html
+  bug418986-3.js
   bug591249_iframe.xul
   bug602962.xul
   file_bug679494.html
   window_bug617528.xul
   test_bug336682.js
 
 [test_bug336682_2.xul]
 [test_bug368835.html]
 [test_bug415498.xul]
+[test_bug418986-3.xul]
 [test_bug524674.xul]
 [test_bug586961.xul]
 [test_bug591249.xul]
 [test_bug602962.xul]
 [test_bug617528.xul]
 [test_bug679494.xul]
 [test_bug930374-chrome.html]
 [test_bug1128787-1.html]
--- a/dom/events/test/mochitest.ini
+++ b/dom/events/test/mochitest.ini
@@ -2,16 +2,17 @@
 skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
 support-files =
   bug226361_iframe.xhtml
   bug299673.js
   bug322588-popup.html
   bug426082.html
   bug648573.html
   bug656379-1.html
+  bug418986-3.js
   error_event_worker.js
   empty.js
   window_bug493251.html
   window_bug659071.html
   window_wheel_default_action.html
 
 [test_accel_virtual_modifier.html]
 [test_addEventListenerExtraArg.html]
@@ -33,16 +34,19 @@ support-files = test_bug336682.js
 [test_bug368835.html]
 [test_bug379120.html]
 [test_bug391568.xhtml]
 [test_bug402089.html]
 [test_bug405632.html]
 [test_bug409604.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT
 [test_bug412567.html]
+[test_bug418986-3.html]
+# Sometimes fails to finish after tests pass on 'B2G ICS Emulator'.
+skip-if = (os == 'b2g')
 [test_bug422132.html]
 skip-if = buildapp == 'b2g' || e10s # b2g(2 failures out of 8, mousewheel test) b2g-debug(2 failures out of 8, mousewheel test) b2g-desktop(2 failures out of 8, mousewheel test)
 [test_bug426082.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || os == "win" || toolkit == 'android' || e10s # Intermittent failures, bug 921693 # b2g(1 failure out of 6, Moving the mouse down from the label should have unpressed the button) b2g-debug(1 failure out of 6, Moving the mouse down from the label should have unpressed the button) b2g-desktop(1 failure out of 6, Moving the mouse down from the label should have unpressed the button)
 [test_bug427537.html]
 [test_bug428988.html]
 [test_bug432698.html]
 skip-if = buildapp == 'mulet'
new file mode 100644
--- /dev/null
+++ b/dom/events/test/test_bug418986-3.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=418986
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test 3/3 for Bug 418986 - Resist fingerprinting by preventing exposure of screen and system info</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body id="body">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986">Bug 418986</a>
+<p id="display"></p>
+<pre id="test"></pre>
+<script type="application/javascript;version=1.7" src="bug418986-3.js"></script>
+<script type="application/javascript;version=1.7">
+  // This test produces fake mouse events and checks that the screenX and screenY
+  // properties of the received event objects provide client window coordinates.
+  // Run the test once the window has loaded.
+  window.onload = () => test(true);
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/events/test/test_bug418986-3.xul
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+Bug 418986
+-->
+<window title="Mozilla Bug 418986"
+  xmlns:html="http://www.w3.org/1999/xhtml"
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>      
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+<body id="body" xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986">
+Mozilla Bug 418986</a>
+</body>
+
+<script type="application/javascript;version=1.7" src="bug418986-3.js"></script>
+<script type="application/javascript;version=1.7"><![CDATA[
+  // This test produces fake mouse events and checks that the screenX and screenY
+  // properties of the received event objects provide client window coordinates.
+  // Run the test once the window has loaded.
+  test(false);
+]]></script>  
+
+</window>
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -100,16 +100,18 @@ static PRLogModuleInfo* gMediaElementEve
 #include "nsIContentSecurityPolicy.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/FloatingPoint.h"
 
 #include "nsIPermissionManager.h"
 #include "nsContentTypeParser.h"
 
+#include "mozilla/EventStateManager.h"
+
 using namespace mozilla::layers;
 using mozilla::net::nsMediaFragmentURIParser;
 
 namespace mozilla {
 namespace dom {
 
 // Number of milliseconds between progress events as defined by spec
 static const uint32_t PROGRESS_MS = 350;
@@ -2178,16 +2180,23 @@ HTMLMediaElement::ResetConnectionState()
   ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY);
   ChangeDelayLoadStatus(false);
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING);
 }
 
 void
 HTMLMediaElement::Play(ErrorResult& aRv)
 {
+  // Prevent media element from being auto-started by a script when
+  // media.autoplay.enabled=false
+  if (!IsAutoplayEnabled() && !EventStateManager::IsHandlingUserInput() && !nsContentUtils::IsCallerChrome()) {
+    LOG(LogLevel::Debug, ("%p Blocked attempt to autoplay media.", this));
+    return;
+  }
+
   StopSuspendingAfterFirstFrame();
   SetPlayedOrSeeked(true);
 
   if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
     aRv = Load();
     if (aRv.Failed()) {
       return;
     }
@@ -2706,21 +2715,17 @@ nsresult HTMLMediaElement::InitializeDec
 
   LOG(LogLevel::Debug, ("%p Cloned decoder %p from %p", this, decoder.get(), aOriginal));
 
   if (!decoder->Init(this)) {
     LOG(LogLevel::Debug, ("%p Failed to init cloned decoder %p", this, decoder.get()));
     return NS_ERROR_FAILURE;
   }
 
-  double duration = aOriginal->GetDuration();
-  if (duration >= 0) {
-    decoder->SetDuration(duration);
-    decoder->SetMediaSeekable(aOriginal->IsMediaSeekable());
-  }
+  decoder->SetMediaSeekable(aOriginal->IsMediaSeekable());
 
   nsRefPtr<MediaResource> resource = originalResource->CloneData(decoder);
   if (!resource) {
     LOG(LogLevel::Debug, ("%p Failed to cloned stream for decoder %p", this, decoder.get()));
     return NS_ERROR_FAILURE;
   }
 
   return FinishDecoderSetup(decoder, resource, nullptr, aOriginal);
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -3251,17 +3251,16 @@ nsHTMLDocument::ExecCommand(const nsAStr
     return false;
   }
 
   bool isCutCopy = (commandID.LowerCaseEqualsLiteral("cut") ||
                     commandID.LowerCaseEqualsLiteral("copy"));
 
   // if editing is not on, bail
   if (!isCutCopy && !IsEditingOnAfterFlush()) {
-    rv.Throw(NS_ERROR_FAILURE);
     return false;
   }
 
   // if they are requesting UI from us, let's fail since we have no UI
   if (doShowUI) {
     return false;
   }
 
@@ -3288,17 +3287,16 @@ nsHTMLDocument::ExecCommand(const nsAStr
 
   if (commandID.LowerCaseEqualsLiteral("gethtml")) {
     rv.Throw(NS_ERROR_FAILURE);
     return false;
   }
 
   bool restricted = commandID.LowerCaseEqualsLiteral("paste");
   if (restricted && !nsContentUtils::IsCallerChrome()) {
-    rv = NS_ERROR_DOM_SECURITY_ERR;
     return false;
   }
 
   // get command manager and dispatch command to our window if it's acceptable
   nsCOMPtr<nsICommandManager> cmdMgr;
   GetMidasCommandManager(getter_AddRefs(cmdMgr));
   if (!cmdMgr) {
     rv.Throw(NS_ERROR_FAILURE);
@@ -3385,17 +3383,16 @@ nsHTMLDocument::QueryCommandEnabled(cons
   // Report false for restricted commands
   bool restricted = commandID.LowerCaseEqualsLiteral("paste");
   if (restricted && !nsContentUtils::IsCallerChrome()) {
     return false;
   }
 
   // if editing is not on, bail
   if (!IsEditingOnAfterFlush()) {
-    rv.Throw(NS_ERROR_FAILURE);
     return false;
   }
 
   // get command manager and dispatch command to our window if it's acceptable
   nsCOMPtr<nsICommandManager> cmdMgr;
   GetMidasCommandManager(getter_AddRefs(cmdMgr));
   if (!cmdMgr) {
     rv.Throw(NS_ERROR_FAILURE);
@@ -3428,17 +3425,16 @@ nsHTMLDocument::QueryCommandIndeterm(con
 {
   nsAutoCString cmdToDispatch;
   if (!ConvertToMidasInternalCommand(commandID, cmdToDispatch)) {
     return false;
   }
 
   // if editing is not on, bail
   if (!IsEditingOnAfterFlush()) {
-    rv.Throw(NS_ERROR_FAILURE);
     return false;
   }
 
   // get command manager and dispatch command to our window if it's acceptable
   nsCOMPtr<nsICommandManager> cmdMgr;
   GetMidasCommandManager(getter_AddRefs(cmdMgr));
   if (!cmdMgr) {
     rv.Throw(NS_ERROR_FAILURE);
@@ -3489,17 +3485,16 @@ nsHTMLDocument::QueryCommandState(const 
   if (!ConvertToMidasInternalCommand(commandID, commandID,
                                      cmdToDispatch, paramToCheck,
                                      dummy, dummy2)) {
     return false;
   }
 
   // if editing is not on, bail
   if (!IsEditingOnAfterFlush()) {
-    rv.Throw(NS_ERROR_FAILURE);
     return false;
   }
 
   // get command manager and dispatch command to our window if it's acceptable
   nsCOMPtr<nsICommandManager> cmdMgr;
   GetMidasCommandManager(getter_AddRefs(cmdMgr));
   if (!cmdMgr) {
     rv.Throw(NS_ERROR_FAILURE);
@@ -3612,17 +3607,16 @@ nsHTMLDocument::QueryCommandValue(const 
   nsAutoCString cmdToDispatch, paramStr;
   if (!ConvertToMidasInternalCommand(commandID, cmdToDispatch)) {
     // Return empty string
     return;
   }
 
   // if editing is not on, bail
   if (!IsEditingOnAfterFlush()) {
-    rv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   // get command manager and dispatch command to our window if it's acceptable
   nsCOMPtr<nsICommandManager> cmdMgr;
   GetMidasCommandManager(getter_AddRefs(cmdMgr));
   if (!cmdMgr) {
     rv.Throw(NS_ERROR_FAILURE);
--- a/dom/imptests/failures/editing/conformancetest/test_event.html.json
+++ b/dom/imptests/failures/editing/conformancetest/test_event.html.json
@@ -4,18 +4,16 @@
   "Simple editable div: beforeinput event, uncanceled":true,
   "Simple editable div: input event, uncanceled":true,
   "Editable b: execCommand() must not throw, canceled":true,
   "Editable b: beforeinput event, canceled":true,
   "Editable b: input event, canceled":true,
   "Editable b: execCommand() must not throw, uncanceled":true,
   "Editable b: beforeinput event, uncanceled":true,
   "Editable b: input event, uncanceled":true,
-  "No editable content: execCommand() must not throw, canceled":true,
-  "No editable content: execCommand() must not throw, uncanceled":true,
   "Changing selection from handler: beforeinput event, canceled":true,
   "Changing selection from handler: input event, canceled":true,
   "Changing selection from handler: beforeinput event, uncanceled":true,
   "Changing selection from handler: input event, uncanceled":true,
   "Command backColor, value \"\": beforeinput event, canceled":true,
   "Command backColor, value \"\": beforeinput event, uncanceled":true,
   "Command backColor, value \"\": input event, uncanceled":true,
   "Command backColor, value \"quasit\": beforeinput event, canceled":true,
--- a/dom/interfaces/apps/mozIApplication.idl
+++ b/dom/interfaces/apps/mozIApplication.idl
@@ -2,21 +2,23 @@
  * vim: sw=2 ts=8 et :
  */
 /* 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 "domstubs.idl"
 
+interface nsIPrincipal;
+
 /**
  * We expose Gecko-internal helpers related to "web apps" through this
  * sub-interface.
  */
-[scriptable, uuid(1d856b11-ac29-47d3-bd52-a86e3d45fcf5)]
+[scriptable, uuid(e76aa5e0-80b2-404f-bccc-1067828bb6ed)]
 interface mozIApplication: nsISupports
 {
   /* Return true if this app has |permission|. */
   boolean hasPermission(in string permission);
 
   /**
    *  Return true if this app can be a widget and
    *  its |widgetPages| contains |page|
@@ -53,9 +55,12 @@ interface mozIApplication: nsISupports
   /* Store version if the app is installed from a store */
   readonly attribute unsigned long storeVersion;
 
   /* role copied from the manifest */
   readonly attribute DOMString role;
 
   /* Returns the kind of the app. */
   readonly attribute DOMString kind;
+
+  /* Returns the app's principal */
+  readonly attribute nsIPrincipal principal;
 };
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -44,17 +44,17 @@ interface nsIDOMClientRect;
 interface nsIURI;
 interface nsIDOMEventTarget;
 interface nsIRunnable;
 interface nsITranslationNodeList;
 interface nsIJSRAIIHelper;
 interface nsIContentPermissionRequest;
 interface nsIObserver;
 
-[scriptable, uuid(098d9f0d-7809-4d3c-8fc6-e5b3fb71835b)]
+[scriptable, uuid(ec176f3b-2886-4090-938e-dded103c5f1c)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -1809,16 +1809,24 @@ interface nsIDOMWindowUtils : nsISupport
                        in int32_t aLeft);
 
   /**
    * Enable some service workers testing features.
    */
   attribute boolean serviceWorkersTestingEnabled;
 
   /**
+   * Returns a JSObject which contains a list of frame uniformities
+   * when the pref gfx.vsync.collect-scroll-data is enabled.
+   * Every result contains a layer address and a frame uniformity for that layer.
+   * A negative frame uniformity value indicates an invalid frame uniformity and an error has occured.
+   */
+  [implicit_jscontext] jsval getFrameUniformityTestData();
+
+  /*
    * Increase the chaos mode activation level. An equivalent number of
    * calls to leaveChaosMode must be made in order to restore the original
    * chaos mode state. If the activation level is nonzero all chaos mode
    * features are activated.
    */
   void enterChaosMode();
 
   /**
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -20,16 +20,17 @@ include JavaScriptTypes;
 include URIParams;
 include BrowserConfiguration;
 
 
 using class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h";
 using class mozilla::gfx::Matrix from "mozilla/gfx/Matrix.h";
 using struct gfxSize from "gfxPoint.h";
 using CSSRect from "Units.h";
+using CSSSize from "Units.h";
 using LayoutDeviceIntRect from "Units.h";
 using mozilla::LayoutDeviceIntPoint from "Units.h";
 using ScreenIntSize from "Units.h";
 using struct mozilla::layers::FrameMetrics from "FrameMetrics.h";
 using struct mozilla::layers::ScrollableLayerGuid from "FrameMetrics.h";
 using struct mozilla::layers::ZoomConstraints from "FrameMetrics.h";
 using FrameMetrics::ViewID from "FrameMetrics.h";
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
@@ -543,17 +544,17 @@ child:
          uint64_t layersId,
          nullable PRenderFrame renderFrame,
          bool parentIsActive);
 
     LoadURL(nsCString uri, BrowserConfiguration config);
 
     CacheFileDescriptor(nsString path, FileDescriptor fd);
 
-    UpdateDimensions(IntRect rect, ScreenIntSize size, ScreenOrientation orientation,
+    UpdateDimensions(CSSRect rect, CSSSize size, ScreenOrientation orientation,
                      LayoutDeviceIntPoint chromeDisp) compressall;
 
     UpdateFrame(FrameMetrics frame);
 
     // The following methods correspond to functions on the GeckoContentController
     // interface in gfx/layers/apz/public/GeckoContentController.h. Refer to documentation
     // in that file for these functions.
     RequestFlingSnap(ViewID aScrollID, CSSPoint aDestination);
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -87,16 +87,17 @@
 #include "nsDOMClassInfoID.h"
 #include "nsColorPickerProxy.h"
 #include "nsPresShell.h"
 #include "nsIAppsService.h"
 #include "nsNetUtil.h"
 #include "nsIPermissionManager.h"
 #include "nsIScriptError.h"
 #include "mozilla/EventForwards.h"
+#include "nsDeviceContext.h"
 
 #define BROWSER_ELEMENT_CHILD_SCRIPT \
     NS_LITERAL_STRING("chrome://global/content/BrowserElementChild.js")
 
 #define TABC_LOG(...)
 // #define TABC_LOG(...) printf_stderr("TABC: " __VA_ARGS__)
 
 using namespace mozilla;
@@ -160,17 +161,16 @@ UsingCompositorLRU()
 }
 
 NS_IMPL_ISUPPORTS(TabChild::DelayedFireContextMenuEvent,
                   nsITimerCallback)
 
 TabChildBase::TabChildBase()
   : mContentDocumentIsDisplayed(false)
   , mTabChildGlobal(nullptr)
-  , mInnerSize(0, 0)
 {
   mozilla::HoldJSObjects(this);
 }
 
 TabChildBase::~TabChildBase()
 {
   mAnonymousGlobalScopes.Clear();
   mozilla::DropJSObjects(this);
@@ -231,19 +231,21 @@ void
 TabChildBase::InitializeRootMetrics()
 {
   // Calculate a really simple resolution that we probably won't
   // be keeping, as well as putting the scroll offset back to
   // the top-left of the page.
   mLastRootMetrics.SetViewport(CSSRect(CSSPoint(), kDefaultViewportSize));
   mLastRootMetrics.SetCompositionBounds(ParentLayerRect(
       ParentLayerPoint(),
-      ParentLayerSize(ViewAs<ParentLayerPixel>(mInnerSize, PixelCastJustification::ScreenIsParentLayerForRoot))));
+      ParentLayerSize(
+        ViewAs<ParentLayerPixel>(GetInnerSize(),
+                                 PixelCastJustification::ScreenIsParentLayerForRoot))));
   mLastRootMetrics.SetZoom(CSSToParentLayerScale2D(
-      ConvertScaleForRoot(CalculateIntrinsicScale(mInnerSize, kDefaultViewportSize))));
+      ConvertScaleForRoot(CalculateIntrinsicScale(GetInnerSize(), kDefaultViewportSize))));
   mLastRootMetrics.SetDevPixelsPerCSSPixel(WebWidget()->GetDefaultScale());
   // We use ParentLayerToLayerScale(1) below in order to turn the
   // async zoom amount into the gecko zoom amount.
   mLastRootMetrics.SetCumulativeResolution(mLastRootMetrics.GetZoom() / mLastRootMetrics.GetDevPixelsPerCSSPixel() * ParentLayerToLayerScale(1));
   // This is the root layer, so the cumulative resolution is the same
   // as the resolution.
   mLastRootMetrics.SetPresShellResolution(mLastRootMetrics.GetCumulativeResolution().ToScaleFactor().scale);
   mLastRootMetrics.SetScrollOffset(CSSPoint(0, 0));
@@ -294,42 +296,42 @@ bool
 TabChildBase::HandlePossibleViewportChange(const ScreenIntSize& aOldScreenSize)
 {
   nsIWidget* widget = WebWidget();
   if (!widget || !widget->AsyncPanZoomEnabled()) {
     return false;
   }
 
   TABC_LOG("HandlePossibleViewportChange aOldScreenSize=%s mInnerSize=%s\n",
-    Stringify(aOldScreenSize).c_str(), Stringify(mInnerSize).c_str());
+    Stringify(aOldScreenSize).c_str(), Stringify(GetInnerSize()).c_str());
 
   nsCOMPtr<nsIDocument> document(GetDocument());
   if (!document) {
     return false;
   }
 
-  nsViewportInfo viewportInfo = nsContentUtils::GetViewportInfo(document, mInnerSize);
+  nsViewportInfo viewportInfo = nsContentUtils::GetViewportInfo(document, GetInnerSize());
   uint32_t presShellId = 0;
   mozilla::layers::FrameMetrics::ViewID viewId = FrameMetrics::NULL_SCROLL_ID;
   bool scrollIdentifiersValid = APZCCallbackHelper::GetOrCreateScrollIdentifiers(
         document->GetDocumentElement(), &presShellId, &viewId);
   if (scrollIdentifiersValid) {
     ZoomConstraints constraints(
       viewportInfo.IsZoomAllowed(),
       viewportInfo.IsDoubleTapZoomAllowed(),
       ConvertScaleForRoot(viewportInfo.GetMinZoom()),
       ConvertScaleForRoot(viewportInfo.GetMaxZoom()));
     DoUpdateZoomConstraints(presShellId,
                             viewId,
                             /* isRoot = */ true,
                             constraints);
   }
 
-  float screenW = mInnerSize.width;
-  float screenH = mInnerSize.height;
+  float screenW = GetInnerSize().width;
+  float screenH = GetInnerSize().height;
   CSSSize viewport(viewportInfo.GetSize());
 
   // We're not being displayed in any way; don't bother doing anything because
   // that will just confuse future adjustments.
   if (!screenW || !screenH) {
     return false;
   }
 
@@ -353,56 +355,58 @@ TabChildBase::HandlePossibleViewportChan
   // window.innerWidth before they are painted have a correct value (bug
   // 771575).
   if (!mContentDocumentIsDisplayed) {
     return false;
   }
 
   ScreenIntSize oldScreenSize = aOldScreenSize;
   if (oldScreenSize == ScreenIntSize()) {
-    oldScreenSize = mInnerSize;
+    oldScreenSize = GetInnerSize();
   }
 
   FrameMetrics metrics(mLastRootMetrics);
   metrics.SetViewport(CSSRect(CSSPoint(), viewport));
 
-  // Calculate the composition bounds based on mInnerSize, excluding the sizes
+  // Calculate the composition bounds based on the inner size, excluding the sizes
   // of the scrollbars if they are not overlay scrollbars.
-  ScreenSize compositionSize(mInnerSize);
+  ScreenSize compositionSize(GetInnerSize());
   nsCOMPtr<nsIPresShell> shell = GetPresShell();
   if (shell) {
     nsMargin scrollbarsAppUnits =
         nsLayoutUtils::ScrollbarAreaToExcludeFromCompositionBoundsFor(shell->GetRootScrollFrame());
     // Scrollbars are not subject to scaling, so CSS pixels = screen pixels for them.
     ScreenMargin scrollbars = CSSMargin::FromAppUnits(scrollbarsAppUnits)
                             * CSSToScreenScale(1.0f);
     compositionSize.width -= scrollbars.LeftRight();
     compositionSize.height -= scrollbars.TopBottom();
   }
 
   metrics.SetCompositionBounds(ParentLayerRect(
       ParentLayerPoint(),
-      ParentLayerSize(ViewAs<ParentLayerPixel>(compositionSize, PixelCastJustification::ScreenIsParentLayerForRoot))));
+      ParentLayerSize(
+        ViewAs<ParentLayerPixel>(GetInnerSize(),
+                                 PixelCastJustification::ScreenIsParentLayerForRoot))));
   metrics.SetRootCompositionSize(
       ScreenSize(compositionSize) * ScreenToLayoutDeviceScale(1.0f) / metrics.GetDevPixelsPerCSSPixel());
 
   // This change to the zoom accounts for all types of changes I can conceive:
   // 1. screen size changes, CSS viewport does not (pages with no meta viewport
   //    or a fixed size viewport)
   // 2. screen size changes, CSS viewport also does (pages with a device-width
   //    viewport)
   // 3. screen size remains constant, but CSS viewport changes (meta viewport
   //    tag is added or removed)
   // 4. neither screen size nor CSS viewport changes
   //
   // In all of these cases, we maintain how much actual content is visible
   // within the screen width. Note that "actual content" may be different with
   // respect to CSS pixels because of the CSS viewport size changing.
   CSSToScreenScale oldIntrinsicScale = CalculateIntrinsicScale(oldScreenSize, oldBrowserSize);
-  CSSToScreenScale newIntrinsicScale = CalculateIntrinsicScale(mInnerSize, viewport);
+  CSSToScreenScale newIntrinsicScale = CalculateIntrinsicScale(GetInnerSize(), viewport);
   metrics.ZoomBy(newIntrinsicScale.scale / oldIntrinsicScale.scale);
 
   // Changing the zoom when we're not doing a first paint will get ignored
   // by AsyncPanZoomController and causes a blurry flash.
   bool isFirstPaint = true;
   if (shell) {
     isFirstPaint = shell->GetIsFirstPaint();
   }
@@ -876,17 +880,16 @@ TabChild::TabChild(nsIContentChild* aMan
                    const TabId& aTabId,
                    const TabContext& aContext,
                    uint32_t aChromeFlags)
   : TabContext(aContext)
   , mRemoteFrame(nullptr)
   , mManager(aManager)
   , mChromeFlags(aChromeFlags)
   , mLayersId(0)
-  , mOuterRect(0, 0, 0, 0)
   , mActivePointerId(-1)
   , mAppPackageFileDescriptorRecved(false)
   , mLastBackgroundColor(NS_RGB(255, 255, 255))
   , mDidFakeShow(false)
   , mNotified(false)
   , mTriedBrowserInit(false)
   , mOrientation(eScreenOrientation_PortraitPrimary)
   , mUpdateHitRegion(false)
@@ -917,19 +920,19 @@ TabChild::TabChild(nsIContentChild* aMan
 NS_IMETHODIMP
 TabChild::HandleEvent(nsIDOMEvent* aEvent)
 {
   nsAutoString eventType;
   aEvent->GetType(eventType);
   if (eventType.EqualsLiteral("DOMMetaAdded")) {
     // This meta data may or may not have been a meta viewport tag. If it was,
     // we should handle it immediately.
-    HandlePossibleViewportChange(mInnerSize);
+    HandlePossibleViewportChange(GetInnerSize());
   } else if (eventType.EqualsLiteral("FullZoomChange")) {
-    HandlePossibleViewportChange(mInnerSize);
+    HandlePossibleViewportChange(GetInnerSize());
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabChild::Observe(nsISupports *aSubject,
                   const char *aTopic,
@@ -961,24 +964,24 @@ TabChild::Observe(nsISupports *aSubject,
         if (shell) {
           shell->SetIsFirstPaint(true);
         }
 
         mContentDocumentIsDisplayed = true;
 
         // In some cases before-first-paint gets called before
         // RecvUpdateDimensions is called and therefore before we have an
-        // mInnerSize value set. In such cases defer initializing the viewport
+        // inner size value set. In such cases defer initializing the viewport
         // until we we get an inner size.
         if (HasValidInnerSize()) {
           InitializeRootMetrics();
           if (shell) {
             nsLayoutUtils::SetResolutionAndScaleTo(shell, mLastRootMetrics.GetPresShellResolution());
           }
-          HandlePossibleViewportChange(mInnerSize);
+          HandlePossibleViewportChange(GetInnerSize());
         }
       }
     }
   }
 
   return NS_OK;
 }
 
@@ -1308,27 +1311,28 @@ TabChild::SetDimensions(uint32_t aFlags,
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabChild::GetDimensions(uint32_t aFlags, int32_t* aX,
                              int32_t* aY, int32_t* aCx, int32_t* aCy)
 {
+  ScreenIntRect rect = GetOuterRect();
   if (aX) {
-    *aX = mOuterRect.x;
+    *aX = rect.x;
   }
   if (aY) {
-    *aY = mOuterRect.y;
+    *aY = rect.y;
   }
   if (aCx) {
-    *aCx = mOuterRect.width;
+    *aCx = rect.width;
   }
   if (aCy) {
-    *aCy = mOuterRect.height;
+    *aCy = rect.height;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabChild::SetFocus()
 {
@@ -2045,47 +2049,51 @@ TabChild::RecvShow(const ScreenIntSize& 
     bool res = InitTabChildGlobal();
     ApplyShowInfo(aInfo);
     RecvParentActivated(aParentIsActive);
 
     return res;
 }
 
 bool
-TabChild::RecvUpdateDimensions(const nsIntRect& rect, const ScreenIntSize& size,
-                               const ScreenOrientation& orientation, const LayoutDeviceIntPoint& chromeDisp)
+TabChild::RecvUpdateDimensions(const CSSRect& rect, const CSSSize& size,
+                               const ScreenOrientation& orientation,
+                               const LayoutDeviceIntPoint& chromeDisp)
 {
     if (!mRemoteFrame) {
         return true;
     }
 
-    mOuterRect = rect;
+    mUnscaledOuterRect = rect;
     mChromeDisp = chromeDisp;
 
     bool initialSizing = !HasValidInnerSize()
                       && (size.width != 0 && size.height != 0);
+
+    mOrientation = orientation;
+    ScreenIntSize oldScreenSize = GetInnerSize();
+    SetUnscaledInnerSize(size);
+    ScreenIntSize screenSize = GetInnerSize();
     bool sizeChanged = true;
     if (initialSizing) {
       mHasValidInnerSize = true;
-    } else if (mInnerSize == size) {
+    } else if (screenSize == oldScreenSize) {
       sizeChanged = false;
     }
 
-    mOrientation = orientation;
-    ScreenIntSize oldScreenSize = mInnerSize;
-    mInnerSize = size;
-    mWidget->Resize(rect.x + chromeDisp.x, rect.y + chromeDisp.y, size.width, size.height,
-                     true);
+    ScreenIntRect screenRect = GetOuterRect();
+    mWidget->Resize(screenRect.x + chromeDisp.x, screenRect.y + chromeDisp.y,
+                    screenSize.width, screenSize.height, true);
 
     nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(WebNavigation());
-    baseWin->SetPositionAndSize(0, 0, size.width, size.height,
+    baseWin->SetPositionAndSize(0, 0, screenSize.width, screenSize.height,
                                 true);
 
     if (initialSizing && mContentDocumentIsDisplayed) {
-      // If this is the first time we're getting a valid mInnerSize, and the
+      // If this is the first time we're getting a valid inner size, and the
       // before-first-paint event has already been handled, then we need to set
       // up our default viewport here. See the corresponding call to
       // InitializeRootMetrics in the before-first-paint handler.
       InitializeRootMetrics();
     }
 
     if (sizeChanged) {
       HandlePossibleViewportChange(oldScreenSize);
@@ -3239,27 +3247,40 @@ TabChild::RecvRequestNotifyAfterRemotePa
   // RenderFrameParent.
   compositor->RequestNotifyAfterRemotePaint(this);
   return true;
 }
 
 bool
 TabChild::RecvUIResolutionChanged()
 {
+  ScreenIntSize oldScreenSize = GetInnerSize();
   mDPI = 0;
   mDefaultScale = 0;
   static_cast<PuppetWidget*>(mWidget.get())->ClearBackingScaleCache();
   nsCOMPtr<nsIDocument> document(GetDocument());
   nsCOMPtr<nsIPresShell> presShell = document->GetShell();
   if (presShell) {
     nsRefPtr<nsPresContext> presContext = presShell->GetPresContext();
     if (presContext) {
-      presContext->UIResolutionChanged();
+      presContext->UIResolutionChangedSync();
     }
   }
+
+  ScreenIntSize screenSize = GetInnerSize();
+  if (mHasValidInnerSize && oldScreenSize != screenSize) {
+    ScreenIntRect screenRect = GetOuterRect();
+    mWidget->Resize(screenRect.x + mChromeDisp.x, screenRect.y + mChromeDisp.y,
+                    screenSize.width, screenSize.height, true);
+
+    nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(WebNavigation());
+    baseWin->SetPositionAndSize(0, 0, screenSize.width, screenSize.height,
+                                true);
+  }
+
   return true;
 }
 
 bool
 TabChild::RecvThemeChanged(nsTArray<LookAndFeelInt>&& aLookAndFeelIntCache)
 {
   LookAndFeel::SetIntCache(aLookAndFeelIntCache);
   nsCOMPtr<nsIDocument> document(GetDocument());
@@ -3311,16 +3332,32 @@ TabChild::CreatePluginWidget(nsIWidget* 
                                      nsIntSize(0, 0)), &initData);
   if (NS_FAILED(rv)) {
     NS_WARNING("Creating native plugin widget on the chrome side failed.");
   }
   pluginWidget.forget(aOut);
   return rv;
 }
 
+ScreenIntSize
+TabChild::GetInnerSize()
+{
+  LayoutDeviceIntSize innerSize =
+    RoundedToInt(mUnscaledInnerSize * mWidget->GetDefaultScale());
+  return ViewAs<ScreenPixel>(innerSize, PixelCastJustification::LayoutDeviceIsScreenForTabDims);
+};
+
+ScreenIntRect
+TabChild::GetOuterRect()
+{
+  LayoutDeviceIntRect outerRect =
+    RoundedToInt(mUnscaledOuterRect * mWidget->GetDefaultScale());
+  return ViewAs<ScreenPixel>(outerRect, PixelCastJustification::LayoutDeviceIsScreenForTabDims);
+}
+
 TabChildGlobal::TabChildGlobal(TabChildBase* aTabChild)
 : mTabChild(aTabChild)
 {
   SetIsNotDOMBinding();
 }
 
 TabChildGlobal::~TabChildGlobal()
 {
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -184,16 +184,18 @@ public:
     // change, this function doesn't do anything.  However, it should
     // not be called all the time as it is fairly expensive.
     bool HandlePossibleViewportChange(const ScreenIntSize& aOldScreenSize);
     virtual bool DoUpdateZoomConstraints(const uint32_t& aPresShellId,
                                          const mozilla::layers::FrameMetrics::ViewID& aViewId,
                                          const bool& aIsRoot,
                                          const mozilla::layers::ZoomConstraints& aConstraints) = 0;
 
+    virtual ScreenIntSize GetInnerSize() = 0;
+
 protected:
     virtual ~TabChildBase();
     CSSSize GetPageSize(nsCOMPtr<nsIDocument> aDocument, const CSSSize& aViewport);
 
     // Get the DOMWindowUtils for the top-level window in this tab.
     already_AddRefed<nsIDOMWindowUtils> GetDOMWindowUtils();
     // Get the Document for the top-level window in this tab.
     already_AddRefed<nsIDocument> GetDocument() const;
@@ -217,17 +219,16 @@ protected:
     mozilla::layers::FrameMetrics ProcessUpdateFrame(const mozilla::layers::FrameMetrics& aFrameMetrics);
 
     bool UpdateFrameHandler(const mozilla::layers::FrameMetrics& aFrameMetrics);
 
 protected:
     CSSSize mOldViewportSize;
     bool mContentDocumentIsDisplayed;
     nsRefPtr<TabChildGlobal> mTabChildGlobal;
-    ScreenIntSize mInnerSize;
     mozilla::layers::FrameMetrics mLastRootMetrics;
     nsCOMPtr<nsIWebBrowserChrome3> mWebBrowserChrome;
 };
 
 class TabChild final : public TabChildBase,
                        public PBrowserChild,
                        public nsIWebBrowserChrome2,
                        public nsIEmbeddingSiteWindow,
@@ -314,18 +315,18 @@ public:
                                          const FileDescriptor& aFileDescriptor)
                                          override;
     virtual bool RecvShow(const ScreenIntSize& aSize,
                           const ShowInfo& aInfo,
                           const TextureFactoryIdentifier& aTextureFactoryIdentifier,
                           const uint64_t& aLayersId,
                           PRenderFrameChild* aRenderFrame,
                           const bool& aParentIsActive) override;
-    virtual bool RecvUpdateDimensions(const nsIntRect& rect,
-                                      const ScreenIntSize& size,
+    virtual bool RecvUpdateDimensions(const CSSRect& rect,
+                                      const CSSSize& size,
                                       const ScreenOrientation& orientation,
                                       const LayoutDeviceIntPoint& chromeDisp) override;
     virtual bool RecvUpdateFrame(const layers::FrameMetrics& aFrameMetrics) override;
     virtual bool RecvRequestFlingSnap(const ViewID& aScrollId,
                                       const CSSPoint& aDestination) override;
     virtual bool RecvAcknowledgeScrollUpdate(const ViewID& aScrollId,
                                              const uint32_t& aScrollGeneration) override;
     virtual bool RecvHandleDoubleTap(const CSSPoint& aPoint,
@@ -505,16 +506,18 @@ public:
     bool IPCOpen() { return mIPCOpen; }
 
     bool ParentIsActive()
     {
       return mParentIsActive;
     }
     bool AsyncPanZoomEnabled() { return mAsyncPanZoomEnabled; }
 
+    virtual ScreenIntSize GetInnerSize() override;
+
 protected:
     virtual ~TabChild();
 
     virtual PRenderFrameChild* AllocPRenderFrameChild() override;
     virtual bool DeallocPRenderFrameChild(PRenderFrameChild* aFrame) override;
     virtual bool RecvDestroy() override;
     virtual bool RecvSetUpdateHitRegion(const bool& aEnabled) override;
     virtual bool RecvSetIsDocShellActive(const bool& aIsActive) override;
@@ -594,29 +597,35 @@ private:
 
     bool HasValidInnerSize();
 
     // Get the pres shell resolution of the document in this tab.
     float GetPresShellResolution() const;
 
     void SetTabId(const TabId& aTabId);
 
+    ScreenIntRect GetOuterRect();
+
+    void SetUnscaledInnerSize(const CSSSize& aSize) {
+      mUnscaledInnerSize = aSize;
+    }
+
     class CachedFileDescriptorInfo;
     class CachedFileDescriptorCallbackRunnable;
     class DelayedDeleteRunnable;
 
     TextureFactoryIdentifier mTextureFactoryIdentifier;
     nsCOMPtr<nsIWebNavigation> mWebNav;
     nsCOMPtr<nsIWidget> mWidget;
     nsCOMPtr<nsIURI> mLastURI;
     RenderFrameChild* mRemoteFrame;
     nsRefPtr<nsIContentChild> mManager;
     uint32_t mChromeFlags;
     uint64_t mLayersId;
-    nsIntRect mOuterRect;
+    CSSRect mUnscaledOuterRect;
     // When we're tracking a possible tap gesture, this is the "down"
     // point of the touchstart.
     LayoutDevicePoint mGestureDownPoint;
     // The touch identifier of the active gesture.
     int32_t mActivePointerId;
     // A timer task that fires if the tap-hold timeout is exceeded by
     // the touch we're tracking.  That is, if touchend or a touchmove
     // that exceeds the gesture threshold doesn't happen.
@@ -641,16 +650,17 @@ private:
     // Position of tab, relative to parent widget (typically the window)
     LayoutDeviceIntPoint mChromeDisp;
     TabId mUniqueId;
     float mDPI;
     double mDefaultScale;
     bool mIPCOpen;
     bool mParentIsActive;
     bool mAsyncPanZoomEnabled;
+    CSSSize mUnscaledInnerSize;
 
     DISALLOW_EVIL_CONSTRUCTORS(TabChild);
 };
 
 }
 }
 
 #endif // mozilla_dom_TabChild_h
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -85,16 +85,17 @@
 #include "SourceSurfaceRawData.h"
 #include "nsAuthInformationHolder.h"
 #include "nsICancelable.h"
 #include "gfxPrefs.h"
 #include "nsILoginManagerPrompter.h"
 #include "nsPIWindowRoot.h"
 #include "gfxDrawable.h"
 #include "ImageOps.h"
+#include "UnitTransforms.h"
 #include <algorithm>
 
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 using namespace mozilla::layers;
 using namespace mozilla::layout;
 using namespace mozilla::services;
 using namespace mozilla::widget;
@@ -328,19 +329,37 @@ TabParent::CacheFrameLoader(nsFrameLoade
 }
 
 void
 TabParent::SetOwnerElement(Element* aElement)
 {
   // If we held previous content then unregister for its events.
   RemoveWindowListeners();
 
+  // If we change top-level documents then we need to change our
+  // registration with them.
+  nsRefPtr<nsPIWindowRoot> curTopLevelWin, newTopLevelWin;
+  if (mFrameElement) {
+    curTopLevelWin = nsContentUtils::GetWindowRoot(mFrameElement->OwnerDoc());
+  }
+  if (aElement) {
+    newTopLevelWin = nsContentUtils::GetWindowRoot(aElement->OwnerDoc());
+  }
+  bool isSameTopLevelWin = curTopLevelWin == newTopLevelWin;
+  if (curTopLevelWin && !isSameTopLevelWin) {
+    curTopLevelWin->RemoveBrowser(this);
+  }
+
   // Update to the new content, and register to listen for events from it.
   mFrameElement = aElement;
 
+  if (newTopLevelWin && !isSameTopLevelWin) {
+    newTopLevelWin->AddBrowser(this);
+  }
+
   AddWindowListeners();
   TryCacheDPIAndScale();
 }
 
 void
 TabParent::AddWindowListeners()
 {
   if (mFrameElement && mFrameElement->OwnerDoc()) {
@@ -961,17 +980,31 @@ TabParent::UpdateDimensions(const nsIntR
       chromeOffset != mChromeOffset) {
 
     mUpdatedDimensions = true;
     mRect = contentRect;
     mDimensions = size;
     mOrientation = orientation;
     mChromeOffset = chromeOffset;
 
-    unused << SendUpdateDimensions(mRect, mDimensions, mOrientation, mChromeOffset);
+    CSSToLayoutDeviceScale widgetScale;
+    if (widget) {
+      widgetScale = widget->GetDefaultScale();
+    }
+
+    LayoutDeviceIntRect devicePixelRect =
+      ViewAs<LayoutDevicePixel>(mRect,
+                                PixelCastJustification::LayoutDeviceIsScreenForTabDims);
+    LayoutDeviceIntSize devicePixelSize =
+      ViewAs<LayoutDevicePixel>(mDimensions.ToUnknownSize(),
+                                PixelCastJustification::LayoutDeviceIsScreenForTabDims);
+
+    CSSRect unscaledRect = devicePixelRect / widgetScale;
+    CSSSize unscaledSize = devicePixelSize / widgetScale;
+    unused << SendUpdateDimensions(unscaledRect, unscaledSize, orientation, chromeOffset);
   }
 }
 
 void
 TabParent::UpdateFrame(const FrameMetrics& aFrameMetrics)
 {
   if (!mIsDestroyed) {
     unused << SendUpdateFrame(aFrameMetrics);
@@ -980,16 +1013,17 @@ TabParent::UpdateFrame(const FrameMetric
 
 void
 TabParent::UIResolutionChanged()
 {
   if (!mIsDestroyed) {
     // TryCacheDPIAndScale()'s cache is keyed off of
     // mDPI being greater than 0, so this invalidates it.
     mDPI = -1;
+    TryCacheDPIAndScale();
     unused << SendUIResolutionChanged();
   }
 }
 
 void
 TabParent::ThemeChanged()
 {
   if (!mIsDestroyed) {
--- a/dom/media/AbstractMediaDecoder.h
+++ b/dom/media/AbstractMediaDecoder.h
@@ -69,19 +69,16 @@ public:
   // counts.
   // Can be called on any thread.
   virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
                                    uint32_t aDropped) = 0;
 
   // Return the duration of the media in microseconds.
   virtual int64_t GetMediaDuration() = 0;
 
-  // Set the duration of the media in microseconds.
-  virtual void SetMediaDuration(int64_t aDuration) = 0;
-
   // Sets the duration of the media in microseconds. The MediaDecoder
   // fires a durationchange event to its owner (e.g., an HTML audio
   // tag).
   virtual void UpdateEstimatedMediaDuration(int64_t aDuration) = 0;
 
   // Set the media as being seekable or not.
   virtual void SetMediaSeekable(bool aMediaSeekable) = 0;
 
@@ -95,19 +92,16 @@ public:
   virtual bool IsMediaSeekable() = 0;
 
   virtual void MetadataLoaded(nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags, MediaDecoderEventVisibility aEventVisibility) = 0;
   virtual void QueueMetadata(int64_t aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) = 0;
   virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo, MediaDecoderEventVisibility aEventVisibility) = 0;
 
   virtual void RemoveMediaTracks() = 0;
 
-  // Set the media end time in microseconds
-  virtual void SetMediaEndTime(int64_t aTime) = 0;
-
   // May be called by the reader to notify this decoder that the metadata from
   // the media file has been read. Call on the decode thread only.
   virtual void OnReadMetadataCompleted() = 0;
 
   // Returns the owner of this media decoder. The owner should only be used
   // on the main thread.
   virtual MediaDecoderOwner* GetOwner() = 0;
 
--- a/dom/media/DecodedStream.cpp
+++ b/dom/media/DecodedStream.cpp
@@ -64,20 +64,18 @@ public:
 private:
   Mutex mMutex;
   // Members below are protected by mMutex.
   nsRefPtr<MediaStream> mStream;
   int64_t mLastOutputTime; // microseconds
   bool mStreamFinishedOnMainThread;
 };
 
-DecodedStreamData::DecodedStreamData(int64_t aInitialTime,
-                                     SourceMediaStream* aStream)
+DecodedStreamData::DecodedStreamData(SourceMediaStream* aStream)
   : mAudioFramesWritten(0)
-  , mInitialTime(aInitialTime)
   , mNextVideoTime(-1)
   , mNextAudioTime(-1)
   , mStreamInitialized(false)
   , mHaveSentFinish(false)
   , mHaveSentFinishAudio(false)
   , mHaveSentFinishVideo(false)
   , mStream(aStream)
   , mHaveBlockedForPlayState(false)
@@ -98,19 +96,19 @@ DecodedStreamData::~DecodedStreamData()
 
 bool
 DecodedStreamData::IsFinished() const
 {
   return mListener->IsFinishedOnMainThread();
 }
 
 int64_t
-DecodedStreamData::GetClock() const
+DecodedStreamData::GetPosition() const
 {
-  return mInitialTime + mListener->GetLastOutputTime();
+  return mListener->GetLastOutputTime();
 }
 
 class OutputStreamListener : public MediaStreamListener {
   typedef MediaStreamListener::MediaStreamGraphEvent MediaStreamGraphEvent;
 public:
   OutputStreamListener(DecodedStream* aDecodedStream, MediaStream* aStream)
     : mDecodedStream(aDecodedStream), mStream(aStream) {}
 
@@ -218,29 +216,29 @@ DecodedStream::DestroyData()
       os.mStream->ChangeExplicitBlockerCount(1);
     }
   }
 
   mData = nullptr;
 }
 
 void
-DecodedStream::RecreateData(int64_t aInitialTime, MediaStreamGraph* aGraph)
+DecodedStream::RecreateData(MediaStreamGraph* aGraph)
 {
   MOZ_ASSERT(NS_IsMainThread());
   GetReentrantMonitor().AssertCurrentThreadIn();
   MOZ_ASSERT((aGraph && !mData && OutputStreams().IsEmpty()) || // first time
              (!aGraph && mData)); // 2nd time and later
 
   if (!aGraph) {
     aGraph = mData->mStream->Graph();
   }
   auto source = aGraph->CreateSourceStream(nullptr);
   DestroyData();
-  mData.reset(new DecodedStreamData(aInitialTime, source));
+  mData.reset(new DecodedStreamData(source));
 
   // Note that the delay between removing ports in DestroyDecodedStream
   // and adding new ones won't cause a glitch since all graph operations
   // between main-thread stable states take effect atomically.
   auto& outputStreams = OutputStreams();
   for (int32_t i = outputStreams.Length() - 1; i >= 0; --i) {
     OutputStreamData& os = outputStreams[i];
     MOZ_ASSERT(!os.mStream->IsDestroyed(), "Should've been removed in DestroyData()");
--- a/dom/media/DecodedStream.h
+++ b/dom/media/DecodedStream.h
@@ -32,29 +32,26 @@ class Image;
  * We have at most one DecodedStreamDaata per MediaDecoder. Its stream
  * is used as the input for each ProcessedMediaStream created by calls to
  * captureStream(UntilEnded). Seeking creates a new source stream, as does
  * replaying after the input as ended. In the latter case, the new source is
  * not connected to streams created by captureStreamUntilEnded.
  */
 class DecodedStreamData {
 public:
-  DecodedStreamData(int64_t aInitialTime, SourceMediaStream* aStream);
+  explicit DecodedStreamData(SourceMediaStream* aStream);
   ~DecodedStreamData();
   bool IsFinished() const;
-  int64_t GetClock() const;
+  int64_t GetPosition() const;
 
   /* The following group of fields are protected by the decoder's monitor
    * and can be read or written on any thread.
    */
   // Count of audio frames written to the stream
   int64_t mAudioFramesWritten;
-  // Saved value of aInitialTime. Timestamp of the first audio and/or
-  // video packet written.
-  const int64_t mInitialTime; // microseconds
   // mNextVideoTime is the end timestamp for the last packet sent to the stream.
   // Therefore video packets starting at or after this time need to be copied
   // to the output stream.
   int64_t mNextVideoTime; // microseconds
   int64_t mNextAudioTime; // microseconds
   // The last video image sent to the stream. Useful if we need to replicate
   // the image.
   nsRefPtr<layers::Image> mLastVideoImage;
@@ -90,17 +87,17 @@ public:
   nsRefPtr<OutputStreamListener> mListener;
 };
 
 class DecodedStream {
 public:
   explicit DecodedStream(ReentrantMonitor& aMonitor);
   DecodedStreamData* GetData() const;
   void DestroyData();
-  void RecreateData(int64_t aInitialTime, MediaStreamGraph* aGraph);
+  void RecreateData(MediaStreamGraph* aGraph);
   nsTArray<OutputStreamData>& OutputStreams();
   ReentrantMonitor& GetReentrantMonitor() const;
   void Connect(ProcessedMediaStream* aStream, bool aFinishWhenEnded);
 
 private:
   void Connect(OutputStreamData* aStream);
 
   UniquePtr<DecodedStreamData> mData;
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -27,32 +27,39 @@
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/dom/VideoTrack.h"
 #include "mozilla/dom/VideoTrackList.h"
 
 #ifdef MOZ_WMF
 #include "WMFDecoder.h"
 #endif
 
+using namespace mozilla::dom;
 using namespace mozilla::layers;
-using namespace mozilla::dom;
+using namespace mozilla::media;
 
 // Default timeout msecs until try to enter dormant state by heuristic.
 static const int DEFAULT_HEURISTIC_DORMANT_TIMEOUT_MSECS = 60000;
 
 namespace mozilla {
 
 // Number of estimated seconds worth of data we need to have buffered
 // ahead of the current playback position before we allow the media decoder
 // to report that it can play through the entire media without the decode
 // catching up with the download. Having this margin make the
 // MediaDecoder::CanPlayThrough() calculation more stable in the case of
 // fluctuating bitrates.
 static const int64_t CAN_PLAY_THROUGH_MARGIN = 1;
 
+// The amount of instability we tollerate in calls to
+// MediaDecoder::UpdateEstimatedMediaDuration(); changes of duration
+// less than this are ignored, as they're assumed to be the result of
+// instability in the duration estimation.
+static const uint64_t ESTIMATED_DURATION_FUZZ_FACTOR_USECS = USECS_PER_S / 2;
+
 // avoid redefined macro in unified build
 #undef DECODER_LOG
 
 PRLogModuleInfo* gMediaDecoderLog;
 #define DECODER_LOG(x, ...) \
   MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, ("Decoder=%p " x, this, ##__VA_ARGS__))
 
 static const char* const gPlayStateStr[] = {
@@ -338,16 +345,20 @@ MediaDecoder::MediaDecoder() :
   mCurrentPosition(AbstractThread::MainThread(), 0, "MediaDecoder::mCurrentPosition (Mirror)"),
   mVolume(AbstractThread::MainThread(), 0.0, "MediaDecoder::mVolume (Canonical)"),
   mPlaybackRate(AbstractThread::MainThread(), 1.0, "MediaDecoder::mPlaybackRate (Canonical)"),
   mPreservesPitch(AbstractThread::MainThread(), true, "MediaDecoder::mPreservesPitch (Canonical)"),
   mDuration(-1),
   mMediaSeekable(true),
   mSameOriginMedia(false),
   mReentrantMonitor("media.decoder"),
+  mEstimatedDuration(AbstractThread::MainThread(), NullableTimeUnit(),
+                     "MediaDecoder::mEstimatedDuration (Canonical)"),
+  mExplicitDuration(AbstractThread::MainThread(), Maybe<double>(),
+                   "MediaDecoder::mExplicitDuration (Canonical)"),
   mPlayState(AbstractThread::MainThread(), PLAY_STATE_LOADING,
              "MediaDecoder::mPlayState (Canonical)"),
   mNextState(AbstractThread::MainThread(), PLAY_STATE_PAUSED,
              "MediaDecoder::mNextState (Canonical)"),
   mLogicallySeeking(AbstractThread::MainThread(), false,
              "MediaDecoder::mLogicallySeeking (Canonical)"),
   mIgnoreProgressData(false),
   mInfiniteStream(false),
@@ -483,17 +494,16 @@ nsresult MediaDecoder::InitializeStateMa
   SetStateMachineParameters();
 
   return ScheduleStateMachine();
 }
 
 void MediaDecoder::SetStateMachineParameters()
 {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-  mDecoderStateMachine->SetDuration(mDuration);
   if (mMinimizePreroll) {
     mDecoderStateMachine->DispatchMinimizePrerollUntilPlaybackStarts();
   }
 }
 
 void MediaDecoder::SetMinimizePrerollUntilPlaybackStarts()
 {
   DECODER_LOG("SetMinimizePrerollUntilPlaybackStarts()");
@@ -1060,67 +1070,53 @@ void MediaDecoder::UpdateLogicalPosition
   Invalidate();
 
   if (mOwner && logicalPositionChanged &&
       aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
     FireTimeUpdate();
   }
 }
 
-void MediaDecoder::DurationChanged()
+void MediaDecoder::DurationChanged(TimeUnit aNewDuration)
 {
   MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   int64_t oldDuration = mDuration;
-  mDuration = mDecoderStateMachine ? mDecoderStateMachine->GetDuration() : -1;
+  mDuration = aNewDuration.ToMicroseconds();
   // Duration has changed so we should recompute playback rate
   UpdatePlaybackRate();
 
   SetInfinite(mDuration == -1);
 
   if (mOwner && oldDuration != mDuration && !IsInfinite()) {
     DECODER_LOG("Duration changed to %lld", mDuration);
     mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
   }
-}
 
-void MediaDecoder::SetDuration(double aDuration)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  if (mozilla::IsInfinite(aDuration)) {
-    SetInfinite(true);
-  } else if (IsNaN(aDuration)) {
-    mDuration = -1;
-    SetInfinite(true);
-  } else {
-    mDuration = static_cast<int64_t>(NS_round(aDuration * static_cast<double>(USECS_PER_S)));
+  if (CurrentPosition() > aNewDuration.ToMicroseconds()) {
+    Seek(aNewDuration.ToSeconds(), SeekTarget::Accurate);
   }
-
-  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-  if (mDecoderStateMachine) {
-    mDecoderStateMachine->SetDuration(mDuration);
-  }
-
-  // Duration has changed so we should recompute playback rate
-  UpdatePlaybackRate();
-}
-
-void MediaDecoder::SetMediaDuration(int64_t aDuration)
-{
-  NS_ENSURE_TRUE_VOID(GetStateMachine());
-  GetStateMachine()->SetDuration(aDuration);
 }
 
 void MediaDecoder::UpdateEstimatedMediaDuration(int64_t aDuration)
 {
   if (mPlayState <= PLAY_STATE_LOADING) {
     return;
   }
-  NS_ENSURE_TRUE_VOID(GetStateMachine());
-  GetStateMachine()->UpdateEstimatedDuration(aDuration);
+
+  // The duration is only changed if its significantly different than the
+  // the current estimate, as the incoming duration is an estimate and so
+  // often is unstable as more data is read and the estimate is updated.
+  // Can result in a durationchangeevent. aDuration is in microseconds.
+  if (mEstimatedDuration.Ref().isSome() &&
+      mozilla::Abs(mEstimatedDuration.Ref().ref().ToMicroseconds() - aDuration) < ESTIMATED_DURATION_FUZZ_FACTOR_USECS) {
+    return;
+  }
+
+  mEstimatedDuration = Some(TimeUnit::FromMicroseconds(aDuration));
 }
 
 void MediaDecoder::SetMediaSeekable(bool aMediaSeekable) {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   mMediaSeekable = aMediaSeekable;
 }
 
 bool
@@ -1159,22 +1155,16 @@ void MediaDecoder::SetFragmentEndTime(do
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mDecoderStateMachine) {
     ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
     mDecoderStateMachine->SetFragmentEndTime(static_cast<int64_t>(aTime * USECS_PER_S));
   }
 }
 
-void MediaDecoder::SetMediaEndTime(int64_t aTime)
-{
-  NS_ENSURE_TRUE_VOID(GetStateMachine());
-  GetStateMachine()->SetMediaEndTime(aTime);
-}
-
 void MediaDecoder::Suspend()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mResource) {
     mResource->Suspend(true);
   }
 }
 
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -451,24 +451,16 @@ public:
   // Call on the main thread only.
   virtual bool IsSeeking() const;
 
   // Return true if the decoder has reached the end of playback or the decoder
   // has shutdown.
   // Call on the main thread only.
   virtual bool IsEndedOrShutdown() const;
 
-  // Set the duration of the media resource in units of seconds.
-  // This is called via a channel listener if it can pick up the duration
-  // from a content header. Must be called from the main thread only.
-  virtual void SetDuration(double aDuration);
-
-  // Sets the initial duration of the media. Called while the media metadata
-  // is being read and the decode is being setup.
-  void SetMediaDuration(int64_t aDuration) override;
   // Updates the media duration. This is called while the media is being
   // played, calls before the media has reached loaded metadata are ignored.
   // The duration is assumed to be an estimate, and so a degree of
   // instability is expected; if the incoming duration is not significantly
   // different from the existing duration, the change request is ignored.
   // If the incoming duration is significantly different, the duration is
   // changed, this causes a durationchanged event to fire to the media
   // element.
@@ -486,19 +478,16 @@ public:
 
   // Return the time ranges that can be seeked into.
   virtual media::TimeIntervals GetSeekable();
 
   // Set the end time of the media resource. When playback reaches
   // this point the media pauses. aTime is in seconds.
   virtual void SetFragmentEndTime(double aTime);
 
-  // Set the end time of the media. aTime is in microseconds.
-  void SetMediaEndTime(int64_t aTime) final override;
-
   // Invalidate the frame.
   void Invalidate();
   void InvalidateWithFlags(uint32_t aFlags);
 
   // Suspend any media downloads that are in progress. Called by the
   // media element when it is sent to the bfcache, or when we need
   // to throttle the download. Call on the main thread only. This can
   // be called multiple times, there's an internal "suspend count".
@@ -520,17 +509,17 @@ public:
   // SetLoadInBackground() on mResource.
   void SetLoadInBackground(bool aLoadInBackground);
 
   // Returns a weak reference to the media decoder owner.
   MediaDecoderOwner* GetMediaOwner() const;
 
   // Called by the state machine to notify the decoder that the duration
   // has changed.
-  void DurationChanged();
+  void DurationChanged(media::TimeUnit aNewDuration);
 
   bool OnStateMachineTaskQueue() const override;
 
   bool OnDecodeTaskQueue() const override;
 
   MediaDecoderStateMachine* GetStateMachine() { return mDecoderStateMachine; }
   void SetStateMachine(MediaDecoderStateMachine* aStateMachine);
 
@@ -988,17 +977,36 @@ private:
   // to |Wait| on this monitor will block the thread until the next state
   // change.  Explicitly private for force access via GetReentrantMonitor.
   ReentrantMonitor mReentrantMonitor;
 
 #ifdef MOZ_EME
   nsRefPtr<CDMProxy> mProxy;
 #endif
 
+  // Media duration according to the demuxer's current estimate.
+  //
+  // Note that it's quite bizarre for this to live on the main thread - it would
+  // make much more sense for this to be owned by the demuxer's task queue. But
+  // currently this is only every changed in NotifyDataArrived, which runs on
+  // the main thread. That will need to be cleaned up at some point.
+  Canonical<media::NullableTimeUnit> mEstimatedDuration;
+public:
+  AbstractCanonical<media::NullableTimeUnit>* CanonicalEstimatedDuration() { return &mEstimatedDuration; }
 protected:
+
+  // Media duration set explicitly by JS. At present, this is only ever present
+  // for MSE.
+  Canonical<Maybe<double>> mExplicitDuration;
+  double ExplicitDuration() { return mExplicitDuration.Ref().ref(); }
+  void SetExplicitDuration(double aValue) { mExplicitDuration.Set(Some(aValue)); }
+public:
+  AbstractCanonical<Maybe<double>>* CanonicalExplicitDuration() { return &mExplicitDuration; }
+protected:
+
   // Set to one of the valid play states.
   // This can only be changed on the main thread while holding the decoder
   // monitor. Thus, it can be safely read while holding the decoder monitor
   // OR on the main thread.
   // Any change to the state on the main thread must call NotifyAll on the
   // monitor so the decode thread can wake up.
   Canonical<PlayState> mPlayState;
 
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -39,19 +39,20 @@
 #include "nsPrintfCString.h"
 #include "DOMMediaStream.h"
 #include "DecodedStream.h"
 
 #include <algorithm>
 
 namespace mozilla {
 
-using namespace mozilla::layers;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using namespace mozilla::media;
 
 #define NS_DispatchToMainThread(...) CompileError_UseAbstractThreadDispatchInstead
 
 // avoid redefined macro in unified build
 #undef LOG
 #undef DECODER_LOG
 #undef VERBOSE_LOG
 
@@ -157,22 +158,16 @@ static const uint32_t QUICK_BUFFERING_LO
 // quick buffering in a timely fashion, as the decode pauses when it
 // reaches AMPLE_AUDIO_USECS decoded data, and thus we'll never reach
 // QUICK_BUFFERING_LOW_DATA_USECS.
 static_assert(QUICK_BUFFERING_LOW_DATA_USECS <= AMPLE_AUDIO_USECS,
               "QUICK_BUFFERING_LOW_DATA_USECS is too large");
 
 } // namespace detail
 
-// The amount of instability we tollerate in calls to
-// MediaDecoderStateMachine::UpdateEstimatedDuration(); changes of duration
-// less than this are ignored, as they're assumed to be the result of
-// instability in the duration estimation.
-static const uint64_t ESTIMATED_DURATION_FUZZ_FACTOR_USECS = USECS_PER_S / 2;
-
 static TimeDuration UsecsToDuration(int64_t aUsecs) {
   return TimeDuration::FromMicroseconds(aUsecs);
 }
 
 static int64_t DurationToUsecs(TimeDuration aDuration) {
   return static_cast<int64_t>(aDuration.ToSeconds() * USECS_PER_S);
 }
 
@@ -192,27 +187,33 @@ MediaDecoderStateMachine::MediaDecoderSt
   mRealTime(aRealTime),
   mDispatchedStateMachine(false),
   mDelayedScheduler(this),
   mState(DECODER_STATE_DECODING_NONE, "MediaDecoderStateMachine::mState"),
   mPlayDuration(0),
   mStartTime(-1),
   mEndTime(-1),
   mDurationSet(false),
+  mEstimatedDuration(mTaskQueue, NullableTimeUnit(),
+                    "MediaDecoderStateMachine::EstimatedDuration (Mirror)"),
+  mExplicitDuration(mTaskQueue, Maybe<double>(),
+                    "MediaDecoderStateMachine::mExplicitDuration (Mirror)"),
+  mObservedDuration(TimeUnit(), "MediaDecoderStateMachine::mObservedDuration"),
   mPlayState(mTaskQueue, MediaDecoder::PLAY_STATE_LOADING,
              "MediaDecoderStateMachine::mPlayState (Mirror)"),
   mNextPlayState(mTaskQueue, MediaDecoder::PLAY_STATE_PAUSED,
                  "MediaDecoderStateMachine::mNextPlayState (Mirror)"),
   mLogicallySeeking(mTaskQueue, false,
              "MediaDecoderStateMachine::mLogicallySeeking (Mirror)"),
   mNextFrameStatus(mTaskQueue, MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED,
                    "MediaDecoderStateMachine::mNextFrameStatus (Canonical)"),
   mFragmentEndTime(-1),
   mReader(aReader),
   mCurrentPosition(mTaskQueue, 0, "MediaDecoderStateMachine::mCurrentPosition (Canonical)"),
+  mStreamStartTime(-1),
   mAudioStartTime(-1),
   mAudioEndTime(-1),
   mDecodedAudioEndTime(-1),
   mVideoFrameEndTime(-1),
   mDecodedVideoEndTime(-1),
   mVolume(mTaskQueue, 1.0, "MediaDecoderStateMachine::mVolume (Mirror)"),
   mPlaybackRate(1.0),
   mLogicalPlaybackRate(mTaskQueue, 1.0, "MediaDecoderStateMachine::mLogicalPlaybackRate (Mirror)"),
@@ -222,17 +223,16 @@ MediaDecoderStateMachine::MediaDecoderSt
   mQuickBufferingLowDataThresholdUsecs(detail::QUICK_BUFFERING_LOW_DATA_USECS),
   mIsAudioPrerolling(false),
   mIsVideoPrerolling(false),
   mAudioCaptured(false),
   mPositionChangeQueued(false),
   mAudioCompleted(false, "MediaDecoderStateMachine::mAudioCompleted"),
   mGotDurationFromMetaData(false),
   mDispatchedEventToDecode(false),
-  mStopAudioThread(true),
   mQuickBuffering(false),
   mMinimizePreroll(false),
   mDecodeThreadWaiting(false),
   mDropAudioUntilNextDiscontinuity(false),
   mDropVideoUntilNextDiscontinuity(false),
   mDecodeToSeekTarget(false),
   mCurrentTimeBeforeSeek(0),
   mCorruptFrames(30),
@@ -289,29 +289,34 @@ MediaDecoderStateMachine::~MediaDecoderS
 }
 
 void
 MediaDecoderStateMachine::InitializationTask()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   // Connect mirrors.
+  mEstimatedDuration.Connect(mDecoder->CanonicalEstimatedDuration());
+  mExplicitDuration.Connect(mDecoder->CanonicalExplicitDuration());
   mPlayState.Connect(mDecoder->CanonicalPlayState());
   mNextPlayState.Connect(mDecoder->CanonicalNextPlayState());
   mLogicallySeeking.Connect(mDecoder->CanonicalLogicallySeeking());
   mVolume.Connect(mDecoder->CanonicalVolume());
   mLogicalPlaybackRate.Connect(mDecoder->CanonicalPlaybackRate());
   mPreservesPitch.Connect(mDecoder->CanonicalPreservesPitch());
 
   // Initialize watchers.
   mWatchManager.Watch(mState, &MediaDecoderStateMachine::UpdateNextFrameStatus);
   mWatchManager.Watch(mAudioCompleted, &MediaDecoderStateMachine::UpdateNextFrameStatus);
   mWatchManager.Watch(mVolume, &MediaDecoderStateMachine::VolumeChanged);
   mWatchManager.Watch(mLogicalPlaybackRate, &MediaDecoderStateMachine::LogicalPlaybackRateChanged);
   mWatchManager.Watch(mPreservesPitch, &MediaDecoderStateMachine::PreservesPitchChanged);
+  mWatchManager.Watch(mEstimatedDuration, &MediaDecoderStateMachine::RecomputeDuration);
+  mWatchManager.Watch(mExplicitDuration, &MediaDecoderStateMachine::RecomputeDuration);
+  mWatchManager.Watch(mObservedDuration, &MediaDecoderStateMachine::RecomputeDuration);
   mWatchManager.Watch(mPlayState, &MediaDecoderStateMachine::PlayStateChanged);
   mWatchManager.Watch(mLogicallySeeking, &MediaDecoderStateMachine::LogicallySeekingChanged);
   mWatchManager.Watch(mPlayState, &MediaDecoderStateMachine::UpdateStreamBlockingForPlayState);
   mWatchManager.Watch(mLogicallySeeking, &MediaDecoderStateMachine::UpdateStreamBlockingForPlayState);
 }
 
 bool MediaDecoderStateMachine::HasFutureAudio()
 {
@@ -353,17 +358,17 @@ void MediaDecoderStateMachine::SendStrea
                                                AudioSegment* aOutput)
 {
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
 
   // This logic has to mimic AudioSink closely to make sure we write
   // the exact same silences
   CheckedInt64 audioWrittenOffset = aStream->mAudioFramesWritten +
-      UsecsToFrames(mInfo.mAudio.mRate, aStream->mInitialTime + mStartTime);
+      UsecsToFrames(mInfo.mAudio.mRate, mStreamStartTime);
   CheckedInt64 frameOffset = UsecsToFrames(mInfo.mAudio.mRate, aAudio->mTime);
 
   if (!audioWrittenOffset.isValid() ||
       !frameOffset.isValid() ||
       // ignore packet that we've already processed
       frameOffset.value() + aAudio->mFrames <= audioWrittenOffset.value()) {
     return;
   }
@@ -438,16 +443,17 @@ UpdateStreamBlocking(MediaStream* aStrea
   }
 }
 
 void MediaDecoderStateMachine::SendStreamData()
 {
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   MOZ_ASSERT(!mAudioSink, "Should've been stopped in RunStateMachine()");
+  MOZ_ASSERT(mStreamStartTime != -1);
 
   DecodedStreamData* stream = GetDecodedStream();
 
   bool finished =
       (!mInfo.HasAudio() || AudioQueue().IsFinished()) &&
       (!mInfo.HasVideo() || VideoQueue().IsFinished());
   if (mDecoder->IsSameOriginMedia()) {
     SourceMediaStream* mediaStream = stream->mStream;
@@ -456,31 +462,27 @@ void MediaDecoderStateMachine::SendStrea
     if (!stream->mStreamInitialized) {
       if (mInfo.HasAudio()) {
         TrackID audioTrackId = mInfo.mAudio.mTrackId;
         AudioSegment* audio = new AudioSegment();
         mediaStream->AddAudioTrack(audioTrackId, mInfo.mAudio.mRate, 0, audio,
                                    SourceMediaStream::ADDTRACK_QUEUED);
         stream->mStream->DispatchWhenNotEnoughBuffered(audioTrackId,
             TaskQueue(), GetWakeDecoderRunnable());
-        stream->mNextAudioTime = mStartTime + stream->mInitialTime;
+        stream->mNextAudioTime = mStreamStartTime;
       }
       if (mInfo.HasVideo()) {
         TrackID videoTrackId = mInfo.mVideo.mTrackId;
         VideoSegment* video = new VideoSegment();
         mediaStream->AddTrack(videoTrackId, 0, video,
                               SourceMediaStream::ADDTRACK_QUEUED);
         stream->mStream->DispatchWhenNotEnoughBuffered(videoTrackId,
             TaskQueue(), GetWakeDecoderRunnable());
 
-        // TODO: We can't initialize |mNextVideoTime| until |mStartTime|
-        // is set. This is a good indication that DecodedStreamData is in
-        // deep coupling with the state machine and we should move the class
-        // into MediaDecoderStateMachine.
-        stream->mNextVideoTime = mStartTime + stream->mInitialTime;
+        stream->mNextVideoTime = mStreamStartTime;
       }
       mediaStream->FinishAddTracks();
       stream->mStreamInitialized = true;
 
       // Make sure stream blocking is updated before sending stream data so we
       // don't 'leak' data when the stream is supposed to be blocked.
       UpdateStreamBlockingForPlayState();
       UpdateStreamBlockingForStateMachinePlaying();
@@ -573,17 +575,17 @@ void MediaDecoderStateMachine::SendStrea
           MOZ_ASSERT(endSegment.GetDuration() > 0);
           mediaStream->AppendToTrack(videoTrackId, &endSegment);
         }
         mediaStream->EndTrack(videoTrackId);
         stream->mHaveSentFinishVideo = true;
       }
       endPosition = std::max(endPosition,
           mediaStream->MicrosecondsToStreamTimeRoundDown(
-              stream->mNextVideoTime - stream->mInitialTime));
+              stream->mNextVideoTime - mStreamStartTime));
     }
 
     if (!stream->mHaveSentFinish) {
       stream->mStream->AdvanceKnownTracksTime(endPosition);
     }
 
     if (finished && !stream->mHaveSentFinish) {
       stream->mHaveSentFinish = true;
@@ -1339,25 +1341,18 @@ void MediaDecoderStateMachine::UpdatePla
 {
   MOZ_ASSERT(OnTaskQueue());
   SAMPLE_LOG("UpdatePlaybackPositionInternal(%lld) (mStartTime=%lld)", aTime, mStartTime);
   AssertCurrentThreadInMonitor();
 
   NS_ASSERTION(mStartTime >= 0, "Should have positive mStartTime");
   mCurrentPosition = aTime - mStartTime;
   NS_ASSERTION(mCurrentPosition >= 0, "CurrentTime should be positive!");
-  if (aTime > mEndTime) {
-    NS_ASSERTION(mCurrentPosition > GetDuration(),
-                 "CurrentTime must be after duration if aTime > endTime!");
-    DECODER_LOG("Setting new end time to %lld", aTime);
-    mEndTime = aTime;
-    nsCOMPtr<nsIRunnable> event =
-      NS_NewRunnableMethod(mDecoder, &MediaDecoder::DurationChanged);
-    AbstractThread::MainThread()->Dispatch(event.forget());
-  }
+  mObservedDuration = std::max(mObservedDuration.Ref(),
+                               TimeUnit::FromMicroseconds(mCurrentPosition.Ref()));
 }
 
 void MediaDecoderStateMachine::UpdatePlaybackPosition(int64_t aTime)
 {
   MOZ_ASSERT(OnTaskQueue());
   UpdatePlaybackPositionInternal(aTime);
 
   bool fragmentEnded = mFragmentEndTime >= 0 && GetMediaTime() >= mFragmentEndTime;
@@ -1433,89 +1428,80 @@ int64_t MediaDecoderStateMachine::GetDur
 int64_t MediaDecoderStateMachine::GetEndTime()
 {
   if (mEndTime == -1 && mDurationSet) {
     return INT64_MAX;
   }
   return mEndTime;
 }
 
-// Runnable which dispatches an event to the main thread to seek to the new
-// aSeekTarget.
-class SeekRunnable : public nsRunnable {
-public:
-  SeekRunnable(MediaDecoder* aDecoder, double aSeekTarget)
-    : mDecoder(aDecoder), mSeekTarget(aSeekTarget) {}
-  NS_IMETHOD Run() {
-    mDecoder->Seek(mSeekTarget, SeekTarget::Accurate);
-    return NS_OK;
-  }
-private:
-  nsRefPtr<MediaDecoder> mDecoder;
-  double mSeekTarget;
-};
-
-void MediaDecoderStateMachine::SetDuration(int64_t aDuration)
+void MediaDecoderStateMachine::RecomputeDuration()
 {
-  MOZ_ASSERT(NS_IsMainThread() || OnDecodeTaskQueue());
-  AssertCurrentThreadInMonitor();
-
-  if (aDuration < 0) {
-    mDurationSet = false;
+  MOZ_ASSERT(OnTaskQueue());
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+
+  // We dispatch DurationChanged to the MediaDecoder when the duration changes
+  // sometime after initialization, unless it has already been fired by the code
+  // that set the new duration.
+  bool fireDurationChanged = false;
+
+  TimeUnit duration;
+  if (mExplicitDuration.Ref().isSome()) {
+    double d = mExplicitDuration.Ref().ref();
+    if (IsNaN(d)) {
+      // We have an explicit duration (which means that we shouldn't look at
+      // any other duration sources), but the duration isn't ready yet.
+      return;
+    }
+    // We don't fire duration changed for this case because it should have
+    // already been fired on the main thread when the explicit duration was set.
+    duration = TimeUnit::FromSeconds(d);
+  } else if (mEstimatedDuration.Ref().isSome()) {
+    duration = mEstimatedDuration.Ref().ref();
+    fireDurationChanged = true;
+  } else if (mInfo.mMetadataDuration.isSome()) {
+    duration = mInfo.mMetadataDuration.ref();
+  } else if (mInfo.mMetadataEndTime.isSome() && mStartTime >= 0) {
+    duration = mInfo.mMetadataEndTime.ref() - TimeUnit::FromMicroseconds(mStartTime);
+  } else {
     return;
   }
 
+  if (duration < mObservedDuration.Ref()) {
+    duration = mObservedDuration;
+    fireDurationChanged = true;
+  }
+
+  fireDurationChanged = fireDurationChanged && duration.ToMicroseconds() != GetDuration();
+  SetDuration(duration);
+
+  if (fireDurationChanged) {
+    nsCOMPtr<nsIRunnable> event =
+      NS_NewRunnableMethodWithArg<TimeUnit>(mDecoder, &MediaDecoder::DurationChanged, duration);
+    AbstractThread::MainThread()->Dispatch(event.forget());
+  }
+}
+
+void MediaDecoderStateMachine::SetDuration(TimeUnit aDuration)
+{
+  MOZ_ASSERT(OnTaskQueue());
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  MOZ_ASSERT(aDuration.ToMicroseconds() >= 0);
   mDurationSet = true;
 
   if (mStartTime == -1) {
     SetStartTime(0);
   }
 
-  if (aDuration == INT64_MAX) {
+  if (aDuration.IsInfinite()) {
     mEndTime = -1;
     return;
   }
 
-  mEndTime = mStartTime + aDuration;
-
-  if (mDecoder && mEndTime >= 0 && mEndTime < mCurrentPosition.ReadOnWrongThread()) {
-    // The current playback position is now past the end of the element duration
-    // the user agent must also seek to the time of the end of the media
-    // resource.
-    if (NS_IsMainThread()) {
-      // Seek synchronously.
-      mDecoder->Seek(double(mEndTime) / USECS_PER_S, SeekTarget::Accurate);
-    } else {
-      // Queue seek to new end position.
-      nsCOMPtr<nsIRunnable> task =
-        new SeekRunnable(mDecoder, double(mEndTime) / USECS_PER_S);
-      AbstractThread::MainThread()->Dispatch(task.forget());
-    }
-  }
-}
-
-void MediaDecoderStateMachine::UpdateEstimatedDuration(int64_t aDuration)
-{
-  AssertCurrentThreadInMonitor();
-  int64_t duration = GetDuration();
-  if (aDuration != duration &&
-      mozilla::Abs(aDuration - duration) > ESTIMATED_DURATION_FUZZ_FACTOR_USECS) {
-    SetDuration(aDuration);
-    nsCOMPtr<nsIRunnable> event =
-      NS_NewRunnableMethod(mDecoder, &MediaDecoder::DurationChanged);
-    AbstractThread::MainThread()->Dispatch(event.forget());
-  }
-}
-
-void MediaDecoderStateMachine::SetMediaEndTime(int64_t aEndTime)
-{
-  MOZ_ASSERT(OnDecodeTaskQueue());
-  AssertCurrentThreadInMonitor();
-
-  mEndTime = aEndTime;
+  mEndTime = mStartTime + aDuration.ToMicroseconds();
 }
 
 void MediaDecoderStateMachine::SetFragmentEndTime(int64_t aEndTime)
 {
   AssertCurrentThreadInMonitor();
 
   mFragmentEndTime = aEndTime < 0 ? aEndTime : aEndTime + mStartTime;
 }
@@ -1794,38 +1780,25 @@ MediaDecoderStateMachine::Seek(SeekTarge
   return mPendingSeek.mPromise.Ensure(__func__);
 }
 
 void MediaDecoderStateMachine::StopAudioThread()
 {
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
 
-  if (mStopAudioThread) {
-    // Audio sink is being stopped in another thread. Wait until finished.
-    while (mAudioSink) {
-      mDecoder->GetReentrantMonitor().Wait();
-    }
-    return;
-  }
-
-  mStopAudioThread = true;
-  // Wake up audio sink so that it can reach the finish line.
-  mDecoder->GetReentrantMonitor().NotifyAll();
   if (mAudioSink) {
     DECODER_LOG("Shutdown audio thread");
     mAudioSink->PrepareToShutdown();
     {
       ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
       mAudioSink->Shutdown();
     }
     mAudioSink = nullptr;
   }
-  // Wake up those waiting for audio sink to finish.
-  mDecoder->GetReentrantMonitor().NotifyAll();
 }
 
 nsresult
 MediaDecoderStateMachine::EnqueueDecodeFirstFrameTask()
 {
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME);
@@ -1917,26 +1890,19 @@ MediaDecoderStateMachine::InitiateSeek()
   int64_t seekTime = mCurrentSeek.mTarget.mTime + mStartTime;
   seekTime = std::min(seekTime, end);
   seekTime = std::max(mStartTime, seekTime);
   NS_ASSERTION(seekTime >= mStartTime && seekTime <= end,
                "Can only seek in range [0,duration]");
   mCurrentSeek.mTarget.mTime = seekTime;
 
   if (mAudioCaptured) {
-    // TODO: We should re-create the decoded stream after seek completed as we do
-    // for audio thread since it is until then we know which position we seek to
-    // as far as fast-seek is concerned. It also fix the problem where stream
-    // clock seems to go backwards during seeking.
-    nsCOMPtr<nsIRunnable> event =
-      NS_NewRunnableMethodWithArgs<int64_t, MediaStreamGraph*>(this,
-                                                               &MediaDecoderStateMachine::RecreateDecodedStream,
-                                                               seekTime - mStartTime,
-                                                               nullptr);
-    AbstractThread::MainThread()->Dispatch(event.forget());
+    nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethodWithArgs<MediaStreamGraph*>(
+      this, &MediaDecoderStateMachine::RecreateDecodedStream, nullptr);
+    AbstractThread::MainThread()->Dispatch(r.forget());
   }
 
   mDropAudioUntilNextDiscontinuity = HasAudio();
   mDropVideoUntilNextDiscontinuity = HasVideo();
 
   mDecoder->StopProgressUpdates();
   mCurrentTimeBeforeSeek = GetMediaTime();
 
@@ -2087,21 +2053,20 @@ MediaDecoderStateMachine::EnsureVideoDec
 }
 
 nsresult
 MediaDecoderStateMachine::StartAudioThread()
 {
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   if (mAudioCaptured) {
-    NS_ASSERTION(mStopAudioThread, "mStopAudioThread must always be true if audio is captured");
+    MOZ_ASSERT(!mAudioSink);
     return NS_OK;
   }
 
-  mStopAudioThread = false;
   if (HasAudio() && !mAudioSink) {
     // The audio end time should always be at least the audio start time.
     mAudioEndTime = mAudioStartTime;
     MOZ_ASSERT(mAudioStartTime == GetMediaTime());
     mAudioCompleted = false;
     mAudioSink = new AudioSink(this, mAudioStartTime,
                                mInfo.mAudio, mDecoder->GetAudioChannel());
     // OnAudioSinkError() will be called before Init() returns if an error
@@ -2236,26 +2201,29 @@ MediaDecoderStateMachine::OnMetadataRead
   MOZ_ASSERT(mState == DECODER_STATE_DECODING_METADATA);
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   mMetadataRequest.Complete();
 
   mDecoder->SetMediaSeekable(mReader->IsMediaSeekable());
   mInfo = aMetadata->mInfo;
   mMetadataTags = aMetadata->mTags.forget();
 
+  if (mInfo.mMetadataDuration.isSome() || mInfo.mMetadataEndTime.isSome()) {
+    RecomputeDuration();
+  }
+
   if (HasVideo()) {
     DECODER_LOG("Video decode isAsync=%d HWAccel=%d videoQueueSize=%d",
                 mReader->IsAsync(),
                 mReader->VideoIsHardwareAccelerated(),
                 GetAmpleVideoFrames());
   }
 
   mDecoder->StartProgressUpdates();
   mGotDurationFromMetaData = (GetDuration() != -1) || mDurationSet;
-
   if (mGotDurationFromMetaData) {
     // We have all the information required: duration and size
     // Inform the element that we've loaded the metadata.
     EnqueueLoadedMetadataEvent();
   }
 
   if (mReader->IsWaitingOnCDMResource()) {
     // Metadata parsing was successful but we're still waiting for CDM caps
@@ -2481,16 +2449,17 @@ MediaDecoderStateMachine::SeekCompleted(
     // seeking to a position lies before audio or video, we need to check if
     // seekTime is bounded in suitable duration. See Bug 1112438.
     int64_t videoStart = video ? video->mTime : seekTime;
     int64_t audioStart = audio ? audio->mTime : seekTime;
     newCurrentTime = mAudioStartTime = std::min(audioStart, videoStart);
   } else {
     newCurrentTime = video ? video->mTime : seekTime;
   }
+  mStreamStartTime = newCurrentTime;
   mPlayDuration = newCurrentTime - mStartTime;
 
   mDecoder->StartProgressUpdates();
 
   // Change state to DECODING or COMPLETED now. SeekingStopped will
   // call MediaDecoderStateMachine::Seek to reset our state to SEEKING
   // if we need to seek again.
 
@@ -2573,16 +2542,18 @@ MediaDecoderStateMachine::FinishShutdown
   AudioQueue().ClearListeners();
   VideoQueue().ClearListeners();
 
   // Now that those threads are stopped, there's no possibility of
   // mPendingWakeDecoder being needed again. Revoke it.
   mPendingWakeDecoder = nullptr;
 
   // Disconnect canonicals and mirrors before shutting down our task queue.
+  mEstimatedDuration.DisconnectIfConnected();
+  mExplicitDuration.DisconnectIfConnected();
   mPlayState.DisconnectIfConnected();
   mNextPlayState.DisconnectIfConnected();
   mLogicallySeeking.DisconnectIfConnected();
   mVolume.DisconnectIfConnected();
   mLogicalPlaybackRate.DisconnectIfConnected();
   mPreservesPitch.DisconnectIfConnected();
   mNextFrameStatus.DisconnectAll();
   mCurrentPosition.DisconnectAll();
@@ -2616,21 +2587,16 @@ MediaDecoderStateMachine::FinishShutdown
 nsresult MediaDecoderStateMachine::RunStateMachine()
 {
   MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   mDelayedScheduler.Reset(); // Must happen on state machine task queue.
   mDispatchedStateMachine = false;
 
-  // If audio is being captured, stop the audio sink if it's running
-  if (mAudioCaptured) {
-    StopAudioThread();
-  }
-
   MediaResource* resource = mDecoder->GetResource();
   NS_ENSURE_TRUE(resource, NS_ERROR_NULL_POINTER);
 
   switch (mState) {
     case DECODER_STATE_ERROR:
     case DECODER_STATE_SHUTDOWN:
     case DECODER_STATE_DORMANT:
     case DECODER_STATE_WAIT_FOR_CDM:
@@ -2800,16 +2766,17 @@ MediaDecoderStateMachine::Reset()
 
   // Stop the audio thread. Otherwise, AudioSink might be accessing AudioQueue
   // outside of the decoder monitor while we are clearing the queue and causes
   // crash for no samples to be popped.
   StopAudioThread();
 
   mVideoFrameEndTime = -1;
   mDecodedVideoEndTime = -1;
+  mStreamStartTime = -1;
   mAudioStartTime = -1;
   mAudioEndTime = -1;
   mDecodedAudioEndTime = -1;
   mAudioCompleted = false;
   AudioQueue().Reset();
   VideoQueue().Reset();
   mFirstVideoFrameAfterSeek = nullptr;
   mDropAudioUntilNextDiscontinuity = true;
@@ -2886,16 +2853,24 @@ MediaDecoderStateMachine::GetAudioClock(
   // audio sink to ensure that it doesn't get destroyed on the audio sink
   // while we're using it.
   AssertCurrentThreadInMonitor();
   MOZ_ASSERT(HasAudio() && !mAudioCompleted);
   return mAudioStartTime +
          (mAudioSink ? mAudioSink->GetPosition() : 0);
 }
 
+int64_t MediaDecoderStateMachine::GetStreamClock() const
+{
+  MOZ_ASSERT(OnTaskQueue());
+  AssertCurrentThreadInMonitor();
+  MOZ_ASSERT(mStreamStartTime != -1);
+  return mStreamStartTime + GetDecodedStream()->GetPosition();
+}
+
 int64_t MediaDecoderStateMachine::GetVideoStreamPosition() const
 {
   AssertCurrentThreadInMonitor();
 
   if (!IsPlaying()) {
     return mPlayDuration + mStartTime;
   }
 
@@ -2915,27 +2890,25 @@ int64_t MediaDecoderStateMachine::GetClo
   // the end of the audio, use the audio clock. However if we've finished
   // audio, or don't have audio, use the system clock. If our output is being
   // fed to a MediaStream, use that stream as the source of the clock.
   int64_t clock_time = -1;
   if (!IsPlaying()) {
     clock_time = mPlayDuration + mStartTime;
   } else {
     if (mAudioCaptured) {
-      clock_time = mStartTime + GetDecodedStream()->GetClock();
+      clock_time = GetStreamClock();
     } else if (HasAudio() && !mAudioCompleted) {
       clock_time = GetAudioClock();
     } else {
       // Audio is disabled on this system. Sync to the system clock.
       clock_time = GetVideoStreamPosition();
     }
-    // Ensure the clock can never go backwards.
-    // Note we allow clock going backwards in capture mode - we should fix this in bug 1162381.
-    NS_ASSERTION(GetMediaTime() <= clock_time || mPlaybackRate <= 0 || mAudioCaptured,
-    "Clock should go forwards.");
+    NS_ASSERTION(GetMediaTime() <= clock_time || mPlaybackRate <= 0,
+                 "Clock should go forwards.");
   }
 
   return clock_time;
 }
 
 void MediaDecoderStateMachine::AdvanceFrame()
 {
   MOZ_ASSERT(OnTaskQueue());
@@ -3236,17 +3209,20 @@ void MediaDecoderStateMachine::SetStartT
   // Pass along this immutable value to the reader so that it can make
   // calculations independently of the state machine.
   mReader->SetStartTime(mStartTime);
 
   // Set the audio start time to be start of media. If this lies before the
   // first actual audio frame we have, we'll inject silence during playback
   // to ensure the audio starts at the correct time.
   mAudioStartTime = mStartTime;
+  mStreamStartTime = mStartTime;
   DECODER_LOG("Set media start time to %lld", mStartTime);
+
+  RecomputeDuration();
 }
 
 void MediaDecoderStateMachine::UpdateNextFrameStatus()
 {
   MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   MediaDecoderOwner::NextFrameStatus status;
@@ -3518,32 +3494,38 @@ DecodedStreamData* MediaDecoderStateMach
 void MediaDecoderStateMachine::DispatchAudioCaptured()
 {
   nsRefPtr<MediaDecoderStateMachine> self = this;
   nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () -> void
   {
     MOZ_ASSERT(self->OnTaskQueue());
     ReentrantMonitorAutoEnter mon(self->mDecoder->GetReentrantMonitor());
     if (!self->mAudioCaptured) {
+      // Stop the audio sink if it's running.
+      self->StopAudioThread();
+      // GetMediaTime() could return -1 because we haven't decoded
+      // the 1st frame. But this is OK since we will update mStreamStartTime
+      // again in SetStartTime().
+      self->mStreamStartTime = self->GetMediaTime();
       self->mAudioCaptured = true;
       self->ScheduleStateMachine();
     }
   });
   TaskQueue()->Dispatch(r.forget());
 }
 
 void MediaDecoderStateMachine::AddOutputStream(ProcessedMediaStream* aStream,
                                                bool aFinishWhenEnded)
 {
   MOZ_ASSERT(NS_IsMainThread());
   DECODER_LOG("AddOutputStream aStream=%p!", aStream);
 
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   if (!GetDecodedStream()) {
-    RecreateDecodedStream(mCurrentPosition.ReadOnWrongThread(), aStream->Graph());
+    RecreateDecodedStream(aStream->Graph());
   }
   mDecodedStream.Connect(aStream, aFinishWhenEnded);
   DispatchAudioCaptured();
 }
 
 void MediaDecoderStateMachine::UpdateStreamBlockingForPlayState()
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
@@ -3572,23 +3554,21 @@ void MediaDecoderStateMachine::UpdateStr
 
   bool blocking = !IsPlaying();
   if (blocking != stream->mHaveBlockedForStateMachineNotPlaying) {
     stream->mHaveBlockedForStateMachineNotPlaying = blocking;
     UpdateStreamBlocking(stream->mStream, blocking);
   }
 }
 
-void MediaDecoderStateMachine::RecreateDecodedStream(int64_t aInitialTime,
-                                                     MediaStreamGraph* aGraph)
+void MediaDecoderStateMachine::RecreateDecodedStream(MediaStreamGraph* aGraph)
 {
   MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  DECODER_LOG("RecreateDecodedStream aInitialTime=%lld!", aInitialTime);
-  mDecodedStream.RecreateData(aInitialTime, aGraph);
+  mDecodedStream.RecreateData(aGraph);
 }
 
 } // namespace mozilla
 
 // avoid redefined macro in unified build
 #undef LOG
 #undef DECODER_LOG
 #undef VERBOSE_LOG
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -166,19 +166,18 @@ private:
   // mLogicallySeeking change. Decoder monitor must be held.
   void UpdateStreamBlockingForPlayState();
 
   // Call this IsPlaying() changes. Decoder monitor must be held.
   void UpdateStreamBlockingForStateMachinePlaying();
 
   // Recreates mDecodedStream. Call this to create mDecodedStream at first,
   // and when seeking, to ensure a new stream is set up with fresh buffers.
-  // aInitialTime is relative to mStartTime.
   // Decoder monitor must be held.
-  void RecreateDecodedStream(int64_t aInitialTime, MediaStreamGraph* aGraph);
+  void RecreateDecodedStream(MediaStreamGraph* aGraph);
 
   void Shutdown();
 public:
 
   void DispatchShutdown()
   {
     nsCOMPtr<nsIRunnable> runnable =
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::Shutdown);
@@ -200,29 +199,17 @@ public:
   int64_t GetEndTime();
 
   // Called from the main thread to set the duration of the media resource
   // if it is able to be obtained via HTTP headers. Called from the
   // state machine thread to set the duration if it is obtained from the
   // media metadata. The decoder monitor must be obtained before calling this.
   // aDuration is in microseconds.
   // A value of INT64_MAX will be treated as infinity.
-  void SetDuration(int64_t aDuration);
-
-  // Called while decoding metadata to set the end time of the media
-  // resource. The decoder monitor must be obtained before calling this.
-  // aEndTime is in microseconds.
-  void SetMediaEndTime(int64_t aEndTime);
-
-  // Called from main thread to update the duration with an estimated value.
-  // The duration is only changed if its significantly different than the
-  // the current duration, as the incoming duration is an estimate and so
-  // often is unstable as more data is read and the estimate is updated.
-  // Can result in a durationchangeevent. aDuration is in microseconds.
-  void UpdateEstimatedDuration(int64_t aDuration);
+  void SetDuration(media::TimeUnit aDuration);
 
   // Functions used by assertions to ensure we're calling things
   // on the appropriate threads.
   bool OnDecodeTaskQueue() const;
   bool OnTaskQueue() const;
 
   // Seeks to the decoder to aTarget asynchronously.
   // Must be called on the state machine thread.
@@ -544,16 +531,18 @@ protected:
   // |mPlayDuration| are updated to provide a good base for calculating video
   // stream time.
   void ResyncAudioClock();
 
   // Returns the audio clock, if we have audio, or -1 if we don't.
   // Called on the state machine thread.
   int64_t GetAudioClock() const;
 
+  int64_t GetStreamClock() const;
+
   // Get the video stream position, taking the |playbackRate| change into
   // account. This is a position in the media, not the duration of the playback
   // so far.
   int64_t GetVideoStreamPosition() const;
 
   // Return the current time, either the audio clock if available (if the media
   // has audio, and the playback is possible), or a clock for the video.
   // Called on the state machine thread.
@@ -894,21 +883,34 @@ public:
   int64_t mStartTime;
 
   // Time of the last frame in the media, in microseconds. This is the
   // end time of the last frame in the media. Accessed on state
   // machine, decode, and main threads. Access controlled by decoder monitor.
   // It will be set to -1 if the duration is infinite
   int64_t mEndTime;
 
+  // Recomputes the canonical duration from various sources.
+  void RecomputeDuration();
+
   // Will be set when SetDuration has been called with a value != -1
   // mDurationSet false doesn't indicate that we do not have a valid duration
   // as mStartTime and mEndTime could have been set separately.
   bool mDurationSet;
 
+  // The duration according to the demuxer's current estimate, mirrored from the main thread.
+  Mirror<media::NullableTimeUnit> mEstimatedDuration;
+
+  // The duration explicitly set by JS, mirrored from the main thread.
+  Mirror<Maybe<double>> mExplicitDuration;
+
+  // The highest timestamp that our position has reached. Monotonically
+  // increasing.
+  Watchable<media::TimeUnit> mObservedDuration;
+
   // The current play state and next play state, mirrored from the main thread.
   Mirror<MediaDecoder::PlayState> mPlayState;
   Mirror<MediaDecoder::PlayState> mNextPlayState;
   Mirror<bool> mLogicallySeeking;
 
   // Returns true if we're logically playing, that is, if the Play() has
   // been called and Pause() has not or we have not yet reached the end
   // of media. This is irrespective of the seeking state; if the owner
@@ -994,16 +996,19 @@ protected:
 
   // The time of the current frame in microseconds, corresponding to the "current
   // playback position" in HTML5. This is referenced from 0, which is the initial
   // playback position.
   Canonical<int64_t> mCurrentPosition;
 public:
   AbstractCanonical<int64_t>* CanonicalCurrentPosition() { return &mCurrentPosition; }
 protected:
+  // The presentation time of the first audio/video frame that is sent to the
+  // media stream.
+  int64_t mStreamStartTime;
 
   // The presentation time of the first audio frame that was played in
   // microseconds. We can add this to the audio stream position to determine
   // the current audio time. Accessed on audio and state machine thread.
   // Synchronized by decoder monitor.
   int64_t mAudioStartTime;
 
   // The end time of the last audio frame that's been pushed onto the audio
@@ -1181,18 +1186,18 @@ protected:
 
   MediaPromiseRequestHolder<MediaDecoderReader::WaitForDataPromise>& WaitRequestRef(MediaData::Type aType)
   {
     MOZ_ASSERT(OnTaskQueue());
     return aType == MediaData::AUDIO_DATA ? mAudioWaitRequest : mVideoWaitRequest;
   }
 
   // True if we shouldn't play our audio (but still write it to any capturing
-  // streams). When this is true, mStopAudioThread is always true and
-  // the audio thread will never start again after it has stopped.
+  // streams). When this is true, the audio thread will never start again after
+  // it has stopped.
   bool mAudioCaptured;
 
   // True if an event to notify about a change in the playback
   // position has been queued, but not yet run. It is set to false when
   // the event is run. This allows coalescing of these events as they can be
   // produced many times per second. Synchronised via decoder monitor.
   // Accessed on main and state machine threads.
   bool mPositionChangeQueued;
@@ -1210,20 +1215,16 @@ protected:
   // the media index/metadata. Accessed on the state machine thread.
   bool mGotDurationFromMetaData;
 
   // True if we've dispatched an event to the decode task queue to call
   // DecodeThreadRun(). We use this flag to prevent us from dispatching
   // unneccessary runnables, since the decode thread runs in a loop.
   bool mDispatchedEventToDecode;
 
-  // False while audio thread should be running. Accessed state machine
-  // and audio threads. Syncrhonised by decoder monitor.
-  bool mStopAudioThread;
-
   // If this is true while we're in buffering mode, we can exit early,
   // as it's likely we may be able to playback. This happens when we enter
   // buffering mode soon after the decode starts, because the decode-ahead
   // ran fast enough to exhaust all data while the download is starting up.
   // Synchronised via decoder monitor.
   bool mQuickBuffering;
 
   // True if we should not decode/preroll unnecessary samples, unless we're
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -19,16 +19,18 @@
 #include "VideoUtils.h"
 
 #include <algorithm>
 
 #ifdef MOZ_EME
 #include "mozilla/CDMProxy.h"
 #endif
 
+using namespace mozilla::media;
+
 using mozilla::layers::Image;
 using mozilla::layers::LayerManager;
 using mozilla::layers::LayersBackend;
 
 PRLogModuleInfo* GetFormatDecoderLog() {
   static PRLogModuleInfo* log = nullptr;
   if (!log) {
     log = PR_NewLogModule("MediaFormatReader");
@@ -327,18 +329,17 @@ MediaFormatReader::OnDemuxerInitDone(nsr
     mInfo.mCrypto = *crypto;
   }
 
   int64_t videoDuration = HasVideo() ? mInfo.mVideo.mDuration : 0;
   int64_t audioDuration = HasAudio() ? mInfo.mAudio.mDuration : 0;
 
   int64_t duration = std::max(videoDuration, audioDuration);
   if (duration != -1) {
-    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-    mDecoder->SetMediaDuration(duration);
+    mInfo.mMetadataDuration = Some(TimeUnit::FromMicroseconds(duration));
   }
 
   mSeekable = mDemuxer->IsSeekable();
 
   // Create demuxer object for main thread.
   mMainThreadDemuxer = mDemuxer->Clone();
   if (!mMainThreadDemuxer) {
     mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__);
--- a/dom/media/MediaInfo.h
+++ b/dom/media/MediaInfo.h
@@ -10,16 +10,17 @@
 #include "nsRect.h"
 #include "nsRefPtr.h"
 #include "nsSize.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "ImageTypes.h"
 #include "MediaData.h"
 #include "StreamBuffer.h" // for TrackID
+#include "TimeUnits.h"
 
 namespace mozilla {
 
 class AudioInfo;
 class VideoInfo;
 class TextInfo;
 
 class TrackInfo {
@@ -343,14 +344,22 @@ public:
   {
     return HasVideo() || HasAudio();
   }
 
   // TODO: Store VideoInfo and AudioIndo in arrays to support multi-tracks.
   VideoInfo mVideo;
   AudioInfo mAudio;
 
+  // If the metadata includes a duration, we store it here.
+  media::NullableTimeUnit mMetadataDuration;
+
+  // The Ogg reader tries to kinda-sorta compute the duration by seeking to the
+  // end and determining the timestamp of the last frame. This isn't useful as
+  // a duration until we know the start time, so we need to track it separately.
+  media::NullableTimeUnit mMetadataEndTime;
+
   EncryptionInfo mCrypto;
 };
 
 } // namespace mozilla
 
 #endif // MediaInfo_h
--- a/dom/media/MediaResource.cpp
+++ b/dom/media/MediaResource.cpp
@@ -28,16 +28,18 @@
 #include "nsICachingChannel.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "nsContentUtils.h"
 #include "nsHostObjectProtocolHandler.h"
 #include <algorithm>
 #include "nsProxyRelease.h"
 #include "nsIContentPolicy.h"
 
+using mozilla::media::TimeUnit;
+
 PRLogModuleInfo* gMediaResourceLog;
 #define RESOURCE_LOG(msg, ...) MOZ_LOG(gMediaResourceLog, mozilla::LogLevel::Debug, \
                                       (msg, ##__VA_ARGS__))
 // Debug logging macro with object pointer and class name.
 #define CMLOG(msg, ...) \
         RESOURCE_LOG("%p [ChannelMediaResource]: " msg, this, ##__VA_ARGS__)
 
 static const uint32_t HTTP_OK_CODE = 200;
@@ -231,47 +233,16 @@ ChannelMediaResource::OnStartRequest(nsI
     int64_t contentLength = -1;
     hc->GetContentLength(&contentLength);
     if (contentLength >= 0 && responseStatus == HTTP_OK_CODE) {
       // "OK" status means Content-Length is for the whole resource.
       // Since that's bounded, we know we have a finite-length resource.
       dataIsBounded = true;
     }
 
-    if (mOffset == 0) {
-      // Look for duration headers from known Ogg content systems.
-      // In the case of multiple options for obtaining the duration
-      // the order of precedence is:
-      // 1) The Media resource metadata if possible (done by the decoder itself).
-      // 2) Content-Duration message header.
-      // 3) X-AMZ-Meta-Content-Duration.
-      // 4) X-Content-Duration.
-      // 5) Perform a seek in the decoder to find the value.
-      nsAutoCString durationText;
-      nsresult ec = NS_OK;
-      rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("Content-Duration"), durationText);
-      if (NS_FAILED(rv)) {
-        rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-AMZ-Meta-Content-Duration"), durationText);
-      }
-      if (NS_FAILED(rv)) {
-        rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-Content-Duration"), durationText);
-      }
-
-      // If there is a Content-Duration header with a valid value, record
-      // the duration.
-      if (NS_SUCCEEDED(rv)) {
-        double duration = durationText.ToDouble(&ec);
-        if (ec == NS_OK && duration >= 0) {
-          mDecoder->SetDuration(duration);
-          // We know the resource must be bounded.
-          dataIsBounded = true;
-        }
-      }
-    }
-
     // Assume Range requests have a bounded upper limit unless the
     // Content-Range header tells us otherwise.
     bool boundedSeekLimit = true;
     // Check response code for byte-range requests (seeking, chunk requests).
     if (!mByteRange.IsNull() && (responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
       // Parse Content-Range header.
       int64_t rangeStart = 0;
       int64_t rangeEnd = 0;
--- a/dom/media/RtspMediaResource.cpp
+++ b/dom/media/RtspMediaResource.cpp
@@ -14,16 +14,17 @@
 #include "mozilla/Preferences.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIStreamingProtocolService.h"
 #include "nsServiceManagerUtils.h"
 #ifdef NECKO_PROTOCOL_rtsp
 #include "mozilla/net/RtspChannelChild.h"
 #endif
 using namespace mozilla::net;
+using namespace mozilla::media;
 
 PRLogModuleInfo* gRtspMediaResourceLog;
 #define RTSP_LOG(msg, ...) MOZ_LOG(gRtspMediaResourceLog, mozilla::LogLevel::Debug, \
                                   (msg, ##__VA_ARGS__))
 // Debug logging macro with object pointer and class name.
 #define RTSPMLOG(msg, ...) \
         RTSP_LOG("%p [RtspMediaResource]: " msg, this, ##__VA_ARGS__)
 
@@ -720,17 +721,16 @@ RtspMediaResource::OnConnected(uint8_t a
     return NS_ERROR_FAILURE;
   }
 
   // If the durationUs is 0, imply the stream is live stream.
   if (durationUs) {
     // Not live stream.
     mIsLiveStream = false;
     mDecoder->SetInfinite(false);
-    mDecoder->SetDuration((double)(durationUs) / USECS_PER_S);
   } else {
     // Live stream.
     // Check the preference "media.realtime_decoder.enabled".
     if (!Preferences::GetBool("media.realtime_decoder.enabled", false)) {
       // Give up, report error to media element.
       nsCOMPtr<nsIRunnable> event =
         NS_NewRunnableMethod(mDecoder, &MediaDecoder::DecodeError);
       NS_DispatchToMainThread(event);
--- a/dom/media/StateWatching.h
+++ b/dom/media/StateWatching.h
@@ -138,17 +138,18 @@ protected:
  */
 template<typename T>
 class Watchable : public WatchTarget
 {
 public:
   Watchable(const T& aInitialValue, const char* aName)
     : WatchTarget(aName), mValue(aInitialValue) {}
 
-  operator const T&() const { return mValue; }
+  const T& Ref() const { return mValue; }
+  operator const T&() const { return Ref(); }
   Watchable& operator=(const T& aNewValue)
   {
     if (aNewValue != mValue) {
       mValue = aNewValue;
       NotifyWatchers();
     }
 
     return *this;
--- a/dom/media/TimeUnits.h
+++ b/dom/media/TimeUnits.h
@@ -5,39 +5,41 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef TIME_UNITS_H
 #define TIME_UNITS_H
 
 #include "Intervals.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/FloatingPoint.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/dom/TimeRanges.h"
 
 namespace mozilla {
 namespace media {
 class TimeIntervals;
 }
 }
 // CopyChooser specalization for nsTArray
 template<>
 struct nsTArray_CopyChooser<mozilla::media::TimeIntervals>
 {
   typedef nsTArray_CopyWithConstructors<mozilla::media::TimeIntervals> Type;
 };
 
 namespace mozilla {
-namespace media {
 
 // Number of microseconds per second. 1e6.
 static const int64_t USECS_PER_S = 1000000;
 
 // Number of microseconds per millisecond.
 static const int64_t USECS_PER_MS = 1000;
 
+namespace media {
+
 // Number of nanoseconds per second. 1e9.
 static const int64_t NSECS_PER_S = 1000000000;
 
 struct Microseconds {
   Microseconds()
     : mValue(0)
   {}
 
@@ -198,16 +200,18 @@ private:
   explicit TimeUnit(CheckedInt64 aMicroseconds)
     : mValue(aMicroseconds)
   {}
 
   // Our internal representation is in microseconds.
   CheckedInt64 mValue;
 };
 
+typedef Maybe<TimeUnit> NullableTimeUnit;
+
 typedef Interval<TimeUnit> TimeInterval;
 
 class TimeIntervals : public IntervalSet<TimeUnit>
 {
 public:
   typedef IntervalSet<TimeUnit> BaseType;
 
   // We can't use inherited constructors yet. So we have to duplicate all the
--- a/dom/media/VideoUtils.h
+++ b/dom/media/VideoUtils.h
@@ -132,22 +132,16 @@ media::TimeIntervals GetEstimatedBuffere
 CheckedInt64 FramesToUsecs(int64_t aFrames, uint32_t aRate);
 
 // Converts from microseconds (aUsecs) to number of audio frames, given the
 // specified audio rate (aRate). Stores the result in aOutFrames. Returns
 // true if the operation succeeded, or false if there was an integer
 // overflow while calulating the conversion.
 CheckedInt64 UsecsToFrames(int64_t aUsecs, uint32_t aRate);
 
-// Number of microseconds per second. 1e6.
-static const int64_t USECS_PER_S = 1000000;
-
-// Number of microseconds per millisecond.
-static const int64_t USECS_PER_MS = 1000;
-
 // Converts milliseconds to seconds.
 #define MS_TO_SECONDS(ms) ((double)(ms) / (PR_MSEC_PER_SEC))
 
 // Converts seconds to milliseconds.
 #define SECONDS_TO_MS(s) ((int)((s) * (PR_MSEC_PER_SEC)))
 
 // Converts from seconds to microseconds. Returns failure if the resulting
 // integer is too big to fit in an int64_t.
--- a/dom/media/android/AndroidMediaReader.cpp
+++ b/dom/media/android/AndroidMediaReader.cpp
@@ -13,16 +13,17 @@
 #include "MediaDecoderStateMachine.h"
 #include "ImageContainer.h"
 #include "AbstractMediaDecoder.h"
 #include "gfx2DGlue.h"
 
 namespace mozilla {
 
 using namespace mozilla::gfx;
+using namespace mozilla::media;
 
 typedef mozilla::layers::Image Image;
 typedef mozilla::layers::PlanarYCbCrImage PlanarYCbCrImage;
 
 AndroidMediaReader::AndroidMediaReader(AbstractMediaDecoder *aDecoder,
                                        const nsACString& aContentType) :
   MediaDecoderReader(aDecoder),
   mType(aContentType),
@@ -50,18 +51,17 @@ nsresult AndroidMediaReader::ReadMetadat
       return NS_ERROR_FAILURE;
     }
   }
 
   // Set the total duration (the max of the audio and video track).
   int64_t durationUs;
   mPlugin->GetDuration(mPlugin, &durationUs);
   if (durationUs) {
-    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-    mDecoder->SetMediaDuration(durationUs);
+    mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(durationUs));
   }
 
   if (mPlugin->HasVideo(mPlugin)) {
     int32_t width, height;
     mPlugin->GetVideoParameters(mPlugin, &width, &height);
     nsIntRect pictureRect(0, 0, width, height);
 
     // Validate the container-reported frame and pictureRect sizes. This ensures
--- a/dom/media/apple/AppleMP3Reader.cpp
+++ b/dom/media/apple/AppleMP3Reader.cpp
@@ -14,16 +14,18 @@
 // Maximum number of audio frames we will accept from the audio decoder in one
 // go.  Carefully select this to work well with both the mp3 1152 max frames
 // per block and power-of-2 allocation sizes.  Since we must pre-allocate the
 // buffer we cannot use AudioCompactor without paying for an additional
 // allocation and copy.  Therefore, choosing a value that divides exactly into
 // 1152 is most memory efficient.
 #define MAX_AUDIO_FRAMES 128
 
+using namespace mozilla::media;
+
 namespace mozilla {
 
 extern PRLogModuleInfo* gMediaDecoderLog;
 #define LOGE(...) MOZ_LOG(gMediaDecoderLog, mozilla::LogLevel::Error, (__VA_ARGS__))
 #define LOGW(...) MOZ_LOG(gMediaDecoderLog, mozilla::LogLevel::Warning, (__VA_ARGS__))
 #define LOGD(...) MOZ_LOG(gMediaDecoderLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
 
 #define PROPERTY_ID_FORMAT "%c%c%c%c"
@@ -408,21 +410,21 @@ AppleMP3Reader::ReadMetadata(MediaInfo* 
     return NS_ERROR_FAILURE;
   }
 
   if (mStreamReady) {
     aInfo->mAudio.mRate = mAudioSampleRate;
     aInfo->mAudio.mChannels = mAudioChannels;
   }
 
-  {
-    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-    mDuration = mMP3FrameParser.GetDuration();
-    mDecoder->SetMediaDuration(mDuration);
-  }
+  // This special snowflake reader doesn't seem to set *aInfo = mInfo like all
+  // the others. Yuck.
+  mDuration = mMP3FrameParser.GetDuration();
+  mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(mDuration));
+  aInfo->mMetadataDuration.emplace(TimeUnit::FromMicroseconds(mDuration));
 
   return NS_OK;
 }
 
 
 void
 AppleMP3Reader::AudioMetadataCallback(AudioFileStreamID aFileStream,
                                       AudioFileStreamPropertyID aPropertyID,
--- a/dom/media/directshow/DirectShowReader.cpp
+++ b/dom/media/directshow/DirectShowReader.cpp
@@ -9,16 +9,18 @@
 #include "mozilla/RefPtr.h"
 #include "DirectShowUtils.h"
 #include "AudioSinkFilter.h"
 #include "SourceFilter.h"
 #include "SampleSink.h"
 #include "MediaResource.h"
 #include "VideoUtils.h"
 
+using namespace mozilla::media;
+
 namespace mozilla {
 
 
 PRLogModuleInfo*
 GetDirectShowLog() {
   static PRLogModuleInfo* log = nullptr;
   if (!log) {
     log = PR_NewLogModule("DirectShowDecoder");
@@ -189,40 +191,39 @@ DirectShowReader::ReadMetadata(MediaInfo
   mAudioSinkFilter->GetSampleSink()->GetAudioFormat(&format);
   NS_ENSURE_TRUE(format.wFormatTag == WAVE_FORMAT_PCM, NS_ERROR_FAILURE);
 
   mInfo.mAudio.mChannels = mNumChannels = format.nChannels;
   mInfo.mAudio.mRate = mAudioRate = format.nSamplesPerSec;
   mInfo.mAudio.mBitDepth = format.wBitsPerSample;
   mBytesPerSample = format.wBitsPerSample / 8;
 
-  *aInfo = mInfo;
-  // Note: The SourceFilter strips ID3v2 tags out of the stream.
-  *aTags = nullptr;
-
   // Begin decoding!
   hr = mControl->Run();
   NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
 
   DWORD seekCaps = 0;
   hr = mMediaSeeking->GetCapabilities(&seekCaps);
 
   int64_t duration = mMP3FrameParser.GetDuration();
   if (SUCCEEDED(hr)) {
-    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-    mDecoder->SetMediaDuration(duration);
+    mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(duration));
   }
 
   LOG("Successfully initialized DirectShow MP3 decoder.");
   LOG("Channels=%u Hz=%u duration=%lld bytesPerSample=%d",
       mInfo.mAudio.mChannels,
       mInfo.mAudio.mRate,
       RefTimeToUsecs(duration),
       mBytesPerSample);
 
+  *aInfo = mInfo;
+  // Note: The SourceFilter strips ID3v2 tags out of the stream.
+  *aTags = nullptr;
+
   return NS_OK;
 }
 
 bool
 DirectShowReader::IsMediaSeekable()
 {
   DWORD seekCaps = 0;
   HRESULT hr = mMediaSeeking->GetCapabilities(&seekCaps);
--- a/dom/media/fmp4/MP4Reader.cpp
+++ b/dom/media/fmp4/MP4Reader.cpp
@@ -27,16 +27,17 @@
 #ifdef MOZ_EME
 #include "mozilla/CDMProxy.h"
 #endif
 
 using mozilla::layers::Image;
 using mozilla::layers::LayerManager;
 using mozilla::layers::ImageContainer;
 using mozilla::layers::LayersBackend;
+using mozilla::media::TimeUnit;
 
 PRLogModuleInfo* GetDemuxerLog() {
   static PRLogModuleInfo* log = nullptr;
   if (!log) {
     log = PR_NewLogModule("MP4Demuxer");
   }
   return log;
 }
@@ -426,18 +427,17 @@ MP4Reader::ReadMetadata(MediaInfo* aInfo
 
   // Get the duration, and report it to the decoder if we have it.
   Microseconds duration;
   {
     MonitorAutoLock lock(mDemuxerMonitor);
     duration = mDemuxer->Duration();
   }
   if (duration != -1) {
-    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-    mDecoder->SetMediaDuration(duration);
+    mInfo.mMetadataDuration = Some(TimeUnit::FromMicroseconds(duration));
   }
 
   *aInfo = mInfo;
   *aTags = nullptr;
 
   if (!IsWaitingOnCDMResource()) {
     NS_ENSURE_TRUE(EnsureDecodersSetup(), NS_ERROR_FAILURE);
   }
--- a/dom/media/gstreamer/GStreamerReader.cpp
+++ b/dom/media/gstreamer/GStreamerReader.cpp
@@ -20,16 +20,17 @@
 #include "mozilla/unused.h"
 #include "GStreamerLoader.h"
 #include "gfx2DGlue.h"
 
 namespace mozilla {
 
 using namespace gfx;
 using namespace layers;
+using namespace media;
 
 // Un-comment to enable logging of seek bisections.
 //#define SEEK_LOGGING
 
 extern PRLogModuleInfo* gMediaDecoderLog;
 #define LOG(type, msg, ...) \
   MOZ_LOG(gMediaDecoderLog, type, ("GStreamerReader(%p) " msg, this, ##__VA_ARGS__))
 
@@ -465,35 +466,33 @@ nsresult GStreamerReader::ReadMetadata(M
     return ret;
 
   /* report the duration */
   gint64 duration;
 
   if (isMP3 && mMP3FrameParser.IsMP3()) {
     // The MP3FrameParser has reported a duration; use that over the gstreamer
     // reported duration for inter-platform consistency.
-    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     mUseParserDuration = true;
     mLastParserDuration = mMP3FrameParser.GetDuration();
-    mDecoder->SetMediaDuration(mLastParserDuration);
+    mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(mLastParserDuration));
   } else {
     LOG(LogLevel::Debug, "querying duration");
     // Otherwise use the gstreamer duration.
 #if GST_VERSION_MAJOR >= 1
     if (gst_element_query_duration(GST_ELEMENT(mPlayBin),
           GST_FORMAT_TIME, &duration)) {
 #else
     GstFormat format = GST_FORMAT_TIME;
     if (gst_element_query_duration(GST_ELEMENT(mPlayBin),
       &format, &duration) && format == GST_FORMAT_TIME) {
 #endif
-      ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
       LOG(LogLevel::Debug, "have duration %" GST_TIME_FORMAT, GST_TIME_ARGS(duration));
       duration = GST_TIME_AS_USECONDS (duration);
-      mDecoder->SetMediaDuration(duration);
+      mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(duration));
     }
   }
 
   int n_video = 0, n_audio = 0;
   g_object_get(mPlayBin, "n-video", &n_video, "n-audio", &n_audio, nullptr);
 
   if (!n_video) {
     mInfo.mVideo = VideoInfo();
--- a/dom/media/mediasource/MediaSourceDecoder.cpp
+++ b/dom/media/mediasource/MediaSourceDecoder.cpp
@@ -15,24 +15,26 @@
 #include "SourceBufferDecoder.h"
 #include "VideoUtils.h"
 
 extern PRLogModuleInfo* GetMediaSourceLog();
 
 #define MSE_DEBUG(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, ("MediaSourceDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
 #define MSE_DEBUGV(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Verbose, ("MediaSourceDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
 
+using namespace mozilla::media;
+
 namespace mozilla {
 
 class SourceBufferDecoder;
 
 MediaSourceDecoder::MediaSourceDecoder(dom::HTMLMediaElement* aElement)
   : mMediaSource(nullptr)
-  , mMediaSourceDuration(UnspecifiedNaN<double>())
 {
+  SetExplicitDuration(UnspecifiedNaN<double>());
   Init(aElement);
 }
 
 MediaDecoder*
 MediaSourceDecoder::Clone()
 {
   // TODO: Sort out cloning.
   return nullptr;
@@ -164,119 +166,66 @@ MediaSourceDecoder::Ended(bool aEnded)
 
 bool
 MediaSourceDecoder::IsExpectingMoreData()
 {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   return !mReader->IsEnded();
 }
 
-class DurationChangedRunnable : public nsRunnable {
-public:
-  DurationChangedRunnable(MediaSourceDecoder* aDecoder,
-                          double aOldDuration,
-                          double aNewDuration)
-    : mDecoder(aDecoder)
-    , mOldDuration(aOldDuration)
-    , mNewDuration(aNewDuration)
-  { }
-
-  NS_IMETHOD Run() override final {
-    mDecoder->DurationChanged(mOldDuration, mNewDuration);
-    return NS_OK;
-  }
-
-private:
-  RefPtr<MediaSourceDecoder> mDecoder;
-  double mOldDuration;
-  double mNewDuration;
-};
-
-void
-MediaSourceDecoder::DurationChanged(double aOldDuration, double aNewDuration)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  // Run the MediaSource duration changed algorithm
-  if (mMediaSource) {
-    mMediaSource->DurationChange(aOldDuration, aNewDuration);
-  }
-  // Run the MediaElement duration changed algorithm
-  MediaDecoder::DurationChanged();
-}
-
 void
 MediaSourceDecoder::SetInitialDuration(int64_t aDuration)
 {
+  MOZ_ASSERT(NS_IsMainThread());
   // Only use the decoded duration if one wasn't already
   // set.
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-  if (!mMediaSource || !IsNaN(mMediaSourceDuration)) {
+  if (!mMediaSource || !IsNaN(ExplicitDuration())) {
     return;
   }
   double duration = aDuration;
   // A duration of -1 is +Infinity.
   if (aDuration >= 0) {
     duration /= USECS_PER_S;
   }
   SetMediaSourceDuration(duration, MSRangeRemovalAction::SKIP);
 }
 
 void
 MediaSourceDecoder::SetMediaSourceDuration(double aDuration, MSRangeRemovalAction aAction)
 {
+  MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-  double oldDuration = mMediaSourceDuration;
+  double oldDuration = ExplicitDuration();
   if (aDuration >= 0) {
     int64_t checkedDuration;
     if (NS_FAILED(SecondsToUsecs(aDuration, checkedDuration))) {
       // INT64_MAX is used as infinity by the state machine.
       // We want a very bigger number, but not infinity.
       checkedDuration = INT64_MAX - 1;
     }
-    GetStateMachine()->SetDuration(checkedDuration);
-    mMediaSourceDuration = aDuration;
+    SetExplicitDuration(aDuration);
   } else {
-    GetStateMachine()->SetDuration(INT64_MAX);
-    mMediaSourceDuration = PositiveInfinity<double>();
+    SetExplicitDuration(PositiveInfinity<double>());
   }
   if (mReader) {
-    mReader->SetMediaSourceDuration(mMediaSourceDuration);
+    mReader->SetMediaSourceDuration(ExplicitDuration());
   }
-  ScheduleDurationChange(oldDuration, aDuration, aAction);
-}
 
-void
-MediaSourceDecoder::ScheduleDurationChange(double aOldDuration,
-                                           double aNewDuration,
-                                           MSRangeRemovalAction aAction)
-{
-  if (aAction == MSRangeRemovalAction::SKIP) {
-    if (NS_IsMainThread()) {
-      MediaDecoder::DurationChanged();
-    } else {
-      nsCOMPtr<nsIRunnable> task =
-        NS_NewRunnableMethod(this, &MediaDecoder::DurationChanged);
-      NS_DispatchToMainThread(task);
-    }
-  } else {
-    if (NS_IsMainThread()) {
-      DurationChanged(aOldDuration, aNewDuration);
-    } else {
-      nsCOMPtr<nsIRunnable> task =
-        new DurationChangedRunnable(this, aOldDuration, aNewDuration);
-      NS_DispatchToMainThread(task);
-    }
+  MediaDecoder::DurationChanged(TimeUnit::FromSeconds(ExplicitDuration()));
+  if (mMediaSource && aAction != MSRangeRemovalAction::SKIP) {
+    mMediaSource->DurationChange(oldDuration, aDuration);
   }
 }
 
 double
 MediaSourceDecoder::GetMediaSourceDuration()
 {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-  return mMediaSourceDuration;
+  return ExplicitDuration();
 }
 
 void
 MediaSourceDecoder::NotifyTimeRangesChanged()
 {
   MOZ_ASSERT(mReader);
   mReader->NotifyTimeRangesChanged();
 }
@@ -324,17 +273,17 @@ MediaSourceDecoder::IsActiveReader(Media
 {
   return mReader->IsActiveReader(aReader);
 }
 
 double
 MediaSourceDecoder::GetDuration()
 {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-  return mMediaSourceDuration;
+  return ExplicitDuration();
 }
 
 already_AddRefed<SourceBufferDecoder>
 MediaSourceDecoder::SelectDecoder(int64_t aTarget,
                                   int64_t aTolerance,
                                   const nsTArray<nsRefPtr<SourceBufferDecoder>>& aTrackDecoders)
 {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
--- a/dom/media/mediasource/MediaSourceDecoder.h
+++ b/dom/media/mediasource/MediaSourceDecoder.h
@@ -57,17 +57,16 @@ public:
   bool IsExpectingMoreData() override;
 
   // Return the duration of the video in seconds.
   virtual double GetDuration() override;
 
   void SetInitialDuration(int64_t aDuration);
   void SetMediaSourceDuration(double aDuration, MSRangeRemovalAction aAction);
   double GetMediaSourceDuration();
-  void DurationChanged(double aOldDuration, double aNewDuration);
 
   // Called whenever a TrackBuffer has new data appended or a new decoder
   // initializes.  Safe to call from any thread.
   void NotifyTimeRangesChanged();
 
   // Indicates the point in time at which the reader should consider
   // registered TrackBuffers essential for initialization.
   void PrepareReaderInitialization();
@@ -89,25 +88,19 @@ public:
                                                       const nsTArray<nsRefPtr<SourceBufferDecoder>>& aTrackDecoders);
 
   // Returns a string describing the state of the MediaSource internal
   // buffered data. Used for debugging purposes.
   void GetMozDebugReaderData(nsAString& aString);
 
 private:
   void DoSetMediaSourceDuration(double aDuration);
-  void ScheduleDurationChange(double aOldDuration,
-                              double aNewDuration,
-                              MSRangeRemovalAction aAction);
 
   // The owning MediaSource holds a strong reference to this decoder, and
   // calls Attach/DetachMediaSource on this decoder to set and clear
   // mMediaSource.
   dom::MediaSource* mMediaSource;
   nsRefPtr<MediaSourceReader> mReader;
-
-  // Protected by GetReentrantMonitor()
-  double mMediaSourceDuration;
 };
 
 } // namespace mozilla
 
 #endif /* MOZILLA_MEDIASOURCEDECODER_H_ */
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -1132,30 +1132,30 @@ MediaSourceReader::ReadMetadata(MediaInf
     mAudioSourceDecoder = mAudioTrack->Decoders()[0];
 
     const MediaInfo& info = GetAudioReader()->GetMediaInfo();
     MOZ_ASSERT(info.HasAudio());
     mInfo.mAudio = info.mAudio;
     mInfo.mCrypto.AddInitData(info.mCrypto);
     MSE_DEBUG("audio reader=%p duration=%lld",
               mAudioSourceDecoder.get(),
-              mAudioSourceDecoder->GetReader()->GetDecoder()->GetMediaDuration());
+              mInfo.mMetadataDuration.isSome() ? mInfo.mMetadataDuration.ref().ToMicroseconds() : -1);
   }
 
   if (mVideoTrack) {
     MOZ_ASSERT(mVideoTrack->IsReady());
     mVideoSourceDecoder = mVideoTrack->Decoders()[0];
 
     const MediaInfo& info = GetVideoReader()->GetMediaInfo();
     MOZ_ASSERT(info.HasVideo());
     mInfo.mVideo = info.mVideo;
     mInfo.mCrypto.AddInitData(info.mCrypto);
     MSE_DEBUG("video reader=%p duration=%lld",
               GetVideoReader(),
-              GetVideoReader()->GetDecoder()->GetMediaDuration());
+              mInfo.mMetadataDuration.isSome() ? mInfo.mMetadataDuration.ref().ToMicroseconds() : -1);
   }
 
   *aInfo = mInfo;
   *aTags = nullptr; // TODO: Handle metadata.
 
   return NS_OK;
 }
 
--- a/dom/media/mediasource/SourceBufferDecoder.cpp
+++ b/dom/media/mediasource/SourceBufferDecoder.cpp
@@ -32,17 +32,16 @@ NS_IMPL_ISUPPORTS0(SourceBufferDecoder)
 
 SourceBufferDecoder::SourceBufferDecoder(MediaResource* aResource,
                                          AbstractMediaDecoder* aParentDecoder,
                                          int64_t aTimestampOffset)
   : mResource(aResource)
   , mParentDecoder(aParentDecoder)
   , mReader(nullptr)
   , mTimestampOffset(aTimestampOffset)
-  , mMediaDuration(-1)
   , mRealMediaDuration(0)
   , mTrimmedOffset(-1)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_COUNT_CTOR(SourceBufferDecoder);
 }
 
 SourceBufferDecoder::~SourceBufferDecoder()
@@ -59,22 +58,16 @@ SourceBufferDecoder::IsShutdown() const
 }
 
 void
 SourceBufferDecoder::NotifyBytesConsumed(int64_t aBytes, int64_t aOffset)
 {
   MSE_DEBUG("UNIMPLEMENTED");
 }
 
-int64_t
-SourceBufferDecoder::GetMediaDuration()
-{
-  return mMediaDuration;
-}
-
 VideoFrameContainer*
 SourceBufferDecoder::GetVideoFrameContainer()
 {
   MSE_DEBUG("UNIMPLEMENTED");
   return nullptr;
 }
 
 bool
@@ -115,22 +108,16 @@ SourceBufferDecoder::QueueMetadata(int64
 }
 
 void
 SourceBufferDecoder::RemoveMediaTracks()
 {
   MSE_DEBUG("UNIMPLEMENTED");
 }
 
-void
-SourceBufferDecoder::SetMediaEndTime(int64_t aTime)
-{
-  MSE_DEBUG("UNIMPLEMENTED");
-}
-
 bool
 SourceBufferDecoder::HasInitializationData()
 {
   return true;
 }
 
 void
 SourceBufferDecoder::OnReadMetadataCompleted()
@@ -175,22 +162,16 @@ SourceBufferDecoder::GetResource() const
 void
 SourceBufferDecoder::NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
                                          uint32_t aDropped)
 {
   return mParentDecoder->NotifyDecodedFrames(aParsed, aDecoded, aDropped);
 }
 
 void
-SourceBufferDecoder::SetMediaDuration(int64_t aDuration)
-{
-  mMediaDuration = aDuration;
-}
-
-void
 SourceBufferDecoder::SetRealMediaDuration(int64_t aDuration)
 {
   mRealMediaDuration = aDuration;
 }
 
 void
 SourceBufferDecoder::Trim(int64_t aDuration)
 {
--- a/dom/media/mediasource/SourceBufferDecoder.h
+++ b/dom/media/mediasource/SourceBufferDecoder.h
@@ -31,17 +31,17 @@ public:
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
   virtual bool IsMediaSeekable() final override;
   virtual bool IsShutdown() const final override;
   virtual bool IsTransportSeekable() final override;
   virtual bool OnDecodeTaskQueue() const final override;
   virtual bool OnStateMachineTaskQueue() const final override;
-  virtual int64_t GetMediaDuration() final override;
+  virtual int64_t GetMediaDuration() final override { MOZ_ASSERT_UNREACHABLE(""); return -1; };
   virtual layers::ImageContainer* GetImageContainer() final override;
   virtual MediaDecoderOwner* GetOwner() final override;
   virtual SourceBufferResource* GetResource() const final override;
   virtual ReentrantMonitor& GetReentrantMonitor() final override;
   virtual VideoFrameContainer* GetVideoFrameContainer() final override;
   virtual void MetadataLoaded(nsAutoPtr<MediaInfo> aInfo,
                               nsAutoPtr<MetadataTags> aTags,
                               MediaDecoderEventVisibility aEventVisibility) final override;
@@ -49,18 +49,16 @@ public:
                                 MediaDecoderEventVisibility aEventVisibility) final override;
   virtual void NotifyBytesConsumed(int64_t aBytes, int64_t aOffset) final override;
   virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) final override;
   virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded, uint32_t aDropped) final override;
   virtual void NotifyWaitingForResourcesStatusChanged() final override;
   virtual void OnReadMetadataCompleted() final override;
   virtual void QueueMetadata(int64_t aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) final override;
   virtual void RemoveMediaTracks() final override;
-  virtual void SetMediaDuration(int64_t aDuration) final override;
-  virtual void SetMediaEndTime(int64_t aTime) final override;
   virtual void SetMediaSeekable(bool aMediaSeekable) final override;
   virtual void UpdateEstimatedMediaDuration(int64_t aDuration) final override;
   virtual bool HasInitializationData() final override;
 
   // SourceBufferResource specific interface below.
   int64_t GetTimestampOffset() const { return mTimestampOffset; }
   void SetTimestampOffset(int64_t aOffset)  { mTimestampOffset = aOffset; }
 
@@ -145,18 +143,16 @@ private:
   RefPtr<MediaTaskQueue> mTaskQueue;
 
   nsRefPtr<MediaResource> mResource;
 
   AbstractMediaDecoder* mParentDecoder;
   nsRefPtr<MediaDecoderReader> mReader;
   // in microseconds
   int64_t mTimestampOffset;
-  // mMediaDuration contains the apparent buffer duration, excluding trimmed data.
-  int64_t mMediaDuration;
   // mRealMediaDuration contains the real buffer duration, including trimmed data.
   int64_t mRealMediaDuration;
   // in seconds
   double mTrimmedOffset;
 
 #ifdef MOZ_EME
   nsRefPtr<CDMProxy> mCDMProxy;
 #endif
--- a/dom/media/mediasource/TrackBuffer.cpp
+++ b/dom/media/mediasource/TrackBuffer.cpp
@@ -804,17 +804,19 @@ TrackBuffer::CompleteInitializeDecoder(S
   if (!RegisterDecoder(aDecoder)) {
     MSE_DEBUG("Reader %p not activated",
               aDecoder->GetReader());
     RemoveDecoder(aDecoder);
     mInitializationPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
     return;
   }
 
-  int64_t duration = aDecoder->GetMediaDuration();
+
+  int64_t duration = mInfo.mMetadataDuration.isSome()
+    ? mInfo.mMetadataDuration.ref().ToMicroseconds() : -1;
   if (!duration) {
     // Treat a duration of 0 as infinity
     duration = -1;
   }
   mParentDecoder->SetInitialDuration(duration);
 
   // Tell our reader that we have more data to ensure that playback starts if
   // required when data is appended.
--- a/dom/media/ogg/OggReader.cpp
+++ b/dom/media/ogg/OggReader.cpp
@@ -19,16 +19,17 @@ extern "C" {
 }
 #include "mozilla/TimeStamp.h"
 #include "VorbisUtils.h"
 #include "MediaMetadataManager.h"
 #include "nsISeekableStream.h"
 #include "gfx2DGlue.h"
 
 using namespace mozilla::gfx;
+using namespace mozilla::media;
 
 namespace mozilla {
 
 // On B2G estimate the buffered ranges rather than calculating them explicitly.
 // This prevents us doing I/O on the main thread, which is prohibited in B2G.
 #ifdef MOZ_WIDGET_GONK
 #define OGG_ESTIMATE_BUFFERED 1
 #endif
@@ -284,19 +285,18 @@ void OggReader::SetupTargetSkeleton(Skel
       aSkeletonState->Deactivate();
     } else if (ReadHeaders(aSkeletonState) && aSkeletonState->HasIndex()) {
       // Extract the duration info out of the index, so we don't need to seek to
       // the end of resource to get it.
       nsAutoTArray<uint32_t, 2> tracks;
       BuildSerialList(tracks);
       int64_t duration = 0;
       if (NS_SUCCEEDED(aSkeletonState->GetDuration(tracks, duration))) {
-        ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-        mDecoder->SetMediaDuration(duration);
         LOG(LogLevel::Debug, ("Got duration from Skeleton index %lld", duration));
+        mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(duration));
       }
     }
   }
 }
 
 void OggReader::SetupMediaTracksInfo(const nsTArray<uint32_t>& aSerials)
 {
   // For each serial number
@@ -469,34 +469,32 @@ nsresult OggReader::ReadMetadata(MediaIn
 
   SetupTargetSkeleton(mSkeletonState);
   SetupMediaTracksInfo(serials);
 
   if (HasAudio() || HasVideo()) {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
     MediaResource* resource = mDecoder->GetResource();
-    if (mDecoder->GetMediaDuration() == -1 &&
-        !mDecoder->IsShutdown() &&
-        resource->GetLength() >= 0 &&
-        mDecoder->IsMediaSeekable())
+    if (mInfo.mMetadataDuration.isNothing() && !mDecoder->IsShutdown() &&
+        resource->GetLength() >= 0 && mDecoder->IsMediaSeekable())
     {
       // We didn't get a duration from the index or a Content-Duration header.
       // Seek to the end of file to find the end time.
       int64_t length = resource->GetLength();
 
       NS_ASSERTION(length > 0, "Must have a content length to get end time");
 
       int64_t endTime = 0;
       {
         ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
         endTime = RangeEndTime(length);
       }
       if (endTime != -1) {
-        mDecoder->SetMediaEndTime(endTime);
+        mInfo.mMetadataEndTime.emplace(TimeUnit::FromMicroseconds(endTime));
         LOG(LogLevel::Debug, ("Got Ogg duration from seeking to end %lld", endTime));
       }
     }
   } else {
     return NS_ERROR_FAILURE;
   }
   *aInfo = mInfo;
 
--- a/dom/media/omx/MediaCodecReader.cpp
+++ b/dom/media/omx/MediaCodecReader.cpp
@@ -37,16 +37,17 @@
 #include "nsThreadUtils.h"
 #include "ImageContainer.h"
 #include "SharedThreadPool.h"
 #include "VideoFrameContainer.h"
 #include "VideoUtils.h"
 
 using namespace android;
 using namespace mozilla::layers;
+using namespace mozilla::media;
 
 namespace mozilla {
 
 static const int64_t sInvalidDurationUs = INT64_C(-1);
 static const int64_t sInvalidTimestampUs = INT64_C(-1);
 
 // Try not to spend more than this much time (in seconds) in a single call
 // to GetCodecOutputData.
@@ -707,18 +708,17 @@ MediaCodecReader::HandleResourceAllocate
   }
   int64_t videoDuration = INT64_C(-1);
   {
     MonitorAutoLock al(mVideoTrack.mTrackMonitor);
     videoDuration = mVideoTrack.mDurationUs;
   }
   int64_t duration = audioDuration > videoDuration ? audioDuration : videoDuration;
   if (duration >= INT64_C(0)) {
-    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-    mDecoder->SetMediaDuration(duration);
+    mInfo.mMetadataDuration = Some(TimeUnit::FromMicroseconds(duration));
   }
 
   // Video track's frame sizes will not overflow. Activate the video track.
   VideoFrameContainer* container = mDecoder->GetVideoFrameContainer();
   if (container) {
     container->SetCurrentFrame(
       gfxIntSize(mInfo.mVideo.mDisplay.width, mInfo.mVideo.mDisplay.height),
       nullptr,
--- a/dom/media/omx/MediaOmxReader.cpp
+++ b/dom/media/omx/MediaOmxReader.cpp
@@ -18,16 +18,17 @@
 #include "gfx2DGlue.h"
 #include "MediaStreamSource.h"
 
 #define MAX_DROPPED_FRAMES 25
 // Try not to spend more than this much time in a single call to DecodeVideoFrame.
 #define MAX_VIDEO_DECODE_SECONDS 0.1
 
 using namespace mozilla::gfx;
+using namespace mozilla::media;
 using namespace android;
 
 namespace mozilla {
 
 extern PRLogModuleInfo* gMediaDecoderLog;
 #define DECODER_LOG(type, msg) MOZ_LOG(gMediaDecoderLog, type, msg)
 
 class MediaOmxReader::ProcessCachedDataTask : public Task
@@ -276,26 +277,24 @@ void MediaOmxReader::HandleResourceAlloc
   bool isMP3 = mDecoder->GetResource()->GetContentType().EqualsASCII(AUDIO_MP3);
   if (isMP3 && mMP3FrameParser.IsMP3()) {
     // Check if the MP3 frame parser found a duration.
     mLastParserDuration = mMP3FrameParser.GetDuration();
   }
 
   if (mLastParserDuration >= 0) {
     // Prefer the parser duration if we have it.
-    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-    mDecoder->SetMediaDuration(mLastParserDuration);
+    mInfo.mMetadataDuration = Some(TimeUnit::FromMicroseconds(mLastParserDuration));
   } else {
     // MP3 parser failed to find a duration.
     // Set the total duration (the max of the audio and video track).
     int64_t durationUs;
     mOmxDecoder->GetDuration(&durationUs);
     if (durationUs) {
-      ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-      mDecoder->SetMediaDuration(durationUs);
+      mInfo.mMetadataDuration = Some(TimeUnit::FromMicroseconds(durationUs));
     }
   }
 
   if (mOmxDecoder->HasVideo()) {
     int32_t displayWidth, displayHeight, width, height;
     mOmxDecoder->GetVideoParameters(&displayWidth, &displayHeight,
                                     &width, &height);
     nsIntRect pictureRect(0, 0, width, height);
--- a/dom/media/raw/RawReader.cpp
+++ b/dom/media/raw/RawReader.cpp
@@ -7,16 +7,17 @@
 #include "AbstractMediaDecoder.h"
 #include "RawReader.h"
 #include "RawDecoder.h"
 #include "VideoUtils.h"
 #include "nsISeekableStream.h"
 #include "gfx2DGlue.h"
 
 using namespace mozilla;
+using namespace mozilla::media;
 
 RawReader::RawReader(AbstractMediaDecoder* aDecoder)
   : MediaDecoderReader(aDecoder),
     mCurrentFrame(0), mFrameSize(0)
 {
   MOZ_COUNT_CTOR(RawReader);
 }
 
@@ -92,20 +93,18 @@ nsresult RawReader::ReadMetadata(MediaIn
     return NS_ERROR_FAILURE;
 
   mFrameSize = mMetadata.frameWidth * mMetadata.frameHeight *
     (mMetadata.lumaChannelBpp + mMetadata.chromaChannelBpp) / 8.0 +
     sizeof(RawPacketHeader);
 
   int64_t length = resource->GetLength();
   if (length != -1) {
-    ReentrantMonitorAutoEnter autoMonitor(mDecoder->GetReentrantMonitor());
-    mDecoder->SetMediaDuration(USECS_PER_S *
-                                      (length - sizeof(RawVideoHeader)) /
-                                      (mFrameSize * mFrameRate));
+    mInfo.mMetadataDuration.emplace(TimeUnit::FromSeconds((length - sizeof(RawVideoHeader)) /
+                                                          (mFrameSize * mFrameRate)));
   }
 
   *aInfo = mInfo;
 
   *aTags = nullptr;
 
   return NS_OK;
 }
--- a/dom/media/test/bug500311.ogv^headers^
+++ b/dom/media/test/bug500311.ogv^headers^
@@ -1,2 +1,1 @@
-X-Content-Duration: 1.96
 Cache-Control: no-store
--- a/dom/media/test/bug520908.ogv^headers^
+++ b/dom/media/test/bug520908.ogv^headers^
@@ -1,2 +1,1 @@
-X-Content-Duration: 9000
 Cache-Control: no-store
deleted file mode 100644
--- a/dom/media/test/contentDuration1.sjs
+++ /dev/null
@@ -1,25 +0,0 @@
-function handleRequest(request, response)
-{
-  var file = Components.classes["@mozilla.org/file/directory_service;1"].
-                        getService(Components.interfaces.nsIProperties).
-                        get("CurWorkD", Components.interfaces.nsILocalFile);
-  var fis  = Components.classes['@mozilla.org/network/file-input-stream;1'].
-                        createInstance(Components.interfaces.nsIFileInputStream);
-  var bis  = Components.classes["@mozilla.org/binaryinputstream;1"].
-                        createInstance(Components.interfaces.nsIBinaryInputStream);
-  var paths = "tests/dom/media/test/320x240.ogv";
-  var split = paths.split("/");
-  for(var i = 0; i < split.length; ++i) {
-    file.append(split[i]);
-  }
-  fis.init(file, -1, -1, false);
-  bis.setInputStream(fis);
-  var bytes = bis.readBytes(bis.available());
-  response.setHeader("Content-Duration", "0.233", false);
-  response.setHeader("Content-Type", "video/ogg", false);
-  response.write(bytes, bytes.length);
-  // Make this request async to prevent a default Content-Length from being provided.
-  response.processAsync();
-  response.finish();
-  bis.close();
-}
deleted file mode 100644
--- a/dom/media/test/contentDuration2.sjs
+++ /dev/null
@@ -1,25 +0,0 @@
-function handleRequest(request, response)
-{
-  var file = Components.classes["@mozilla.org/file/directory_service;1"].
-                        getService(Components.interfaces.nsIProperties).
-                        get("CurWorkD", Components.interfaces.nsILocalFile);
-  var fis  = Components.classes['@mozilla.org/network/file-input-stream;1'].
-                        createInstance(Components.interfaces.nsIFileInputStream);
-  var bis  = Components.classes["@mozilla.org/binaryinputstream;1"].
-                        createInstance(Components.interfaces.nsIBinaryInputStream);
-  var paths = "tests/dom/media/test/320x240.ogv";
-  var split = paths.split("/");
-  for(var i = 0; i < split.length; ++i) {
-    file.append(split[i]);
-  }
-  fis.init(file, -1, -1, false);
-  bis.setInputStream(fis);
-  var bytes = bis.readBytes(bis.available());
-  response.setHeader("x-amz-meta-content-duration", "0.233", false);
-  response.setHeader("Content-Type", "video/ogg", false);
-  response.write(bytes, bytes.length);
-  // Make this request async to prevent a default Content-Length from being provided.
-  response.processAsync();
-  response.finish();
-  bis.close();
-}
deleted file mode 100644
--- a/dom/media/test/contentDuration3.sjs
+++ /dev/null
@@ -1,25 +0,0 @@
-function handleRequest(request, response)
-{
-  var file = Components.classes["@mozilla.org/file/directory_service;1"].
-                        getService(Components.interfaces.nsIProperties).
-                        get("CurWorkD", Components.interfaces.nsILocalFile);
-  var fis  = Components.classes['@mozilla.org/network/file-input-stream;1'].
-                        createInstance(Components.interfaces.nsIFileInputStream);
-  var bis  = Components.classes["@mozilla.org/binaryinputstream;1"].
-                        createInstance(Components.interfaces.nsIBinaryInputStream);
-  var paths = "tests/dom/media/test/320x240.ogv";
-  var split = paths.split("/");
-  for(var i = 0; i < split.length; ++i) {
-    file.append(split[i]);
-  }
-  fis.init(file, -1, -1, false);
-  bis.setInputStream(fis);
-  var bytes = bis.readBytes(bis.available());
-  response.setStatusLine(request.httpVersion, 200, "Content Follows");
-  response.setHeader("Content-Type", "video/ogg", false);
-  response.write(bytes, bytes.length);
-  // Make this request async to prevent a default Content-Length from being provided.
-  response.processAsync();
-  response.finish();
-  bis.close();
-}
deleted file mode 100644
--- a/dom/media/test/contentDuration4.sjs
+++ /dev/null
@@ -1,26 +0,0 @@
-function handleRequest(request, response)
-{
-  var file = Components.classes["@mozilla.org/file/directory_service;1"].
-                        getService(Components.interfaces.nsIProperties).
-                        get("CurWorkD", Components.interfaces.nsILocalFile);
-  var fis  = Components.classes['@mozilla.org/network/file-input-stream;1'].
-                        createInstance(Components.interfaces.nsIFileInputStream);
-  var bis  = Components.classes["@mozilla.org/binaryinputstream;1"].
-                        createInstance(Components.interfaces.nsIBinaryInputStream);
-  var paths = "tests/dom/media/test/320x240.ogv";
-  var split = paths.split("/");
-  for(var i = 0; i < split.length; ++i) {
-    file.append(split[i]);
-  }
-  fis.init(file, -1, -1, false);
-  bis.setInputStream(fis);
-  var bytes = bis.readBytes(bis.available());
-  response.setStatusLine(request.httpVersion, 200, "Content Follows");
-  response.setHeader("Content-Duration", "-5", false);
-  response.setHeader("Content-Type", "video/ogg", false);
-  response.write(bytes, bytes.length);
-  // Make this request async to prevent a default Content-Length from being provided.
-  response.processAsync();
-  response.finish();
-  bis.close();
-}
deleted file mode 100644
--- a/dom/media/test/contentDuration5.sjs
+++ /dev/null
@@ -1,26 +0,0 @@
-function handleRequest(request, response)
-{
-  var file = Components.classes["@mozilla.org/file/directory_service;1"].
-                        getService(Components.interfaces.nsIProperties).
-                        get("CurWorkD", Components.interfaces.nsILocalFile);
-  var fis  = Components.classes['@mozilla.org/network/file-input-stream;1'].
-                        createInstance(Components.interfaces.nsIFileInputStream);
-  var bis  = Components.classes["@mozilla.org/binaryinputstream;1"].
-                        createInstance(Components.interfaces.nsIBinaryInputStream);
-  var paths = "tests/dom/media/test/320x240.ogv";
-  var split = paths.split("/");
-  for(var i = 0; i < split.length; ++i) {
-    file.append(split[i]);
-  }
-  fis.init(file, -1, -1, false);
-  bis.setInputStream(fis);
-  var bytes = bis.readBytes(bis.available());
-  response.setStatusLine(request.httpVersion, 200, "Content Follows");
-  response.setHeader("Content-Duration", "-6", false);
-  response.setHeader("Content-Type", "video/ogg", false);
-  response.write(bytes, bytes.length);
-  // Make this request async to prevent a default Content-Length from being provided.
-  response.processAsync();
-  response.finish();
-  bis.close();
-}
deleted file mode 100644
--- a/dom/media/test/contentDuration6.sjs
+++ /dev/null
@@ -1,26 +0,0 @@
-function handleRequest(request, response)
-{
-  var file = Components.classes["@mozilla.org/file/directory_service;1"].
-                        getService(Components.interfaces.nsIProperties).
-                        get("CurWorkD", Components.interfaces.nsILocalFile);
-  var fis  = Components.classes['@mozilla.org/network/file-input-stream;1'].
-                        createInstance(Components.interfaces.nsIFileInputStream);
-  var bis  = Components.classes["@mozilla.org/binaryinputstream;1"].
-                        createInstance(Components.interfaces.nsIBinaryInputStream);
-  var paths = "tests/dom/media/test/320x240.ogv";
-  var split = paths.split("/");
-  for(var i = 0; i < split.length; ++i) {
-    file.append(split[i]);
-  }
-  fis.init(file, -1, -1, false);
-  bis.setInputStream(fis);
-  var bytes = bis.readBytes(bis.available());
-  response.setStatusLine(request.httpVersion, 200, "Content Follows");
-  response.setHeader("Content-Duration", "Invalid Float Value", false);
-  response.setHeader("Content-Type", "video/ogg", false);
-  response.write(bytes, bytes.length);
-  // Make this request async to prevent a default Content-Length from being provided.
-  response.processAsync();
-  response.finish();
-  bis.close();
-}
deleted file mode 100644
--- a/dom/media/test/contentDuration7.sjs
+++ /dev/null
@@ -1,25 +0,0 @@
-function handleRequest(request, response)
-{
-  var file = Components.classes["@mozilla.org/file/directory_service;1"].
-                        getService(Components.interfaces.nsIProperties).
-                        get("CurWorkD", Components.interfaces.nsILocalFile);
-  var fis  = Components.classes['@mozilla.org/network/file-input-stream;1'].
-                        createInstance(Components.interfaces.nsIFileInputStream);
-  var bis  = Components.classes["@mozilla.org/binaryinputstream;1"].
-                        createInstance(Components.interfaces.nsIBinaryInputStream);
-  var paths = "tests/dom/media/test/320x240.ogv";
-  var split = paths.split("/");
-  for(var i = 0; i < split.length; ++i) {
-    file.append(split[i]);
-  }
-  fis.init(file, -1, -1, false);
-  bis.setInputStream(fis);
-  var bytes = bis.readBytes(bis.available());
-  response.setHeader("X-Content-Duration", "0.233", false);
-  response.setHeader("Content-Type", "video/ogg", false);
-  response.write(bytes, bytes.length);
-  // Make this request async to prevent a default Content-Length from being provided.
-  response.processAsync();
-  response.finish();
-  bis.close();
-}
--- a/dom/media/test/make-headers.sh
+++ b/dom/media/test/make-headers.sh
@@ -7,16 +7,12 @@
 # so that our detection as to whether the server supports byte range
 # requests is not interferred with by Necko's cache. See bug 977398
 # for details. Necko will fix this in bug 977314. 
 
 FILES=(`ls *.ogg *.ogv *.webm *.mp3 *.opus *.mp4 *.m4s *.wav`)
 
 rm -f *.ogg^headers^ *.ogv^headers^ *.webm^headers^ *.mp3^headers^ *.opus^headers^ *.mp4^headers^ *.m4s^headers^ *.wav^headers^
 
-# Restore special headers.
-echo "X-Content-Duration: 1.96" > bug500311.ogv^headers^
-echo "X-Content-Duration: 9000" > bug520908.ogv^headers^
-
 for i in "${FILES[@]}"
 do
   echo "Cache-Control: no-store" >> $i^headers^
 done
--- a/dom/media/test/manifest.js
+++ b/dom/media/test/manifest.js
@@ -82,18 +82,17 @@ var gPlayedTests = [
   //{ name:"vbr.mp3", type:"audio/mpeg", duration:10.0 },
   { name:"bug495794.ogg", type:"audio/ogg", duration:0.3 }
 ];
 
 // Used by test_mozLoadFrom.  Need one test file per decoder backend, plus
 // anything for testing clone-specific bugs.
 var cloneKey = Math.floor(Math.random()*100000000);
 var gCloneTests = gSmallTests.concat([
-  // Actual duration is ~200ms, we have Content-Duration lie about it.
-  { name:"bug520908.ogv", type:"video/ogg", duration:9000 },
+  { name:"bug520908.ogv", type:"video/ogg", duration:0.2 },
   // short-video is more like 1s, so if you load this twice you'll get an unexpected duration
   { name:"dynamic_resource.sjs?key=" + cloneKey + "&res1=320x240.ogv&res2=short-video.ogv",
     type:"video/ogg", duration:0.266 },
 ]);
 
 // Used by test_play_twice.  Need one test file per decoder backend, plus
 // anything for testing bugs that occur when replying a played file.
 var gReplayTests = gSmallTests.concat([
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -142,23 +142,16 @@ support-files =
   chain.ogv
   chain.ogv^headers^
   chain.opus
   chain.opus^headers^
   chained-audio-video.ogg
   chained-audio-video.ogg^headers^
   chained-video.ogv
   chained-video.ogv^headers^
-  contentDuration1.sjs
-  contentDuration2.sjs
-  contentDuration3.sjs
-  contentDuration4.sjs
-  contentDuration5.sjs
-  contentDuration6.sjs
-  contentDuration7.sjs
   contentType.sjs
   detodos.opus
   detodos.opus^headers^
   detodos.webm
   detodos.webm^headers^
   dirac.ogg
   dirac.ogg^headers^
   dynamic_redirect.sjs
@@ -206,17 +199,16 @@ support-files =
   long.vtt
   manifest.js
   multiple-bos.ogg
   multiple-bos.ogg^headers^
   multiple-bos-more-header-fileds.ogg
   multiple-bos-more-header-fileds.ogg^headers^
   no-cues.webm
   no-cues.webm^headers^
-  noContentLength.sjs
   notags.mp3
   notags.mp3^headers^
   owl-funnier-id3.mp3
   owl-funnier-id3.mp3^headers^
   owl-funny-id3.mp3
   owl-funny-id3.mp3^headers^
   owl.mp3
   owl.mp3^headers^
@@ -367,23 +359,16 @@ tags=capturestream
 skip-if = buildapp == 'b2g' || (toolkit == 'android' && processor == 'x86') # bug 1021675 #x86 only bug 914439
 [test_can_play_type_no_ogg.html]
 [test_can_play_type_ogg.html]
 [test_chaining.html]
 [test_clone_media_element.html]
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_closing_connections.html]
 [test_constants.html]
-[test_contentDuration1.html]
-[test_contentDuration2.html]
-[test_contentDuration3.html]
-[test_contentDuration4.html]
-[test_contentDuration5.html]
-[test_contentDuration6.html]
-[test_contentDuration7.html]
 [test_controls.html]
 [test_currentTime.html]
 [test_decode_error.html]
 [test_decoder_disable.html]
 [test_defaultMuted.html]
 [test_delay_load.html]
 skip-if = buildapp == 'b2g' && toolkit != 'gonk' # bug 1082984
 [test_dormant_playback.html]
@@ -541,18 +526,16 @@ skip-if = android_version == '10' # bug 
 [test_seek-8.html]
 [test_seek-9.html]
 [test_seek-10.html]
 [test_seek-11.html]
 [test_seek-12.html]
 [test_seek-13.html]
 [test_seekable1.html]
 skip-if = (toolkit == 'android' && processor == 'x86') #timeout x86 only bug 914439
-[test_seekable2.html]
-[test_seekable3.html]
 [test_seekLies.html]
 [test_source.html]
 [test_source_media.html]
 [test_source_null.html]
 [test_source_write.html]
 [test_standalone.html]
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_streams_autoplay.html]
deleted file mode 100644
--- a/dom/media/test/noContentLength.sjs
+++ /dev/null
@@ -1,26 +0,0 @@
-// Serve a media file without using content-range header
-function handleRequest(request, response)
-{
-  var file = Components.classes["@mozilla.org/file/directory_service;1"].
-                        getService(Components.interfaces.nsIProperties).
-                        get("CurWorkD", Components.interfaces.nsILocalFile);
-  var fis  = Components.classes['@mozilla.org/network/file-input-stream;1'].
-                        createInstance(Components.interfaces.nsIFileInputStream);
-  var bis  = Components.classes["@mozilla.org/binaryinputstream;1"].
-                        createInstance(Components.interfaces.nsIBinaryInputStream);
-  var paths = "tests/dom/media/test/320x240.ogv";
-  var split = paths.split("/");
-  for(var i = 0; i < split.length; ++i) {
-    file.append(split[i]);
-  }
-  fis.init(file, -1, -1, false);
-  bis.setInputStream(fis);
-  var bytes = bis.readBytes(bis.available());
-  response.setStatusLine(request.httpVersion, 200, "Content Follows");
-  response.setHeader("Content-Type", "video/ogg", false);
-  response.write(bytes, bytes.length);
-  // Make this request async to prevent a default Content-Length from being provided.
-  response.processAsync();
-  response.finish();
-  bis.close();
-}
deleted file mode 100644
--- a/dom/media/test/test_contentDuration1.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Media test: seek test 1</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="manifest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body onunload="mediaTestCleanup();">
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-function on_metadataloaded() {
-  var v = document.getElementById('v');
-  var d = Math.round(v.duration*1000);
-  ok(d == 233, "Checking duration: " + d);
-  SimpleTest.finish();
-}
-
-SimpleTest.waitForExplicitFinish();
-</script>
-</pre>
-<video id='v'
-       preload="metadata"
-       src='contentDuration1.sjs'
-       onloadedmetadata='on_metadataloaded();'></video>
-</body>
-</html>
deleted file mode 100644
--- a/dom/media/test/test_contentDuration2.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Media test: seek test 1</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="manifest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body onunload="mediaTestCleanup();">
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-function on_metadataloaded() {
-  var v = document.getElementById('v');
-  var d = Math.round(v.duration*1000);
-  ok(d == 233, "Checking duration: " + d);
-  SimpleTest.finish();
-}
-
-SimpleTest.waitForExplicitFinish();
-</script>
-</pre>
-<video id='v'
-       preload="metadata"
-       src='contentDuration2.sjs'
-       onloadedmetadata='on_metadataloaded();'></video>
-</body>
-</html>
deleted file mode 100644
--- a/dom/media/test/test_contentDuration3.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Media test: seek test 1</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="manifest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body onunload="mediaTestCleanup();">
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-function on_metadataloaded() {
-  var v = document.getElementById('v');
-  var d = Math.round(v.duration*1000);
-  ok(d.toString() == "Infinity", "Checking duration: " + d);
-  SimpleTest.finish();
-}
-
-SimpleTest.waitForExplicitFinish();
-</script>
-</pre>
-<video id='v'
-       preload="metadata"
-       src='contentDuration3.sjs'
-       onloadedmetadata='on_metadataloaded();'></video>
-</body>
-</html>
deleted file mode 100644
--- a/dom/media/test/test_contentDuration4.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Media test: seek test 1</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="manifest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body onunload="mediaTestCleanup();">
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-function on_metadataloaded() {
-  var v = document.getElementById('v');
-  var d = Math.round(v.duration*1000);
-  ok(d.toString() == "Infinity", "Checking duration: " + d);
-  SimpleTest.finish();
-}
-
-SimpleTest.waitForExplicitFinish();
-</script>
-</pre>
-<video id='v'
-       preload="metadata"
-       src='contentDuration4.sjs'
-       onloadedmetadata='on_metadataloaded();'></video>
-</body>
-</html>
deleted file mode 100644
--- a/dom/media/test/test_contentDuration5.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Media test: seek test 1</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="manifest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body onunload="mediaTestCleanup();">
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-function on_metadataloaded() {
-  var v = document.getElementById('v');
-  var d = Math.round(v.duration*1000);
-  ok(d.toString() == "Infinity", "Checking duration: " + d);
-  SimpleTest.finish();
-}
-
-SimpleTest.waitForExplicitFinish();
-</script>
-</pre>
-<video id='v'
-       preload="metadata"
-       src='contentDuration5.sjs'
-       onloadedmetadata='on_metadataloaded();'></video>
-</body>
-</html>
deleted file mode 100644
--- a/dom/media/test/test_contentDuration6.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Media test: seek test 1</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="manifest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body onunload="mediaTestCleanup();">
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-function on_metadataloaded() {
-  var v = document.getElementById('v');
-  var d = Math.round(v.duration*1000);
-  ok(d.toString() == "Infinity", "Checking duration: " + d);
-  SimpleTest.finish();
-}
-
-SimpleTest.waitForExplicitFinish();
-</script>
-</pre>
-<video id='v'
-       preload="metadata"
-       src='contentDuration6.sjs'
-       onloadedmetadata='on_metadataloaded();'></video>
-</body>
-</html>
deleted file mode 100644
--- a/dom/media/test/test_contentDuration7.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Media test: X-Content-Duration</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="manifest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body onunload="mediaTestCleanup();">
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-function on_metadataloaded() {
-  var v = document.getElementById('v');
-  var d = Math.round(v.duration*1000);
-  ok(d == 233, "Checking duration: " + d);
-  SimpleTest.finish();
-}
-
-SimpleTest.waitForExplicitFinish();
-</script>
-</pre>
-<video id='v'
-       preload="metadata"
-       src='contentDuration7.sjs'
-       onloadedmetadata='on_metadataloaded();'></video>
-</body>
-</html>
deleted file mode 100644
--- a/dom/media/test/test_seekable2.html
+++ /dev/null
@@ -1,37 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-<title>Media test: seek test 3</title>
-<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-<script type="text/javascript" src="manifest.js"></script>
-<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body onunload="mediaTestCleanup();">
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-function on_metadataloaded() {
-  var v = document.getElementById('v');
-  ok(range_equals(v.seekable, v.buffered), "seekable.length should be initialy empty, or equal to buffered");
-  v.addEventListener("playing", function() {
-    ok(v.seekable.length > 0, "seekable.length should not be empty while playing.");
-  }, false);
-  v.addEventListener("ended", function() {
-    is(v.seekable.length, 1, "seekable.length should be 1 at end of playback.");
-    is(v.seekable.start(0), 0.0, "start of first range should 0.0");
-    is(v.seekable.end(0), v.duration, "end of first range should be equal to duration");
-    SimpleTest.finish();
-  }, false);
-
-  v.play();
-}
-
-SimpleTest.waitForExplicitFinish();
-</script>
-</pre>
-<video id='v'
-preload="metadata"
-src='contentDuration6.sjs'
-onloadedmetadata='on_metadataloaded();'></video>
-</body>
-</html>
deleted file mode 100644
--- a/dom/media/test/test_seekable3.html
+++ /dev/null
@@ -1,37 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-<title>Media test: seek test 3</title>
-<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-<script type="text/javascript" src="manifest.js"></script>
-<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body onunload="mediaTestCleanup();">
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-function on_metadataloaded() {
-  var v = document.getElementById('v');
-  ok(range_equals(v.seekable, v.buffered), "seekable.length should be initialy empty");
-  v.addEventListener("playing", function() {
-    ok(v.seekable.length > 0, "seekable.length should not empty while playing.");
-  }, false);
-  v.addEventListener("ended", function() {
-    is(v.seekable.length, 1, "seekable.length should be 1 at end of playback.");
-    is(v.seekable.start(0), 0.0, "start of first range should 0.0");
-    is(v.seekable.end(0), v.duration, "end of first range should be equal to duration");
-    SimpleTest.finish();
-  }, false);
-
-  v.play();
-}
-
-SimpleTest.waitForExplicitFinish();
-</script>
-</pre>
-<video id='v'
-preload='metadata'
-src='noContentLength.sjs'
-onloadedmetadata='on_metadataloaded();'></video>
-</body>
-</html>
--- a/dom/media/wave/WaveReader.cpp
+++ b/dom/media/wave/WaveReader.cpp
@@ -12,16 +12,18 @@
 #include "nsISeekableStream.h"
 
 #include <stdint.h>
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/Endian.h"
 #include <algorithm>
 
+using namespace mozilla::media;
+
 namespace mozilla {
 
 // Un-comment to enable logging of seek bisections.
 //#define SEEK_LOGGING
 
 extern PRLogModuleInfo* gMediaDecoderLog;
 #define LOG(type, msg) MOZ_LOG(gMediaDecoderLog, type, msg)
 #ifdef SEEK_LOGGING
@@ -133,25 +135,22 @@ nsresult WaveReader::ReadMetadata(MediaI
 
   bool loadAllChunks = LoadAllChunks(tags);
   if (!loadAllChunks) {
     return NS_ERROR_FAILURE;
   }
 
   mInfo.mAudio.mRate = mSampleRate;
   mInfo.mAudio.mChannels = mChannels;
+  mInfo.mMetadataDuration.emplace(TimeUnit::FromSeconds(BytesToTime(GetDataLength())));
 
   *aInfo = mInfo;
 
   *aTags = tags.forget();
 
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-
-  mDecoder->SetMediaDuration(
-    static_cast<int64_t>(BytesToTime(GetDataLength()) * USECS_PER_S));
 
   return NS_OK;
 }
 
 bool
 WaveReader::IsMediaSeekable()
 {
   // not used
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -777,27 +777,31 @@ AudioContext::OnStateChanged(void* aProm
   // This can happen if close() was called right after creating the
   // AudioContext, before the context has switched to "running".
   if (mAudioContextState == AudioContextState::Closed &&
       aNewState == AudioContextState::Running &&
       !aPromise) {
     return;
   }
 
+#ifndef WIN32 // Bug 1170547
+
   MOZ_ASSERT((mAudioContextState == AudioContextState::Suspended &&
               aNewState == AudioContextState::Running)   ||
              (mAudioContextState == AudioContextState::Running   &&
               aNewState == AudioContextState::Suspended) ||
              (mAudioContextState == AudioContextState::Running   &&
               aNewState == AudioContextState::Closed)    ||
              (mAudioContextState == AudioContextState::Suspended &&
               aNewState == AudioContextState::Closed)    ||
              (mAudioContextState == aNewState),
              "Invalid AudioContextState transition");
 
+#endif // WIN32
+
   MOZ_ASSERT(
     mIsOffline || aPromise || aNewState == AudioContextState::Running,
     "We should have a promise here if this is a real-time AudioContext."
     "Or this is the first time we switch to \"running\".");
 
   if (aPromise) {
     Promise* promise = reinterpret_cast<Promise*>(aPromise);
     promise->MaybeResolve(JS::UndefinedHandleValue);
--- a/dom/media/webaudio/BufferDecoder.cpp
+++ b/dom/media/webaudio/BufferDecoder.cpp
@@ -88,22 +88,16 @@ BufferDecoder::NotifyDecodedFrames(uint3
 int64_t
 BufferDecoder::GetMediaDuration()
 {
   // unknown
   return -1;
 }
 
 void
-BufferDecoder::SetMediaDuration(int64_t aDuration)
-{
-  // ignore
-}
-
-void
 BufferDecoder::UpdateEstimatedMediaDuration(int64_t aDuration)
 {
   // ignore
 }
 
 void
 BufferDecoder::SetMediaSeekable(bool aMediaSeekable)
 {
@@ -156,22 +150,16 @@ BufferDecoder::QueueMetadata(int64_t aTi
 
 void
 BufferDecoder::RemoveMediaTracks()
 {
   // ignore
 }
 
 void
-BufferDecoder::SetMediaEndTime(int64_t aTime)
-{
-  // ignore
-}
-
-void
 BufferDecoder::OnReadMetadataCompleted()
 {
   // ignore
 }
 
 void
 BufferDecoder::NotifyWaitingForResourcesStatusChanged()
 {
--- a/dom/media/webaudio/BufferDecoder.h
+++ b/dom/media/webaudio/BufferDecoder.h
@@ -42,18 +42,16 @@ public:
 
   virtual void NotifyBytesConsumed(int64_t aBytes, int64_t aOffset) final override;
 
   virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
                                    uint32_t aDropped) final override;
 
   virtual int64_t GetMediaDuration() final override;
 
-  virtual void SetMediaDuration(int64_t aDuration) final override;
-
   virtual void UpdateEstimatedMediaDuration(int64_t aDuration) final override;
 
   virtual void SetMediaSeekable(bool aMediaSeekable) final override;
 
   virtual VideoFrameContainer* GetVideoFrameContainer() final override;
   virtual layers::ImageContainer* GetImageContainer() final override;
 
   virtual bool IsTransportSeekable() final override;
@@ -64,18 +62,16 @@ public:
                               nsAutoPtr<MetadataTags> aTags,
                               MediaDecoderEventVisibility aEventVisibility) final override;
   virtual void QueueMetadata(int64_t aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) final override;
   virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
                                 MediaDecoderEventVisibility aEventVisibility) final override;
 
   virtual void RemoveMediaTracks() final override;
 
-  virtual void SetMediaEndTime(int64_t aTime) final override;
-
   virtual void OnReadMetadataCompleted() final override;
 
   virtual MediaDecoderOwner* GetOwner() final override;
 
   virtual void NotifyWaitingForResourcesStatusChanged() final override;
 
   virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) final override;
 
--- a/dom/media/webaudio/test/corsServer.sjs
+++ b/dom/media/webaudio/test/corsServer.sjs
@@ -10,17 +10,16 @@ function handleRequest(request, response
   var paths = "tests/dom/media/webaudio/test/small-shot.ogg";
   var split = paths.split("/");
   for(var i = 0; i < split.length; ++i) {
     file.append(split[i]);
   }
   fis.init(file, -1, -1, false);
   bis.setInputStream(fis);
   var bytes = bis.readBytes(bis.available());
-  response.setHeader("Content-Duration", "0.233", false);
   response.setHeader("Content-Type", "video/ogg", false);
   response.setHeader("Content-Length", ""+ bytes.length, false);
   response.setHeader("Access-Control-Allow-Origin", "*", false);
   response.write(bytes, bytes.length);
   response.processAsync();
   response.finish();
   bis.close();
 }
--- a/dom/media/webm/WebMReader.cpp
+++ b/dom/media/webm/WebMReader.cpp
@@ -41,16 +41,17 @@
 #else
 #define SEEK_LOG(type, msg)
 #endif
 
 namespace mozilla {
 
 using namespace gfx;
 using namespace layers;
+using namespace media;
 
 extern PRLogModuleInfo* gMediaDecoderLog;
 PRLogModuleInfo* gNesteggLog;
 
 // Functions for reading and seeking using MediaResource required for
 // nestegg_io. The 'user data' passed to these functions is the
 // decoder from which the media resource is obtained.
 static int webm_read(void *aBuffer, size_t aLength, void *aUserData)
@@ -338,18 +339,17 @@ nsresult WebMReader::ReadMetadata(MediaI
   int r = nestegg_init(&mContext, io, &webm_log, maxOffset);
   if (r == -1) {
     return NS_ERROR_FAILURE;
   }
 
   uint64_t duration = 0;
   r = nestegg_duration(mContext, &duration);
   if (r == 0) {
-    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-    mDecoder->SetMediaDuration(duration / NS_PER_USEC);
+    mInfo.mMetadataDuration.emplace(TimeUnit::FromNanoseconds(duration));
   }
 
   unsigned int ntracks = 0;
   r = nestegg_track_count(mContext, &ntracks);
   if (r == -1) {
     Cleanup();
     return NS_ERROR_FAILURE;
   }
--- a/dom/media/webspeech/recognition/Makefile.in
+++ b/dom/media/webspeech/recognition/Makefile.in
@@ -6,11 +6,11 @@ MODELSPS_FILES := models/dict/cmu07a.dic
 		models/en-us-semi/feat.params \
 		models/en-us-semi/mdef \
 		models/en-us-semi/means \
 		models/en-us-semi/noisedict \
 		models/en-us-semi/sendump \
 		models/en-us-semi/transition_matrices \
 		models/en-us-semi/variances
 
-MODELSPS_DEST := $(DIST)/bin/models
+MODELSPS_DEST := $(DIST)/bin
 INSTALL_TARGETS += MODELSPS
 endif
--- a/dom/media/wmf/WMFReader.cpp
+++ b/dom/media/wmf/WMFReader.cpp
@@ -22,16 +22,17 @@
 #error We expect 32bit float audio samples on desktop for the Windows Media Foundation media backend.
 #endif
 
 #include "MediaDecoder.h"
 #include "VideoUtils.h"
 #include "gfx2DGlue.h"
 
 using namespace mozilla::gfx;
+using namespace mozilla::media;
 using mozilla::layers::Image;
 using mozilla::layers::LayerManager;
 using mozilla::layers::LayersBackend;
 
 namespace mozilla {
 
 extern PRLogModuleInfo* gMediaDecoderLog;
 #define DECODER_LOG(...) MOZ_LOG(gMediaDecoderLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
@@ -530,18 +531,17 @@ WMFReader::ReadMetadata(MediaInfo* aInfo
 
   // Abort if both video and audio failed to initialize.
   NS_ENSURE_TRUE(mInfo.HasValidMedia(), NS_ERROR_FAILURE);
 
   // Get the duration, and report it to the decoder if we have it.
   int64_t duration = 0;
   hr = GetSourceReaderDuration(mSourceReader, duration);
   if (SUCCEEDED(hr)) {
-    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-    mDecoder->SetMediaEndTime(duration);
+    mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(duration));
   }
 
   *aInfo = mInfo;
   *aTags = nullptr;
   // aTags can be retrieved using techniques like used here:
   // http://blogs.msdn.com/b/mf/archive/2010/01/12/mfmediapropdump.aspx
 
   return NS_OK;
--- a/dom/webidl/APZTestData.webidl
+++ b/dom/webidl/APZTestData.webidl
@@ -29,9 +29,19 @@ dictionary APZBucket {
   unsigned long sequenceNumber;
   sequence<ScrollFrameData> scrollFrames;
 };
 
 // All the paints and repaint requests. This is the top-level data structure.
 dictionary APZTestData {
   sequence<APZBucket> paints;
   sequence<APZBucket> repaintRequests;
-};
\ No newline at end of file
+};
+
+// A frame uniformity measurement for every scrollable layer
+dictionary FrameUniformity {
+  unsigned long layerAddress;
+  float frameUniformity;
+};
+
+dictionary FrameUniformityResults {
+  sequence<FrameUniformity> layerUniformities;
+};
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -4285,16 +4285,31 @@ WorkerDebugger::GetIsFrozen(bool* aResul
     return NS_ERROR_UNEXPECTED;
   }
 
   *aResult = mIsFrozen;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+WorkerDebugger::GetIsInitialized(bool* aResult)
+{
+  AssertIsOnMainThread();
+
+  MutexAutoLock lock(mMutex);
+
+  if (!mWorkerPrivate) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  *aResult = mIsInitialized;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 WorkerDebugger::GetParent(nsIWorkerDebugger** aResult)
 {
   AssertIsOnMainThread();
 
   MutexAutoLock lock(mMutex);
 
   if (!mWorkerPrivate) {
     return NS_ERROR_UNEXPECTED;
--- a/dom/workers/nsIWorkerDebugger.idl
+++ b/dom/workers/nsIWorkerDebugger.idl
@@ -12,29 +12,31 @@ interface nsIWorkerDebuggerListener : ns
 
   void onFreeze();
 
   void onMessage(in DOMString message);
 
   void onThaw();
 };
 
-[scriptable, builtinclass, uuid(d7c73e54-3c41-4393-9d13-fa2ed4Ba6764)]
+[scriptable, builtinclass, uuid(bdcaf96f-916a-4b24-bb53-165c1785668b)]
 interface nsIWorkerDebugger : nsISupports
 {
   const unsigned long TYPE_DEDICATED = 0;
   const unsigned long TYPE_SHARED = 1;
   const unsigned long TYPE_SERVICE = 2;
 
   readonly attribute bool isClosed;
 
   readonly attribute bool isChrome;
 
   readonly attribute bool isFrozen;
 
+  readonly attribute bool isInitialized;
+
   readonly attribute nsIWorkerDebugger parent;
 
   readonly attribute unsigned long type;
 
   readonly attribute DOMString url;
 
   readonly attribute nsIDOMWindow window;
 
--- a/editor/libeditor/tests/test_bug676401.html
+++ b/editor/libeditor/tests/test_bug676401.html
@@ -91,23 +91,18 @@ function runTests() {
 
   // These are privileged, and available only to chrome.
   commands = ["paste"];
   for (i = 0; i < commands.length; i++) {
     is(document.queryCommandEnabled(commands[i]), false,
        "Command should not be enabled for non-privileged code");
     is(SpecialPowers.wrap(document).queryCommandEnabled(commands[i]), true,
        "Command should be enabled for privileged code");
-    try {
-      document.execCommand(commands[i], false, false);
-      ok(false, "Thould have thrown: " + commands[i]);
-    } catch (e) {
-      ok(/insecure|denied/.test(e), "Threw correctly: " + commands[i] + " - " + e);
-    }
-    SpecialPowers.wrap(document).execCommand(commands[i], false, false);
+    is(document.execCommand(commands[i], false, false), false, "Should return false: " + commands[i]);
+    is(SpecialPowers.wrap(document).execCommand(commands[i], false, false), true, "Should return true: " + commands[i]);
   }
 
   // delete/undo/redo -- we have to execute this commands because:
   //  * there's nothing to undo if we haven't modified the selection first
   //  * there's nothing to redo if we haven't undone something first
   commands = ["delete", "undo", "redo"];
   for (i = 0; i < commands.length; i++) {
     IsCommandEnabled(commands[i]);
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -87,16 +87,17 @@ class ReadbackLayer;
 class ReadbackProcessor;
 class RefLayer;
 class LayerComposite;
 class ShadowableLayer;
 class ShadowLayerForwarder;
 class LayerManagerComposite;
 class SpecificLayerAttributes;
 class Compositor;
+class FrameUniformityData;
 
 namespace layerscope {
 class LayersPacket;
 }
 
 #define MOZ_LAYER_DECL_NAME(n, e)                              \
   virtual const char* Name() const override { return n; }  \
   virtual LayerType GetType() const override { return e; }
@@ -638,16 +639,17 @@ public:
     // LayersBackend::LAYERS_NONE is an error state, but in that case we should try to
     // avoid loading the compositor!
     return LayersBackend::LAYERS_BASIC != aBackend && LayersBackend::LAYERS_NONE != aBackend;
   }
 
   virtual bool IsCompositingCheap() { return true; }
 
   bool IsInTransaction() const { return mInTransaction; }
+  virtual void GetFrameUniformity(FrameUniformityData* aOutData) { }
   virtual bool RequestOverfill(mozilla::dom::OverfillCallback* aCallback) { return true; }
   virtual void RunOverfillCallback(const uint32_t aOverfill) { }
 
   virtual void SetRegionToClear(const nsIntRegion& aRegion)
   {
     mRegionToClear = aRegion;
   }
 
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/chrome.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+  apz_test_native_event_utils.js
+tags = apz-chrome
+
+[test_smoothness.html]
+# hardware vsync only on win/mac
+# e10s only since APZ is only enabled on e10s
+skip-if = debug || (os != 'mac' && os != 'win') || !e10s
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/test_smoothness.html
@@ -0,0 +1,83 @@
+<html>
+<head>
+  <title>Test Frame Uniformity While Scrolling</title>
+  <script type="text/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+
+  <style>
+  #content {
+    height: 5000px;
+    background: repeating-linear-gradient(#EEE, #EEE 100px, #DDD 100px, #DDD 200px);
+  }
+  </style>
+  <script type="text/javascript">
+    var scrollEvents = 100;
+    var i = 0;
+    var testPref = "gfx.vsync.collect-scroll-transforms";
+    // Scroll points
+    var x = 100;
+    var y = 150;
+
+    SimpleTest.waitForExplicitFinish();
+    var utils = _getDOMWindowUtils(window);
+
+    function sendScrollEvent(aRafTimestamp) {
+      var scrollDiv = document.getElementById("content");
+
+      if (i < scrollEvents) {
+        i++;
+        // Scroll diff
+        var dx = 0;
+        var dy = -10; // Negative to scroll down
+        synthesizeNativeWheelAndWaitForEvent(scrollDiv, x, y, dx, dy);
+        window.requestAnimationFrame(sendScrollEvent);
+      } else {
+        // Locally, with silk and apz + e10s, retina 15" mbp usually get ~1.0 - 1.5
+        // w/o silk + e10s + apz, I get up to 7. Lower is better.
+        // Windows, I get ~3. Values are not valid w/o hardware vsync
+        var uniformities = _getDOMWindowUtils().getFrameUniformityTestData();
+        for (var j = 0; j < uniformities.layerUniformities.length; j++) {
+          var layerResult = uniformities.layerUniformities[j];
+          var layerAddr = layerResult.layerAddress;
+          var uniformity = layerResult.frameUniformity;
+          var msg = "Layer: " + layerAddr.toString(16) + " Uniformity: " + uniformity;
+          SimpleTest.ok((uniformity >= 0) && (uniformity < 4.0), msg);
+        }
+        SimpleTest.finish();
+      }
+    }
+
+    function startTest() {
+      window.requestAnimationFrame(sendScrollEvent);
+    }
+
+    window.onload = function() {
+      var apzEnabled = SpecialPowers.getBoolPref("layers.async-pan-zoom.enabled");
+      if (!apzEnabled) {
+        SimpleTest.ok(true, "APZ not enabled, skipping test");
+        SimpleTest.finish();
+      }
+
+      var hwVsyncEnabled = SpecialPowers.getBoolPref("gfx.vsync.hw-vsync.enabled");
+      if (!hwVsyncEnabled) {
+        SimpleTest.ok(true, "Hardware vsync not enabled, skipping test");
+        SimpleTest.finish();
+      }
+
+      SpecialPowers.pushPrefEnv({
+        "set" : [
+          [testPref, true]
+        ]
+      }, startTest);
+    }
+    </script>
+</head>
+
+<body>
+  <div id="content">
+  </div>
+</body>
+</html>
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -9,16 +9,17 @@
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
 #include "mozilla/Hal.h"
 #include "mozilla/dom/ScreenOrientation.h"  // for ScreenOrientation
 #include "mozilla/dom/TabChild.h"       // for TabChild
 #include "mozilla/hal_sandbox/PHal.h"   // for ScreenConfiguration
 #include "mozilla/layers/CompositableClient.h"
 #include "mozilla/layers/CompositorChild.h" // for CompositorChild
 #include "mozilla/layers/ContentClient.h"
+#include "mozilla/layers/FrameUniformityData.h"
 #include "mozilla/layers/ISurfaceAllocator.h"
 #include "mozilla/layers/LayersMessages.h"  // for EditReply, etc
 #include "mozilla/layers/LayersSurfaces.h"  // for SurfaceDescriptor
 #include "mozilla/layers/PLayerChild.h"  // for PLayerChild
 #include "mozilla/layers/LayerTransactionChild.h"
 #include "mozilla/layers/TextureClientPool.h" // for TextureClientPool
 #include "ClientReadbackLayer.h"        // for ClientReadbackLayer
 #include "nsAString.h"
@@ -421,16 +422,30 @@ ClientLayerManager::RequestProperty(cons
 void
 ClientLayerManager::StartNewRepaintRequest(SequenceNumber aSequenceNumber)
 {
   if (gfxPrefs::APZTestLoggingEnabled()) {
     mApzTestData.StartNewRepaintRequest(aSequenceNumber);
   }
 }
 
+void
+ClientLayerManager::GetFrameUniformity(FrameUniformityData* aOutData)
+{
+  MOZ_ASSERT(XRE_IsParentProcess(), "Frame Uniformity only supported in parent process");
+
+  if (HasShadowManager()) {
+    CompositorChild* child = GetRemoteRenderer();
+    child->SendGetFrameUniformity(aOutData);
+    return;
+  }
+
+  return LayerManager::GetFrameUniformity(aOutData);
+}
+
 bool
 ClientLayerManager::RequestOverfill(mozilla::dom::OverfillCallback* aCallback)
 {
   MOZ_ASSERT(aCallback != nullptr);
   MOZ_ASSERT(HasShadowManager(), "Request Overfill only supported on b2g for now");
 
   if (HasShadowManager()) {
     CompositorChild* child = GetRemoteRenderer();
--- a/gfx/layers/client/ClientLayerManager.h
+++ b/gfx/layers/client/ClientLayerManager.h
@@ -29,16 +29,17 @@
 
 namespace mozilla {
 namespace layers {
 
 class ClientPaintedLayer;
 class CompositorChild;
 class ImageLayer;
 class PLayerChild;
+class FrameUniformityData;
 class TextureClientPool;
 
 class ClientLayerManager final : public LayerManager
 {
   typedef nsTArray<nsRefPtr<Layer> > LayerRefArray;
 
 public:
   explicit ClientLayerManager(nsIWidget* aWidget);
@@ -195,16 +196,17 @@ public:
 
   void SetNeedsComposite(bool aNeedsComposite)
   {
     mNeedsComposite = aNeedsComposite;
   }
   bool NeedsComposite() const { return mNeedsComposite; }
 
   virtual void Composite() override;
+  virtual void GetFrameUniformity(FrameUniformityData* aFrameUniformityData) override;
   virtual bool RequestOverfill(mozilla::dom::OverfillCallback* aCallback) override;
   virtual void RunOverfillCallback(const uint32_t aOverfill) override;
 
   virtual void DidComposite(uint64_t aTransactionId);
 
   virtual bool SupportsMixBlendModes(EnumSet<gfx::CompositionOp>& aMixBlendModes) override
   {
    return (GetTextureFactoryIdentifier().mSupportedBlendModes & aMixBlendModes) == aMixBlendModes;
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -32,16 +32,17 @@
 #include "nsTArray.h"                   // for nsTArray, nsTArray_Impl, etc
 #include "nsTArrayForwardDeclare.h"     // for InfallibleTArray
 #include "UnitTransforms.h"             // for TransformTo
 #if defined(MOZ_WIDGET_ANDROID)
 # include <android/log.h>
 # include "AndroidBridge.h"
 #endif
 #include "GeckoProfiler.h"
+#include "FrameUniformityData.h"
 
 struct nsCSSValueSharedList;
 
 namespace mozilla {
 namespace layers {
 
 using namespace mozilla::gfx;
 
@@ -89,16 +90,28 @@ WalkTheTree(Layer* aLayer,
     }
   }
   for (Layer* child = aLayer->GetFirstChild();
        child; child = child->GetNextSibling()) {
     WalkTheTree<OP>(child, aReady, aTargetConfig);
   }
 }
 
+AsyncCompositionManager::AsyncCompositionManager(LayerManagerComposite* aManager)
+  : mLayerManager(aManager)
+  , mIsFirstPaint(true)
+  , mLayersUpdated(false)
+  , mReadyForCompose(true)
+{
+}
+
+AsyncCompositionManager::~AsyncCompositionManager()
+{
+}
+
 void
 AsyncCompositionManager::ResolveRefLayers()
 {
   if (!mLayerManager->GetRoot()) {
     return;
   }
 
   mReadyForCompose = true;
@@ -540,16 +553,46 @@ SampleAPZAnimations(const LayerMetricsWr
 
   if (AsyncPanZoomController* apzc = aLayer.GetApzc()) {
     activeAnimations |= apzc->AdvanceAnimations(aSampleTime);
   }
 
   return activeAnimations;
 }
 
+void
+AsyncCompositionManager::RecordShadowTransforms(Layer* aLayer)
+{
+  MOZ_ASSERT(gfxPrefs::CollectScrollTransforms());
+  MOZ_ASSERT(CompositorParent::IsInCompositorThread());
+
+  for (Layer* child = aLayer->GetFirstChild();
+      child; child = child->GetNextSibling()) {
+      RecordShadowTransforms(child);
+  }
+
+  for (uint32_t i = 0; i < aLayer->GetFrameMetricsCount(); i++) {
+    AsyncPanZoomController* apzc = aLayer->GetAsyncPanZoomController(i);
+    if (!apzc) {
+      continue;
+    }
+    gfx::Matrix4x4 shadowTransform = aLayer->AsLayerComposite()->GetShadowTransform();
+    if (!shadowTransform.Is2D()) {
+      continue;
+    }
+
+    Matrix transform = shadowTransform.As2D();
+    if (transform.IsTranslation() && !shadowTransform.IsIdentity()) {
+      Point translation = transform.GetTranslation();
+      mLayerTransformRecorder.RecordTransform(aLayer, translation);
+      return;
+    }
+  }
+}
+
 Matrix4x4
 AdjustForClip(const Matrix4x4& asyncTransform, Layer* aLayer)
 {
   Matrix4x4 result = asyncTransform;
 
   // Container layers start at the origin, but they are clipped to where they
   // actually have content on the screen. The tree transform is meant to apply
   // to the clipped area. If the tree transform includes a scale component,
@@ -1046,16 +1089,23 @@ AsyncCompositionManager::TransformScroll
   oldTransform.PreScale(underZoomScale.width, underZoomScale.height, 1);
 
   // Make sure fixed position layers don't move away from their anchor points
   // when we're asynchronously panning or zooming
   AlignFixedAndStickyLayers(aLayer, aLayer, metrics.GetScrollId(), oldTransform,
                             aLayer->GetLocalTransform(), fixedLayerMargins);
 }
 
+void
+AsyncCompositionManager::GetFrameUniformity(FrameUniformityData* aOutData)
+{
+  MOZ_ASSERT(CompositorParent::IsInCompositorThread());
+  mLayerTransformRecorder.EndTest(aOutData);
+}
+
 bool
 AsyncCompositionManager::TransformShadowTree(TimeStamp aCurrentFrame,
                                              TransformsToSkip aSkip)
 {
   PROFILER_LABEL("AsyncCompositionManager", "TransformShadowTree",
     js::ProfileEntry::Category::GRAPHICS);
 
   Layer* root = mLayerManager->GetRoot();
@@ -1098,16 +1148,19 @@ AsyncCompositionManager::TransformShadow
   }
 
   LayerComposite* rootComposite = root->AsLayerComposite();
 
   gfx::Matrix4x4 trans = rootComposite->GetShadowTransform();
   trans *= gfx::Matrix4x4::From2D(mWorldTransform);
   rootComposite->SetShadowTransform(trans);
 
+  if (gfxPrefs::CollectScrollTransforms()) {
+    RecordShadowTransforms(root);
+  }
 
   return wantNextFrame;
 }
 
 void
 AsyncCompositionManager::SetFirstPaintViewport(const LayerIntPoint& aOffset,
                                                const CSSToLayerScale& aZoom,
                                                const CSSRect& aCssPageRect)
--- a/gfx/layers/composite/AsyncCompositionManager.h
+++ b/gfx/layers/composite/AsyncCompositionManager.h
@@ -9,16 +9,17 @@
 #include "Units.h"                      // for ScreenPoint, etc
 #include "mozilla/layers/LayerManagerComposite.h"  // for LayerManagerComposite
 #include "mozilla/Attributes.h"         // for final, etc
 #include "mozilla/RefPtr.h"             // for RefCounted
 #include "mozilla/TimeStamp.h"          // for TimeStamp
 #include "mozilla/dom/ScreenOrientation.h"  // for ScreenOrientation
 #include "mozilla/gfx/BasePoint.h"      // for BasePoint
 #include "mozilla/gfx/Matrix.h"         // for Matrix4x4
+#include "mozilla/layers/FrameUniformityData.h" // For FrameUniformityData
 #include "mozilla/layers/LayersMessages.h"  // for TargetConfig
 #include "nsRefPtr.h"                   // for nsRefPtr
 #include "nsISupportsImpl.h"            // for LayerManager::AddRef, etc
 
 namespace mozilla {
 namespace layers {
 
 class AsyncPanZoomController;
@@ -65,29 +66,22 @@ struct ViewTransform {
  * (LayerManagerComposite) which deals with elements of composition which are
  * usually dealt with by dom or layout when main thread rendering, but which can
  * short circuit that stuff to directly affect layers as they are composited,
  * for example, off-main thread animation, async video, async pan/zoom.
  */
 class AsyncCompositionManager final
 {
   friend class AutoResolveRefLayers;
-  ~AsyncCompositionManager()
-  {
-  }
+  ~AsyncCompositionManager();
+
 public:
   NS_INLINE_DECL_REFCOUNTING(AsyncCompositionManager)
 
-  explicit AsyncCompositionManager(LayerManagerComposite* aManager)
-    : mLayerManager(aManager)
-    , mIsFirstPaint(true)
-    , mLayersUpdated(false)
-    , mReadyForCompose(true)
-  {
-  }
+  explicit AsyncCompositionManager(LayerManagerComposite* aManager);
 
   /**
    * This forces the is-first-paint flag to true. This is intended to
    * be called by the widget code when it loses its viewport information
    * (or for whatever reason wants to refresh the viewport information).
    * The information refresh happens because the compositor will call
    * SetFirstPaintViewport on the next frame of composition.
    */
@@ -118,16 +112,20 @@ public:
 
   // True if the underlying layer tree is ready to be composited.
   bool ReadyForCompose() { return mReadyForCompose; }
 
   // Returns true if the next composition will be the first for a
   // particular document.
   bool IsFirstPaint() { return mIsFirstPaint; }
 
+  // GetFrameUniformity will return the frame uniformity for each layer attached to an APZ
+  // from the recorded data in RecordShadowTransform
+  void GetFrameUniformity(FrameUniformityData* aFrameUniformityData);
+
 private:
   void TransformScrollableLayer(Layer* aLayer);
   // Return true if an AsyncPanZoomController content transform was
   // applied for |aLayer|.
   bool ApplyAsyncContentTransformToTree(Layer* aLayer);
   /**
    * Update the shadow transform for aLayer assuming that is a scrollbar,
    * so that it stays in sync with the content that is being scrolled by APZ.
@@ -185,16 +183,19 @@ private:
   void ResolveRefLayers();
   /**
    * Detaches all referents resolved by ResolveRefLayers.
    * Assumes that mLayerManager->GetRoot() and mTargetConfig have not changed
    * since ResolveRefLayers was called.
    */
   void DetachRefLayers();
 
+  // Records the shadow transforms for the tree of layers rooted at the given layer
+  void RecordShadowTransforms(Layer* aLayer);
+
   TargetConfig mTargetConfig;
   CSSRect mContentRect;
 
   nsRefPtr<LayerManagerComposite> mLayerManager;
   // When this flag is set, the next composition will be the first for a
   // particular document (i.e. the document displayed on the screen will change).
   // This happens when loading a new page or switching tabs. We notify the
   // front-end (e.g. Java on Android) about this so that it take the new page
@@ -203,16 +204,17 @@ private:
 
   // This flag is set during a layers update, so that the first composition
   // after a layers update has it set. It is cleared after that first composition.
   bool mLayersUpdated;
 
   bool mReadyForCompose;
 
   gfx::Matrix mWorldTransform;
+  LayerTransformRecorder mLayerTransformRecorder;
 };
 
 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(AsyncCompositionManager::TransformsToSkip)
 
 class MOZ_STACK_CLASS AutoResolveRefLayers {
 public:
   explicit AutoResolveRefLayers(AsyncCompositionManager* aManager) : mManager(aManager)
   {
new file mode 100644
--- /dev/null
+++ b/gfx/layers/composite/FrameUniformityData.cpp
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 20; 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 "FrameUniformityData.h"
+
+#include <map>
+
+#include "Units.h"
+#include "gfxPoint.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/APZTestDataBinding.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace layers {
+
+using namespace gfx;
+
+Point
+LayerTransforms::GetAverage()
+{
+  MOZ_ASSERT(!mTransforms.IsEmpty());
+
+  Point current = mTransforms[0];
+  Point average;
+  size_t length = mTransforms.Length();
+
+  for (size_t i = 1; i < length; i++) {
+    Point nextTransform = mTransforms[i];
+    Point movement = nextTransform - current;
+    average += Point(std::fabs(movement.x), std::fabs(movement.y));
+    current = nextTransform;
+  }
+
+  average = average / (float) length;
+  return average;
+}
+
+Point
+LayerTransforms::GetStdDev()
+{
+  Point average = GetAverage();
+  Point stdDev;
+  Point current = mTransforms[0];
+
+  for (size_t i = 1; i < mTransforms.Length(); i++) {
+    Point next = mTransforms[i];
+    Point move = next - current;
+    move.x = fabs(move.x);
+    move.y = fabs(move.y);
+
+    Point diff = move - average;
+    diff.x = diff.x * diff.x;
+    diff.y = diff.y * diff.y;
+    stdDev += diff;
+
+    current = next;
+  }
+
+  stdDev = stdDev / mTransforms.Length();
+  stdDev.x = sqrt(stdDev.x);
+  stdDev.y = sqrt(stdDev.y);
+  return stdDev;
+}
+
+LayerTransformRecorder::~LayerTransformRecorder()
+{
+  Reset();
+}
+
+void
+LayerTransformRecorder::RecordTransform(Layer* aLayer, const Point& aTransform)
+{
+  LayerTransforms* layerTransforms = GetLayerTransforms((uintptr_t) aLayer);
+  layerTransforms->mTransforms.AppendElement(aTransform);
+}
+
+void
+LayerTransformRecorder::EndTest(FrameUniformityData* aOutData)
+{
+  for (auto iter = mFrameTransforms.begin(); iter != mFrameTransforms.end(); ++iter) {
+    uintptr_t layer = iter->first;
+    float uniformity = CalculateFrameUniformity(layer);
+
+    std::pair<uintptr_t,float> result(layer, uniformity);
+    aOutData->mUniformities.insert(result);
+  }
+
+  Reset();
+}
+
+LayerTransforms*
+LayerTransformRecorder::GetLayerTransforms(uintptr_t aLayer)
+{
+  if (!mFrameTransforms.count(aLayer)) {
+    LayerTransforms* newTransform = new LayerTransforms();
+    std::pair<uintptr_t, LayerTransforms*> newLayer(aLayer, newTransform);
+    mFrameTransforms.insert(newLayer);
+  }
+
+  return mFrameTransforms.find(aLayer)->second;
+}
+
+void
+LayerTransformRecorder::Reset()
+{
+  for (auto iter = mFrameTransforms.begin(); iter != mFrameTransforms.end(); ++iter) {
+    LayerTransforms* layerTransforms = iter->second;
+    delete layerTransforms;
+  }
+
+  mFrameTransforms.clear();
+}
+
+float
+LayerTransformRecorder::CalculateFrameUniformity(uintptr_t aLayer)
+{
+  LayerTransforms* layerTransform = GetLayerTransforms(aLayer);
+  float yUniformity = -1;
+  if (!layerTransform->mTransforms.IsEmpty()) {
+    Point stdDev = layerTransform->GetStdDev();
+    yUniformity = stdDev.y;
+  }
+  return yUniformity;
+}
+
+bool
+FrameUniformityData::ToJS(JS::MutableHandleValue aOutValue, JSContext* aContext)
+{
+  dom::FrameUniformityResults results;
+  dom::Sequence<dom::FrameUniformity>& layers = results.mLayerUniformities.Construct();
+
+  for (auto iter = mUniformities.begin(); iter != mUniformities.end(); ++iter) {
+    uintptr_t layerAddr = iter->first;
+    float uniformity = iter->second;
+
+    layers.AppendElement();
+    dom::FrameUniformity& entry = layers.LastElement();
+
+    entry.mLayerAddress.Construct() = layerAddr;
+    entry.mFrameUniformity.Construct() = uniformity;
+  }
+
+  return dom::ToJSValue(aContext, results, aOutValue);
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/gfx/layers/composite/FrameUniformityData.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 20; 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_layers_FrameUniformityData_h_
+#define mozilla_layers_FrameUniformityData_h_
+
+#include "ipc/IPCMessageUtils.h"
+#include "js/TypeDecls.h"
+#include "nsRefPtr.h"
+
+namespace mozilla {
+namespace layers {
+class Layer;
+
+class FrameUniformityData {
+  friend struct IPC::ParamTraits<FrameUniformityData>;
+
+public:
+  bool ToJS(JS::MutableHandleValue aOutValue, JSContext* aContext);
+  // Contains the calculated frame uniformities
+  std::map<uintptr_t,float> mUniformities;
+};
+
+struct LayerTransforms {
+  LayerTransforms() {}
+
+  gfx::Point GetAverage();
+  gfx::Point GetStdDev();
+
+  // 60 fps * 5 seconds worth of data
+  nsAutoTArray<gfx::Point, 300> mTransforms;
+};
+
+class LayerTransformRecorder {
+public:
+  LayerTransformRecorder() {}
+  ~LayerTransformRecorder();
+
+  void RecordTransform(Layer* aLayer, const gfx::Point& aTransform);
+  void Reset();
+  void EndTest(FrameUniformityData* aOutData);
+
+private:
+  float CalculateFrameUniformity(uintptr_t aLayer);
+  LayerTransforms* GetLayerTransforms(uintptr_t aLayer);
+  std::map<uintptr_t,LayerTransforms*> mFrameTransforms;
+};
+
+} // mozilla
+} // layers
+
+namespace IPC {
+template<>
+struct ParamTraits<mozilla::layers::FrameUniformityData>
+{
+  typedef mozilla::layers::FrameUniformityData paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mUniformities);
+  }
+
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
+  {
+    return ParamTraitsStd<std::map<uintptr_t,float>>::Read(aMsg, aIter, &aResult->mUniformities);
+  }
+};
+
+}// ipc
+
+#endif // mozilla_layers_FrameUniformityData_h_
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -32,16 +32,17 @@
 #include "mozilla/layers/APZCTreeManager.h"  // for APZCTreeManager
 #include "mozilla/layers/APZThreadUtils.h"  // for APZCTreeManager
 #include "mozilla/layers/AsyncCompositionManager.h"
 #include "mozilla/layers/BasicCompositor.h"  // for BasicCompositor
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/CompositorLRU.h"  // for CompositorLRU
 #include "mozilla/layers/CompositorOGL.h"  // for CompositorOGL
 #include "mozilla/layers/CompositorTypes.h"
+#include "mozilla/layers/FrameUniformityData.h"
 #include "mozilla/layers/LayerManagerComposite.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "mozilla/layers/PLayerTransactionParent.h"
 #include "mozilla/layers/ShadowLayersManager.h" // for ShadowLayersManager
 #include "mozilla/mozalloc.h"           // for operator new, etc
 #include "mozilla/Telemetry.h"
 #ifdef MOZ_WIDGET_GTK
 #include "basic/X11BasicCompositor.h" // for X11BasicCompositor
@@ -1320,16 +1321,23 @@ CompositorParent::ApplyAsyncProperties(L
       CancelCurrentCompositeTask();
       // Pretend we composited in case someone is waiting for this event.
       DidComposite();
     }
   }
 }
 
 bool
+CompositorParent::RecvGetFrameUniformity(FrameUniformityData* aOutData)
+{
+  mCompositionManager->GetFrameUniformity(aOutData);
+  return true;
+}
+
+bool
 CompositorParent::RecvRequestOverfill()
 {
   uint32_t overfillRatio = mCompositor->GetFillRatio();
   unused << SendOverfill(overfillRatio);
   return true;
 }
 
 void
@@ -1708,16 +1716,23 @@ public:
   virtual bool RecvStopFrameTimeRecording(const uint32_t& aStartIndex, InfallibleTArray<float>* intervals) override  { return true; }
   virtual bool RecvGetTileSize(int32_t* aWidth, int32_t* aHeight) override
   {
     *aWidth = gfxPlatform::GetPlatform()->GetTileWidth();
     *aHeight = gfxPlatform::GetPlatform()->GetTileHeight();
     return true;
   }
 
+  virtual bool RecvGetFrameUniformity(FrameUniformityData* aOutData) override
+  {
+    // Don't support calculating frame uniformity on the child process and
+    // this is just a stub for now.
+    MOZ_ASSERT(false);
+    return true;
+  }
 
   /**
    * Tells this CompositorParent to send a message when the compositor has received the transaction.
    */
   virtual bool RecvRequestNotifyAfterRemotePaint() override;
 
   virtual PLayerTransactionParent*
     AllocPLayerTransactionParent(const nsTArray<LayersBackend>& aBackendHints,
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -224,16 +224,17 @@ public:
                             int aSurfaceWidth = -1, int aSurfaceHeight = -1);
 
   // IToplevelProtocol::CloneToplevel()
   virtual IToplevelProtocol*
   CloneToplevel(const InfallibleTArray<mozilla::ipc::ProtocolFdMapping>& aFds,
                 base::ProcessHandle aPeerProcess,
                 mozilla::ipc::ProtocolCloneContext* aCtx) override;
 
+  virtual bool RecvGetFrameUniformity(FrameUniformityData* aOutData) override;
   virtual bool RecvRequestOverfill() override;
   virtual bool RecvWillStop() override;
   virtual bool RecvStop() override;
   virtual bool RecvPause() override;
   virtual bool RecvResume() override;
   virtual bool RecvNotifyHidden(const uint64_t& id) override { return true; }
   virtual bool RecvNotifyVisible(const uint64_t& id) override { return true; }
   virtual bool RecvNotifyChildCreated(const uint64_t& child) override;
--- a/gfx/layers/ipc/PCompositor.ipdl
+++ b/gfx/layers/ipc/PCompositor.ipdl
@@ -14,16 +14,17 @@ include "nsRegion.h";
 using struct mozilla::null_t from "ipc/IPCMessageUtils.h";
 using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h";
 using struct mozilla::layers::FrameMetrics from "FrameMetrics.h";
 using mozilla::layers::FrameMetrics::ViewID from "FrameMetrics.h";
 using mozilla::layers::LayersBackend from "mozilla/layers/LayersTypes.h";
 using mozilla::CrossProcessMutexHandle from "mozilla/ipc/CrossProcessMutex.h";
 using mozilla::ipc::SharedMemoryBasic::Handle from "mozilla/ipc/SharedMemoryBasic.h";
 using class mozilla::TimeStamp from "mozilla/TimeStamp.h";
+using class mozilla::layers::FrameUniformityData from "mozilla/layers/FrameUniformityData.h";
 
 namespace mozilla {
 namespace layers {
 
 
 /**
  * The PCompositor protocol is used to manage communication between
  * the main thread and the compositor thread context. It's primary
@@ -75,16 +76,19 @@ child:
    * side.
    */
   async ClearCachedResources(uint64_t id);
 
 parent:
   // Child sends the parent a request for fill ratio numbers.
   async RequestOverfill();
 
+  // Child requests frame uniformity measurements
+  sync GetFrameUniformity() returns (FrameUniformityData data);
+
   // The child is about to be destroyed, so perform any necessary cleanup.
   sync WillStop();
 
   // Clean up in preparation for own destruction.
   sync Stop();
 
   // Pause/resume the compositor. These are intended to be used on mobile, when
   // the compositor needs to pause/resume in lockstep with the application.
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -120,16 +120,17 @@ EXPORTS.mozilla.layers += [
     'client/TextureClientRecycleAllocator.h',
     'client/TextureClientSharedSurface.h',
     'client/TiledContentClient.h',
     'composite/AsyncCompositionManager.h',
     'composite/CanvasLayerComposite.h',
     'composite/ColorLayerComposite.h',
     'composite/ContainerLayerComposite.h',
     'composite/ContentHost.h',
+    'composite/FrameUniformityData.h',
     'composite/ImageHost.h',
     'composite/ImageLayerComposite.h',
     'composite/LayerManagerComposite.h',
     'composite/PaintedLayerComposite.h',
     'composite/TextureHost.h',
     'Compositor.h',
     'CompositorTypes.h',
     'D3D11ShareHandleImage.h',
@@ -272,16 +273,17 @@ UNIFIED_SOURCES += [
     'client/TiledContentClient.cpp',
     'composite/AsyncCompositionManager.cpp',
     'composite/CanvasLayerComposite.cpp',
     'composite/ColorLayerComposite.cpp',
     'composite/CompositableHost.cpp',
     'composite/ContainerLayerComposite.cpp',
     'composite/ContentHost.cpp',
     'composite/FPSCounter.cpp',
+    'composite/FrameUniformityData.cpp',
     'composite/ImageHost.cpp',
     'composite/ImageLayerComposite.cpp',
     'composite/LayerManagerComposite.cpp',
     'composite/PaintedLayerComposite.cpp',
     'composite/TextRenderer.cpp',
     'composite/TextureHost.cpp',
     'composite/TiledContentHost.cpp',
     'Compositor.cpp',
@@ -386,11 +388,12 @@ CXXFLAGS += [
         'frameworks/base/include/media/stagefright',
         'frameworks/base/include/media/stagefright/openmax',
         'frameworks/av/include/media/stagefright',
         'frameworks/native/include/media/openmax',
     ]
 ]
 
 MOCHITEST_MANIFESTS += ['apz/test/mochitest.ini']
+MOCHITEST_CHROME_MANIFESTS += ['apz/test/chrome.ini']
 
 CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
 CXXFLAGS += CONFIG['TK_CFLAGS']
--- a/gfx/thebes/gfxFcPlatformFontList.cpp
+++ b/gfx/thebes/gfxFcPlatformFontList.cpp
@@ -53,16 +53,18 @@ ToFcChar8Ptr(const char* aStr)
 
 static const char*
 ToCharPtr(const FcChar8 *aStr)
 {
     return reinterpret_cast<const char*>(aStr);
 }
 
 FT_Library gfxFcPlatformFontList::sCairoFTLibrary = nullptr;
+FcChar8* gfxFcPlatformFontList::sSentinelFirstFamily = nullptr;
+
 static cairo_user_data_key_t sFcFontlistUserFontDataKey;
 
 // canonical name ==> first en name or first name if no en name
 // This is the required logic for fullname lookups as per CSS3 Fonts spec.
 static uint32_t
 FindCanonicalNameIndex(FcPattern* aFont, const char* aLangField)
 {
     uint32_t n = 0, en = 0;
@@ -1041,16 +1043,18 @@ gfxFcPlatformFontList::InitFontList()
 
 #ifdef MOZ_BUNDLED_FONTS
     ActivateBundledFonts();
     FcFontSet* appFonts = FcConfigGetFonts(nullptr, FcSetApplication);
     AddFontSetFamilies(appFonts);
 #endif
 
     mOtherFamilyNamesInitialized = true;
+    sSentinelFirstFamily = nullptr;
+
     return NS_OK;
 }
 
 // For displaying the fontlist in UI, use explicit call to FcFontList. Using
 // FcFontList results in the list containing the localized names as dictated
 // by system defaults.
 static void
 GetSystemFontList(nsTArray<nsString>& aListOfFonts, nsIAtom *aLangGroup)
@@ -1231,40 +1235,41 @@ gfxFcPlatformFontList::FindFamily(const 
     // Example:
     //
     //   serif ==> DejaVu Serif, ...
     //   Helvetica, serif ==> Helvetica, TeX Gyre Heros, Nimbus Sans L, DejaVu Serif
     //
     // In this case fontconfig is including Tex Gyre Heros and
     // Nimbus Sans L as alternatives for Helvetica.
 
-    // substitutions for serif pattern
     const FcChar8* kSentinelName = ToFcChar8Ptr("-moz-sentinel");
-    nsAutoRef<FcPattern> sentinelSubst(FcPatternCreate());
-    FcPatternAddString(sentinelSubst, FC_FAMILY, kSentinelName);
-    FcConfigSubstitute(nullptr, sentinelSubst, FcMatchPattern);
-    FcChar8* sentinelFirstFamily = nullptr;
-    FcPatternGetString(sentinelSubst, FC_FAMILY, 0, &sentinelFirstFamily);
+    if (!sSentinelFirstFamily) {
+        nsAutoRef<FcPattern> sentinelSubst(FcPatternCreate());
+        FcPatternAddString(sentinelSubst, FC_FAMILY, kSentinelName);
+        FcConfigSubstitute(nullptr, sentinelSubst, FcMatchPattern);
+        FcPatternGetString(sentinelSubst, FC_FAMILY, 0, &sSentinelFirstFamily);
+    }
 
     // substitutions for font, -moz-sentinel pattern
     nsAutoRef<FcPattern> fontWithSentinel(FcPatternCreate());
     NS_ConvertUTF16toUTF8 familyToFind(familyName);
     FcPatternAddString(fontWithSentinel, FC_FAMILY, ToFcChar8Ptr(familyToFind.get()));
     FcPatternAddString(fontWithSentinel, FC_FAMILY, kSentinelName);
     FcConfigSubstitute(nullptr, fontWithSentinel, FcMatchPattern);
 
-    // iterate through substitutions until hitting the first serif font
+    // iterate through substitutions until hitting the sentinel
     FcChar8* substName = nullptr;
     for (int i = 0;
          FcPatternGetString(fontWithSentinel, FC_FAMILY,
                             i, &substName) == FcResultMatch;
          i++)
     {
         NS_ConvertUTF8toUTF16 subst(ToCharPtr(substName));
-        if (sentinelFirstFamily && FcStrCmp(substName, sentinelFirstFamily) == 0) {
+        if (sSentinelFirstFamily &&
+            FcStrCmp(substName, sSentinelFirstFamily) == 0) {
             break;
         }
         gfxFontFamily* foundFamily = gfxPlatformFontList::FindFamily(subst);
         if (foundFamily) {
             return foundFamily;
         }
     }
 
--- a/gfx/thebes/gfxFcPlatformFontList.h
+++ b/gfx/thebes/gfxFcPlatformFontList.h
@@ -255,11 +255,12 @@ protected:
 
     // caching generic/lang ==> font family
     nsRefPtrHashtable<nsCStringHashKey, gfxFontFamily> mGenericMappings;
 
     nsCOMPtr<nsITimer> mCheckFontUpdatesTimer;
     nsCountedRef<FcConfig> mLastConfig;
 
     static FT_Library sCairoFTLibrary;
+    static FcChar8* sSentinelFirstFamily;
 };
 
 #endif /* GFXPLATFORMFONTLIST_H_ */
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -235,16 +235,17 @@ private:
   DECL_GFX_PREF(Once, "gfx.touch.resample",                    TouchResampling, bool, false);
 
   // These times should be in milliseconds
   DECL_GFX_PREF(Once, "gfx.touch.resample.delay-threshold",    TouchResampleVsyncDelayThreshold, int32_t, 20);
   DECL_GFX_PREF(Once, "gfx.touch.resample.max-predict",        TouchResampleMaxPredict, int32_t, 8);
   DECL_GFX_PREF(Once, "gfx.touch.resample.old-touch-threshold",TouchResampleOldTouchThreshold, int32_t, 17);
   DECL_GFX_PREF(Once, "gfx.touch.resample.vsync-adjust",       TouchVsyncSampleAdjust, int32_t, 5);
 
+  DECL_GFX_PREF(Live, "gfx.vsync.collect-scroll-transforms",   CollectScrollTransforms, bool, false);
   DECL_GFX_PREF(Once, "gfx.vsync.compositor",                  VsyncAlignedCompositor, bool, false);
   // On b2g, in really bad cases, I've seen up to 80 ms delays between touch events and the main thread
   // processing them. So 80 ms / 16 = 5 vsync events. Double it up just to be on the safe side, so 10.
   DECL_GFX_PREF(Once, "gfx.vsync.compositor.unobserve-count",  CompositorUnobserveCount, int32_t, 10);
   // Use vsync events generated by hardware
   DECL_GFX_PREF(Once, "gfx.vsync.hw-vsync.enabled",            HardwareVsyncEnabled, bool, false);
   DECL_GFX_PREF(Once, "gfx.vsync.refreshdriver",               VsyncAlignedRefreshDriver, bool, false);
   DECL_GFX_PREF(Once, "gfx.work-around-driver-bugs",           WorkAroundDriverBugs, bool, true);
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -1828,16 +1828,33 @@ bool DoesD3D11TextureSharingWork(ID3D11D
   if (checked)
     return result;
   checked = true;
 
   result = DoesD3D11TextureSharingWorkInternal(device, DXGI_FORMAT_B8G8R8A8_UNORM, D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE);
   return result;
 }
 
+bool DoesD3D11AlphaTextureSharingWork(ID3D11Device *device)
+{
+  nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
+  if (gfxInfo) {
+    // Disable texture sharing if we're blocking d2d since that's the only other time we use it
+    // and it might be broken.
+    int32_t status;
+    if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT2D, &status))) {
+      if (status != nsIGfxInfo::FEATURE_STATUS_OK) {
+        return false;
+      }
+    }
+  }
+
+  return DoesD3D11TextureSharingWorkInternal(device, DXGI_FORMAT_A8_UNORM, D3D11_BIND_SHADER_RESOURCE);
+}
+
 void
 gfxWindowsPlatform::InitD3D11Devices()
 {
   // This function attempts to initialize our D3D11 devices. If the hardware
   // is not blacklisted for D3D11 layers. This will first attempt to create a
   // hardware accelerated device. If this creation fails or the hardware is
   // blacklisted, then this function will abort if WARP is disabled, causing us
   // to fallback to D3D9 or Basic layers. If WARP is not disabled it will use
@@ -2017,17 +2034,17 @@ gfxWindowsPlatform::InitD3D11Devices()
 
     if (FAILED(hr)) {
       d3d11Module.disown();
       return;
     }
 
     mD3D11ImageBridgeDevice->SetExceptionMode(0);
 
-    if (!DoesD3D11TextureSharingWorkInternal(mD3D11ImageBridgeDevice, DXGI_FORMAT_A8_UNORM, D3D11_BIND_SHADER_RESOURCE)) {
+    if (!DoesD3D11AlphaTextureSharingWork(mD3D11ImageBridgeDevice)) {
       mD3D11ImageBridgeDevice = nullptr;
     }
   }
 
   // We leak these everywhere and we need them our entire runtime anyway, let's
   // leak it here as well.
   d3d11Module.disown();
 }
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/dense-elements-hole-negative.js
@@ -0,0 +1,31 @@
+// The index is negative before code generation.
+
+let v = {};
+let negativeIndex = -1;
+
+function f(obj) {
+  assertEq(obj[negativeIndex] === v, true);
+}
+for (let i = 0; i < 2000; i++) {
+  let obj = {};
+  obj[1] = {};
+  obj[negativeIndex] = v;
+  f(obj);
+}
+
+// The sign of the index changes after the code generation.
+
+function g(obj, i) {
+  for (let j = 0; j < 4; j++) {
+    assertEq(obj[i-j] === v, true);
+  }
+}
+for (let i = 0; i < 2000; i++) {
+  let obj = {};
+  obj[1] = {};
+  let X = 2000 - i;
+  for (let j = 0; j < 10; j++) {
+    obj[X-j] = v;
+  }
+  g(obj, X);
+}
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -3,27 +3,27 @@
  * 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 "jit/BaselineCompiler.h"
 
 #include "mozilla/UniquePtr.h"
 
-#include "jit/BaselineHelpers.h"
 #include "jit/BaselineIC.h"
 #include "jit/BaselineJIT.h"
 #include "jit/FixedList.h"
 #include "jit/IonAnalysis.h"
 #include "jit/JitcodeMap.h"
 #include "jit/JitSpewer.h"
 #include "jit/Linker.h"
 #ifdef JS_ION_PERF
 # include "jit/PerfSpewer.h"
 #endif
+#include "jit/SharedICHelpers.h"
 #include "jit/VMFunctions.h"
 #include "vm/TraceLogging.h"
 
 #include "jsscriptinlines.h"
 
 #include "vm/Interpreter-inl.h"
 #include "vm/NativeObject-inl.h"
 
--- a/js/src/jit/BaselineFrameInfo.h
+++ b/js/src/jit/BaselineFrameInfo.h
@@ -5,19 +5,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jit_BaselineFrameInfo_h
 #define jit_BaselineFrameInfo_h
 
 #include "mozilla/Alignment.h"
 
 #include "jit/BaselineFrame.h"
-#include "jit/BaselineRegisters.h"
 #include "jit/FixedList.h"
 #include "jit/MacroAssembler.h"
+#include "jit/SharedICRegisters.h"
 
 namespace js {
 namespace jit {
 
 struct BytecodeInfo;
 
 // FrameInfo overview.
 //
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -12,24 +12,24 @@
 #include "mozilla/TemplateLib.h"
 
 #include "jslibmath.h"
 #include "jstypes.h"
 
 #include "builtin/Eval.h"
 #include "builtin/SIMD.h"
 #include "jit/BaselineDebugModeOSR.h"
-#include "jit/BaselineHelpers.h"
 #include "jit/BaselineJIT.h"
 #include "jit/JitSpewer.h"
 #include "jit/Linker.h"
 #include "jit/Lowering.h"
 #ifdef JS_ION_PERF
 # include "jit/PerfSpewer.h"
 #endif
+#include "jit/SharedICHelpers.h"
 #include "jit/VMFunctions.h"
 #include "js/Conversions.h"
 #include "vm/Opcodes.h"
 #include "vm/TypedArrayCommon.h"
 
 #include "jsboolinlines.h"
 #include "jsscriptinlines.h"
 
@@ -40,767 +40,16 @@
 #include "vm/UnboxedObject-inl.h"
 
 using mozilla::BitwiseCast;
 using mozilla::DebugOnly;
 
 namespace js {
 namespace jit {
 
-#ifdef DEBUG
-void
-FallbackICSpew(JSContext* cx, ICFallbackStub* stub, const char* fmt, ...)
-{
-    if (JitSpewEnabled(JitSpew_BaselineICFallback)) {
-        RootedScript script(cx, GetTopJitJSScript(cx));
-        jsbytecode* pc = stub->icEntry()->pc(script);
-
-        char fmtbuf[100];
-        va_list args;
-        va_start(args, fmt);
-        vsnprintf(fmtbuf, 100, fmt, args);
-        va_end(args);
-
-        JitSpew(JitSpew_BaselineICFallback,
-                "Fallback hit for (%s:%" PRIuSIZE ") (pc=%" PRIuSIZE ",line=%d,uses=%d,stubs=%" PRIuSIZE "): %s",
-                script->filename(),
-                script->lineno(),
-                script->pcToOffset(pc),
-                PCToLineNumber(script, pc),
-                script->getWarmUpCount(),
-                stub->numOptimizedStubs(),
-                fmtbuf);
-    }
-}
-
-void
-TypeFallbackICSpew(JSContext* cx, ICTypeMonitor_Fallback* stub, const char* fmt, ...)
-{
-    if (JitSpewEnabled(JitSpew_BaselineICFallback)) {
-        RootedScript script(cx, GetTopJitJSScript(cx));
-        jsbytecode* pc = stub->icEntry()->pc(script);
-
-        char fmtbuf[100];
-        va_list args;
-        va_start(args, fmt);
-        vsnprintf(fmtbuf, 100, fmt, args);
-        va_end(args);
-
-        JitSpew(JitSpew_BaselineICFallback,
-                "Type monitor fallback hit for (%s:%" PRIuSIZE ") (pc=%" PRIuSIZE ",line=%d,uses=%d,stubs=%d): %s",
-                script->filename(),
-                script->lineno(),
-                script->pcToOffset(pc),
-                PCToLineNumber(script, pc),
-                script->getWarmUpCount(),
-                (int) stub->numOptimizedMonitorStubs(),
-                fmtbuf);
-    }
-}
-
-#else
-#define FallbackICSpew(...)
-#define TypeFallbackICSpew(...)
-#endif
-
-
-ICFallbackStub*
-ICEntry::fallbackStub() const
-{
-    return firstStub()->getChainFallback();
-}
-
-
-ICStubConstIterator&
-ICStubConstIterator::operator++()
-{
-    MOZ_ASSERT(currentStub_ != nullptr);
-    currentStub_ = currentStub_->next();
-    return *this;
-}
-
-
-ICStubIterator::ICStubIterator(ICFallbackStub* fallbackStub, bool end)
-  : icEntry_(fallbackStub->icEntry()),
-    fallbackStub_(fallbackStub),
-    previousStub_(nullptr),
-    currentStub_(end ? fallbackStub : icEntry_->firstStub()),
-    unlinked_(false)
-{ }
-
-ICStubIterator&
-ICStubIterator::operator++()
-{
-    MOZ_ASSERT(currentStub_->next() != nullptr);
-    if (!unlinked_)
-        previousStub_ = currentStub_;
-    currentStub_ = currentStub_->next();
-    unlinked_ = false;
-    return *this;
-}
-
-void
-ICStubIterator::unlink(JSContext* cx)
-{
-    MOZ_ASSERT(currentStub_->next() != nullptr);
-    MOZ_ASSERT(currentStub_ != fallbackStub_);
-    MOZ_ASSERT(!unlinked_);
-
-    fallbackStub_->unlinkStub(cx->zone(), previousStub_, currentStub_);
-
-    // Mark the current iterator position as unlinked, so operator++ works properly.
-    unlinked_ = true;
-}
-
-
-void
-ICStub::markCode(JSTracer* trc, const char* name)
-{
-    JitCode* stubJitCode = jitCode();
-    TraceManuallyBarrieredEdge(trc, &stubJitCode, name);
-}
-
-void
-ICStub::updateCode(JitCode* code)
-{
-    // Write barrier on the old code.
-    JitCode::writeBarrierPre(jitCode());
-    stubCode_ = code->raw();
-}
-
-/* static */ void
-ICStub::trace(JSTracer* trc)
-{
-    markCode(trc, "baseline-stub-jitcode");
-
-    // If the stub is a monitored fallback stub, then mark the monitor ICs hanging
-    // off of that stub.  We don't need to worry about the regular monitored stubs,
-    // because the regular monitored stubs will always have a monitored fallback stub
-    // that references the same stub chain.
-    if (isMonitoredFallback()) {
-        ICTypeMonitor_Fallback* lastMonStub = toMonitoredFallbackStub()->fallbackMonitorStub();
-        for (ICStubConstIterator iter(lastMonStub->firstMonitorStub()); !iter.atEnd(); iter++) {
-            MOZ_ASSERT_IF(iter->next() == nullptr, *iter == lastMonStub);
-            iter->trace(trc);
-        }
-    }
-
-    if (isUpdated()) {
-        for (ICStubConstIterator iter(toUpdatedStub()->firstUpdateStub()); !iter.atEnd(); iter++) {
-            MOZ_ASSERT_IF(iter->next() == nullptr, iter->isTypeUpdate_Fallback());
-            iter->trace(trc);
-        }
-    }
-
-    switch (kind()) {
-      case ICStub::Call_Scripted: {
-        ICCall_Scripted* callStub = toCall_Scripted();
-        TraceEdge(trc, &callStub->callee(), "baseline-callscripted-callee");
-        if (callStub->templateObject())
-            TraceEdge(trc, &callStub->templateObject(), "baseline-callscripted-template");
-        break;
-      }
-      case ICStub::Call_Native: {
-        ICCall_Native* callStub = toCall_Native();
-        TraceEdge(trc, &callStub->callee(), "baseline-callnative-callee");
-        if (callStub->templateObject())
-            TraceEdge(trc, &callStub->templateObject(), "baseline-callnative-template");
-        break;
-      }
-      case ICStub::Call_ClassHook: {
-        ICCall_ClassHook* callStub = toCall_ClassHook();
-        if (callStub->templateObject())
-            TraceEdge(trc, &callStub->templateObject(), "baseline-callclasshook-template");
-        break;
-      }
-      case ICStub::Call_StringSplit: {
-        ICCall_StringSplit* callStub = toCall_StringSplit();
-        TraceEdge(trc, &callStub->templateObject(), "baseline-callstringsplit-template");
-        TraceEdge(trc, &callStub->expectedArg(), "baseline-callstringsplit-arg");
-        TraceEdge(trc, &callStub->expectedThis(), "baseline-callstringsplit-this");
-        break;
-      }
-      case ICStub::GetElem_NativeSlot: {
-        ICGetElem_NativeSlot* getElemStub = toGetElem_NativeSlot();
-        TraceEdge(trc, &getElemStub->shape(), "baseline-getelem-native-shape");
-        TraceEdge(trc, &getElemStub->name(), "baseline-getelem-native-name");
-        break;
-      }
-      case ICStub::GetElem_NativePrototypeSlot: {
-        ICGetElem_NativePrototypeSlot* getElemStub = toGetElem_NativePrototypeSlot();
-        TraceEdge(trc, &getElemStub->shape(), "baseline-getelem-nativeproto-shape");
-        TraceEdge(trc, &getElemStub->name(), "baseline-getelem-nativeproto-name");
-        TraceEdge(trc, &getElemStub->holder(), "baseline-getelem-nativeproto-holder");
-        TraceEdge(trc, &getElemStub->holderShape(), "baseline-getelem-nativeproto-holdershape");
-        break;
-      }
-      case ICStub::GetElem_NativePrototypeCallNative:
-      case ICStub::GetElem_NativePrototypeCallScripted: {
-        ICGetElemNativePrototypeCallStub* callStub =
-            reinterpret_cast<ICGetElemNativePrototypeCallStub*>(this);
-        TraceEdge(trc, &callStub->shape(), "baseline-getelem-nativeprotocall-shape");
-        TraceEdge(trc, &callStub->name(), "baseline-getelem-nativeprotocall-name");
-        TraceEdge(trc, &callStub->getter(), "baseline-getelem-nativeprotocall-getter");
-        TraceEdge(trc, &callStub->holder(), "baseline-getelem-nativeprotocall-holder");
-        TraceEdge(trc, &callStub->holderShape(), "baseline-getelem-nativeprotocall-holdershape");
-        break;
-      }
-      case ICStub::GetElem_Dense: {
-        ICGetElem_Dense* getElemStub = toGetElem_Dense();
-        TraceEdge(trc, &getElemStub->shape(), "baseline-getelem-dense-shape");
-        break;
-      }
-      case ICStub::GetElem_UnboxedArray: {
-        ICGetElem_UnboxedArray* getElemStub = toGetElem_UnboxedArray();
-        TraceEdge(trc, &getElemStub->group(), "baseline-getelem-unboxed-array-group");
-        break;
-      }
-      case ICStub::GetElem_TypedArray: {
-        ICGetElem_TypedArray* getElemStub = toGetElem_TypedArray();
-        TraceEdge(trc, &getElemStub->shape(), "baseline-getelem-typedarray-shape");
-        break;
-      }
-      case ICStub::SetElem_DenseOrUnboxedArray: {
-        ICSetElem_DenseOrUnboxedArray* setElemStub = toSetElem_DenseOrUnboxedArray();
-        if (setElemStub->shape())
-            TraceEdge(trc, &setElemStub->shape(), "baseline-getelem-dense-shape");
-        TraceEdge(trc, &setElemStub->group(), "baseline-setelem-dense-group");
-        break;
-      }
-      case ICStub::SetElem_DenseOrUnboxedArrayAdd: {
-        ICSetElem_DenseOrUnboxedArrayAdd* setElemStub = toSetElem_DenseOrUnboxedArrayAdd();
-        TraceEdge(trc, &setElemStub->group(), "baseline-setelem-denseadd-group");
-
-        JS_STATIC_ASSERT(ICSetElem_DenseOrUnboxedArrayAdd::MAX_PROTO_CHAIN_DEPTH == 4);
-
-        switch (setElemStub->protoChainDepth()) {
-          case 0: setElemStub->toImpl<0>()->traceShapes(trc); break;
-          case 1: setElemStub->toImpl<1>()->traceShapes(trc); break;
-          case 2: setElemStub->toImpl<2>()->traceShapes(trc); break;
-          case 3: setElemStub->toImpl<3>()->traceShapes(trc); break;
-          case 4: setElemStub->toImpl<4>()->traceShapes(trc); break;
-          default: MOZ_CRASH("Invalid proto stub.");
-        }
-        break;
-      }
-      case ICStub::SetElem_TypedArray: {
-        ICSetElem_TypedArray* setElemStub = toSetElem_TypedArray();
-        TraceEdge(trc, &setElemStub->shape(), "baseline-setelem-typedarray-shape");
-        break;
-      }
-      case ICStub::TypeMonitor_SingleObject: {
-        ICTypeMonitor_SingleObject* monitorStub = toTypeMonitor_SingleObject();
-        TraceEdge(trc, &monitorStub->object(), "baseline-monitor-singleton");
-        break;
-      }
-      case ICStub::TypeMonitor_ObjectGroup: {
-        ICTypeMonitor_ObjectGroup* monitorStub = toTypeMonitor_ObjectGroup();
-        TraceEdge(trc, &monitorStub->group(), "baseline-monitor-group");
-        break;
-      }
-      case ICStub::TypeUpdate_SingleObject: {
-        ICTypeUpdate_SingleObject* updateStub = toTypeUpdate_SingleObject();
-        TraceEdge(trc, &updateStub->object(), "baseline-update-singleton");
-        break;
-      }
-      case ICStub::TypeUpdate_ObjectGroup: {
-        ICTypeUpdate_ObjectGroup* updateStub = toTypeUpdate_ObjectGroup();
-        TraceEdge(trc, &updateStub->group(), "baseline-update-group");
-        break;
-      }
-      case ICStub::In_Native: {
-        ICIn_Native* inStub = toIn_Native();
-        TraceEdge(trc, &inStub->shape(), "baseline-innative-stub-shape");
-        TraceEdge(trc, &inStub->name(), "baseline-innative-stub-name");
-        break;
-      }
-      case ICStub::In_NativePrototype: {
-        ICIn_NativePrototype* inStub = toIn_NativePrototype();
-        TraceEdge(trc, &inStub->shape(), "baseline-innativeproto-stub-shape");
-        TraceEdge(trc, &inStub->name(), "baseline-innativeproto-stub-name");
-        TraceEdge(trc, &inStub->holder(), "baseline-innativeproto-stub-holder");
-        TraceEdge(trc, &inStub->holderShape(), "baseline-innativeproto-stub-holdershape");
-        break;
-      }
-      case ICStub::In_NativeDoesNotExist: {
-        ICIn_NativeDoesNotExist* inStub = toIn_NativeDoesNotExist();
-        TraceEdge(trc, &inStub->name(), "baseline-innativedoesnotexist-stub-name");
-        JS_STATIC_ASSERT(ICIn_NativeDoesNotExist::MAX_PROTO_CHAIN_DEPTH == 8);
-        switch (inStub->protoChainDepth()) {
-          case 0: inStub->toImpl<0>()->traceShapes(trc); break;
-          case 1: inStub->toImpl<1>()->traceShapes(trc); break;
-          case 2: inStub->toImpl<2>()->traceShapes(trc); break;
-          case 3: inStub->toImpl<3>()->traceShapes(trc); break;
-          case 4: inStub->toImpl<4>()->traceShapes(trc); break;
-          case 5: inStub->toImpl<5>()->traceShapes(trc); break;
-          case 6: inStub->toImpl<6>()->traceShapes(trc); break;
-          case 7: inStub->toImpl<7>()->traceShapes(trc); break;
-          case 8: inStub->toImpl<8>()->traceShapes(trc); break;
-          default: MOZ_CRASH("Invalid proto stub.");
-        }
-        break;
-      }
-      case ICStub::In_Dense: {
-        ICIn_Dense* inStub = toIn_Dense();
-        TraceEdge(trc, &inStub->shape(), "baseline-in-dense-shape");
-        break;
-      }
-      case ICStub::GetName_Global: {
-        ICGetName_Global* globalStub = toGetName_Global();
-        TraceEdge(trc, &globalStub->shape(), "baseline-global-stub-shape");
-        break;
-      }
-      case ICStub::GetName_Scope0:
-        static_cast<ICGetName_Scope<0>*>(this)->traceScopes(trc);
-        break;
-      case ICStub::GetName_Scope1:
-        static_cast<ICGetName_Scope<1>*>(this)->traceScopes(trc);
-        break;
-      case ICStub::GetName_Scope2:
-        static_cast<ICGetName_Scope<2>*>(this)->traceScopes(trc);
-        break;
-      case ICStub::GetName_Scope3:
-        static_cast<ICGetName_Scope<3>*>(this)->traceScopes(trc);
-        break;
-      case ICStub::GetName_Scope4:
-        static_cast<ICGetName_Scope<4>*>(this)->traceScopes(trc);
-        break;
-      case ICStub::GetName_Scope5:
-        static_cast<ICGetName_Scope<5>*>(this)->traceScopes(trc);
-        break;
-      case ICStub::GetName_Scope6:
-        static_cast<ICGetName_Scope<6>*>(this)->traceScopes(trc);
-        break;
-      case ICStub::GetIntrinsic_Constant: {
-        ICGetIntrinsic_Constant* constantStub = toGetIntrinsic_Constant();
-        TraceEdge(trc, &constantStub->value(), "baseline-getintrinsic-constant-value");
-        break;
-      }
-      case ICStub::GetProp_Primitive: {
-        ICGetProp_Primitive* propStub = toGetProp_Primitive();
-        TraceEdge(trc, &propStub->protoShape(), "baseline-getprop-primitive-stub-shape");
-        break;
-      }
-      case ICStub::GetProp_Native: {
-        ICGetProp_Native* propStub = toGetProp_Native();
-        propStub->receiverGuard().trace(trc);
-        break;
-      }
-      case ICStub::GetProp_NativePrototype: {
-        ICGetProp_NativePrototype* propStub = toGetProp_NativePrototype();
-        propStub->receiverGuard().trace(trc);
-        TraceEdge(trc, &propStub->holder(), "baseline-getpropnativeproto-stub-holder");
-        TraceEdge(trc, &propStub->holderShape(), "baseline-getpropnativeproto-stub-holdershape");
-        break;
-      }
-      case ICStub::GetProp_NativeDoesNotExist: {
-        ICGetProp_NativeDoesNotExist* propStub = toGetProp_NativeDoesNotExist();
-        propStub->guard().trace(trc);
-        JS_STATIC_ASSERT(ICGetProp_NativeDoesNotExist::MAX_PROTO_CHAIN_DEPTH == 8);
-        switch (propStub->protoChainDepth()) {
-          case 0: propStub->toImpl<0>()->traceShapes(trc); break;
-          case 1: propStub->toImpl<1>()->traceShapes(trc); break;
-          case 2: propStub->toImpl<2>()->traceShapes(trc); break;
-          case 3: propStub->toImpl<3>()->traceShapes(trc); break;
-          case 4: propStub->toImpl<4>()->traceShapes(trc); break;
-          case 5: propStub->toImpl<5>()->traceShapes(trc); break;
-          case 6: propStub->toImpl<6>()->traceShapes(trc); break;
-          case 7: propStub->toImpl<7>()->traceShapes(trc); break;
-          case 8: propStub->toImpl<8>()->traceShapes(trc); break;
-          default: MOZ_CRASH("Invalid proto stub.");
-        }
-        break;
-      }
-      case ICStub::GetProp_Unboxed: {
-        ICGetProp_Unboxed* propStub = toGetProp_Unboxed();
-        TraceEdge(trc, &propStub->group(), "baseline-getprop-unboxed-stub-group");
-        break;
-      }
-      case ICStub::GetProp_TypedObject: {
-        ICGetProp_TypedObject* propStub = toGetProp_TypedObject();
-        TraceEdge(trc, &propStub->shape(), "baseline-getprop-typedobject-stub-shape");
-        break;
-      }
-      case ICStub::GetProp_CallDOMProxyNative:
-      case ICStub::GetProp_CallDOMProxyWithGenerationNative: {
-        ICGetPropCallDOMProxyNativeStub* propStub;
-        if (kind() == ICStub::GetProp_CallDOMProxyNative)
-            propStub = toGetProp_CallDOMProxyNative();
-        else
-            propStub = toGetProp_CallDOMProxyWithGenerationNative();
-        propStub->receiverGuard().trace(trc);
-        if (propStub->expandoShape()) {
-            TraceEdge(trc, &propStub->expandoShape(),
-                      "baseline-getproplistbasenative-stub-expandoshape");
-        }
-        TraceEdge(trc, &propStub->holder(), "baseline-getproplistbasenative-stub-holder");
-        TraceEdge(trc, &propStub->holderShape(), "baseline-getproplistbasenative-stub-holdershape");
-        TraceEdge(trc, &propStub->getter(), "baseline-getproplistbasenative-stub-getter");
-        break;
-      }
-      case ICStub::GetProp_DOMProxyShadowed: {
-        ICGetProp_DOMProxyShadowed* propStub = toGetProp_DOMProxyShadowed();
-        TraceEdge(trc, &propStub->shape(), "baseline-getproplistbaseshadowed-stub-shape");
-        TraceEdge(trc, &propStub->name(), "baseline-getproplistbaseshadowed-stub-name");
-        break;
-      }
-      case ICStub::GetProp_CallScripted: {
-        ICGetProp_CallScripted* callStub = toGetProp_CallScripted();
-        callStub->receiverGuard().trace(trc);
-        TraceEdge(trc, &callStub->holder(), "baseline-getpropcallscripted-stub-holder");
-        TraceEdge(trc, &callStub->holderShape(), "baseline-getpropcallscripted-stub-holdershape");
-        TraceEdge(trc, &callStub->getter(), "baseline-getpropcallscripted-stub-getter");
-        break;
-      }
-      case ICStub::GetProp_CallNative: {
-        ICGetProp_CallNative* callStub = toGetProp_CallNative();
-        callStub->receiverGuard().trace(trc);
-        TraceEdge(trc, &callStub->holder(), "baseline-getpropcallnative-stub-holder");
-        TraceEdge(trc, &callStub->holderShape(), "baseline-getpropcallnative-stub-holdershape");
-        TraceEdge(trc, &callStub->getter(), "baseline-getpropcallnative-stub-getter");
-        break;
-      }
-      case ICStub::SetProp_Native: {
-        ICSetProp_Native* propStub = toSetProp_Native();
-        TraceEdge(trc, &propStub->shape(), "baseline-setpropnative-stub-shape");
-        TraceEdge(trc, &propStub->group(), "baseline-setpropnative-stub-group");
-        break;
-      }
-      case ICStub::SetProp_NativeAdd: {
-        ICSetProp_NativeAdd* propStub = toSetProp_NativeAdd();
-        TraceEdge(trc, &propStub->group(), "baseline-setpropnativeadd-stub-group");
-        TraceEdge(trc, &propStub->newShape(), "baseline-setpropnativeadd-stub-newshape");
-        if (propStub->newGroup())
-            TraceEdge(trc, &propStub->newGroup(), "baseline-setpropnativeadd-stub-new-group");
-        JS_STATIC_ASSERT(ICSetProp_NativeAdd::MAX_PROTO_CHAIN_DEPTH == 4);
-        switch (propStub->protoChainDepth()) {
-          case 0: propStub->toImpl<0>()->traceShapes(trc); break;
-          case 1: propStub->toImpl<1>()->traceShapes(trc); break;
-          case 2: propStub->toImpl<2>()->traceShapes(trc); break;
-          case 3: propStub->toImpl<3>()->traceShapes(trc); break;
-          case 4: propStub->toImpl<4>()->traceShapes(trc); break;
-          default: MOZ_CRASH("Invalid proto stub.");
-        }
-        break;
-      }
-      case ICStub::SetProp_Unboxed: {
-        ICSetProp_Unboxed* propStub = toSetProp_Unboxed();
-        TraceEdge(trc, &propStub->group(), "baseline-setprop-unboxed-stub-group");
-        break;
-      }
-      case ICStub::SetProp_TypedObject: {
-        ICSetProp_TypedObject* propStub = toSetProp_TypedObject();
-        TraceEdge(trc, &propStub->shape(), "baseline-setprop-typedobject-stub-shape");
-        TraceEdge(trc, &propStub->group(), "baseline-setprop-typedobject-stub-group");
-        break;
-      }
-      case ICStub::SetProp_CallScripted: {
-        ICSetProp_CallScripted* callStub = toSetProp_CallScripted();
-        callStub->receiverGuard().trace(trc);
-        TraceEdge(trc, &callStub->holder(), "baseline-setpropcallscripted-stub-holder");
-        TraceEdge(trc, &callStub->holderShape(), "baseline-setpropcallscripted-stub-holdershape");
-        TraceEdge(trc, &callStub->setter(), "baseline-setpropcallscripted-stub-setter");
-        break;
-      }
-      case ICStub::SetProp_CallNative: {
-        ICSetProp_CallNative* callStub = toSetProp_CallNative();
-        callStub->receiverGuard().trace(trc);
-        TraceEdge(trc, &callStub->holder(), "baseline-setpropcallnative-stub-holder");
-        TraceEdge(trc, &callStub->holderShape(), "baseline-setpropcallnative-stub-holdershape");
-        TraceEdge(trc, &callStub->setter(), "baseline-setpropcallnative-stub-setter");
-        break;
-      }
-      case ICStub::InstanceOf_Function: {
-        ICInstanceOf_Function* instanceofStub = toInstanceOf_Function();
-        TraceEdge(trc, &instanceofStub->shape(), "baseline-instanceof-fun-shape");
-        TraceEdge(trc, &instanceofStub->prototypeObject(), "baseline-instanceof-fun-prototype");
-        break;
-      }
-      case ICStub::NewArray_Fallback: {
-        ICNewArray_Fallback* stub = toNewArray_Fallback();
-        if (stub->templateObject())
-            TraceEdge(trc, &stub->templateObject(), "baseline-newarray-template");
-        TraceEdge(trc, &stub->templateGroup(), "baseline-newarray-template-group");
-        break;
-      }
-      case ICStub::NewObject_Fallback: {
-        ICNewObject_Fallback* stub = toNewObject_Fallback();
-        if (stub->templateObject())
-            TraceEdge(trc, &stub->templateObject(), "baseline-newobject-template");
-        break;
-      }
-      case ICStub::Rest_Fallback: {
-        ICRest_Fallback* stub = toRest_Fallback();
-        TraceEdge(trc, &stub->templateObject(), "baseline-rest-template");
-        break;
-      }
-      default:
-        break;
-    }
-}
-
-void
-ICFallbackStub::unlinkStub(Zone* zone, ICStub* prev, ICStub* stub)
-{
-    MOZ_ASSERT(stub->next());
-
-    // If stub is the last optimized stub, update lastStubPtrAddr.
-    if (stub->next() == this) {
-        MOZ_ASSERT(lastStubPtrAddr_ == stub->addressOfNext());
-        if (prev)
-            lastStubPtrAddr_ = prev->addressOfNext();
-        else
-            lastStubPtrAddr_ = icEntry()->addressOfFirstStub();
-        *lastStubPtrAddr_ = this;
-    } else {
-        if (prev) {
-            MOZ_ASSERT(prev->next() == stub);
-            prev->setNext(stub->next());
-        } else {
-            MOZ_ASSERT(icEntry()->firstStub() == stub);
-            icEntry()->setFirstStub(stub->next());
-        }
-    }
-
-    MOZ_ASSERT(numOptimizedStubs_ > 0);
-    numOptimizedStubs_--;
-
-    if (zone->needsIncrementalBarrier()) {
-        // We are removing edges from ICStub to gcthings. Perform one final trace
-        // of the stub for incremental GC, as it must know about those edges.
-        stub->trace(zone->barrierTracer());
-    }
-
-    if (ICStub::CanMakeCalls(stub->kind()) && stub->isMonitored()) {
-        // This stub can make calls so we can return to it if it's on the stack.
-        // We just have to reset its firstMonitorStub_ field to avoid a stale
-        // pointer when purgeOptimizedStubs destroys all optimized monitor
-        // stubs (unlinked stubs won't be updated).
-        ICTypeMonitor_Fallback* monitorFallback = toMonitoredFallbackStub()->fallbackMonitorStub();
-        stub->toMonitoredStub()->resetFirstMonitorStub(monitorFallback);
-    }
-
-#ifdef DEBUG
-    // Poison stub code to ensure we don't call this stub again. However, if this
-    // stub can make calls, a pointer to it may be stored in a stub frame on the
-    // stack, so we can't touch the stubCode_ or GC will crash when marking this
-    // pointer.
-    if (!ICStub::CanMakeCalls(stub->kind()))
-        stub->stubCode_ = (uint8_t*)0xbad;
-#endif
-}
-
-void
-ICFallbackStub::unlinkStubsWithKind(JSContext* cx, ICStub::Kind kind)
-{
-    for (ICStubIterator iter = beginChain(); !iter.atEnd(); iter++) {
-        if (iter->kind() == kind)
-            iter.unlink(cx);
-    }
-}
-
-void
-ICTypeMonitor_Fallback::resetMonitorStubChain(Zone* zone)
-{
-    if (zone->needsIncrementalBarrier()) {
-        // We are removing edges from monitored stubs to gcthings (JitCode).
-        // Perform one final trace of all monitor stubs for incremental GC,
-        // as it must know about those edges.
-        for (ICStub* s = firstMonitorStub_; !s->isTypeMonitor_Fallback(); s = s->next())
-            s->trace(zone->barrierTracer());
-    }
-
-    firstMonitorStub_ = this;
-    numOptimizedMonitorStubs_ = 0;
-
-    if (hasFallbackStub_) {
-        lastMonitorStubPtrAddr_ = nullptr;
-
-        // Reset firstMonitorStub_ field of all monitored stubs.
-        for (ICStubConstIterator iter = mainFallbackStub_->beginChainConst();
-             !iter.atEnd(); iter++)
-        {
-            if (!iter->isMonitored())
-                continue;
-            iter->toMonitoredStub()->resetFirstMonitorStub(this);
-        }
-    } else {
-        icEntry_->setFirstStub(this);
-        lastMonitorStubPtrAddr_ = icEntry_->addressOfFirstStub();
-    }
-}
-
-ICMonitoredStub::ICMonitoredStub(Kind kind, JitCode* stubCode, ICStub* firstMonitorStub)
-  : ICStub(kind, ICStub::Monitored, stubCode),
-    firstMonitorStub_(firstMonitorStub)
-{
-    // If the first monitored stub is a ICTypeMonitor_Fallback stub, then
-    // double check that _its_ firstMonitorStub is the same as this one.
-    MOZ_ASSERT_IF(firstMonitorStub_->isTypeMonitor_Fallback(),
-                  firstMonitorStub_->toTypeMonitor_Fallback()->firstMonitorStub() ==
-                     firstMonitorStub_);
-}
-
-bool
-ICMonitoredFallbackStub::initMonitoringChain(JSContext* cx, ICStubSpace* space)
-{
-    MOZ_ASSERT(fallbackMonitorStub_ == nullptr);
-
-    ICTypeMonitor_Fallback::Compiler compiler(cx, this);
-    ICTypeMonitor_Fallback* stub = compiler.getStub(space);
-    if (!stub)
-        return false;
-    fallbackMonitorStub_ = stub;
-    return true;
-}
-
-bool
-ICMonitoredFallbackStub::addMonitorStubForValue(JSContext* cx, JSScript* script, HandleValue val)
-{
-    return fallbackMonitorStub_->addMonitorStubForValue(cx, script, val);
-}
-
-bool
-ICUpdatedStub::initUpdatingChain(JSContext* cx, ICStubSpace* space)
-{
-    MOZ_ASSERT(firstUpdateStub_ == nullptr);
-
-    ICTypeUpdate_Fallback::Compiler compiler(cx);
-    ICTypeUpdate_Fallback* stub = compiler.getStub