merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 09 Aug 2016 15:44:51 +0200
changeset 308748 6cf0089510fad8deb866136f5b92bbced9498447
parent 308654 0813514a828bf931605bd22596d8fe876c1bb368 (current diff)
parent 308747 7bbd0953ab0b4d9ce9dab06e86a0f245b568b72c (diff)
child 308753 4dd7d0ccddf9d06268d2061efe525226b5cf7c2b
child 308793 613fec9a571e48c64918ebd0a8153096300ff992
child 308819 99f20b0c58d01780b427936cc216403535e6b46c
push id30547
push usercbook@mozilla.com
push dateTue, 09 Aug 2016 13:45:15 +0000
treeherdermozilla-central@6cf0089510fa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone51.0a1
first release with
nightly linux32
6cf0089510fa / 51.0a1 / 20160810030202 / files
nightly linux64
6cf0089510fa / 51.0a1 / 20160810030202 / files
nightly mac
6cf0089510fa / 51.0a1 / 20160810030202 / files
nightly win32
6cf0089510fa / 51.0a1 / 20160810030202 / files
nightly win64
6cf0089510fa / 51.0a1 / 20160810030202 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
accessible/interfaces/nsIAccessibilityService.h
accessible/interfaces/nsIAccessibleRetrieval.idl
browser/themes/linux/content-contextmenu.svg
browser/themes/osx/content-contextmenu.svg
gfx/thebes/DeviceManagerD3D11.cpp
gfx/thebes/DeviceManagerD3D11.h
layout/inspector/inDOMUtils.cpp
layout/style/StyleAnimationValue.cpp
layout/style/nsCSSParser.cpp
layout/style/nsCSSPropList.h
layout/style/nsCSSProps.cpp
layout/style/nsCSSProps.h
layout/style/nsCSSValue.cpp
layout/style/nsComputedDOMStyle.cpp
layout/style/nsComputedDOMStyle.h
layout/style/nsComputedDOMStylePropertyList.h
layout/style/nsRuleNode.cpp
layout/style/nsStyleConsts.h
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/test/property_database.js
layout/svg/nsCSSClipPathInstance.cpp
layout/svg/nsCSSClipPathInstance.h
layout/svg/nsSVGEffects.cpp
modules/libpref/init/all.js
taskcluster/ci/legacy/tasks/builds/b2g_aries_debug.yml
taskcluster/ci/legacy/tasks/builds/b2g_aries_eng.yml
taskcluster/ci/legacy/tasks/builds/b2g_nexus_5l_debug.yml
taskcluster/ci/legacy/tasks/builds/b2g_nexus_5l_eng.yml
taskcluster/mach_commands.py
testing/profiles/prefs_general.js
testing/web-platform/meta/XMLHttpRequest/abort-after-send.htm.ini
testing/web-platform/meta/XMLHttpRequest/abort-during-upload.htm.ini
testing/web-platform/meta/XMLHttpRequest/abort-event-order.htm.ini
testing/web-platform/meta/XMLHttpRequest/event-progress.htm.ini
testing/web-platform/meta/XMLHttpRequest/response-data-progress.htm.ini
testing/web-platform/meta/XMLHttpRequest/send-network-error-async-events.sub.htm.ini
testing/web-platform/meta/XMLHttpRequest/send-timeout-events.htm.ini
toolkit/components/telemetry/Histograms.json
toolkit/themes/linux/global/icons/panelarrow-horizontal.svg
toolkit/themes/linux/global/icons/panelarrow-vertical.svg
--- a/accessible/base/DocManager.cpp
+++ b/accessible/base/DocManager.cpp
@@ -84,24 +84,33 @@ DocManager::FindAccessibleInCache(nsINod
   }
   return nullptr;
 }
 
 void
 DocManager::NotifyOfDocumentShutdown(DocAccessible* aDocument,
                                      nsIDocument* aDOMDocument)
 {
+  // We need to remove listeners in both cases, when document is being shutdown
+  // or when accessibility service is being shut down as well.
+  RemoveListeners(aDOMDocument);
+
+  // Document will already be removed when accessibility service is shutting
+  // down so we do not need to remove it twice.
+  if (nsAccessibilityService::IsShutdown()) {
+    return;
+  }
+
   xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument);
   if (xpcDoc) {
     xpcDoc->Shutdown();
     mXPCDocumentCache.Remove(aDocument);
   }
 
   mDocAccessibleCache.Remove(aDOMDocument);
-  RemoveListeners(aDOMDocument);
 }
 
 void
 DocManager::NotifyOfRemoteDocShutdown(DocAccessibleParent* aDoc)
 {
   xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc);
   if (doc) {
     doc->Shutdown();
@@ -525,29 +534,42 @@ DocManager::CreateDocOrRootAccessible(ns
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // DocManager static
 
 void
 DocManager::ClearDocCache()
 {
-  // This unusual do-one-element-per-iterator approach is required because each
-  // DocAccessible is removed elsewhere upon its Shutdown() method being
-  // called, which invalidates the existing iterator.
   while (mDocAccessibleCache.Count() > 0) {
     auto iter = mDocAccessibleCache.Iter();
     MOZ_ASSERT(!iter.Done());
     DocAccessible* docAcc = iter.UserData();
     NS_ASSERTION(docAcc,
                  "No doc accessible for the object in doc accessible cache!");
     if (docAcc) {
       docAcc->Shutdown();
     }
+
+    iter.Remove();
   }
+
+  // Ensure that all xpcom accessible documents are shut down as well.
+  while (mXPCDocumentCache.Count() > 0) {
+    auto iter = mXPCDocumentCache.Iter();
+    MOZ_ASSERT(!iter.Done());
+    xpcAccessibleDocument* xpcDoc = iter.UserData();
+    NS_ASSERTION(xpcDoc, "No xpc doc for the object in xpc doc cache!");
+
+    if (xpcDoc) {
+      xpcDoc->Shutdown();
+    }
+
+    iter.Remove();
+   }
 }
 
 void
 DocManager::RemoteDocAdded(DocAccessibleParent* aDoc)
 {
   if (!sRemoteDocuments) {
     sRemoteDocuments = new nsTArray<DocAccessibleParent*>;
     ClearOnShutdown(&sRemoteDocuments);
--- a/accessible/base/NotificationController.cpp
+++ b/accessible/base/NotificationController.cpp
@@ -214,34 +214,16 @@ NotificationController::WillRefresh(mozi
     NS_ASSERTION(mContentInsertions.Count() == 0,
                  "Pending content insertions while initial accessible tree isn't created!");
   }
 
   // Initialize scroll support if needed.
   if (!(mDocument->mDocFlags & DocAccessible::eScrollInitialized))
     mDocument->AddScrollListener();
 
-  // Process content inserted notifications to update the tree. Process other
-  // notifications like DOM events and then flush event queue. If any new
-  // notifications are queued during this processing then they will be processed
-  // on next refresh. If notification processing queues up new events then they
-  // are processed in this refresh. If events processing queues up new events
-  // then new events are processed on next refresh.
-  // Note: notification processing or event handling may shut down the owning
-  // document accessible.
-
-  // Process only currently queued content inserted notifications.
-  for (auto iter = mContentInsertions.ConstIter(); !iter.Done(); iter.Next()) {
-    mDocument->ProcessContentInserted(iter.Key(), iter.UserData());
-    if (!mDocument) {
-      return;
-    }
-  }
-  mContentInsertions.Clear();
-
   // Process rendered text change notifications.
   for (auto iter = mTextHash.Iter(); !iter.Done(); iter.Next()) {
     nsCOMPtrHashKey<nsIContent>* entry = iter.Get();
     nsIContent* textNode = entry->GetKey();
     Accessible* textAcc = mDocument->GetAccessible(textNode);
 
     // If the text node is not in tree or doesn't have frame then this case should
     // have been handled already by content removal notifications.
@@ -306,27 +288,37 @@ NotificationController::WillRefresh(mozi
       if (logging::IsEnabled(logging::eTree | logging::eText)) {
         logging::MsgBegin("TREE", "text node gains new content; doc: %p", mDocument);
         logging::Node("container", containerElm);
         logging::Node("content", textNode);
         logging::MsgEnd();
       }
   #endif
 
-      // Make sure the text node is in accessible document still.
       Accessible* container = mDocument->AccessibleOrTrueContainer(containerNode);
       MOZ_ASSERT(container,
                  "Text node having rendered text hasn't accessible document!");
       if (container) {
-        mDocument->ProcessContentInserted(container, textNode);
+        nsTArray<nsCOMPtr<nsIContent>>* list =
+          mContentInsertions.LookupOrAdd(container);
+        list->AppendElement(textNode);
       }
     }
   }
   mTextHash.Clear();
 
+  // Process content inserted notifications to update the tree.
+  for (auto iter = mContentInsertions.ConstIter(); !iter.Done(); iter.Next()) {
+    mDocument->ProcessContentInserted(iter.Key(), iter.UserData());
+    if (!mDocument) {
+      return;
+    }
+  }
+  mContentInsertions.Clear();
+
   // Bind hanging child documents.
   uint32_t hangingDocCnt = mHangingChildDocuments.Length();
   nsTArray<RefPtr<DocAccessible>> newChildDocs;
   for (uint32_t idx = 0; idx < hangingDocCnt; idx++) {
     DocAccessible* childDoc = mHangingChildDocuments[idx];
     if (childDoc->IsDefunct())
       continue;
 
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -15,17 +15,16 @@
 #include "HTMLElementAccessibles.h"
 #include "HTMLImageMapAccessible.h"
 #include "HTMLLinkAccessible.h"
 #include "HTMLListAccessible.h"
 #include "HTMLSelectAccessible.h"
 #include "HTMLTableAccessibleWrap.h"
 #include "HyperTextAccessibleWrap.h"
 #include "RootAccessible.h"
-#include "nsAccessiblePivot.h"
 #include "nsAccUtils.h"
 #include "nsArrayUtils.h"
 #include "nsAttrName.h"
 #include "nsEventShell.h"
 #include "nsIURI.h"
 #include "OuterDocAccessible.h"
 #include "Platform.h"
 #include "Role.h"
@@ -260,16 +259,17 @@ static const MarkupMapInfo sMarkupMapLis
 ////////////////////////////////////////////////////////////////////////////////
 // nsAccessibilityService
 ////////////////////////////////////////////////////////////////////////////////
 
 nsAccessibilityService *nsAccessibilityService::gAccessibilityService = nullptr;
 ApplicationAccessible* nsAccessibilityService::gApplicationAccessible = nullptr;
 xpcAccessibleApplication* nsAccessibilityService::gXPCApplicationAccessible = nullptr;
 bool nsAccessibilityService::gIsShutdown = true;
+bool nsAccessibilityService::gIsPlatformCaller = false;
 
 nsAccessibilityService::nsAccessibilityService() :
   DocManager(), FocusManager(), mMarkupMaps(ArrayLength(sMarkupMapList))
 {
 }
 
 nsAccessibilityService::~nsAccessibilityService()
 {
@@ -335,18 +335,16 @@ nsAccessibilityService::ListenersChanged
   return NS_OK;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsISupports
 
 NS_IMPL_ISUPPORTS_INHERITED(nsAccessibilityService,
                             DocManager,
-                            nsIAccessibilityService,
-                            nsIAccessibleRetrieval,
                             nsIObserver,
                             nsIListenerChangeListener,
                             nsISelectionListener) // from SelectionManager
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsIObserver
 
 NS_IMETHODIMP
@@ -354,39 +352,34 @@ nsAccessibilityService::Observe(nsISuppo
                          const char16_t *aData)
 {
   if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID))
     Shutdown();
 
   return NS_OK;
 }
 
-// nsIAccessibilityService
 void
 nsAccessibilityService::NotifyOfAnchorJumpTo(nsIContent* aTargetNode)
 {
   nsIDocument* documentNode = aTargetNode->GetUncomposedDoc();
   if (documentNode) {
     DocAccessible* document = GetDocAccessible(documentNode);
     if (document)
       document->SetAnchorJump(aTargetNode);
   }
 }
 
-// nsIAccessibilityService
 void
 nsAccessibilityService::FireAccessibleEvent(uint32_t aEvent,
                                             Accessible* aTarget)
 {
   nsEventShell::FireEvent(aEvent, aTarget);
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// nsIAccessibilityService
-
 Accessible*
 nsAccessibilityService::GetRootDocumentAccessible(nsIPresShell* aPresShell,
                                                   bool aCanCreate)
 {
   nsIPresShell* ps = aPresShell;
   nsIDocument* documentNode = aPresShell->GetDocument();
   if (documentNode) {
     nsCOMPtr<nsIDocShellTreeItem> treeItem(documentNode->GetDocShell());
@@ -732,290 +725,233 @@ void
 nsAccessibilityService::RecreateAccessible(nsIPresShell* aPresShell,
                                            nsIContent* aContent)
 {
   DocAccessible* document = GetDocAccessible(aPresShell);
   if (document)
     document->RecreateAccessible(aContent);
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// nsIAccessibleRetrieval
-
-NS_IMETHODIMP
-nsAccessibilityService::GetApplicationAccessible(nsIAccessible** aAccessibleApplication)
-{
-  NS_ENSURE_ARG_POINTER(aAccessibleApplication);
-
-  NS_IF_ADDREF(*aAccessibleApplication = XPCApplicationAcc());
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsAccessibilityService::GetAccessibleFor(nsIDOMNode *aNode,
-                                         nsIAccessible **aAccessible)
-{
-  NS_ENSURE_ARG_POINTER(aAccessible);
-  *aAccessible = nullptr;
-  if (!aNode)
-    return NS_OK;
-
-  nsCOMPtr<nsINode> node(do_QueryInterface(aNode));
-  if (!node)
-    return NS_ERROR_INVALID_ARG;
-
-  DocAccessible* document = GetDocAccessible(node->OwnerDoc());
-  if (document)
-    NS_IF_ADDREF(*aAccessible = ToXPC(document->GetAccessible(node)));
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
+void
 nsAccessibilityService::GetStringRole(uint32_t aRole, nsAString& aString)
 {
 #define ROLE(geckoRole, stringRole, atkRole, \
              macRole, msaaRole, ia2Role, nameRule) \
   case roles::geckoRole: \
     CopyUTF8toUTF16(stringRole, aString); \
-    return NS_OK;
+    return;
 
   switch (aRole) {
 #include "RoleMap.h"
     default:
       aString.AssignLiteral("unknown");
-      return NS_OK;
+      return;
   }
 
 #undef ROLE
 }
 
-NS_IMETHODIMP
+void
 nsAccessibilityService::GetStringStates(uint32_t aState, uint32_t aExtraState,
                                         nsISupports **aStringStates)
 {
   RefPtr<DOMStringList> stringStates = new DOMStringList();
 
   uint64_t state = nsAccUtils::To64State(aState, aExtraState);
 
   // states
-  if (state & states::UNAVAILABLE)
+  if (state & states::UNAVAILABLE) {
     stringStates->Add(NS_LITERAL_STRING("unavailable"));
-  if (state & states::SELECTED)
+  }
+  if (state & states::SELECTED) {
     stringStates->Add(NS_LITERAL_STRING("selected"));
-  if (state & states::FOCUSED)
+  }
+  if (state & states::FOCUSED) {
     stringStates->Add(NS_LITERAL_STRING("focused"));
-  if (state & states::PRESSED)
+  }
+  if (state & states::PRESSED) {
     stringStates->Add(NS_LITERAL_STRING("pressed"));
-  if (state & states::CHECKED)
+  }
+  if (state & states::CHECKED) {
     stringStates->Add(NS_LITERAL_STRING("checked"));
-  if (state & states::MIXED)
+  }
+  if (state & states::MIXED) {
     stringStates->Add(NS_LITERAL_STRING("mixed"));
-  if (state & states::READONLY)
+  }
+  if (state & states::READONLY) {
     stringStates->Add(NS_LITERAL_STRING("readonly"));
-  if (state & states::HOTTRACKED)
+  }
+  if (state & states::HOTTRACKED) {
     stringStates->Add(NS_LITERAL_STRING("hottracked"));
-  if (state & states::DEFAULT)
+  }
+  if (state & states::DEFAULT) {
     stringStates->Add(NS_LITERAL_STRING("default"));
-  if (state & states::EXPANDED)
+  }
+  if (state & states::EXPANDED) {
     stringStates->Add(NS_LITERAL_STRING("expanded"));
-  if (state & states::COLLAPSED)
+  }
+  if (state & states::COLLAPSED) {
     stringStates->Add(NS_LITERAL_STRING("collapsed"));
-  if (state & states::BUSY)
+  }
+  if (state & states::BUSY) {
     stringStates->Add(NS_LITERAL_STRING("busy"));
-  if (state & states::FLOATING)
+  }
+  if (state & states::FLOATING) {
     stringStates->Add(NS_LITERAL_STRING("floating"));
-  if (state & states::ANIMATED)
+  }
+  if (state & states::ANIMATED) {
     stringStates->Add(NS_LITERAL_STRING("animated"));
-  if (state & states::INVISIBLE)
+  }
+  if (state & states::INVISIBLE) {
     stringStates->Add(NS_LITERAL_STRING("invisible"));
-  if (state & states::OFFSCREEN)
+  }
+  if (state & states::OFFSCREEN) {
     stringStates->Add(NS_LITERAL_STRING("offscreen"));
-  if (state & states::SIZEABLE)
+  }
+  if (state & states::SIZEABLE) {
     stringStates->Add(NS_LITERAL_STRING("sizeable"));
-  if (state & states::MOVEABLE)
+  }
+  if (state & states::MOVEABLE) {
     stringStates->Add(NS_LITERAL_STRING("moveable"));
-  if (state & states::SELFVOICING)
+  }
+  if (state & states::SELFVOICING) {
     stringStates->Add(NS_LITERAL_STRING("selfvoicing"));
-  if (state & states::FOCUSABLE)
+  }
+  if (state & states::FOCUSABLE) {
     stringStates->Add(NS_LITERAL_STRING("focusable"));
-  if (state & states::SELECTABLE)
+  }
+  if (state & states::SELECTABLE) {
     stringStates->Add(NS_LITERAL_STRING("selectable"));
-  if (state & states::LINKED)
+  }
+  if (state & states::LINKED) {
     stringStates->Add(NS_LITERAL_STRING("linked"));
-  if (state & states::TRAVERSED)
+  }
+  if (state & states::TRAVERSED) {
     stringStates->Add(NS_LITERAL_STRING("traversed"));
-  if (state & states::MULTISELECTABLE)
+  }
+  if (state & states::MULTISELECTABLE) {
     stringStates->Add(NS_LITERAL_STRING("multiselectable"));
-  if (state & states::EXTSELECTABLE)
+  }
+  if (state & states::EXTSELECTABLE) {
     stringStates->Add(NS_LITERAL_STRING("extselectable"));
-  if (state & states::PROTECTED)
+  }
+  if (state & states::PROTECTED) {
     stringStates->Add(NS_LITERAL_STRING("protected"));
-  if (state & states::HASPOPUP)
+  }
+  if (state & states::HASPOPUP) {
     stringStates->Add(NS_LITERAL_STRING("haspopup"));
-  if (state & states::REQUIRED)
+  }
+  if (state & states::REQUIRED) {
     stringStates->Add(NS_LITERAL_STRING("required"));
-  if (state & states::ALERT)
+  }
+  if (state & states::ALERT) {
     stringStates->Add(NS_LITERAL_STRING("alert"));
-  if (state & states::INVALID)
+  }
+  if (state & states::INVALID) {
     stringStates->Add(NS_LITERAL_STRING("invalid"));
-  if (state & states::CHECKABLE)
+  }
+  if (state & states::CHECKABLE) {
     stringStates->Add(NS_LITERAL_STRING("checkable"));
+  }
 
   // extraStates
-  if (state & states::SUPPORTS_AUTOCOMPLETION)
+  if (state & states::SUPPORTS_AUTOCOMPLETION) {
     stringStates->Add(NS_LITERAL_STRING("autocompletion"));
-  if (state & states::DEFUNCT)
+  }
+  if (state & states::DEFUNCT) {
     stringStates->Add(NS_LITERAL_STRING("defunct"));
-  if (state & states::SELECTABLE_TEXT)
+  }
+  if (state & states::SELECTABLE_TEXT) {
     stringStates->Add(NS_LITERAL_STRING("selectable text"));
-  if (state & states::EDITABLE)
+  }
+  if (state & states::EDITABLE) {
     stringStates->Add(NS_LITERAL_STRING("editable"));
-  if (state & states::ACTIVE)
+  }
+  if (state & states::ACTIVE) {
     stringStates->Add(NS_LITERAL_STRING("active"));
-  if (state & states::MODAL)
+  }
+  if (state & states::MODAL) {
     stringStates->Add(NS_LITERAL_STRING("modal"));
-  if (state & states::MULTI_LINE)
+  }
+  if (state & states::MULTI_LINE) {
     stringStates->Add(NS_LITERAL_STRING("multi line"));
-  if (state & states::HORIZONTAL)
+  }
+  if (state & states::HORIZONTAL) {
     stringStates->Add(NS_LITERAL_STRING("horizontal"));
-  if (state & states::OPAQUE1)
+  }
+  if (state & states::OPAQUE1) {
     stringStates->Add(NS_LITERAL_STRING("opaque"));
-  if (state & states::SINGLE_LINE)
+  }
+  if (state & states::SINGLE_LINE) {
     stringStates->Add(NS_LITERAL_STRING("single line"));
-  if (state & states::TRANSIENT)
+  }
+  if (state & states::TRANSIENT) {
     stringStates->Add(NS_LITERAL_STRING("transient"));
-  if (state & states::VERTICAL)
+  }
+  if (state & states::VERTICAL) {
     stringStates->Add(NS_LITERAL_STRING("vertical"));
-  if (state & states::STALE)
+  }
+  if (state & states::STALE) {
     stringStates->Add(NS_LITERAL_STRING("stale"));
-  if (state & states::ENABLED)
+  }
+  if (state & states::ENABLED) {
     stringStates->Add(NS_LITERAL_STRING("enabled"));
-  if (state & states::SENSITIVE)
+  }
+  if (state & states::SENSITIVE) {
     stringStates->Add(NS_LITERAL_STRING("sensitive"));
-  if (state & states::EXPANDABLE)
+  }
+  if (state & states::EXPANDABLE) {
     stringStates->Add(NS_LITERAL_STRING("expandable"));
+  }
 
   //unknown states
-  if (!stringStates->Length())
+  if (!stringStates->Length()) {
     stringStates->Add(NS_LITERAL_STRING("unknown"));
+  }
 
   stringStates.forget(aStringStates);
-  return NS_OK;
 }
 
-// nsIAccessibleRetrieval::getStringEventType()
-NS_IMETHODIMP
+void
 nsAccessibilityService::GetStringEventType(uint32_t aEventType,
                                            nsAString& aString)
 {
   NS_ASSERTION(nsIAccessibleEvent::EVENT_LAST_ENTRY == ArrayLength(kEventTypeNames),
                "nsIAccessibleEvent constants are out of sync to kEventTypeNames");
 
   if (aEventType >= ArrayLength(kEventTypeNames)) {
     aString.AssignLiteral("unknown");
-    return NS_OK;
+    return;
   }
 
   CopyUTF8toUTF16(kEventTypeNames[aEventType], aString);
-  return NS_OK;
 }
 
-// nsIAccessibleRetrieval::getStringRelationType()
-NS_IMETHODIMP
+void
 nsAccessibilityService::GetStringRelationType(uint32_t aRelationType,
                                               nsAString& aString)
 {
-  NS_ENSURE_ARG(aRelationType <= static_cast<uint32_t>(RelationType::LAST));
+  NS_ENSURE_TRUE_VOID(aRelationType <= static_cast<uint32_t>(RelationType::LAST));
 
 #define RELATIONTYPE(geckoType, geckoTypeName, atkType, msaaType, ia2Type) \
   case RelationType::geckoType: \
     aString.AssignLiteral(geckoTypeName); \
-    return NS_OK;
+    return;
 
   RelationType relationType = static_cast<RelationType>(aRelationType);
   switch (relationType) {
 #include "RelationTypeMap.h"
     default:
       aString.AssignLiteral("unknown");
-      return NS_OK;
+      return;
   }
 
 #undef RELATIONTYPE
 }
 
-NS_IMETHODIMP
-nsAccessibilityService::GetAccessibleFromCache(nsIDOMNode* aNode,
-                                               nsIAccessible** aAccessible)
-{
-  NS_ENSURE_ARG_POINTER(aAccessible);
-  *aAccessible = nullptr;
-  if (!aNode)
-    return NS_OK;
-
-  nsCOMPtr<nsINode> node(do_QueryInterface(aNode));
-  if (!node)
-    return NS_ERROR_INVALID_ARG;
-
-  // Search for an accessible in each of our per document accessible object
-  // caches. If we don't find it, and the given node is itself a document, check
-  // our cache of document accessibles (document cache). Note usually shutdown
-  // document accessibles are not stored in the document cache, however an
-  // "unofficially" shutdown document (i.e. not from DocManager) can still
-  // exist in the document cache.
-  Accessible* accessible = FindAccessibleInCache(node);
-  if (!accessible) {
-    nsCOMPtr<nsIDocument> document(do_QueryInterface(node));
-    if (document)
-      accessible = GetExistingDocAccessible(document);
-  }
-
-  NS_IF_ADDREF(*aAccessible = ToXPC(accessible));
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsAccessibilityService::CreateAccessiblePivot(nsIAccessible* aRoot,
-                                              nsIAccessiblePivot** aPivot)
-{
-  NS_ENSURE_ARG_POINTER(aPivot);
-  NS_ENSURE_ARG(aRoot);
-  *aPivot = nullptr;
-
-  Accessible* accessibleRoot = aRoot->ToInternalAccessible();
-  NS_ENSURE_TRUE(accessibleRoot, NS_ERROR_INVALID_ARG);
-
-  nsAccessiblePivot* pivot = new nsAccessiblePivot(accessibleRoot);
-  NS_ADDREF(*aPivot = pivot);
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsAccessibilityService::SetLogging(const nsACString& aModules)
-{
-#ifdef A11Y_LOG
-  logging::Enable(PromiseFlatCString(aModules));
-#endif
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsAccessibilityService::IsLogged(const nsAString& aModule, bool* aIsLogged)
-{
-  NS_ENSURE_ARG_POINTER(aIsLogged);
-  *aIsLogged = false;
-
-#ifdef A11Y_LOG
-  *aIsLogged = logging::IsEnabled(aModule);
-#endif
-
-  return NS_OK;
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // nsAccessibilityService public
 
 Accessible*
 nsAccessibilityService::CreateAccessible(nsINode* aNode,
                                          Accessible* aContext,
                                          bool* aIsSubtreeHidden)
 {
@@ -1322,16 +1258,17 @@ nsAccessibilityService::Init()
   for (uint32_t i = 0; i < ArrayLength(sMarkupMapList); i++)
     mMarkupMaps.Put(*sMarkupMapList[i].tag, &sMarkupMapList[i]);
 
 #ifdef A11Y_LOG
   logging::CheckEnv();
 #endif
 
   gAccessibilityService = this;
+  NS_ADDREF(gAccessibilityService); // will release in Shutdown()
 
   if (XRE_IsParentProcess())
     gApplicationAccessible = new ApplicationAccessibleWrap();
   else
     gApplicationAccessible = new ApplicationAccessible();
 
   NS_ADDREF(gApplicationAccessible); // will release in Shutdown()
   gApplicationAccessible->Init();
@@ -1354,16 +1291,25 @@ nsAccessibilityService::Init()
     PlatformInit();
 
   return true;
 }
 
 void
 nsAccessibilityService::Shutdown()
 {
+  // Application is going to be closed, shutdown accessibility and mark
+  // accessibility service as shutdown to prevent calls of its methods.
+  // Don't null accessibility service static member at this point to be safe
+  // if someone will try to operate with it.
+
+  MOZ_ASSERT(!gIsShutdown, "Accessibility was shutdown already");
+
+  gIsShutdown = true;
+
   // Remove observers.
   nsCOMPtr<nsIObserverService> observerService =
       mozilla::services::GetObserverService();
   if (observerService) {
     observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
 
     static const char16_t kShutdownIndicator[] = { '0', 0 };
     observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown", kShutdownIndicator);
@@ -1379,34 +1325,29 @@ nsAccessibilityService::Shutdown()
 
   uint32_t timerCount = sPluginTimers->Length();
   for (uint32_t i = 0; i < timerCount; i++)
     sPluginTimers->ElementAt(i)->Cancel();
 
   sPluginTimers = nullptr;
 #endif
 
-  // Application is going to be closed, shutdown accessibility and mark
-  // accessibility service as shutdown to prevent calls of its methods.
-  // Don't null accessibility service static member at this point to be safe
-  // if someone will try to operate with it.
-
-  NS_ASSERTION(!gIsShutdown, "Accessibility was shutdown already");
-
-  gIsShutdown = true;
-
   if (XRE_IsParentProcess())
     PlatformShutdown();
 
   gApplicationAccessible->Shutdown();
   NS_RELEASE(gApplicationAccessible);
   gApplicationAccessible = nullptr;
 
   NS_IF_RELEASE(gXPCApplicationAccessible);
   gXPCApplicationAccessible = nullptr;
+
+  NS_RELEASE(gAccessibilityService);
+  gAccessibilityService = nullptr;
+  gIsPlatformCaller = false;
 }
 
 already_AddRefed<Accessible>
 nsAccessibilityService::CreateAccessibleByType(nsIContent* aContent,
                                                DocAccessible* aDoc)
 {
   nsAutoString role;
   nsCoreUtils::XBLBindingRole(aContent, role);
@@ -1751,19 +1692,16 @@ nsAccessibilityService::MarkupAttributes
 
       continue;
     }
 
     nsAccUtils::SetAccAttr(aAttributes, *info->name, *info->value);
   }
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// nsIAccessibilityService (DON'T put methods here)
-
 Accessible*
 nsAccessibilityService::AddNativeRootAccessible(void* aAtkAccessible)
 {
 #ifdef MOZ_ACCESSIBILITY_ATK
   ApplicationAccessible* applicationAcc = ApplicationAcc();
   if (!applicationAcc)
     return nullptr;
 
@@ -1798,48 +1736,16 @@ nsAccessibilityService::HasAccessible(ns
   DocAccessible* document = GetDocAccessible(node->OwnerDoc());
   if (!document)
     return false;
 
   return document->HasAccessible(node);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// NS_GetAccessibilityService
-////////////////////////////////////////////////////////////////////////////////
-
-/**
- * Return accessibility service; creating one if necessary.
- */
-nsresult
-NS_GetAccessibilityService(nsIAccessibilityService** aResult)
-{
-  NS_ENSURE_TRUE(aResult, NS_ERROR_NULL_POINTER);
-  *aResult = nullptr;
-
-  if (nsAccessibilityService::gAccessibilityService) {
-    NS_ADDREF(*aResult = nsAccessibilityService::gAccessibilityService);
-    return NS_OK;
-  }
-
-  RefPtr<nsAccessibilityService> service = new nsAccessibilityService();
-  NS_ENSURE_TRUE(service, NS_ERROR_OUT_OF_MEMORY);
-
-  if (!service->Init()) {
-    service->Shutdown();
-    return NS_ERROR_FAILURE;
-  }
-
-  statistics::A11yInitialized();
-
-  NS_ADDREF(*aResult = service);
-  return NS_OK;
-}
-
-////////////////////////////////////////////////////////////////////////////////
 // nsAccessibilityService private (DON'T put methods here)
 
 #ifdef MOZ_XUL
 already_AddRefed<Accessible>
 nsAccessibilityService::CreateAccessibleForXULTree(nsIContent* aContent,
                                                    DocAccessible* aDoc)
 {
   nsIContent* child = nsTreeUtils::GetDescendantChild(aContent,
@@ -1864,16 +1770,48 @@ nsAccessibilityService::CreateAccessible
 
   // Table or tree table accessible.
   RefPtr<Accessible> accessible =
     new XULTreeGridAccessibleWrap(aContent, aDoc, treeFrame);
   return accessible.forget();
 }
 #endif
 
+nsAccessibilityService*
+GetOrCreateAccService(bool aIsPlatformCaller)
+{
+  if (aIsPlatformCaller) {
+    nsAccessibilityService::gIsPlatformCaller = aIsPlatformCaller;
+  }
+
+  if (!nsAccessibilityService::gAccessibilityService) {
+    RefPtr<nsAccessibilityService> service = new nsAccessibilityService();
+    if (!service->Init()) {
+      service->Shutdown();
+      return nullptr;
+    }
+  }
+
+  MOZ_ASSERT(nsAccessibilityService::gAccessibilityService,
+             "Accessible service is not initialized.");
+  return nsAccessibilityService::gAccessibilityService;
+}
+
+bool
+CanShutdownAccService()
+{
+  nsAccessibilityService* accService = nsAccessibilityService::gAccessibilityService;
+  if (!accService) {
+    return false;
+  }
+  return !xpcAccessibilityService::IsInUse() &&
+         !accService->IsPlatformCaller() && !accService->IsShutdown() &&
+         !nsCoreUtils::AccEventObserversExist();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Services
 ////////////////////////////////////////////////////////////////////////////////
 
 namespace mozilla {
 namespace a11y {
 
 FocusManager*
--- a/accessible/base/nsAccessibilityService.h
+++ b/accessible/base/nsAccessibilityService.h
@@ -1,26 +1,26 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef __nsAccessibilityService_h__
 #define __nsAccessibilityService_h__
 
-#include "nsIAccessibilityService.h"
-
 #include "mozilla/a11y/DocManager.h"
 #include "mozilla/a11y/FocusManager.h"
 #include "mozilla/a11y/Role.h"
 #include "mozilla/a11y/SelectionManager.h"
 #include "mozilla/Preferences.h"
 
 #include "nsIObserver.h"
+#include "nsIAccessibleEvent.h"
 #include "nsIEventListenerService.h"
+#include "xpcAccessibilityService.h"
 
 class nsImageFrame;
 class nsIArray;
 class nsIPersistentProperties;
 class nsPluginFrame;
 class nsITreeView;
 
 namespace mozilla {
@@ -63,50 +63,68 @@ struct MarkupMapInfo {
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 class nsAccessibilityService final : public mozilla::a11y::DocManager,
                                      public mozilla::a11y::FocusManager,
                                      public mozilla::a11y::SelectionManager,
-                                     public nsIAccessibilityService,
                                      public nsIListenerChangeListener,
                                      public nsIObserver
 {
 public:
   typedef mozilla::a11y::Accessible Accessible;
   typedef mozilla::a11y::DocAccessible DocAccessible;
 
   // nsIListenerChangeListener
   NS_IMETHOD ListenersChanged(nsIArray* aEventChanges) override;
 
 protected:
-  virtual ~nsAccessibilityService();
+  ~nsAccessibilityService();
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_NSIACCESSIBLERETRIEVAL
   NS_DECL_NSIOBSERVER
 
-  // nsIAccessibilityService
-  virtual Accessible* GetRootDocumentAccessible(nsIPresShell* aPresShell,
-                                                bool aCanCreate) override;
+  Accessible* GetRootDocumentAccessible(nsIPresShell* aPresShell,
+                                        bool aCanCreate);
   already_AddRefed<Accessible>
     CreatePluginAccessible(nsPluginFrame* aFrame, nsIContent* aContent,
                            Accessible* aContext);
 
   /**
    * Adds/remove ATK root accessible for gtk+ native window to/from children
    * of the application accessible.
    */
-  virtual Accessible* AddNativeRootAccessible(void* aAtkAccessible) override;
-  virtual void RemoveNativeRootAccessible(Accessible* aRootAccessible) override;
+  Accessible* AddNativeRootAccessible(void* aAtkAccessible);
+  void RemoveNativeRootAccessible(Accessible* aRootAccessible);
+
+  bool HasAccessible(nsIDOMNode* aDOMNode);
+
+  /**
+   * Get a string equivalent for an accessilbe role value.
+   */
+  void GetStringRole(uint32_t aRole, nsAString& aString);
 
-  virtual bool HasAccessible(nsIDOMNode* aDOMNode) override;
+  /**
+   * Get a string equivalent for an accessible state/extra state.
+   */
+  void GetStringStates(uint32_t aState, uint32_t aExtraState,
+                       nsISupports **aStringStates);
+
+  /**
+   * Get a string equivalent for an accessible event value.
+   */
+  void GetStringEventType(uint32_t aEventType, nsAString& aString);
+
+  /**
+   * Get a string equivalent for an accessible relation type.
+   */
+  void GetStringRelationType(uint32_t aRelationType, nsAString& aString);
 
   // nsAccesibilityService
   /**
    * Notification used to update the accessible tree when deck panel is
    * switched.
    */
   void DeckPanelSwitched(nsIPresShell* aPresShell, nsIContent* aDeckNode,
                          nsIFrame* aPrevBoxFrame, nsIFrame* aCurrentBoxFrame);
@@ -118,35 +136,35 @@ public:
   void ContentRangeInserted(nsIPresShell* aPresShell, nsIContent* aContainer,
                             nsIContent* aStartChild, nsIContent* aEndChild);
 
   /**
    * Notification used to update the accessible tree when content is removed.
    */
   void ContentRemoved(nsIPresShell* aPresShell, nsIContent* aChild);
 
-  virtual void UpdateText(nsIPresShell* aPresShell, nsIContent* aContent);
+  void UpdateText(nsIPresShell* aPresShell, nsIContent* aContent);
 
   /**
    * Update XUL:tree accessible tree when treeview is changed.
    */
   void TreeViewChanged(nsIPresShell* aPresShell, nsIContent* aContent,
                        nsITreeView* aView);
 
   /**
    * Notify of input@type="element" value change.
    */
   void RangeValueChanged(nsIPresShell* aPresShell, nsIContent* aContent);
 
   /**
    * Update list bullet accessible.
    */
-  virtual void UpdateListBullet(nsIPresShell* aPresShell,
-                                nsIContent* aHTMLListItemContent,
-                                bool aHasBullet);
+  void UpdateListBullet(nsIPresShell* aPresShell,
+                        nsIContent* aHTMLListItemContent,
+                        bool aHasBullet);
 
   /**
    * Update the image map.
    */
   void UpdateImageMap(nsImageFrame* aImageFrame);
 
   /**
    * Update the label accessible tree when rendered @value is changed.
@@ -158,33 +176,38 @@ public:
    * Notify accessibility that anchor jump has been accomplished to the given
    * target. Used by layout.
    */
   void NotifyOfAnchorJumpTo(nsIContent *aTarget);
 
   /**
    * Notify that presshell is activated.
    */
-  virtual void PresShellActivated(nsIPresShell* aPresShell);
+  void PresShellActivated(nsIPresShell* aPresShell);
 
   /**
    * Recreate an accessible for the given content node in the presshell.
    */
   void RecreateAccessible(nsIPresShell* aPresShell, nsIContent* aContent);
 
-  virtual void FireAccessibleEvent(uint32_t aEvent, Accessible* aTarget) override;
+  void FireAccessibleEvent(uint32_t aEvent, Accessible* aTarget);
 
   // nsAccessibiltiyService
 
   /**
    * Return true if accessibility service has been shutdown.
    */
   static bool IsShutdown() { return gIsShutdown; }
 
   /**
+   * Return true if accessibility service has been initialized by platform.
+   */
+  static bool IsPlatformCaller() { return gIsPlatformCaller; };
+
+  /**
    * Creates an accessible for the given DOM node.
    *
    * @param  aNode             [in] the given node
    * @param  aContext          [in] context the accessible is created in
    * @param  aIsSubtreeHidden  [out, optional] indicates whether the node's
    *                             frame and its subtree is hidden
    */
   Accessible* CreateAccessible(nsINode* aNode, Accessible* aContext,
@@ -200,17 +223,17 @@ public:
   /**
    * Set the object attribute defined by markup for the given element.
    */
   void MarkupAttributes(const nsIContent* aContent,
                         nsIPersistentProperties* aAttributes) const;
 
 private:
   // nsAccessibilityService creation is controlled by friend
-  // NS_GetAccessibilityService, keep constructors private.
+  // GetOrCreateAccService, keep constructors private.
   nsAccessibilityService();
   nsAccessibilityService(const nsAccessibilityService&);
   nsAccessibilityService& operator =(const nsAccessibilityService&);
 
 private:
   /**
    * Initialize accessibility service.
    */
@@ -253,53 +276,69 @@ private:
   static mozilla::a11y::ApplicationAccessible* gApplicationAccessible;
   static mozilla::a11y::xpcAccessibleApplication* gXPCApplicationAccessible;
 
   /**
    * Indicates whether accessibility service was shutdown.
    */
   static bool gIsShutdown;
 
+  /**
+   * Indicates whether accessibility service was initialized by platform.
+   */
+  static bool gIsPlatformCaller;
+
   nsDataHashtable<nsPtrHashKey<const nsIAtom>, const mozilla::a11y::MarkupMapInfo*> mMarkupMaps;
 
   friend nsAccessibilityService* GetAccService();
+  friend nsAccessibilityService* GetOrCreateAccService(bool);
+  friend bool CanShutdownAccService();
   friend mozilla::a11y::FocusManager* mozilla::a11y::FocusMgr();
   friend mozilla::a11y::SelectionManager* mozilla::a11y::SelectionMgr();
   friend mozilla::a11y::ApplicationAccessible* mozilla::a11y::ApplicationAcc();
   friend mozilla::a11y::xpcAccessibleApplication* mozilla::a11y::XPCApplicationAcc();
-
-  friend nsresult NS_GetAccessibilityService(nsIAccessibilityService** aResult);
+  friend class xpcAccessibilityService;
 };
 
 /**
  * Return the accessibility service instance. (Handy global function)
  */
 inline nsAccessibilityService*
 GetAccService()
 {
   return nsAccessibilityService::gAccessibilityService;
 }
 
 /**
+ * Return accessibility service instance; creating one if necessary.
+ */
+nsAccessibilityService* GetOrCreateAccService(bool aIsPlatformCaller = true);
+
+/**
+ * Return a flag indicating if accessibility service can be shutdown.
+ */
+bool CanShutdownAccService();
+
+/**
  * Return true if we're in a content process and not B2G.
  */
 inline bool
 IPCAccessibilityActive()
 {
 #ifdef MOZ_B2G
   return false;
 #else
   return XRE_IsContentProcess() &&
     mozilla::Preferences::GetBool("accessibility.ipc_architecture.enabled", true);
 #endif
 }
 
 /**
  * Map nsIAccessibleEvents constants to strings. Used by
- * nsIAccessibleRetrieval::getStringEventType() method.
+ * nsAccessibilityService::GetStringEventType() method.
  */
 static const char kEventTypeNames[][40] = {
   "unknown",                                 //
   "show",                                    // EVENT_SHOW
   "hide",                                    // EVENT_HIDE
   "reorder",                                 // EVENT_REORDER
   "active decendent change",                 // EVENT_ACTIVE_DECENDENT_CHANGED
   "focus",                                   // EVENT_FOCUS
@@ -382,10 +421,9 @@ static const char kEventTypeNames[][40] 
   "hyperlink start index changed",           // EVENT_HYPERLINK_START_INDEX_CHANGED
   "hypertext changed",                       // EVENT_HYPERTEXT_CHANGED
   "hypertext links count changed",           // EVENT_HYPERTEXT_NLINKS_CHANGED
   "object attribute changed",                // EVENT_OBJECT_ATTRIBUTE_CHANGED
   "virtual cursor changed",                   // EVENT_VIRTUALCURSOR_CHANGED
   "text value change",                       // EVENT_TEXT_VALUE_CHANGE
 };
 
-#endif /* __nsIAccessibilityService_h__ */
-
+#endif
--- a/accessible/interfaces/moz.build
+++ b/accessible/interfaces/moz.build
@@ -3,43 +3,38 @@
 # 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/.
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows' and CONFIG['COMPILE_ENVIRONMENT']:
     DIRS += ['msaa', 'ia2']
 
 XPIDL_SOURCES += [
+    'nsIAccessibilityService.idl',
     'nsIAccessible.idl',
     'nsIAccessibleApplication.idl',
     'nsIAccessibleCaretMoveEvent.idl',
     'nsIAccessibleDocument.idl',
     'nsIAccessibleEditableText.idl',
     'nsIAccessibleEvent.idl',
     'nsIAccessibleHideEvent.idl',
     'nsIAccessibleHyperLink.idl',
     'nsIAccessibleHyperText.idl',
     'nsIAccessibleImage.idl',
     'nsIAccessibleObjectAttributeChangedEvent.idl',
     'nsIAccessiblePivot.idl',
     'nsIAccessibleRelation.idl',
-    'nsIAccessibleRetrieval.idl',
     'nsIAccessibleRole.idl',
     'nsIAccessibleSelectable.idl',
     'nsIAccessibleStateChangeEvent.idl',
     'nsIAccessibleStates.idl',
     'nsIAccessibleTable.idl',
     'nsIAccessibleTableChangeEvent.idl',
     'nsIAccessibleText.idl',
     'nsIAccessibleTextChangeEvent.idl',
     'nsIAccessibleTextRange.idl',
     'nsIAccessibleTypes.idl',
     'nsIAccessibleValue.idl',
     'nsIAccessibleVirtualCursorChangeEvent.idl',
     'nsIXBLAccessible.idl',
 ]
 
 XPIDL_MODULE = 'accessibility'
-
-EXPORTS += [
-    'nsIAccessibilityService.h',
-]
-
deleted file mode 100644
--- a/accessible/interfaces/nsIAccessibilityService.h
+++ /dev/null
@@ -1,79 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef _nsIAccessibilityService_h_
-#define _nsIAccessibilityService_h_
-
-#include "nsIAccessibleRetrieval.h"
-#include "nsIAccessibleEvent.h"
-
-namespace mozilla {
-namespace a11y {
-
-class Accessible;
-
-} // namespace a11y
-} // namespace mozilla
-
-class nsIPresShell;
-
-// 0e7e6879-854b-4260-bc6e-525b5fb5cf34
-#define NS_IACCESSIBILITYSERVICE_IID \
-{ 0x0e7e6879, 0x854b, 0x4260, \
- { 0xbc, 0x6e, 0x52, 0x5b, 0x5f, 0xb5, 0xcf, 0x34 } }
-
-class nsIAccessibilityService : public nsIAccessibleRetrieval
-{
-public:
-  NS_DECLARE_STATIC_IID_ACCESSOR(NS_IACCESSIBILITYSERVICE_IID)
-
-  /**
-   * Return root document accessible that is or contains a document accessible
-   * for the given presshell.
-   *
-   * @param aPresShell  [in] the presshell
-   * @param aCanCreate  [in] points whether the root document accessible
-   *                        should be returned from the cache or can be created
-   */
-  virtual mozilla::a11y::Accessible*
-    GetRootDocumentAccessible(nsIPresShell* aPresShell, bool aCanCreate) = 0;
-
-   /**
-   * Adds/remove ATK root accessible for gtk+ native window to/from children
-   * of the application accessible.
-   */
-  virtual mozilla::a11y::Accessible*
-    AddNativeRootAccessible(void* aAtkAccessible) = 0;
-  virtual void
-    RemoveNativeRootAccessible(mozilla::a11y::Accessible* aRootAccessible) = 0;
-
-  /**
-   * Fire accessible event of the given type for the given target.
-   *
-   * @param aEvent   [in] accessible event type
-   * @param aTarget  [in] target of accessible event
-   */
-  virtual void FireAccessibleEvent(uint32_t aEvent,
-                                   mozilla::a11y::Accessible* aTarget) = 0;
-
-  /**
-   * Return true if the given DOM node has accessible object.
-   */
-  virtual bool HasAccessible(nsIDOMNode* aDOMNode) = 0;
-};
-
-NS_DEFINE_STATIC_IID_ACCESSOR(nsIAccessibilityService,
-                              NS_IACCESSIBILITYSERVICE_IID)
-
-// for component registration
-// {DE401C37-9A7F-4278-A6F8-3DE2833989EF}
-#define NS_ACCESSIBILITY_SERVICE_CID \
-{ 0xde401c37, 0x9a7f, 0x4278, { 0xa6, 0xf8, 0x3d, 0xe2, 0x83, 0x39, 0x89, 0xef } }
-
-extern nsresult
-NS_GetAccessibilityService(nsIAccessibilityService** aResult);
-
-#endif
new file mode 100644
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibilityService.idl
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMNode;
+interface nsIAccessible;
+interface nsIWeakReference;
+interface nsIPresShell;
+interface nsIAccessiblePivot;
+
+/**
+ * An interface for in-process accessibility clients wishing to get an
+ * nsIAccessible for a given DOM node.  More documentation at:
+ *   http://www.mozilla.org/projects/ui/accessibility
+ */
+[scriptable, builtinclass, uuid(9a6f80fe-25cc-405c-9f8f-25869bc9f94e)]
+interface nsIAccessibilityService : nsISupports
+{
+  /**
+   * Return application accessible.
+   */
+  nsIAccessible getApplicationAccessible();
+
+  /**
+   * Return an nsIAccessible for a DOM node in pres shell 0.
+   * Create a new accessible of the appropriate type if necessary,
+   * or use one from the accessibility cache if it already exists.
+   * @param aNode The DOM node to get an accessible for.
+   * @return The nsIAccessible for the given DOM node.
+   */
+  nsIAccessible getAccessibleFor(in nsIDOMNode aNode);
+
+   /**
+    * Returns accessible role as a string.
+    *
+    * @param aRole - the accessible role constants.
+    */
+  AString getStringRole(in unsigned long aRole);
+
+   /**
+    * Returns list which contains accessible states as a strings.
+    *
+    * @param aStates - accessible states.
+    * @param aExtraStates - accessible extra states.
+    */
+  nsISupports getStringStates(in unsigned long aStates,
+                              in unsigned long aExtraStates);
+
+  /**
+   * Get the type of accessible event as a string.
+   *
+   * @param aEventType - the accessible event type constant
+   * @return - accessible event type presented as human readable string
+   */
+  AString getStringEventType(in unsigned long aEventType);
+
+  /**
+   * Get the type of accessible relation as a string.
+   *
+   * @param aRelationType - the accessible relation type constant
+   * @return - accessible relation type presented as human readable string
+   */
+  AString getStringRelationType(in unsigned long aRelationType);
+
+  /**
+   * Return an accessible for the given DOM node from the cache.
+   * @note  the method is intended for testing purposes
+   *
+   * @param aNode  [in] the DOM node to get an accessible for
+   *
+   * @return       cached accessible for the given DOM node if any
+   */
+  nsIAccessible getAccessibleFromCache(in nsIDOMNode aNode);
+
+  /**
+   * Create a new pivot for tracking a position and traversing a subtree.
+   *
+   * @param aRoot [in] the accessible root for the pivot
+   * @return a new pivot
+   */
+  nsIAccessiblePivot createAccessiblePivot(in nsIAccessible aRoot);
+
+  /**
+   * Enable logging for the given modules, all other modules aren't logged.
+   *
+   * @param aModules [in] list of modules, format is comma separated list
+   *                      like 'docload,doccreate'.
+   * @note Works on debug build only.
+   * @see Logging.cpp for list of possible values.
+   */
+  void setLogging(in ACString aModules);
+
+  /**
+   * Return true if the given module is logged.
+   */
+  boolean isLogged(in AString aModule);
+};
+
+/**
+ * @deprecated, use nsIAccessibilityService instead.
+ */
+[scriptable, builtinclass, uuid(d85e0cbe-47ce-490c-8488-f821dd2be0c2)]
+interface nsIAccessibleRetrieval : nsIAccessibilityService
+{
+};
deleted file mode 100644
--- a/accessible/interfaces/nsIAccessibleRetrieval.idl
+++ /dev/null
@@ -1,110 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "nsISupports.idl"
-
-interface nsIDOMNode;
-interface nsIAccessible;
-interface nsIWeakReference;
-interface nsIPresShell;
-interface nsIAccessiblePivot;
-
-/**
- * An interface for in-process accessibility clients wishing to get an
- * nsIAccessible for a given DOM node.  More documentation at:
- *   http://www.mozilla.org/projects/ui/accessibility
- */
-[scriptable, builtinclass, uuid(17f86615-1a3d-4021-b227-3a2ef5cbffd8)]
-interface nsIAccessibleRetrieval : nsISupports
-{
-  /**
-   * Return application accessible.
-   */
-  nsIAccessible getApplicationAccessible();
-
-  /**
-   * Return an nsIAccessible for a DOM node in pres shell 0.
-   * Create a new accessible of the appropriate type if necessary,
-   * or use one from the accessibility cache if it already exists.
-   * @param aNode The DOM node to get an accessible for.
-   * @return The nsIAccessible for the given DOM node.
-   */
-  nsIAccessible getAccessibleFor(in nsIDOMNode aNode);
-
-   /**
-    * Returns accessible role as a string.
-    *
-    * @param aRole - the accessible role constants.
-    */
-  AString getStringRole(in unsigned long aRole);
-
-   /**
-    * Returns list which contains accessible states as a strings.
-    *
-    * @param aStates - accessible states.
-    * @param aExtraStates - accessible extra states.
-    */
-  nsISupports getStringStates(in unsigned long aStates,
-                              in unsigned long aExtraStates);
-
-  /**
-   * Get the type of accessible event as a string.
-   *
-   * @param aEventType - the accessible event type constant
-   * @return - accessible event type presented as human readable string
-   */
-  AString getStringEventType(in unsigned long aEventType);
-
-  /**
-   * Get the type of accessible relation as a string.
-   *
-   * @param aRelationType - the accessible relation type constant
-   * @return - accessible relation type presented as human readable string
-   */
-  AString getStringRelationType(in unsigned long aRelationType);
-
-  /**
-   * Return an accessible for the given DOM node from the cache.
-   * @note  the method is intended for testing purposes
-   *
-   * @param aNode  [in] the DOM node to get an accessible for
-   *
-   * @return       cached accessible for the given DOM node if any
-   */
-  nsIAccessible getAccessibleFromCache(in nsIDOMNode aNode);
-
-  /**
-   * Create a new pivot for tracking a position and traversing a subtree.
-   *
-   * @param aRoot [in] the accessible root for the pivot
-   * @return a new pivot
-   */
-  nsIAccessiblePivot createAccessiblePivot(in nsIAccessible aRoot);
-
-  /**
-   * Enable logging for the given modules, all other modules aren't logged.
-   *
-   * @param aModules [in] list of modules, format is comma separated list
-   *                      like 'docload,doccreate'.
-   * @note Works on debug build only.
-   * @see Logging.cpp for list of possible values.
-   */
-  void setLogging(in ACString aModules);
-
-  /**
-   * Return true if the given module is logged.
-   */
-  boolean isLogged(in AString aModule);
-};
-
-
-%{ C++
-
-// for component registration
-// {663CA4A8-D219-4000-925D-D8F66406B626}
-#define NS_ACCESSIBLE_RETRIEVAL_CID \
-{ 0x663ca4a8, 0xd219, 0x4000, { 0x92, 0x5d, 0xd8, 0xf6, 0x64, 0x6, 0xb6, 0x26 } }
-
-%}
--- a/accessible/xpcom/moz.build
+++ b/accessible/xpcom/moz.build
@@ -1,16 +1,17 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 UNIFIED_SOURCES += [
     'nsAccessibleRelation.cpp',
+    'xpcAccessibilityService.cpp',
     'xpcAccessible.cpp',
     'xpcAccessibleApplication.cpp',
     'xpcAccessibleDocument.cpp',
     'xpcAccessibleGeneric.cpp',
     'xpcAccessibleHyperLink.cpp',
     'xpcAccessibleHyperText.cpp',
     'xpcAccessibleImage.cpp',
     'xpcAccessibleSelectable.cpp',
@@ -21,16 +22,17 @@ UNIFIED_SOURCES += [
 ]
 
 SOURCES += [
     '!xpcAccEvents.cpp',
 ]
 
 EXPORTS += [
     '!xpcAccEvents.h',
+    'xpcAccessibilityService.h',
 ]
 
 LOCAL_INCLUDES += [
     '/accessible/base',
     '/accessible/generic',
 ]
 
 if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
new file mode 100644
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibilityService.cpp
@@ -0,0 +1,249 @@
+/* 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 "xpcAccessibilityService.h"
+
+#include "nsAccessiblePivot.h"
+#include "nsAccessibilityService.h"
+
+#ifdef A11Y_LOG
+#include "Logging.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using namespace mozilla::dom;
+
+xpcAccessibilityService *xpcAccessibilityService::gXPCAccessibilityService = nullptr;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+void
+xpcAccessibilityService::ShutdownCallback(nsITimer* aTimer, void* aClosure)
+{
+  if (CanShutdownAccService()) {
+    GetAccService()->Shutdown();
+  }
+
+  xpcAccessibilityService* xpcAccService =
+    reinterpret_cast<xpcAccessibilityService*>(aClosure);
+
+  if (xpcAccService->mShutdownTimer) {
+    xpcAccService->mShutdownTimer->Cancel();
+    xpcAccService->mShutdownTimer = nullptr;
+  }
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+xpcAccessibilityService::AddRef(void)
+{
+  MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(xpcAccessibilityService)
+  MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
+  if (!mRefCnt.isThreadSafe)
+    NS_ASSERT_OWNINGTHREAD(xpcAccessibilityService);
+  nsrefcnt count = ++mRefCnt;
+  NS_LOG_ADDREF(this, count, "xpcAccessibilityService", sizeof(*this));
+
+  if (mRefCnt > 1) {
+    GetOrCreateAccService(false);
+  }
+
+  return count;
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+xpcAccessibilityService::Release(void)
+{
+  MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
+
+  if (!mRefCnt.isThreadSafe) {
+    NS_ASSERT_OWNINGTHREAD(xpcAccessibilityService);
+  }
+
+  nsrefcnt count = --mRefCnt;
+  NS_LOG_RELEASE(this, count, "xpcAccessibilityService");
+
+  if (count == 0) {
+    if (!mRefCnt.isThreadSafe) {
+      NS_ASSERT_OWNINGTHREAD(xpcAccessibilityService);
+    }
+
+    mRefCnt = 1; /* stabilize */
+    delete (this);
+    return 0;
+  }
+
+  // When ref count goes down to 1 (held internally as a static reference),
+  // it means that there are no more external references to the
+  // xpcAccessibilityService and we can attempt to shut down acceessiblity
+  // service.
+  if (count == 1 && !mShutdownTimer) {
+    mShutdownTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+    if (mShutdownTimer) {
+      mShutdownTimer->InitWithFuncCallback(ShutdownCallback, this, 100,
+                                           nsITimer::TYPE_ONE_SHOT);
+    }
+  }
+
+  return count;
+}
+
+NS_IMPL_QUERY_INTERFACE(xpcAccessibilityService, nsIAccessibilityService,
+                                                 nsIAccessibleRetrieval)
+
+NS_IMETHODIMP
+xpcAccessibilityService::GetApplicationAccessible(nsIAccessible** aAccessibleApplication)
+{
+  NS_ENSURE_ARG_POINTER(aAccessibleApplication);
+
+  NS_IF_ADDREF(*aAccessibleApplication = XPCApplicationAcc());
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibilityService::GetAccessibleFor(nsIDOMNode *aNode,
+                                          nsIAccessible **aAccessible)
+{
+  NS_ENSURE_ARG_POINTER(aAccessible);
+  *aAccessible = nullptr;
+  if (!aNode) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsINode> node(do_QueryInterface(aNode));
+  if (!node) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  DocAccessible* document = GetAccService()->GetDocAccessible(node->OwnerDoc());
+  if (document) {
+    NS_IF_ADDREF(*aAccessible = ToXPC(document->GetAccessible(node)));
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibilityService::GetStringRole(uint32_t aRole, nsAString& aString)
+{
+  GetAccService()->GetStringRole(aRole, aString);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibilityService::GetStringStates(uint32_t aState, uint32_t aExtraState,
+                                         nsISupports **aStringStates)
+{
+  GetAccService()->GetStringStates(aState, aExtraState, aStringStates);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibilityService::GetStringEventType(uint32_t aEventType,
+                                            nsAString& aString)
+{
+  GetAccService()->GetStringEventType(aEventType, aString);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibilityService::GetStringRelationType(uint32_t aRelationType,
+                                               nsAString& aString)
+{
+  GetAccService()->GetStringRelationType(aRelationType, aString);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibilityService::GetAccessibleFromCache(nsIDOMNode* aNode,
+                                                nsIAccessible** aAccessible)
+{
+  NS_ENSURE_ARG_POINTER(aAccessible);
+  *aAccessible = nullptr;
+  if (!aNode) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsINode> node(do_QueryInterface(aNode));
+  if (!node) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  // Search for an accessible in each of our per document accessible object
+  // caches. If we don't find it, and the given node is itself a document, check
+  // our cache of document accessibles (document cache). Note usually shutdown
+  // document accessibles are not stored in the document cache, however an
+  // "unofficially" shutdown document (i.e. not from DocManager) can still
+  // exist in the document cache.
+  Accessible* accessible = GetAccService()->FindAccessibleInCache(node);
+  if (!accessible) {
+    nsCOMPtr<nsIDocument> document(do_QueryInterface(node));
+    if (document) {
+      accessible = mozilla::a11y::GetExistingDocAccessible(document);
+    }
+  }
+
+  NS_IF_ADDREF(*aAccessible = ToXPC(accessible));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibilityService::CreateAccessiblePivot(nsIAccessible* aRoot,
+                                               nsIAccessiblePivot** aPivot)
+{
+  NS_ENSURE_ARG_POINTER(aPivot);
+  NS_ENSURE_ARG(aRoot);
+  *aPivot = nullptr;
+
+  Accessible* accessibleRoot = aRoot->ToInternalAccessible();
+  NS_ENSURE_TRUE(accessibleRoot, NS_ERROR_INVALID_ARG);
+
+  nsAccessiblePivot* pivot = new nsAccessiblePivot(accessibleRoot);
+  NS_ADDREF(*aPivot = pivot);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibilityService::SetLogging(const nsACString& aModules)
+{
+#ifdef A11Y_LOG
+  logging::Enable(PromiseFlatCString(aModules));
+#endif
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibilityService::IsLogged(const nsAString& aModule, bool* aIsLogged)
+{
+  NS_ENSURE_ARG_POINTER(aIsLogged);
+  *aIsLogged = false;
+
+#ifdef A11Y_LOG
+  *aIsLogged = logging::IsEnabled(aModule);
+#endif
+
+  return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NS_GetAccessibilityService
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult
+NS_GetAccessibilityService(nsIAccessibilityService** aResult)
+{
+  NS_ENSURE_TRUE(aResult, NS_ERROR_NULL_POINTER);
+  *aResult = nullptr;
+
+  GetOrCreateAccService(false);
+
+  xpcAccessibilityService* service = new xpcAccessibilityService();
+  NS_ENSURE_TRUE(service, NS_ERROR_OUT_OF_MEMORY);
+  xpcAccessibilityService::gXPCAccessibilityService = service;
+  NS_ADDREF(*aResult = service);
+
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibilityService.h
@@ -0,0 +1,66 @@
+/* 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_a11y_xpcAccessibilityService_h_
+#define mozilla_a11y_xpcAccessibilityService_h_
+
+#include "nsIAccessibilityService.h"
+
+class xpcAccessibilityService : public nsIAccessibleRetrieval
+{
+
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIACCESSIBILITYSERVICE
+  NS_DECL_NSIACCESSIBLERETRIEVAL
+
+  /**
+   * Return true if xpc accessibility service is in use.
+   */
+  static bool IsInUse() {
+    // When ref count goes down to 1 (held internally as a static reference),
+    // it means that there are no more external references and thus it is not in
+    // use.
+    return gXPCAccessibilityService ? gXPCAccessibilityService->mRefCnt > 1 : false;
+  }
+
+protected:
+  virtual ~xpcAccessibilityService() {
+    if (mShutdownTimer) {
+      mShutdownTimer->Cancel();
+      mShutdownTimer = nullptr;
+    }
+    gXPCAccessibilityService = nullptr;
+  }
+
+private:
+  // xpcAccessibilityService creation is controlled by friend
+  // NS_GetAccessibilityService, keep constructor private.
+  xpcAccessibilityService() { };
+
+  nsCOMPtr<nsITimer> mShutdownTimer;
+
+  /**
+   * Reference for xpc accessibility service instance.
+   */
+  static xpcAccessibilityService* gXPCAccessibilityService;
+
+  /**
+   * Used to shutdown nsAccessibilityService if xpcom accessible service is not
+   * in use any more.
+   */
+  static void ShutdownCallback(nsITimer* aTimer, void* aClosure);
+
+  friend nsresult NS_GetAccessibilityService(nsIAccessibilityService** aResult);
+};
+
+// for component registration
+// {3b265b69-f813-48ff-880d-d88d101af404}
+#define NS_ACCESSIBILITY_SERVICE_CID \
+{ 0x3b265b69, 0xf813, 0x48ff, { 0x88, 0x0d, 0xd8, 0x8d, 0x10, 0x1a, 0xf4, 0x04 } }
+
+extern nsresult
+NS_GetAccessibilityService(nsIAccessibilityService** aResult);
+
+#endif
--- a/browser/components/contextualidentity/test/browser/browser.ini
+++ b/browser/components/contextualidentity/test/browser/browser.ini
@@ -19,8 +19,9 @@ skip-if = os == "mac" || os == "win" # I
 [browser_windowName.js]
 tags = openwindow
 [browser_windowOpen.js]
 tags = openwindow
 [browser_serviceworkers.js]
 [browser_broadcastchannel.js]
 [browser_blobUrl.js]
 [browser_middleClick.js]
+[browser_imageCache.js]
--- a/browser/components/contextualidentity/test/browser/browser_forgetaboutsite.js
+++ b/browser/components/contextualidentity/test/browser/browser_forgetaboutsite.js
@@ -10,17 +10,16 @@ let {HttpServer} = Cu.import("resource:/
 let LoadContextInfo = Cc["@mozilla.org/load-context-info-factory;1"]
                       .getService(Ci.nsILoadContextInfoFactory);
 let css = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
            .getService(Ci.nsICacheStorageService);
 
 const USER_CONTEXTS = [
   "default",
   "personal",
-  "work",
 ];
 const TEST_HOST = "example.com";
 const TEST_URL = "http://" + TEST_HOST + "/browser/browser/components/contextualidentity/test/browser/";
 const COOKIE_NAME = "userContextId";
 
 // Counter for image load hits.
 let gHits = 0;
 
@@ -128,17 +127,17 @@ function OpenCacheEntry(key, where, flag
 // Test functions.
 //
 
 // Cookies
 function* test_cookie_cleared() {
   let tabs = [];
 
   for (let userContextId of Object.keys(USER_CONTEXTS)) {
-    // Load the page in 3 different contexts and set a cookie
+    // Load the page in 2 different contexts and set a cookie
     // which should only be visible in that context.
     let value = USER_CONTEXTS[userContextId];
 
     // Open our tab in the given user context.
     tabs[userContextId] = yield* openTabInUserContext(TEST_URL+ "file_reflect_cookie_into_title.html?" + value, userContextId);
 
     // Close this tab.
     yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
@@ -207,41 +206,43 @@ function* test_image_cache_cleared() {
 
   for (let userContextId of Object.keys(USER_CONTEXTS)) {
     // Open our tab in the given user context to cache image.
     tabs[userContextId] = yield* openTabInUserContext('http://localhost:' + gHttpServer.identity.primaryPort + '/loadImage.html',
                                                       userContextId);
     yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
   }
 
+  let expectedHits = USER_CONTEXTS.length;
+
   // Check that image cache works with the userContextId.
-  todo_is(gHits, 3, "The image should be loaded three times. This test should be enabled after the bug 1270680 landed");
+  is(gHits, expectedHits, "The image should be loaded" + expectedHits + "times.");
 
   // Reset the cache count.
   gHits = 0;
 
   // Forget the site.
   ForgetAboutSite.removeDataFromDomain("localhost:" + gHttpServer.identity.primaryPort + "/");
 
   // Load again.
   for (let userContextId of Object.keys(USER_CONTEXTS)) {
     // Open our tab in the given user context to cache image.
     tabs[userContextId] = yield* openTabInUserContext('http://localhost:' + gHttpServer.identity.primaryPort + '/loadImage.html',
                                                       userContextId);
     yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
   }
 
-  // Check that image cache was cleared and the server gets another three hits.
-  todo_is(gHits, 3, "The image should be loaded three times. This test should be enabled after the bug 1270680 landed");
+  // Check that image cache was cleared and the server gets another two hits.
+  is(gHits, expectedHits, "The image should be loaded" + expectedHits + "times.");
 }
 
 // Offline Storage
 function* test_storage_cleared() {
   for (let userContextId of Object.keys(USER_CONTEXTS)) {
-    // Load the page in 3 different contexts and set the local storage
+    // Load the page in 2 different contexts and set the local storage
     // which should only be visible in that context.
     let value = USER_CONTEXTS[userContextId];
 
     // Open our tab in the given user context.
     let tabInfo = yield* openTabInUserContext(TEST_URL+ "file_set_storages.html?" + value, userContextId);
 
     // Check that the storages has been set correctly.
     yield ContentTask.spawn(tabInfo.browser, { userContext: USER_CONTEXTS[userContextId] }, function* (arg) {
new file mode 100644
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_imageCache.js
@@ -0,0 +1,59 @@
+let Cu = Components.utils;
+let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
+
+const NUM_USER_CONTEXTS = 3;
+
+let gHits = 0;
+
+let server = new HttpServer();
+server.registerPathHandler('/image.png', imageHandler);
+server.registerPathHandler('/file.html', fileHandler);
+server.start(-1);
+
+let BASE_URI = 'http://localhost:' + server.identity.primaryPort;
+let IMAGE_URI = BASE_URI + '/image.png';
+let FILE_URI = BASE_URI + '/file.html';
+
+function imageHandler(metadata, response) {
+  gHits++;
+  response.setHeader("Cache-Control", "max-age=10000", false);
+  response.setStatusLine(metadata.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "image/png", false);
+  var body = "iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII=";
+  response.bodyOutputStream.write(body, body.length);
+}
+
+function fileHandler(metadata, response) {
+  response.setStatusLine(metadata.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "text/html", false);
+  let body = `<html><body><image src=${IMAGE_URI}></body></html>`;
+  response.bodyOutputStream.write(body, body.length);
+}
+
+add_task(function* setup() {
+  // make sure userContext is enabled.
+  yield SpecialPowers.pushPrefEnv({"set": [["privacy.userContext.enabled", true]]});
+});
+
+// opens `uri' in a new tab with the provided userContextId and focuses it.
+// returns the newly opened tab
+function* openTabInUserContext(uri, userContextId) {
+  // open the tab in the correct userContextId
+  let tab = gBrowser.addTab(uri, {userContextId});
+
+  // select tab and make sure its browser is focused
+  gBrowser.selectedTab = tab;
+  tab.ownerDocument.defaultView.focus();
+
+  let browser = gBrowser.getBrowserForTab(tab);
+  yield BrowserTestUtils.browserLoaded(browser);
+  return tab;
+}
+
+add_task(function* test() {
+  for (let userContextId = 0; userContextId < NUM_USER_CONTEXTS; userContextId++) {
+    let tab = yield* openTabInUserContext(FILE_URI, userContextId);
+    gBrowser.removeTab(tab);
+  }
+  is(gHits, NUM_USER_CONTEXTS, "should get an image request for each user contexts");
+});
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -211,24 +211,28 @@ class BasePopup {
       this.browser.addEventListener("DOMTitleChanged", this, true);
       this.browser.addEventListener("DOMWindowClose", this, true);
       this.browser.addEventListener("MozScrolledAreaChanged", this, true);
     });
   }
   // Resizes the browser to match the preferred size of the content (debounced).
   resizeBrowser() {
     if (this.resizeTimeout == null) {
-      this._resizeBrowser();
-      this.resizeTimeout = this.window.setTimeout(this._resizeBrowser.bind(this), RESIZE_TIMEOUT);
+      this.resizeTimeout = this.window.setTimeout(() => {
+        try {
+          this._resizeBrowser();
+        } finally {
+          this.resizeTimeout = null;
+        }
+      }, RESIZE_TIMEOUT);
+      this._resizeBrowser(false);
     }
   }
 
-  _resizeBrowser() {
-    this.resizeTimeout = null;
-
+  _resizeBrowser(clearTimeout = true) {
     if (!this.browser) {
       return;
     }
 
     if (this.fixedWidth) {
       // If we're in a fixed-width area (namely a slide-in subview of the main
       // menu panel), we need to calculate the view height based on the
       // preferred height of the content document's root scrollable element at the
--- a/browser/components/extensions/schemas/browser_action.json
+++ b/browser/components/extensions/schemas/browser_action.json
@@ -37,16 +37,17 @@
           }
         }
       }
     ]
   },
   {
     "namespace": "browserAction",
     "description": "Use browser actions to put icons in the main browser toolbar, to the right of the address bar. In addition to its icon, a browser action can also have a tooltip, a badge, and a popup.",
+    "permissions": ["manifest:browser_action"],
     "types": [
       {
         "id": "ColorArray",
         "type": "array",
         "items": {
           "type": "integer",
           "minimum": 0,
           "maximum": 255
--- a/browser/components/extensions/schemas/page_action.json
+++ b/browser/components/extensions/schemas/page_action.json
@@ -37,16 +37,17 @@
           }
         }
       }
     ]
   },
   {
     "namespace": "pageAction",
     "description": "Use the <code>browser.pageAction</code> API to put icons inside the address bar. Page actions represent actions that can be taken on the current page, but that aren't applicable to all pages.",
+    "permissions": ["manifest:page_action"],
     "types": [
       {
         "id": "ImageDataType",
         "type": "object",
         "isInstanceOf": "ImageData",
         "additionalProperties": { "type": "any" },
         "description": "Pixel data for an image. Must be an ImageData object (for example, from a <code>canvas</code> element)."
       }
--- a/browser/components/extensions/schemas/windows.json
+++ b/browser/components/extensions/schemas/windows.json
@@ -1,28 +1,14 @@
 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 [
   {
-    "namespace": "manifest",
-    "types": [
-      {
-        "$extend": "Permission",
-        "choices": [{
-          "type": "string",
-          "enum": [
-            "windows"
-          ]
-        }]
-      }
-    ]
-  },
-  {
     "namespace": "windows",
     "description": "Use the <code>browser.windows</code> API to interact with browser windows. You can use this API to create, modify, and rearrange windows in the browser.",
     "types": [
       {
         "id": "WindowType",
         "type": "string",
         "description": "The type of browser window this is. Under some circumstances a Window may not be assigned type property, for example when querying closed windows from the $(ref:sessions) API.",
         "enum": ["normal", "popup", "panel", "app", "devtools"]
--- a/browser/components/extensions/test/browser/browser_ext_tabs_sendMessage.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_sendMessage.js
@@ -16,42 +16,41 @@ add_task(function* tabsSendMessageReply(
 
     background: function() {
       let firstTab;
       let promiseResponse = new Promise(resolve => {
         browser.runtime.onMessage.addListener((msg, sender, respond) => {
           if (msg == "content-script-ready") {
             let tabId = sender.tab.id;
 
-            browser.tabs.sendMessage(tabId, "respond-never", response => {
-              browser.test.fail(`Got unexpected response callback: ${response}`);
-              browser.test.notifyFail("sendMessage");
-            });
-
             Promise.all([
               promiseResponse,
 
               browser.tabs.sendMessage(tabId, "respond-now"),
               browser.tabs.sendMessage(tabId, "respond-now-2"),
               new Promise(resolve => browser.tabs.sendMessage(tabId, "respond-soon", resolve)),
               browser.tabs.sendMessage(tabId, "respond-promise"),
               browser.tabs.sendMessage(tabId, "respond-never"),
+              new Promise(resolve => {
+                browser.runtime.sendMessage("respond-never", response => { resolve(response); });
+              }),
 
               browser.tabs.sendMessage(tabId, "respond-error").catch(error => Promise.resolve({error})),
               browser.tabs.sendMessage(tabId, "throw-error").catch(error => Promise.resolve({error})),
 
               browser.tabs.sendMessage(firstTab, "no-listener").catch(error => Promise.resolve({error})),
-            ]).then(([response, respondNow, respondNow2, respondSoon, respondPromise, respondNever, respondError, throwError, noListener]) => {
+            ]).then(([response, respondNow, respondNow2, respondSoon, respondPromise, respondNever, respondNever2, respondError, throwError, noListener]) => {
               browser.test.assertEq("expected-response", response, "Content script got the expected response");
 
               browser.test.assertEq("respond-now", respondNow, "Got the expected immediate response");
               browser.test.assertEq("respond-now-2", respondNow2, "Got the expected immediate response from the second listener");
               browser.test.assertEq("respond-soon", respondSoon, "Got the expected delayed response");
               browser.test.assertEq("respond-promise", respondPromise, "Got the expected promise response");
               browser.test.assertEq(undefined, respondNever, "Got the expected no-response resolution");
+              browser.test.assertEq(undefined, respondNever2, "Got the expected no-response resolution");
 
               browser.test.assertEq("respond-error", respondError.error.message, "Got the expected error response");
               browser.test.assertEq("throw-error", throwError.error.message, "Got the expected thrown error response");
 
               browser.test.assertEq("Could not establish connection. Receiving end does not exist.",
                                     noListener.error.message,
                                     "Got the expected no listener response");
 
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/xpcshell/test_ext_manifest_permissions.js
@@ -0,0 +1,57 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+/* globals chrome */
+
+function* testPermission(options) {
+  function background(options) {
+    browser.test.sendMessage("typeof-namespace", {
+      browser: typeof browser[options.namespace],
+      chrome: typeof chrome[options.namespace],
+    });
+  }
+
+  let extensionDetails = {
+    background: `(${background})(${JSON.stringify(options)})`,
+  };
+
+  let extension = ExtensionTestUtils.loadExtension(extensionDetails);
+
+  yield extension.startup();
+
+  let types = yield extension.awaitMessage("typeof-namespace");
+  equal(types.browser, "undefined", `Type of browser.${options.namespace} without manifest entry`);
+  equal(types.chrome, "undefined", `Type of chrome.${options.namespace} without manifest entry`);
+
+  yield extension.unload();
+
+  extensionDetails.manifest = options.manifest;
+  extension = ExtensionTestUtils.loadExtension(extensionDetails);
+
+  yield extension.startup();
+
+  types = yield extension.awaitMessage("typeof-namespace");
+  equal(types.browser, "object", `Type of browser.${options.namespace} with manifest entry`);
+  equal(types.chrome, "object", `Type of chrome.${options.namespace} with manifest entry`);
+
+  yield extension.unload();
+}
+
+add_task(function* test_browserAction() {
+  yield testPermission({
+    namespace: "browserAction",
+    manifest: {
+      browser_action: {},
+    },
+  });
+});
+
+add_task(function* test_pageAction() {
+  yield testPermission({
+    namespace: "pageAction",
+    manifest: {
+      page_action: {},
+    },
+  });
+});
--- a/browser/components/extensions/test/xpcshell/xpcshell.ini
+++ b/browser/components/extensions/test/xpcshell/xpcshell.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 head = head.js
 tail =
 firefox-appdir = browser
 
 [test_ext_bookmarks.js]
 [test_ext_history.js]
 [test_ext_manifest_commands.js]
+[test_ext_manifest_permissions.js]
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1698,22 +1698,22 @@ toolbarbutton.chevron > .toolbarbutton-m
 toolbarbutton.chevron > .toolbarbutton-icon {
   margin: 0;
 }
 
 /* Ctrl-Tab */
 
 #ctrlTab-panel {
   -moz-appearance: none;
-  background: rgba(27%,27%,27%,.7);
+  background: hsla(0,0%,33%,.85);
   color: white;
   border-style: none;
   padding: 20px 10px 10px;
   font-weight: bold;
-  text-shadow: 0 0 1px rgb(27%,27%,27%), 0 0 2px rgb(27%,27%,27%);
+  text-shadow: 0 0 1px hsl(0,0%,12%), 0 0 2px hsl(0,0%,12%);
 }
 
 .ctrlTab-favicon[src] {
   background-color: white;
   width: 20px;
   height: 20px;
   padding: 2px;
 }
deleted file mode 100644
--- a/browser/themes/linux/content-contextmenu.svg
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16">
-  <style>
-    use:not(:target) {
-      display: none;
-    }
-    use {
-      fill: menutext;
-    }
-    use[id$="-active"] {
-      fill: -moz-menuhovertext;
-    }
-    use[id$="-disabled"] {
-      fill: graytext;
-    }
-  </style>
-  <defs>
-    <path id="back-shape" fill-rule="evenodd" d="M1.192,8.893L2.21,9.964c0.064,0.065,0.136,0.117,0.214,0.159 l5.199,5.301c0.607,0.63,1.465,0.764,1.915,0.297l1.02-1.082c0.449-0.467,0.32-1.357-0.288-1.99l-2.116-2.158h5.705 c0.671,0,1.215-0.544,1.215-1.215v-2.43c0-0.671-0.544-1.215-1.215-1.215H8.094l2.271-2.309c0.609-0.626,0.737-1.512,0.288-1.974 L9.635,0.278C9.184-0.188,8.327-0.055,7.718,0.575L2.479,5.901C2.38,5.946,2.289,6.008,2.21,6.089L1.192,7.171 c-0.21,0.219-0.293,0.53-0.26,0.864C0.899,8.367,0.981,8.676,1.192,8.893z"/>
-    <path id="forward-shape" fill-rule="evenodd" d="M14.808,7.107L13.79,6.036c-0.064-0.065-0.136-0.117-0.214-0.159 L8.377,0.576C7.77-0.054,6.912-0.189,6.461,0.278L5.441,1.36c-0.449,0.467-0.32,1.357,0.288,1.99l2.116,2.158H2.14 c-0.671,0-1.215,0.544-1.215,1.215v2.43c0,0.671,0.544,1.215,1.215,1.215h5.765l-2.271,2.309c-0.609,0.626-0.737,1.512-0.288,1.974 l1.019,1.072c0.451,0.465,1.308,0.332,1.917-0.297l5.238-5.326c0.1-0.045,0.191-0.107,0.269-0.188l1.019-1.082 c0.21-0.219,0.293-0.53,0.26-0.864C15.101,7.633,15.019,7.324,14.808,7.107z"/>
-    <path id="reload-shape" fill-rule="evenodd" d="M15.429,8h-8l3.207-3.207C9.889,4.265,8.986,3.947,8,3.947 c-2.554,0-4.625,2.071-4.625,4.625S5.446,13.196,8,13.196c1.638,0,3.069-0.857,3.891-2.141l2.576,1.104 C13.199,14.439,10.794,16,8,16c-4.103,0-7.429-3.326-7.429-7.429S3.897,1.143,8,1.143c1.762,0,3.366,0.624,4.631,1.654L15.429,0V8z"/>
-    <polygon id="stop-shape" points="16,2.748 13.338,0.079 8.038,5.391 2.661,0 0,2.669 5.377,8.059 0.157,13.292 2.819,15.961 8.039,10.728 13.298,16 15.959,13.331 10.701,8.06"/>
-    <path id="bookmark-shape" d="M8.008,3.632l0.986,2.012l0.452,0.922l1.014,0.169l2.326,0.389l-1.719,1.799l-0.676,0.708l0.145,0.967 L10.896,13l-1.959-1.039l-0.937-0.497l-0.937,0.497l-1.957,1.038L5.468,10.6l0.146-0.968L4.937,8.924L3.219,7.126l2.351-0.39 l1.023-0.17l0.45-0.934L8.008,3.632 M8,0C7.72,0,7.44,0.217,7.228,0.65L5.242,4.766L0.907,5.485c-0.958,0.159-1.195,0.861-0.53,1.56 l3.113,3.258l-0.69,4.583c-0.105,0.689,0.172,1.092,0.658,1.092c0.185,0,0.399-0.058,0.635-0.181l3.906-2.072l3.906,2.072 c0.236,0.123,0.45,0.181,0.635,0.181c0.486,0,0.762-0.403,0.659-1.092l-0.687-4.583l3.109-3.255c0.666-0.702,0.428-1.404-0.53-1.564 l-4.303-0.719L8.772,0.65C8.56,0.217,8.28,0,8,0L8,0z"/>
-    <path id="bookmarked-shape" d="M8,0C7.719,0,7.438,0.217,7.225,0.651L5.233,4.773l-4.35,0.72c-0.961,0.159-1.199,0.862-0.531,1.562 l3.124,3.262l-0.692,4.589C2.679,15.596,2.957,16,3.444,16c0.185,0,0.401-0.058,0.637-0.181L8,13.744l3.919,2.075 C12.156,15.942,12.372,16,12.557,16c0.487,0,0.764-0.404,0.661-1.094l-0.69-4.589l3.12-3.259c0.668-0.703,0.43-1.406-0.532-1.566 l-4.317-0.72L8.775,0.651C8.562,0.217,8.281,0,8,0L8,0z"/>
-  </defs>
-  <use id="back" xlink:href="#back-shape"/>
-  <use id="back-active" xlink:href="#back-shape"/>
-  <use id="back-disabled" xlink:href="#back-shape"/>
-  <use id="forward" xlink:href="#forward-shape"/>
-  <use id="forward-active" xlink:href="#forward-shape"/>
-  <use id="forward-disabled" xlink:href="#forward-shape"/>
-  <use id="reload" xlink:href="#reload-shape"/>
-  <use id="reload-active" xlink:href="#reload-shape"/>
-  <use id="reload-disabled" xlink:href="#reload-shape"/>
-  <use id="stop" xlink:href="#stop-shape"/>
-  <use id="stop-active" xlink:href="#stop-shape"/>
-  <use id="stop-disabled" xlink:href="#stop-shape"/>
-  <use id="bookmark" xlink:href="#bookmark-shape"/>
-  <use id="bookmark-active" xlink:href="#bookmark-shape"/>
-  <use id="bookmark-disabled" xlink:href="#bookmark-shape"/>
-  <use id="bookmarked" xlink:href="#bookmarked-shape"/>
-  <use id="bookmarked-active" xlink:href="#bookmarked-shape"/>
-  <use id="bookmarked-disabled" xlink:href="#bookmarked-shape"/>
-</svg>
--- a/browser/themes/linux/customizableui/panelUI.css
+++ b/browser/themes/linux/customizableui/panelUI.css
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 %include ../../shared/customizableui/panelUI.inc.css
 
 .panel-subviews {
-  background-color: -moz-dialog;
+  background-color: var(--panel-arrowcontent-background);
 }
 
 #BMB_bookmarksPopup > menuitem[type="checkbox"] {
   -moz-appearance: none !important; /* important, to override toolkit rule */
 }
 
 #BMB_bookmarksPopup menupopup {
   -moz-appearance: none;
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -10,17 +10,16 @@ browser.jar:
   skin/classic/browser/aboutSessionRestore-window-icon.png
   skin/classic/browser/aboutSyncTabs.css
 * skin/classic/browser/syncedtabs/sidebar.css     (syncedtabs/sidebar.css)
   skin/classic/browser/actionicon-tab.png
 * skin/classic/browser/browser.css
 * skin/classic/browser/devedition.css
 * skin/classic/browser/browser-lightweightTheme.css
   skin/classic/browser/click-to-play-warning-stripes.png
-  skin/classic/browser/content-contextmenu.svg
   skin/classic/browser/Info.png
   skin/classic/browser/menuPanel.png
   skin/classic/browser/menuPanel@2x.png
   skin/classic/browser/menuPanel-customize.png
   skin/classic/browser/menuPanel-customize@2x.png
   skin/classic/browser/menuPanel-exit.png
   skin/classic/browser/menuPanel-exit@2x.png
   skin/classic/browser/menuPanel-help.png
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -3111,21 +3111,21 @@ menulist.translate-infobar-element > .me
   font-weight: bold;
 }
 
 /* Ctrl-Tab */
 
 #ctrlTab-panel {
   -moz-appearance: none;
   -moz-window-shadow: none;
-  background: rgba(27%,27%,27%,.7);
+  background: hsla(0,0%,33%,.85);
   color: white;
   border-style: none;
   padding: 20px 10px 10px;
-  text-shadow: 0 0 1px rgb(27%,27%,27%), 0 0 2px rgb(27%,27%,27%);
+  text-shadow: 0 0 1px hsl(0,0%,12%), 0 0 2px hsl(0,0%,12%);
 }
 
 .ctrlTab-favicon[src] {
   background-color: white;
   width: 20px;
   height: 20px;
   padding: 2px;
 }
deleted file mode 100644
--- a/browser/themes/osx/content-contextmenu.svg
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16">
-  <style>
-    use:not(:target) {
-      display: none;
-    }
-    use {
-      fill: menutext;
-    }
-    use[id$="-active"] {
-      fill: -moz-mac-menutextselect;
-    }
-    use[id$="-disabled"] {
-      fill: -moz-mac-menutextdisable;
-    }
-  </style>
-  <defs>
-    <path id="back-shape" fill-rule="evenodd" d="M1.192,8.893L2.21,9.964c0.064,0.065,0.136,0.117,0.214,0.159 l5.199,5.301c0.607,0.63,1.465,0.764,1.915,0.297l1.02-1.082c0.449-0.467,0.32-1.357-0.288-1.99l-2.116-2.158h5.705 c0.671,0,1.215-0.544,1.215-1.215v-2.43c0-0.671-0.544-1.215-1.215-1.215H8.094l2.271-2.309c0.609-0.626,0.737-1.512,0.288-1.974 L9.635,0.278C9.184-0.188,8.327-0.055,7.718,0.575L2.479,5.901C2.38,5.946,2.289,6.008,2.21,6.089L1.192,7.171 c-0.21,0.219-0.293,0.53-0.26,0.864C0.899,8.367,0.981,8.676,1.192,8.893z"/>
-    <path id="forward-shape" fill-rule="evenodd" d="M14.808,7.107L13.79,6.036c-0.064-0.065-0.136-0.117-0.214-0.159 L8.377,0.576C7.77-0.054,6.912-0.189,6.461,0.278L5.441,1.36c-0.449,0.467-0.32,1.357,0.288,1.99l2.116,2.158H2.14 c-0.671,0-1.215,0.544-1.215,1.215v2.43c0,0.671,0.544,1.215,1.215,1.215h5.765l-2.271,2.309c-0.609,0.626-0.737,1.512-0.288,1.974 l1.019,1.072c0.451,0.465,1.308,0.332,1.917-0.297l5.238-5.326c0.1-0.045,0.191-0.107,0.269-0.188l1.019-1.082 c0.21-0.219,0.293-0.53,0.26-0.864C15.101,7.633,15.019,7.324,14.808,7.107z"/>
-    <path id="reload-shape" fill-rule="evenodd" d="M15.429,8h-8l3.207-3.207C9.889,4.265,8.986,3.947,8,3.947 c-2.554,0-4.625,2.071-4.625,4.625S5.446,13.196,8,13.196c1.638,0,3.069-0.857,3.891-2.141l2.576,1.104 C13.199,14.439,10.794,16,8,16c-4.103,0-7.429-3.326-7.429-7.429S3.897,1.143,8,1.143c1.762,0,3.366,0.624,4.631,1.654L15.429,0V8z"/>
-    <polygon id="stop-shape" points="16,2.748 13.338,0.079 8.038,5.391 2.661,0 0,2.669 5.377,8.059 0.157,13.292 2.819,15.961 8.039,10.728 13.298,16 15.959,13.331 10.701,8.06"/>
-    <path id="bookmark-shape" d="M8.008,3.632l0.986,2.012l0.452,0.922l1.014,0.169l2.326,0.389l-1.719,1.799l-0.676,0.708l0.145,0.967 L10.896,13l-1.959-1.039l-0.937-0.497l-0.937,0.497l-1.957,1.038L5.468,10.6l0.146-0.968L4.937,8.924L3.219,7.126l2.351-0.39 l1.023-0.17l0.45-0.934L8.008,3.632 M8,0C7.72,0,7.44,0.217,7.228,0.65L5.242,4.766L0.907,5.485c-0.958,0.159-1.195,0.861-0.53,1.56 l3.113,3.258l-0.69,4.583c-0.105,0.689,0.172,1.092,0.658,1.092c0.185,0,0.399-0.058,0.635-0.181l3.906-2.072l3.906,2.072 c0.236,0.123,0.45,0.181,0.635,0.181c0.486,0,0.762-0.403,0.659-1.092l-0.687-4.583l3.109-3.255c0.666-0.702,0.428-1.404-0.53-1.564 l-4.303-0.719L8.772,0.65C8.56,0.217,8.28,0,8,0L8,0z"/>
-    <path id="bookmarked-shape" d="M8,0C7.719,0,7.438,0.217,7.225,0.651L5.233,4.773l-4.35,0.72c-0.961,0.159-1.199,0.862-0.531,1.562 l3.124,3.262l-0.692,4.589C2.679,15.596,2.957,16,3.444,16c0.185,0,0.401-0.058,0.637-0.181L8,13.744l3.919,2.075 C12.156,15.942,12.372,16,12.557,16c0.487,0,0.764-0.404,0.661-1.094l-0.69-4.589l3.12-3.259c0.668-0.703,0.43-1.406-0.532-1.566 l-4.317-0.72L8.775,0.651C8.562,0.217,8.281,0,8,0L8,0z"/>
-  </defs>
-  <use id="back" xlink:href="#back-shape"/>
-  <use id="back-active" xlink:href="#back-shape"/>
-  <use id="back-disabled" xlink:href="#back-shape"/>
-  <use id="forward" xlink:href="#forward-shape"/>
-  <use id="forward-active" xlink:href="#forward-shape"/>
-  <use id="forward-disabled" xlink:href="#forward-shape"/>
-  <use id="reload" xlink:href="#reload-shape"/>
-  <use id="reload-active" xlink:href="#reload-shape"/>
-  <use id="reload-disabled" xlink:href="#reload-shape"/>
-  <use id="stop" xlink:href="#stop-shape"/>
-  <use id="stop-active" xlink:href="#stop-shape"/>
-  <use id="stop-disabled" xlink:href="#stop-shape"/>
-  <use id="bookmark" xlink:href="#bookmark-shape"/>
-  <use id="bookmark-active" xlink:href="#bookmark-shape"/>
-  <use id="bookmark-disabled" xlink:href="#bookmark-shape"/>
-  <use id="bookmarked" xlink:href="#bookmarked-shape"/>
-  <use id="bookmarked-active" xlink:href="#bookmarked-shape"/>
-  <use id="bookmarked-disabled" xlink:href="#bookmarked-shape"/>
-</svg>
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -10,17 +10,16 @@ browser.jar:
   skin/classic/browser/aboutSyncTabs.css
 * skin/classic/browser/syncedtabs/sidebar.css          (syncedtabs/sidebar.css)
   skin/classic/browser/actionicon-tab.png
   skin/classic/browser/actionicon-tab@2x.png
 * skin/classic/browser/browser.css
 * skin/classic/browser/devedition.css
 * skin/classic/browser/browser-lightweightTheme.css
   skin/classic/browser/click-to-play-warning-stripes.png
-  skin/classic/browser/content-contextmenu.svg
   skin/classic/browser/Info.png
   skin/classic/browser/keyhole-circle.png
   skin/classic/browser/keyhole-circle@2x.png
   skin/classic/browser/subtle-pattern.png
   skin/classic/browser/menu-back.png
   skin/classic/browser/menu-forward.png
   skin/classic/browser/menuPanel.png
   skin/classic/browser/menuPanel@2x.png
copy from browser/themes/windows/content-contextmenu.svg
copy to browser/themes/shared/content-contextmenu.svg
--- a/browser/themes/windows/content-contextmenu.svg
+++ b/browser/themes/shared/content-contextmenu.svg
@@ -1,46 +1,18 @@
 <?xml version="1.0"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16">
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
   <style>
-    use:not(:target) {
+    path:not(:target),
+    polygon:not(:target) {
       display: none;
     }
-    use {
-      fill: menutext;
-    }
-    use[id$="-active"] {
-      fill: -moz-menuhovertext;
-    }
-    use[id$="-disabled"] {
-      fill: graytext;
-    }
   </style>
-  <defs>
-    <path id="back-shape" fill-rule="evenodd" d="M1.192,8.893L2.21,9.964c0.064,0.065,0.136,0.117,0.214,0.159 l5.199,5.301c0.607,0.63,1.465,0.764,1.915,0.297l1.02-1.082c0.449-0.467,0.32-1.357-0.288-1.99l-2.116-2.158h5.705 c0.671,0,1.215-0.544,1.215-1.215v-2.43c0-0.671-0.544-1.215-1.215-1.215H8.094l2.271-2.309c0.609-0.626,0.737-1.512,0.288-1.974 L9.635,0.278C9.184-0.188,8.327-0.055,7.718,0.575L2.479,5.901C2.38,5.946,2.289,6.008,2.21,6.089L1.192,7.171 c-0.21,0.219-0.293,0.53-0.26,0.864C0.899,8.367,0.981,8.676,1.192,8.893z"/>
-    <path id="forward-shape" fill-rule="evenodd" d="M14.808,7.107L13.79,6.036c-0.064-0.065-0.136-0.117-0.214-0.159 L8.377,0.576C7.77-0.054,6.912-0.189,6.461,0.278L5.441,1.36c-0.449,0.467-0.32,1.357,0.288,1.99l2.116,2.158H2.14 c-0.671,0-1.215,0.544-1.215,1.215v2.43c0,0.671,0.544,1.215,1.215,1.215h5.765l-2.271,2.309c-0.609,0.626-0.737,1.512-0.288,1.974 l1.019,1.072c0.451,0.465,1.308,0.332,1.917-0.297l5.238-5.326c0.1-0.045,0.191-0.107,0.269-0.188l1.019-1.082 c0.21-0.219,0.293-0.53,0.26-0.864C15.101,7.633,15.019,7.324,14.808,7.107z"/>
-    <path id="reload-shape" fill-rule="evenodd" d="M15.429,8h-8l3.207-3.207C9.889,4.265,8.986,3.947,8,3.947 c-2.554,0-4.625,2.071-4.625,4.625S5.446,13.196,8,13.196c1.638,0,3.069-0.857,3.891-2.141l2.576,1.104 C13.199,14.439,10.794,16,8,16c-4.103,0-7.429-3.326-7.429-7.429S3.897,1.143,8,1.143c1.762,0,3.366,0.624,4.631,1.654L15.429,0V8z"/>
-    <polygon id="stop-shape" points="16,2.748 13.338,0.079 8.038,5.391 2.661,0 0,2.669 5.377,8.059 0.157,13.292 2.819,15.961 8.039,10.728 13.298,16 15.959,13.331 10.701,8.06"/>
-    <path id="bookmark-shape" d="M8.008,3.632l0.986,2.012l0.452,0.922l1.014,0.169l2.326,0.389l-1.719,1.799l-0.676,0.708l0.145,0.967 L10.896,13l-1.959-1.039l-0.937-0.497l-0.937,0.497l-1.957,1.038L5.468,10.6l0.146-0.968L4.937,8.924L3.219,7.126l2.351-0.39 l1.023-0.17l0.45-0.934L8.008,3.632 M8,0C7.72,0,7.44,0.217,7.228,0.65L5.242,4.766L0.907,5.485c-0.958,0.159-1.195,0.861-0.53,1.56 l3.113,3.258l-0.69,4.583c-0.105,0.689,0.172,1.092,0.658,1.092c0.185,0,0.399-0.058,0.635-0.181l3.906-2.072l3.906,2.072 c0.236,0.123,0.45,0.181,0.635,0.181c0.486,0,0.762-0.403,0.659-1.092l-0.687-4.583l3.109-3.255c0.666-0.702,0.428-1.404-0.53-1.564 l-4.303-0.719L8.772,0.65C8.56,0.217,8.28,0,8,0L8,0z"/>
-    <path id="bookmarked-shape" d="M8,0C7.719,0,7.438,0.217,7.225,0.651L5.233,4.773l-4.35,0.72c-0.961,0.159-1.199,0.862-0.531,1.562 l3.124,3.262l-0.692,4.589C2.679,15.596,2.957,16,3.444,16c0.185,0,0.401-0.058,0.637-0.181L8,13.744l3.919,2.075 C12.156,15.942,12.372,16,12.557,16c0.487,0,0.764-0.404,0.661-1.094l-0.69-4.589l3.12-3.259c0.668-0.703,0.43-1.406-0.532-1.566 l-4.317-0.72L8.775,0.651C8.562,0.217,8.281,0,8,0L8,0z"/>
-  </defs>
-  <use id="back" xlink:href="#back-shape"/>
-  <use id="back-active" xlink:href="#back-shape"/>
-  <use id="back-disabled" xlink:href="#back-shape"/>
-  <use id="forward" xlink:href="#forward-shape"/>
-  <use id="forward-active" xlink:href="#forward-shape"/>
-  <use id="forward-disabled" xlink:href="#forward-shape"/>
-  <use id="reload" xlink:href="#reload-shape"/>
-  <use id="reload-active" xlink:href="#reload-shape"/>
-  <use id="reload-disabled" xlink:href="#reload-shape"/>
-  <use id="stop" xlink:href="#stop-shape"/>
-  <use id="stop-active" xlink:href="#stop-shape"/>
-  <use id="stop-disabled" xlink:href="#stop-shape"/>
-  <use id="bookmark" xlink:href="#bookmark-shape"/>
-  <use id="bookmark-active" xlink:href="#bookmark-shape"/>
-  <use id="bookmark-disabled" xlink:href="#bookmark-shape"/>
-  <use id="bookmarked" xlink:href="#bookmarked-shape"/>
-  <use id="bookmarked-active" xlink:href="#bookmarked-shape"/>
-  <use id="bookmarked-disabled" xlink:href="#bookmarked-shape"/>
+  <path id="back" fill-rule="evenodd" d="M1.192,8.893L2.21,9.964c0.064,0.065,0.136,0.117,0.214,0.159 l5.199,5.301c0.607,0.63,1.465,0.764,1.915,0.297l1.02-1.082c0.449-0.467,0.32-1.357-0.288-1.99l-2.116-2.158h5.705 c0.671,0,1.215-0.544,1.215-1.215v-2.43c0-0.671-0.544-1.215-1.215-1.215H8.094l2.271-2.309c0.609-0.626,0.737-1.512,0.288-1.974 L9.635,0.278C9.184-0.188,8.327-0.055,7.718,0.575L2.479,5.901C2.38,5.946,2.289,6.008,2.21,6.089L1.192,7.171 c-0.21,0.219-0.293,0.53-0.26,0.864C0.899,8.367,0.981,8.676,1.192,8.893z"/>
+  <path id="forward" fill-rule="evenodd" d="M14.808,7.107L13.79,6.036c-0.064-0.065-0.136-0.117-0.214-0.159 L8.377,0.576C7.77-0.054,6.912-0.189,6.461,0.278L5.441,1.36c-0.449,0.467-0.32,1.357,0.288,1.99l2.116,2.158H2.14 c-0.671,0-1.215,0.544-1.215,1.215v2.43c0,0.671,0.544,1.215,1.215,1.215h5.765l-2.271,2.309c-0.609,0.626-0.737,1.512-0.288,1.974 l1.019,1.072c0.451,0.465,1.308,0.332,1.917-0.297l5.238-5.326c0.1-0.045,0.191-0.107,0.269-0.188l1.019-1.082 c0.21-0.219,0.293-0.53,0.26-0.864C15.101,7.633,15.019,7.324,14.808,7.107z"/>
+  <path id="reload" fill-rule="evenodd" d="M15.429,8h-8l3.207-3.207C9.889,4.265,8.986,3.947,8,3.947 c-2.554,0-4.625,2.071-4.625,4.625S5.446,13.196,8,13.196c1.638,0,3.069-0.857,3.891-2.141l2.576,1.104 C13.199,14.439,10.794,16,8,16c-4.103,0-7.429-3.326-7.429-7.429S3.897,1.143,8,1.143c1.762,0,3.366,0.624,4.631,1.654L15.429,0V8z"/>
+  <polygon id="stop" points="16,2.748 13.338,0.079 8.038,5.391 2.661,0 0,2.669 5.377,8.059 0.157,13.292 2.819,15.961 8.039,10.728 13.298,16 15.959,13.331 10.701,8.06"/>
+  <path id="bookmark" d="M8.008,3.632l0.986,2.012l0.452,0.922l1.014,0.169l2.326,0.389l-1.719,1.799l-0.676,0.708l0.145,0.967 L10.896,13l-1.959-1.039l-0.937-0.497l-0.937,0.497l-1.957,1.038L5.468,10.6l0.146-0.968L4.937,8.924L3.219,7.126l2.351-0.39 l1.023-0.17l0.45-0.934L8.008,3.632 M8,0C7.72,0,7.44,0.217,7.228,0.65L5.242,4.766L0.907,5.485c-0.958,0.159-1.195,0.861-0.53,1.56 l3.113,3.258l-0.69,4.583c-0.105,0.689,0.172,1.092,0.658,1.092c0.185,0,0.399-0.058,0.635-0.181l3.906-2.072l3.906,2.072 c0.236,0.123,0.45,0.181,0.635,0.181c0.486,0,0.762-0.403,0.659-1.092l-0.687-4.583l3.109-3.255c0.666-0.702,0.428-1.404-0.53-1.564 l-4.303-0.719L8.772,0.65C8.56,0.217,8.28,0,8,0L8,0z"/>
+  <path id="bookmarked" d="M8,0C7.719,0,7.438,0.217,7.225,0.651L5.233,4.773l-4.35,0.72c-0.961,0.159-1.199,0.862-0.531,1.562 l3.124,3.262l-0.692,4.589C2.679,15.596,2.957,16,3.444,16c0.185,0,0.401-0.058,0.637-0.181L8,13.744l3.919,2.075 C12.156,15.942,12.372,16,12.557,16c0.487,0,0.764-0.404,0.661-1.094l-0.69-4.589l3.12-3.259c0.668-0.703,0.43-1.406-0.532-1.566 l-4.317-0.72L8.775,0.651C8.562,0.217,8.281,0,8,0L8,0z"/>
 </svg>
--- a/browser/themes/shared/contextmenu.inc.css
+++ b/browser/themes/shared/contextmenu.inc.css
@@ -3,95 +3,49 @@
   -moz-box-pack: center;
   -moz-box-align: center;
 }
 
 #context-navigation > .menuitem-iconic > .menu-iconic-left {
   -moz-appearance: none;
 }
 
+#context-navigation > .menuitem-iconic > .menu-iconic-left > .menu-iconic-icon {
+  width: 16px;
+  height: 16px;
+  margin: 7px;
+  filter: url(chrome://browser/skin/filters.svg#fill);
+  fill: currentColor;
+}
+
 #context-back {
   list-style-image: url("chrome://browser/skin/content-contextmenu.svg#back");
 }
 
-#context-back[_moz-menuactive=true] {
-  list-style-image: url("chrome://browser/skin/content-contextmenu.svg#back-active");
-}
-
-#context-back[disabled=true] {
-  list-style-image: url("chrome://browser/skin/content-contextmenu.svg#back-disabled");
-}
-
 #context-forward {
   list-style-image: url("chrome://browser/skin/content-contextmenu.svg#forward");
 }
 
-#context-forward[_moz-menuactive=true] {
-  list-style-image: url("chrome://browser/skin/content-contextmenu.svg#forward-active");
-}
-
-#context-forward[disabled=true] {
-  list-style-image: url("chrome://browser/skin/content-contextmenu.svg#forward-disabled");
-}
-
 #context-reload {
   list-style-image: url("chrome://browser/skin/content-contextmenu.svg#reload");
 }
 
-#context-reload[_moz-menuactive=true] {
-  list-style-image: url("chrome://browser/skin/content-contextmenu.svg#reload-active");
-}
-
-#context-reload[disabled=true] {
-  list-style-image: url("chrome://browser/skin/content-contextmenu.svg#reload-disabled");
-}
-
 #context-stop {
   list-style-image: url("chrome://browser/skin/content-contextmenu.svg#stop");
 }
 
-#context-stop[_moz-menuactive=true] {
-  list-style-image: url("chrome://browser/skin/content-contextmenu.svg#stop-active");
-}
-
-#context-stop[disabled=true] {
-  list-style-image: url("chrome://browser/skin/content-contextmenu.svg#stop-disabled");
-}
-
 #context-bookmarkpage {
   list-style-image: url("chrome://browser/skin/content-contextmenu.svg#bookmark");
 }
 
-#context-bookmarkpage[_moz-menuactive=true] {
-  list-style-image: url("chrome://browser/skin/content-contextmenu.svg#bookmark-active");
-}
-
-#context-bookmarkpage[disabled=true] {
-  list-style-image: url("chrome://browser/skin/content-contextmenu.svg#bookmark-disabled");
-}
-
 #context-bookmarkpage[starred=true] {
   list-style-image: url("chrome://browser/skin/content-contextmenu.svg#bookmarked");
 }
 
-#context-bookmarkpage[starred=true][_moz-menuactive=true] {
-  list-style-image: url("chrome://browser/skin/content-contextmenu.svg#bookmarked-active");
-}
-
-#context-bookmarkpage[starred=true][disabled=true] {
-  list-style-image: url("chrome://browser/skin/content-contextmenu.svg#bookmarked-disabled");
-}
-
 #context-back:-moz-locale-dir(rtl),
 #context-forward:-moz-locale-dir(rtl),
 #context-reload:-moz-locale-dir(rtl) {
   transform: scaleX(-1);
 }
 
-#context-navigation > .menuitem-iconic > .menu-iconic-left > .menu-iconic-icon {
-  width: 16px;
-  height: 16px;
-  margin: 7px;
-}
-
 #context-media-eme-learnmore {
   list-style-image: url("chrome://browser/skin/drm-icon.svg#chains");
 }
--- a/browser/themes/shared/downloads/downloads.inc.css
+++ b/browser/themes/shared/downloads/downloads.inc.css
@@ -47,17 +47,17 @@
   min-width: 0;
   border-left: 1px solid hsla(210,4%,10%,.14);
   -moz-appearance: none !important;
 }
 
 .downloadsPanelFooterButton {
   -moz-appearance: none;
   background-color: transparent;
-  color: black;
+  color: inherit;
   margin: 0;
   padding: 0;
   min-height: 40px;
 }
 
 .downloadsPanelFooterButton:hover {
   outline: 1px solid hsla(210,4%,10%,.07);
   background-color: hsla(210,4%,10%,.07);
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -10,16 +10,17 @@
   skin/classic/browser/aboutNetError.css                       (../shared/aboutNetError.css)
   skin/classic/browser/blockedSite.css                         (../shared/blockedSite.css)
   skin/classic/browser/error-pages.css                         (../shared/error-pages.css)
 * skin/classic/browser/aboutProviderDirectory.css              (../shared/aboutProviderDirectory.css)
 * skin/classic/browser/aboutSessionRestore.css                 (../shared/aboutSessionRestore.css)
   skin/classic/browser/aboutSocialError.css                    (../shared/aboutSocialError.css)
   skin/classic/browser/aboutTabCrashed.css                     (../shared/aboutTabCrashed.css)
   skin/classic/browser/aboutWelcomeBack.css                    (../shared/aboutWelcomeBack.css)
+  skin/classic/browser/content-contextmenu.svg                 (../shared/content-contextmenu.svg)
   skin/classic/browser/addons/addon-install-blocked.svg        (../shared/addons/addon-install-blocked.svg)
   skin/classic/browser/addons/addon-install-confirm.svg        (../shared/addons/addon-install-confirm.svg)
   skin/classic/browser/addons/addon-install-downloading.svg    (../shared/addons/addon-install-downloading.svg)
   skin/classic/browser/addons/addon-install-error.svg          (../shared/addons/addon-install-error.svg)
   skin/classic/browser/addons/addon-install-installed.svg      (../shared/addons/addon-install-installed.svg)
   skin/classic/browser/addons/addon-install-restart.svg        (../shared/addons/addon-install-restart.svg)
   skin/classic/browser/addons/addon-install-warning.svg        (../shared/addons/addon-install-warning.svg)
   skin/classic/browser/addons/addon-install-anchor.svg         (../shared/addons/addon-install-anchor.svg)
--- a/browser/themes/windows/browser-aero.css
+++ b/browser/themes/windows/browser-aero.css
@@ -327,23 +327,16 @@
     }
   }
 
   #main-window[darkwindowframe="true"] #toolbar-menubar:not(:-moz-lwtheme):not(:-moz-window-inactive),
   #main-window[darkwindowframe="true"] #TabsToolbar:not(:-moz-lwtheme):not(:-moz-window-inactive) {
     color: white;
   }
 
-  @media (-moz-os-version: windows-vista),
-         (-moz-os-version: windows-win7),{
-    #toolbar-menubar:not(:-moz-lwtheme) {
-      text-shadow: 0 0 .5em white, 0 0 .5em white, 0 1px 0 rgba(255,255,255,.4);
-    }
-  }
-
   /* Show borders on vista through win8, but not on win10 and later: */
   @media (-moz-os-version: windows-vista),
          (-moz-os-version: windows-win7),
          (-moz-os-version: windows-win8) {
     /* Vertical toolbar border */
     #main-window:not([customizing])[sizemode=normal] #navigator-toolbox:not(:-moz-lwtheme)::after,
     #main-window:not([customizing])[sizemode=normal] #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(:-moz-lwtheme),
     #main-window:not([customizing])[sizemode=normal] #navigator-toolbox:-moz-lwtheme,
@@ -371,52 +364,50 @@
   #main-window[sizemode=normal] #TabsToolbar {
     padding-left: 1px;
     padding-right: 1px;
   }
 
   #appcontent:not(:-moz-lwtheme) {
     background-color: -moz-dialog;
   }
-
-  #main-menubar:not(:-moz-lwtheme):not(:-moz-window-inactive) {
-    background-color: rgba(255,255,255,.5);
-    color: black;
-  }
-
-  @media (-moz-os-version: windows-vista),
-         (-moz-os-version: windows-win7) {
-    #main-menubar:not(:-moz-lwtheme):not(:-moz-window-inactive) {
-      border-radius: 4px;
-    }
-
-    /* Artificially draw window borders that are covered by lwtheme, see bug 591930.
-     * We use a different border for win8, and this is not necessary on win10+ */
-    #main-window[sizemode="normal"] > #tab-view-deck > #browser-panel:-moz-lwtheme {
-      border-top: 2px solid;
-      -moz-border-top-colors: @glassActiveBorderColor@ rgba(255,255,255,.6);
-    }
-
-    #main-window[sizemode="normal"] > #tab-view-deck > #browser-panel:-moz-lwtheme:-moz-window-inactive {
-      -moz-border-top-colors: @glassInactiveBorderColor@ rgba(255,255,255,.6);
-    }
-  }
 }
 
 @media (-moz-windows-glass) {
   #main-window[sizemode=normal] #nav-bar {
     border-top-left-radius: 2.5px;
     border-top-right-radius: 2.5px;
   }
 
   #main-window[sizemode=fullscreen]:not(:-moz-lwtheme) {
     -moz-appearance: none;
     background-color: #556;
   }
 
+  #toolbar-menubar:not(:-moz-lwtheme) {
+    text-shadow: 0 0 .5em white, 0 0 .5em white, 0 1px 0 rgba(255,255,255,.4);
+  }
+
+  #main-menubar:not(:-moz-lwtheme):not(:-moz-window-inactive) {
+    background-color: rgba(255,255,255,.5);
+    color: black;
+    border-radius: 4px;
+  }
+
+  /* Artificially draw window borders that are covered by lwtheme, see bug 591930.
+   * We use a different border for win8, and this is not necessary on win10+ */
+  #main-window[sizemode="normal"] > #tab-view-deck > #browser-panel:-moz-lwtheme {
+    border-top: 2px solid;
+    -moz-border-top-colors: @glassActiveBorderColor@ rgba(255,255,255,.6);
+  }
+
+  #main-window[sizemode="normal"] > #tab-view-deck > #browser-panel:-moz-lwtheme:-moz-window-inactive {
+    -moz-border-top-colors: @glassInactiveBorderColor@ rgba(255,255,255,.6);
+  }
+
   /* Glass Fog */
 
   #TabsToolbar:not(:-moz-lwtheme) {
     position: relative;
   }
 
   #TabsToolbar:not(:-moz-lwtheme)::after {
     /* Because we use placeholders for window controls etc. in the tabstrip,
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2348,22 +2348,22 @@ notification[value="translation"] {
   list-style-image: url("chrome://browser/skin/places/unsortedBookmarks.png");
   -moz-image-region: auto;
 }
 
 /* Ctrl-Tab */
 
 #ctrlTab-panel {
   -moz-appearance: none;
-  background: rgba(27%,27%,27%,.7);
+  background: hsla(0,0%,33%,.85);
   color: white;
   border-style: none;
   padding: 20px 10px 10px;
   font-weight: bold;
-  text-shadow: 0 0 1px rgb(27%,27%,27%), 0 0 2px rgb(27%,27%,27%);
+  text-shadow: 0 0 1px hsl(0,0%,12%), 0 0 2px hsl(0,0%,12%);
 }
 
 .ctrlTab-favicon[src] {
   background-color: white;
   width: 20px;
   height: 20px;
   padding: 2px;
 }
--- a/browser/themes/windows/customizableui/panelUI.css
+++ b/browser/themes/windows/customizableui/panelUI.css
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 %include ../../shared/customizableui/panelUI.inc.css
 
 .panel-subviews {
-  background-color: -moz-field;
+  background-color: var(--panel-arrowcontent-background);
 }
 
 #PanelUI-contents #zoom-out-btn {
   padding-left: 12px;
   padding-right: 12px;
 }
 
 #PanelUI-contents #zoom-in-btn {
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -12,17 +12,16 @@ browser.jar:
   skin/classic/browser/actionicon-tab.png
   skin/classic/browser/actionicon-tab@2x.png
   skin/classic/browser/actionicon-tab-XPVista7.png
 * skin/classic/browser/browser.css
 * skin/classic/browser/devedition.css
 * skin/classic/browser/browser-lightweightTheme.css
   skin/classic/browser/caption-buttons.svg
   skin/classic/browser/click-to-play-warning-stripes.png
-  skin/classic/browser/content-contextmenu.svg
   skin/classic/browser/Info.png
   skin/classic/browser/Info-XP.png
   skin/classic/browser/keyhole-forward-mask.svg
   skin/classic/browser/livemark-folder.png
   skin/classic/browser/livemark-folder-XP.png
   skin/classic/browser/menu-back.png
   skin/classic/browser/menu-back-XP.png
   skin/classic/browser/menu-forward.png
--- a/devtools/client/inspector/breadcrumbs.js
+++ b/devtools/client/inspector/breadcrumbs.js
@@ -6,17 +6,16 @@
 
 "use strict";
 
 /* eslint-disable mozilla/reject-some-requires */
 const {Ci} = require("chrome");
 /* eslint-enable mozilla/reject-some-requires */
 const Services = require("Services");
 const promise = require("promise");
-const FocusManager = Services.focus;
 
 const ELLIPSIS = Services.prefs.getComplexValue(
     "intl.ellipsis",
     Ci.nsIPrefLocalizedString).data;
 const MAX_LABEL_LENGTH = 40;
 
 const NS_XHTML = "http://www.w3.org/1999/xhtml";
 const SCROLL_REPEAT_MS = 100;
@@ -351,25 +350,26 @@ HTMLBreadcrumbs.prototype = {
     this.outer.addEventListener("mouseout", this, true);
     this.outer.addEventListener("focus", this, true);
 
     this.shortcuts = new KeyShortcuts({ window: this.chromeWin, target: this.outer });
     this.handleShortcut = this.handleShortcut.bind(this);
 
     this.shortcuts.on("Right", this.handleShortcut);
     this.shortcuts.on("Left", this.handleShortcut);
-    this.shortcuts.on("Tab", this.handleShortcut);
-    this.shortcuts.on("Shift+Tab", this.handleShortcut);
 
     // We will save a list of already displayed nodes in this array.
     this.nodeHierarchy = [];
 
     // Last selected node in nodeHierarchy.
     this.currentIndex = -1;
 
+    // Used to build a unique breadcrumb button Id.
+    this.breadcrumbsWidgetItemId = 0;
+
     this.update = this.update.bind(this);
     this.updateSelectors = this.updateSelectors.bind(this);
     this.selection.on("new-node-front", this.update);
     this.selection.on("pseudoclass", this.updateSelectors);
     this.selection.on("attribute-changed", this.updateSelectors);
     this.inspector.on("markupmutation", this.update);
     this.update();
   },
@@ -482,41 +482,35 @@ HTMLBreadcrumbs.prototype = {
     } else if (event.type == "mouseout") {
       this.handleMouseOut(event);
     } else if (event.type == "focus") {
       this.handleFocus(event);
     }
   },
 
   /**
-   * Focus event handler. When breadcrumbs container gets focus, if there is an
-   * already selected breadcrumb, move focus to it.
+   * Focus event handler. When breadcrumbs container gets focus,
+   * aria-activedescendant needs to be updated to currently selected
+   * breadcrumb. Ensures that the focus stays on the container at all times.
    * @param {DOMEvent} event.
    */
   handleFocus: function (event) {
-    let control = this.container.querySelector(
-      ".breadcrumbs-widget-item[checked]");
-    if (!this.suspendFocus && control && control !== event.target) {
-      // If we already have a selected breadcrumb and focus target is not it,
-      // move focus to selected breadcrumb
-      event.preventDefault();
-      control.focus();
-    }
-    this.suspendFocus = false;
+    event.stopPropagation();
+
+    this.outer.setAttribute("aria-activedescendant",
+      this.nodeHierarchy[this.currentIndex].button.id);
+
+    this.outer.focus();
   },
 
   /**
    * On click navigate to the correct node.
    * @param {DOMEvent} event.
    */
   handleClick: function (event) {
-    // When clicking a button temporarily suspend the behaviour that refocuses
-    // the currently selected button, to prevent flicking back to that button
-    // See Bug 1272011
-    this.suspendFocus = true;
     let target = event.originalTarget;
     if (target.tagName == "button") {
       target.onBreadcrumbsClick();
     }
   },
 
   /**
    * On mouse over, highlight the corresponding content DOM Node.
@@ -549,38 +543,27 @@ HTMLBreadcrumbs.prototype = {
     if (!this.selection.isElementNode()) {
       return;
     }
 
     event.preventDefault();
     event.stopPropagation();
 
     this.keyPromise = (this.keyPromise || promise.resolve(null)).then(() => {
+      let currentnode;
       if (name === "Left" && this.currentIndex != 0) {
-        let node = this.nodeHierarchy[this.currentIndex - 1].node;
-        return this.selection.setNodeFront(node, "breadcrumbs");
+        currentnode = this.nodeHierarchy[this.currentIndex - 1];
       } else if (name === "Right" && this.currentIndex < this.nodeHierarchy.length - 1) {
-        let node = this.nodeHierarchy[this.currentIndex + 1].node;
-        return this.selection.setNodeFront(node, "breadcrumbs");
-      } else if (name === "Tab") {
-        // To move focus to next element following the breadcrumbs, relative
-        // element needs to be the last element in breadcrumbs' subtree.
-        let last = this.container.lastChild;
-        while (last && last.lastChild) {
-          last = last.lastChild;
-        }
-        FocusManager.moveFocus(this.chromeWin, last, FocusManager.MOVEFOCUS_FORWARD, 0);
-      } else if (name === "Shift+Tab") {
-        // Tabbing when breadcrumbs or its contents are focused should move focus to
-        // previous focusable element relative to breadcrumbs themselves.
-        let elt = this.container;
-        FocusManager.moveFocus(this.chromeWin, elt, FocusManager.MOVEFOCUS_BACKWARD, 0);
+        currentnode = this.nodeHierarchy[this.currentIndex + 1];
+      } else {
+        return null;
       }
 
-      return null;
+      this.outer.setAttribute("aria-activedescendant", currentnode.button.id);
+      return this.selection.setNodeFront(currentnode.node, "breadcrumbs");
     });
   },
 
   /**
    * Remove nodes and clean up.
    */
   destroy: function () {
     this.selection.off("new-node-front", this.update);
@@ -666,17 +649,19 @@ HTMLBreadcrumbs.prototype = {
    * Build a button representing the node.
    * @param {NodeFront} node The node from the page.
    * @return {DOMNode} The <button> for this node.
    */
   buildButton: function (node) {
     let button = this.chromeDoc.createElementNS(NS_XHTML, "button");
     button.appendChild(this.prettyPrintNodeAsXHTML(node));
     button.className = "breadcrumbs-widget-item";
+    button.id = "breadcrumbs-widget-item-" + this.breadcrumbsWidgetItemId++;
 
+    button.setAttribute("tabindex", "-1");
     button.setAttribute("title", this.prettyPrintNodeAsText(node));
 
     button.onclick = () => {
       button.focus();
     };
 
     button.onBreadcrumbsClick = () => {
       this.selection.setNodeFront(node, "breadcrumbs");
--- a/devtools/client/inspector/inspector.xul
+++ b/devtools/client/inspector/inspector.xul
@@ -49,17 +49,18 @@
           title="&inspectorEyeDropper.label;"
           class="devtools-button command-button-invertable" />
         <div xmlns="http://www.w3.org/1999/xhtml"
           id="inspector-sidebar-toggle-box" />
       </html:div>
       <vbox flex="1" id="markup-box">
       </vbox>
       <html:div id="inspector-breadcrumbs-toolbar" class="devtools-toolbar">
-        <html:div id="inspector-breadcrumbs" class="breadcrumbs-widget-container"/>
+        <html:div id="inspector-breadcrumbs" class="breadcrumbs-widget-container"
+                  role="group" aria-label="&inspectorBreadcrumbsGroup;" tabindex="0" />
       </html:div>
     </vbox>
     <splitter class="devtools-side-splitter"/>
     <vbox id="inspector-sidebar-container">
       <!-- Specify the XHTML namespace explicitly
         otherwise the layout is broken. -->
       <div xmlns="http://www.w3.org/1999/xhtml"
            id="inspector-sidebar"
--- a/devtools/client/inspector/test/browser_inspector_breadcrumbs_keybinding.js
+++ b/devtools/client/inspector/test/browser_inspector_breadcrumbs_keybinding.js
@@ -57,12 +57,15 @@ add_task(function* () {
     }
 
     EventUtils.synthesizeKey(key, {});
     yield onUpdated;
 
     let newNodeFront = yield getNodeFront(newSelection, inspector);
     is(newNodeFront, inspector.selection.nodeFront,
        "The current selection is correct");
+    is(container.getAttribute("aria-activedescendant"),
+       container.querySelector("button[checked]").id,
+      "aria-activedescendant is set correctly");
 
     currentSelection = newSelection;
   }
 });
--- a/devtools/client/inspector/test/browser_inspector_breadcrumbs_keyboard_trap.js
+++ b/devtools/client/inspector/test/browser_inspector_breadcrumbs_keyboard_trap.js
@@ -56,24 +56,28 @@ add_task(function* () {
   let container = doc.getElementById("inspector-breadcrumbs");
 
   let button = container.querySelector("button[checked]");
   let onHighlight = toolbox.once("node-highlight");
   button.click();
   yield onHighlight;
 
   // Ensure a breadcrumb is focused.
-  is(doc.activeElement, button, "Focus is on selected breadcrumb");
+  is(doc.activeElement, container, "Focus is on selected breadcrumb");
+  is(container.getAttribute("aria-activedescendant"), button.id,
+    "aria-activedescendant is set correctly");
 
   for (let { desc, focused, key, options } of TEST_DATA) {
     info(desc);
 
     EventUtils.synthesizeKey(key, options);
     // Wait until the keyPromise promise resolves.
     yield breadcrumbs.keyPromise;
 
     if (focused) {
-      is(doc.activeElement, button, "Focus is on selected breadcrumb");
+      is(doc.activeElement, container, "Focus is on selected breadcrumb");
     } else {
       ok(!containsFocus(doc, container), "Focus is outside of breadcrumbs");
     }
+    is(container.getAttribute("aria-activedescendant"), button.id,
+      "aria-activedescendant is set correctly");
   }
 });
--- a/devtools/client/locales/en-US/inspector.dtd
+++ b/devtools/client/locales/en-US/inspector.dtd
@@ -14,9 +14,14 @@
      the inspector toolbar for the button that lets users add elements to the
      DOM (as children of the currently selected element). -->
 <!ENTITY inspectorAddNode.label       "Create New Node">
 <!ENTITY inspectorAddNode.accesskey   "C">
 
 
 <!-- LOCALIZATION NOTE (inspectorEyeDropper.label): A string displayed as the tooltip of
      a button in the inspector which toggles the Eyedropper tool -->
-<!ENTITY inspectorEyeDropper.label       "Grab a color from the page">
\ No newline at end of file
+<!ENTITY inspectorEyeDropper.label       "Grab a color from the page">
+
+<!-- LOCALIZATION NOTE (inspectorBreadcrumbsGroup): A string visible only to a
+     screen reader and is used to label (using aria-label attribute) a container
+     for inspector breadcrumbs -->
+<!ENTITY inspectorBreadcrumbsGroup       "Breadcrumbs">
--- a/devtools/client/themes/widgets.css
+++ b/devtools/client/themes/widgets.css
@@ -244,25 +244,16 @@
   min-width: 65px;
   margin: 0;
   padding: 0 8px 0 20px;
   border: none;
   outline: none;
   color: hsl(210,30%,85%);
 }
 
-.breadcrumbs-widget-item:-moz-focusring {
-  outline: none;
-}
-
-.breadcrumbs-widget-item[checked]:-moz-focusring > .button-box {
-  outline: var(--theme-focus-outline);
-  outline-offset: -1px;
-}
-
 .breadcrumbs-widget-item > .button-box {
   border: none;
   padding-top: 0;
   padding-bottom: 0;
 }
 
 :root[platform="win"] .breadcrumbs-widget-item:-moz-focusring > .button-box {
   border-width: 0;
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -35,17 +35,17 @@ namespace mozilla {
 const double kNotPaceable = -1.0;
 
 // For the aAllowList parameter of AppendStringOrStringSequence and
 // GetPropertyValuesPairs.
 enum class ListAllowance { eDisallow, eAllow };
 
 /**
  * A comparator to sort nsCSSProperty values such that longhands are sorted
- * before shorthands, and shorthands with less components are sorted before
+ * before shorthands, and shorthands with fewer components are sorted before
  * shorthands with more components.
  *
  * Using this allows us to prioritize values specified by longhands (or smaller
  * shorthand subsets) when longhands and shorthands are both specified
  * on the one keyframe.
  *
  * Example orderings that result from this:
  *
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -286,16 +286,65 @@ Element::UpdateEditableState(bool aNotif
       AddStatesSilently(NS_EVENT_STATE_MOZ_READWRITE);
     } else {
       RemoveStatesSilently(NS_EVENT_STATE_MOZ_READWRITE);
       AddStatesSilently(NS_EVENT_STATE_MOZ_READONLY);
     }
   }
 }
 
+int32_t
+Element::TabIndex()
+{
+  const nsAttrValue* attrVal = mAttrsAndChildren.GetAttr(nsGkAtoms::tabindex);
+  if (attrVal && attrVal->Type() == nsAttrValue::eInteger) {
+    return attrVal->GetIntegerValue();
+  }
+
+  return TabIndexDefault();
+}
+
+void
+Element::Focus(mozilla::ErrorResult& aError)
+{
+  nsCOMPtr<nsIDOMElement> domElement = do_QueryInterface(this);
+  nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+  if (fm && domElement) {
+    aError = fm->SetFocus(domElement, 0);
+  }
+}
+
+void
+Element::SetTabIndex(int32_t aTabIndex, mozilla::ErrorResult& aError)
+{
+  nsAutoString value;
+  value.AppendInt(aTabIndex);
+
+  SetAttr(nsGkAtoms::tabindex, value, aError);
+}
+
+void
+Element::Blur(mozilla::ErrorResult& aError)
+{
+  if (!ShouldBlur(this)) {
+    return;
+  }
+
+  nsIDocument* doc = GetComposedDoc();
+  if (!doc) {
+    return;
+  }
+
+  nsPIDOMWindowOuter* win = doc->GetWindow();
+  nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+  if (win && fm) {
+    aError = fm->ClearFocus(win);
+  }
+}
+
 EventStates
 Element::StyleStateFromLocks() const
 {
   EventStates locks = LockedStyleStates();
   EventStates state = mState | locks;
 
   if (locks.HasState(NS_EVENT_STATE_VISITED)) {
     return state & ~NS_EVENT_STATE_UNVISITED;
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -188,16 +188,41 @@ public:
    */
   void UpdateState(bool aNotify);
 
   /**
    * Method to update mState with link state information.  This does not notify.
    */
   void UpdateLinkState(EventStates aState);
 
+  virtual int32_t TabIndexDefault()
+  {
+    return -1;
+  }
+
+  /**
+   * Get tabIndex of this element. If not found, return TabIndexDefault.
+   */
+  int32_t TabIndex();
+
+  /**
+   * Set tabIndex value to this element.
+   */
+  void SetTabIndex(int32_t aTabIndex, mozilla::ErrorResult& aError);
+
+  /**
+   * Make focus on this element.
+   */
+  virtual void Focus(mozilla::ErrorResult& aError);
+
+  /**
+   * Show blur and clear focus.
+   */
+  virtual void Blur(mozilla::ErrorResult& aError);
+
   /**
    * The style state of this element. This is the real state of the element
    * with any style locks applied for pseudo-class inspecting.
    */
   EventStates StyleState() const
   {
     if (!HasLockedStyleStates()) {
       return mState;
--- a/dom/base/ScriptSettings.cpp
+++ b/dom/base/ScriptSettings.cpp
@@ -807,9 +807,27 @@ AutoSafeJSContext::AutoSafeJSContext(MOZ
 
   DebugOnly<bool> ok = Init(xpc::UnprivilegedJunkScope());
   MOZ_ASSERT(ok,
              "This is quite odd.  We should have crashed in the "
              "xpc::NativeGlobal() call if xpc::UnprivilegedJunkScope() "
              "returned null, and inited correctly otherwise!");
 }
 
+AutoSlowOperation::AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
+  : AutoJSAPI()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+
+  Init();
+}
+
+void
+AutoSlowOperation::CheckForInterrupt()
+{
+  // JS_CheckForInterrupt expects us to be in a compartment.
+  JSAutoCompartment ac(cx(), xpc::UnprivilegedJunkScope());
+  JS_CheckForInterrupt(cx());
+}
+
 } // namespace mozilla
--- a/dom/base/ScriptSettings.h
+++ b/dom/base/ScriptSettings.h
@@ -438,11 +438,28 @@ public:
   {
     return cx();
   }
 
 private:
   MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
+/**
+ * Use AutoSlowOperation when native side calls many JS callbacks in a row
+ * and slow script dialog should be activated if too much time is spent going
+ * through those callbacks.
+ * AutoSlowOperation puts a JSAutoRequest on the stack so that we don't continue
+ * to reset the watchdog and CheckForInterrupt can be then used to check whether
+ * JS execution should be interrupted.
+ */
+class MOZ_RAII AutoSlowOperation : public dom::AutoJSAPI
+{
+public:
+  explicit AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
+  void CheckForInterrupt();
+private:
+  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
 } // namespace mozilla
 
 #endif // mozilla_dom_ScriptSettings_h
--- a/dom/base/nsDOMMutationObserver.cpp
+++ b/dom/base/nsDOMMutationObserver.cpp
@@ -880,20 +880,17 @@ nsDOMMutationObserver::HandleMutationsIn
     // after previous mutations are handled. But in case some
     // callback calls a sync API, which spins the eventloop, we need to still
     // process other mutations happening during that sync call.
     // This does *not* catch all cases, but should work for stuff running
     // in separate tabs.
     return;
   }
 
-  // We need the AutoSafeJSContext to ensure the slow script dialog is
-  // triggered. AutoSafeJSContext does that by pushing JSAutoRequest to stack.
-  // This needs to be outside the while loop.
-  AutoSafeJSContext cx;
+  AutoSlowOperation aso;
 
   nsTArray<RefPtr<nsDOMMutationObserver> >* suppressedObservers = nullptr;
 
   while (sScheduledMutationObservers) {
     AutoTArray<RefPtr<nsDOMMutationObserver>, 4>* observers =
       sScheduledMutationObservers;
     sScheduledMutationObservers = nullptr;
     for (uint32_t i = 0; i < observers->Length(); ++i) {
@@ -905,17 +902,17 @@ nsDOMMutationObserver::HandleMutationsIn
           suppressedObservers = new nsTArray<RefPtr<nsDOMMutationObserver> >;
         }
         if (!suppressedObservers->Contains(sCurrentObserver)) {
           suppressedObservers->AppendElement(sCurrentObserver);
         }
       }
     }
     delete observers;
-    JS_CheckForInterrupt(cx);
+    aso.CheckForInterrupt();
   }
 
   if (suppressedObservers) {
     for (uint32_t i = 0; i < suppressedObservers->Length(); ++i) {
       static_cast<nsDOMMutationObserver*>(suppressedObservers->ElementAt(i))->
         RescheduleForRun();
     }
     delete suppressedObservers;
--- a/dom/base/test/fileutils.js
+++ b/dom/base/test/fileutils.js
@@ -95,17 +95,17 @@ function getXHRLoadHandler(expectedResul
     is(event.target.status, 200,
        "[XHR] no error in test " + testName);
     // Do not use |is(convertXHRBinary(event.target.responseText), expectedResult, "...");| that may output raw binary data.
     var convertedData = convertXHRBinary(event.target.responseText);
     is(convertedData.length, expectedResult.length,
        "[XHR] Length of result in test " + testName);
     ok(convertedData == expectedResult,
        "[XHR] Content of result in test " + testName);
-    is(event.lengthComputable, true,
+    is(event.lengthComputable, event.total != 0,
        "[XHR] lengthComputable in test " + testName);
     is(event.loaded, expectedLength,
        "[XHR] Loaded length in test " + testName);
     is(event.total, expectedLength,
        "[XHR] Total length in test " + testName);
 
     testHasRun();
   }
--- a/dom/base/test/test_bug435425.html
+++ b/dom/base/test/test_bug435425.html
@@ -19,39 +19,61 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 /** Test for Bug 435425 **/
 
 var xhr = null;
 var upload = null;
 var currentEvents = null;
 var expectedResponseText = null;
 var uploadTotal = 0;
+var currentProgress = -1;
 
 function logEvent(evt) {
   var i = 0;
   while ((currentEvents.length != i) &&
          currentEvents[i].optional &&
          ((currentEvents[i].type != evt.type) ||
           !(evt.target instanceof currentEvents[i].target))) {
     ++i;
   }
+
   if (evt.target instanceof XMLHttpRequestUpload) {
     if (evt.type == "loadstart") {
       uploadTotal = evt.total
     } else {
       if (evt.type == "progress") {
-        ok(evt.lengthComputable, "event(" + evt.type +  ").lengthComputable should be true.");
+        is(evt.lengthComputable, evt.total != 0, "event(" + evt.type +  ").lengthComputable should be " + (evt.total != 0 ? true : false) + ".");
       }
-      is(evt.total, uploadTotal, "event(" + evt.type +  ").total should not change during upload.");
+      if (evt.total != uploadTotal && evt.total != 0) {
+        ok(false, "event(" + evt.type +  ").total should not change during upload except to become 0 on error/abort/timeout.");
+      }
     }
   }
+
+  // There can be any number of repeated progress events, so special-case this.
+  if (evt.type == "progress") {
+    // Progress events can repeat, but their "loaded" value must increase.
+    if (currentProgress >= 0) {
+      ok(currentProgress < evt.loaded, "Progress should increase, got " +
+                                       evt.loaded + " after " + currentProgress);
+      currentProgress = evt.loaded;
+      return; // stay at the currentEvent, since we got progress instead of it.
+    }
+    // Starting a new progress event group.
+    currentProgress = evt.loaded;
+  } else {
+    // Reset the progress indicator on any other event type.
+    currentProgress = -1;
+  }
+
   ok(i != currentEvents.length, "Extra or wrong event?");
   is(evt.type, currentEvents[i].type, "Wrong event!")
   ok(evt.target instanceof currentEvents[i].target,
      "Wrong event target [" + evt.target + "," + evt.type + "]!");
+
   // If we handled non-optional event, remove all optional events before the 
   // handled event and then the non-optional event from the list.
   if (!currentEvents[i].optional) {
     for (;i != -1; --i) {
       currentEvents.shift();
     }
   }
 }
@@ -79,16 +101,17 @@ function openXHR(xhr, method, url, privi
     xhr.open(method, url);
 }
 
 function start(obj) {
   xhr = new XMLHttpRequest();
   upload = xhr.upload;
   currentEvents = obj.expectedEvents;
   expectedResponseText = obj.withUpload;
+  currentProgress = -1;
   xhr.onload =
     function(evt) {
       if (expectedResponseText) {
         is(evt.target.responseText, expectedResponseText, "Wrong responseText");
       }
       logEvent(evt);
     }
   xhr.onerror =
@@ -184,211 +207,219 @@ for (var largeLength = 0; largeLength < 
 
 const XHR = XMLHttpRequest;
 const UPLOAD = XMLHttpRequestUpload;
 
 var tests = 
   [
     { method: "GET", withUpload: none, testAbort: false, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
-                       {target: XHR, type: "progress", optional: true},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "load", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: none, testAbort: true, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "abort", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: none, testAbort: false, testRedirectError: true, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: XHR, type: "error", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: none, testAbort: false, testRedirectError: false, testNetworkError: true,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: XHR, type: "error", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
 
     { method: "GET", withUpload: small, testAbort: false, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
-                       {target: XHR, type: "progress", optional: true},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "load", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: small, testAbort: true, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "abort", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: small, testAbort: false, testRedirectError: true, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: XHR, type: "error", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: small, testAbort: false, testRedirectError: false, testNetworkError: true,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: XHR, type: "error", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
 
     { method: "GET", withUpload: mid, testAbort: false, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
-                       {target: XHR, type: "progress", optional: true},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "load", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: mid, testAbort: true, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "abort", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: mid, testAbort: false, testRedirectError: true, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: XHR, type: "error", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: mid, testAbort: false, testRedirectError: false, testNetworkError: true,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: XHR, type: "error", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
 
     { method: "GET", withUpload: large, testAbort: false, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
-                       {target: XHR, type: "progress", optional: true},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "load", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: large, testAbort: true, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "abort", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: large, testAbort: false, testRedirectError: true, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: XHR, type: "error", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: large, testAbort: false, testRedirectError: false, testNetworkError: true,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: XHR, type: "error", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
 
     { method: "POST", withUpload: none, testAbort: false, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
-                       {target: XHR, type: "progress", optional: true},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "load", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: none, testAbort: true, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "abort", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: none, testAbort: false, testRedirectError: true, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: XHR, type: "error", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: none, testAbort: false, testRedirectError: false, testNetworkError: true,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: XHR, type: "error", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
 
     { method: "POST", withUpload: small, testAbort: false, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
-                       {target: UPLOAD, type: "progress", optional: true},
+                       {target: UPLOAD, type: "progress", optional: false},
                        {target: UPLOAD, type: "load", optional: false},
                        {target: UPLOAD, type: "loadend", optional: false},
-                       {target: XHR, type: "progress", optional: true},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "load", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: small, testAbort: true, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
+                       {target: UPLOAD, type: "progress", optional: false},
+                       {target: UPLOAD, type: "abort", optional: false},
+                       {target: UPLOAD, type: "loadend", optional: false},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "abort", optional: false},
-                       {target: XHR, type: "loadend", optional: false},
-                       {target: UPLOAD, type: "abort", optional: false},
-                       {target: UPLOAD, type: "loadend", optional: false}]},
+                       {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: small, testAbort: false, testRedirectError: true, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "progress", optional: true},
+                       {target: UPLOAD, type: "error", optional: false},
+                       {target: UPLOAD, type: "loadend", optional: false},
                        {target: XHR, type: "error", optional: false},
-                       {target: XHR, type: "loadend", optional: false},
-                       {target: UPLOAD, type: "error", optional: false},
-                       {target: UPLOAD, type: "loadend", optional: false}]},
+                       {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: small, testAbort: false, testRedirectError: false, testNetworkError: true,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
-                       {target: UPLOAD, type: "progress", optional: true},
+                       {target: UPLOAD, type: "error", optional: false},
+                       {target: UPLOAD, type: "loadend", optional: false},
                        {target: XHR, type: "error", optional: false},
-                       {target: XHR, type: "loadend", optional: false},
-                       {target: UPLOAD, type: "error", optional: false},
-                       {target: UPLOAD, type: "loadend", optional: false}]},
+                       {target: XHR, type: "loadend", optional: false}]},
 
     { method: "POST", withUpload: mid, testAbort: false, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
-                       {target: UPLOAD, type: "progress", optional: true},
+                       {target: UPLOAD, type: "progress", optional: false},
                        {target: UPLOAD, type: "load", optional: false},
                        {target: UPLOAD, type: "loadend", optional: false},
-                       {target: XHR, type: "progress", optional: true},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "load", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: mid, testAbort: true, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
+                       {target: UPLOAD, type: "progress", optional: false},
+                       {target: UPLOAD, type: "abort", optional: false},
+                       {target: UPLOAD, type: "loadend", optional: false},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "abort", optional: false},
-                       {target: XHR, type: "loadend", optional: false},
-                       {target: UPLOAD, type: "abort", optional: false},
-                       {target: UPLOAD, type: "loadend", optional: false}]},
+                       {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: mid, testAbort: false, testRedirectError: true, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "progress", optional: true},
+                       {target: UPLOAD, type: "error", optional: false},
+                       {target: UPLOAD, type: "loadend", optional: false},
                        {target: XHR, type: "error", optional: false},
-                       {target: XHR, type: "loadend", optional: false},
-                       {target: UPLOAD, type: "error", optional: false},
-                       {target: UPLOAD, type: "loadend", optional: false}]},
+                       {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: mid, testAbort: false, testRedirectError: false, testNetworkError: true,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
-                       {target: UPLOAD, type: "progress", optional: true},
+                       {target: UPLOAD, type: "error", optional: false},
+                       {target: UPLOAD, type: "loadend", optional: false},
                        {target: XHR, type: "error", optional: false},
-                       {target: XHR, type: "loadend", optional: false},
-                       {target: UPLOAD, type: "error", optional: false},
-                       {target: UPLOAD, type: "loadend", optional: false}]},
+                       {target: XHR, type: "loadend", optional: false}]},
 
     { method: "POST", withUpload: large, testAbort: false, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
-                       {target: UPLOAD, type: "progress", optional: true},
+                       {target: UPLOAD, type: "progress", optional: false},
                        {target: UPLOAD, type: "load", optional: false},
                        {target: UPLOAD, type: "loadend", optional: false},
-                       {target: XHR, type: "progress", optional: true},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "load", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: large, testAbort: true, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
+                       {target: UPLOAD, type: "progress", optional: false},
+                       {target: UPLOAD, type: "abort", optional: false},
+                       {target: UPLOAD, type: "loadend", optional: false},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "abort", optional: false},
-                       {target: XHR, type: "loadend", optional: false},
-                       {target: UPLOAD, type: "abort", optional: false},
-                       {target: UPLOAD, type: "loadend", optional: false}]},
+                       {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: large, testAbort: false, testRedirectError: true, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "progress", optional: true},
+                       {target: UPLOAD, type: "error", optional: false},
+                       {target: UPLOAD, type: "loadend", optional: false},
                        {target: XHR, type: "error", optional: false},
-                       {target: XHR, type: "loadend", optional: false},
-                       {target: UPLOAD, type: "error", optional: false},
-                       {target: UPLOAD, type: "loadend", optional: false}]},
+                       {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: large, testAbort: false, testRedirectError: false, testNetworkError: true,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
-                       {target: UPLOAD, type: "progress", optional: true},
+                       {target: UPLOAD, type: "error", optional: false},
+                       {target: UPLOAD, type: "loadend", optional: false},
                        {target: XHR, type: "error", optional: false},
-                       {target: XHR, type: "loadend", optional: false},
-                       {target: UPLOAD, type: "error", optional: false},
-                       {target: UPLOAD, type: "loadend", optional: false}]},
+                       {target: XHR, type: "loadend", optional: false}]},
 ];
 
 function runTest() {
   var test = tests.shift();
   start(test);
 }
 
 function nextTest() {
-  if (tests.length > 1) {
+  if (tests.length) {
     setTimeout("runTest()", 0);
   } else {
     SimpleTest.finish();
   }
 }
 
 ok("upload" in (new XMLHttpRequest()), "XMLHttpRequest.upload isn't supported!");
 SimpleTest.waitForExplicitFinish();
--- a/dom/gamepad/windows/WindowsGamepad.cpp
+++ b/dom/gamepad/windows/WindowsGamepad.cpp
@@ -36,20 +36,16 @@ using mozilla::ArrayLength;
 const unsigned kUsageDpad = 0x39;
 // USB HID usage tables, page 1, 0x30 = X
 const unsigned kFirstAxis = 0x30;
 
 // USB HID usage tables
 const unsigned kDesktopUsagePage = 0x1;
 const unsigned kButtonUsagePage = 0x9;
 
-// Arbitrary. In practice 10 buttons/6 axes is the near maximum.
-const unsigned kMaxButtons = 32;
-const unsigned kMaxAxes = 32;
-
 // Multiple devices-changed notifications can be sent when a device
 // is connected, because USB devices consist of multiple logical devices.
 // Therefore, we wait a bit after receiving one before looking for
 // device changes.
 const uint32_t kDevicesChangedStableDelay = 200;
 // XInput is a purely polling-driven API, so we need to
 // poll it periodically. 50ms is arbitrarily chosen.
 const uint32_t kXInputPollInterval = 50;
@@ -99,46 +95,65 @@ enum GamepadType {
 class WindowsGamepadService;
 // This pointer holds a windows gamepad backend service,
 // it will be created and destroyed by background thread and
 // used by gMonitorThread
 WindowsGamepadService* MOZ_NON_OWNING_REF gService = nullptr;
 nsCOMPtr<nsIThread> gMonitorThread = nullptr;
 static bool sIsShutdown = false;
 
-struct Gamepad {
+class Gamepad {
+public:
   GamepadType type;
 
   // Handle to raw input device
   HANDLE handle;
 
   // XInput Index of the user's controller. Passed to XInputGetState.
   DWORD userIndex;
 
   // Last-known state of the controller.
   XINPUT_STATE state;
 
   // ID from the GamepadService, also used as the index into
   // WindowsGamepadService::mGamepads.
   int id;
 
+
   // Information about the physical device.
   unsigned numAxes;
   unsigned numButtons;
   bool hasDpad;
   HIDP_VALUE_CAPS dpadCaps;
 
-  bool buttons[kMaxButtons];
-  struct {
+  nsTArray<bool> buttons;
+  struct axisValue {
     HIDP_VALUE_CAPS caps;
     double value;
-  } axes[kMaxAxes];
+  };
+  nsTArray<axisValue> axes;
 
   // Used during rescan to find devices that were disconnected.
   bool present;
+
+  Gamepad(uint32_t aNumAxes,
+          uint32_t aNumButtons,
+          bool aHasDpad,
+          GamepadType aType) :
+    numAxes(aNumAxes),
+    numButtons(aNumButtons),
+    hasDpad(aHasDpad),
+    type(aType),
+    present(true)
+  {
+    buttons.SetLength(numButtons);
+    axes.SetLength(numAxes);
+  }
+private:
+  Gamepad() {}
 };
 
 // Drop this in favor of decltype when we require a new enough SDK.
 typedef void (WINAPI *XInputEnable_func)(BOOL);
 
 // RAII class to wrap loading the XInput DLL
 class XInputLoader {
 public:
@@ -207,17 +222,17 @@ ScaleAxis(ULONG value, LONG min, LONG ma
   return  2.0 * (value - min) / (max - min) - 1.0;
 }
 
 /*
  * Given a value from a d-pad (POV hat in USB HID terminology),
  * represent it as 4 buttons, one for each cardinal direction.
  */
 void
-UnpackDpad(LONG dpad_value, const Gamepad* gamepad, bool buttons[kMaxButtons])
+UnpackDpad(LONG dpad_value, const Gamepad* gamepad, nsTArray<bool>& buttons)
 {
   const unsigned kUp = gamepad->numButtons - 4;
   const unsigned kDown = gamepad->numButtons - 3;
   const unsigned kLeft = gamepad->numButtons - 2;
   const unsigned kRight = gamepad->numButtons - 1;
 
   // Different controllers have different ways of representing
   // "nothing is pressed", but they're all outside the range of values.
@@ -444,23 +459,22 @@ WindowsGamepadService::ScanForXInputDevi
     }
     found = true;
     // See if this device is already present in our list.
     if (HaveXInputGamepad(i)) {
       continue;
     }
 
     // Not already present, add it.
-    Gamepad gamepad = {};
-    gamepad.type = kXInputGamepad;
-    gamepad.present = true;
+    Gamepad gamepad(kStandardGamepadAxes,
+                    kStandardGamepadButtons,
+                    true,
+                    kXInputGamepad);
+    gamepad.userIndex = i;
     gamepad.state = state;
-    gamepad.userIndex = i;
-    gamepad.numButtons = kStandardGamepadButtons;
-    gamepad.numAxes = kStandardGamepadAxes;
     gamepad.id = service->AddGamepad("xinput",
                                      GamepadMappingType::Standard,
                                      kStandardGamepadButtons,
                                      kStandardGamepadAxes);
     mGamepads.AppendElement(gamepad);
   }
 
   return found;
@@ -616,18 +630,16 @@ WindowsGamepadService::GetRawGamepad(HAN
   if (GetRawInputDeviceInfo(handle, RIDI_DEVICEINFO, &rdi, &size) == kRawInputError) {
     return false;
   }
   // Ensure that this is a device we care about
   if (!SupportedUsage(rdi.hid.usUsagePage, rdi.hid.usUsage)) {
     return false;
   }
 
-  Gamepad gamepad = {};
-
   // Device name is a mostly-opaque string.
   if (GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, nullptr, &size) == kRawInputError) {
     return false;
   }
 
   nsTArray<wchar_t> devname(size);
   devname.SetLength(size);
   if (GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, devname.Elements(), &size) == kRawInputError) {
@@ -684,63 +696,68 @@ WindowsGamepadService::GetRawGamepad(HAN
   // Enumerate buttons.
   USHORT count = caps.NumberInputButtonCaps;
   nsTArray<HIDP_BUTTON_CAPS> buttonCaps(count);
   buttonCaps.SetLength(count);
   if (mHID.mHidP_GetButtonCaps(HidP_Input, buttonCaps.Elements(), &count, parsed)
       != HIDP_STATUS_SUCCESS) {
     return false;
   }
+  uint32_t numButtons = 0;
   for (unsigned i = 0; i < count; i++) {
     // Each buttonCaps is typically a range of buttons.
-    gamepad.numButtons +=
+    numButtons +=
       buttonCaps[i].Range.UsageMax - buttonCaps[i].Range.UsageMin + 1;
   }
-  gamepad.numButtons = std::min(gamepad.numButtons, kMaxButtons);
 
   // Enumerate value caps, which represent axes and d-pads.
   count = caps.NumberInputValueCaps;
   nsTArray<HIDP_VALUE_CAPS> valueCaps(count);
   valueCaps.SetLength(count);
   if (mHID.mHidP_GetValueCaps(HidP_Input, valueCaps.Elements(), &count, parsed)
       != HIDP_STATUS_SUCCESS) {
     return false;
   }
   nsTArray<HIDP_VALUE_CAPS> axes;
   // Sort the axes by usagePage and usage to expose a consistent ordering.
+  bool hasDpad;
+  HIDP_VALUE_CAPS dpadCaps;
+
   HidValueComparator comparator;
   for (unsigned i = 0; i < count; i++) {
     if (valueCaps[i].UsagePage == kDesktopUsagePage
         && valueCaps[i].Range.UsageMin == kUsageDpad
         // Don't know how to handle d-pads that return weird values.
-        && valueCaps[i].LogicalMax - valueCaps[i].LogicalMin == 7
-        // Can't overflow buttons
-        && gamepad.numButtons + 4 < kMaxButtons) {
+        && valueCaps[i].LogicalMax - valueCaps[i].LogicalMin == 7) {
       // d-pad gets special handling.
       // Ostensibly HID devices can expose multiple d-pads, but this
       // doesn't happen in practice.
-      gamepad.hasDpad = true;
-      gamepad.dpadCaps = valueCaps[i];
+      hasDpad = true;
+      dpadCaps = valueCaps[i];
       // Expose d-pad as 4 additional buttons.
-      gamepad.numButtons += 4;
+      numButtons += 4;
     } else {
       axes.InsertElementSorted(valueCaps[i], comparator);
     }
   }
 
-  gamepad.numAxes = std::min<size_t>(axes.Length(), kMaxAxes);
+  uint32_t numAxes = axes.Length();
+
+  // Not already present, add it.
+  Gamepad gamepad(numAxes,
+                  numButtons,
+                  true,
+                  kRawInputGamepad);
+
+  gamepad.handle = handle;
+
   for (unsigned i = 0; i < gamepad.numAxes; i++) {
-    if (i >= kMaxAxes) {
-      break;
-    }
     gamepad.axes[i].caps = axes[i];
   }
-  gamepad.type = kRawInputGamepad;
-  gamepad.handle = handle;
-  gamepad.present = true;
+
   gamepad.id = service->AddGamepad(gamepad_id,
                                    GamepadMappingType::_empty,
                                    gamepad.numButtons,
                                    gamepad.numAxes);
   mGamepads.AppendElement(gamepad);
   return true;
 }
 
@@ -748,18 +765,18 @@ bool
 WindowsGamepadService::HandleRawInput(HRAWINPUT handle)
 {
   if (!mHID) {
     return false;
   }
 
   RefPtr<GamepadPlatformService> service =
     GamepadPlatformService::GetParentService();
-  if (service) {
-    return true;
+  if (!service) {
+    return false;
   }
 
   // First, get data from the handle
   UINT size;
   GetRawInputData(handle, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER));
   nsTArray<uint8_t> data(size);
   data.SetLength(size);
   if (GetRawInputData(handle, RID_INPUT, data.Elements(), &size,
@@ -793,18 +810,21 @@ WindowsGamepadService::HandleRawInput(HR
   usages.SetLength(gamepad->numButtons);
   ULONG usageLength = gamepad->numButtons;
   if (mHID.mHidP_GetUsages(HidP_Input, kButtonUsagePage, 0, usages.Elements(),
                      &usageLength, parsed, (PCHAR)raw->data.hid.bRawData,
                      raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
     return false;
   }
 
-  bool buttons[kMaxButtons] = { false };
-  usageLength = std::min<ULONG>(usageLength, kMaxButtons);
+  nsTArray<bool> buttons(gamepad->numButtons);
+  buttons.SetLength(gamepad->numButtons);
+  // If we don't zero out the buttons array first, sometimes it can reuse values.
+  memset(buttons.Elements(), 0, gamepad->numButtons * sizeof(bool));
+
   for (unsigned i = 0; i < usageLength; i++) {
     buttons[usages[i] - 1] = true;
   }
 
   if (gamepad->hasDpad) {
     // Get d-pad position as 4 buttons.
     ULONG value;
     if (mHID.mHidP_GetUsageValue(HidP_Input, gamepad->dpadCaps.UsagePage, 0, gamepad->dpadCaps.Range.UsageMin, &value, parsed, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid) == HIDP_STATUS_SUCCESS) {
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -1140,16 +1140,17 @@ static nsresult FireEventForAccessibilit
 
 HTMLInputElement::HTMLInputElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
                                    FromParser aFromParser)
   : nsGenericHTMLFormElementWithState(aNodeInfo)
   , mType(kInputDefaultType->value)
   , mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown)
   , mDisabledChanged(false)
   , mValueChanged(false)
+  , mLastValueChangeWasInteractive(false)
   , mCheckedChanged(false)
   , mChecked(false)
   , mHandlingSelectEvent(false)
   , mShouldInitChecked(false)
   , mParserCreating(aFromParser != NOT_FROM_PARSER)
   , mInInternalActivate(false)
   , mCheckedIsToggled(false)
   , mIndeterminate(false)
@@ -1321,16 +1322,17 @@ HTMLInputElement::Clone(mozilla::dom::No
       break;
     case VALUE_MODE_DEFAULT:
       if (mType == NS_FORM_INPUT_IMAGE && it->OwnerDoc()->IsStaticDocument()) {
         CreateStaticImageClone(it);
       }
       break;
   }
 
+  it->mLastValueChangeWasInteractive = mLastValueChangeWasInteractive;
   it.forget(aResult);
   return NS_OK;
 }
 
 nsresult
 HTMLInputElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 nsAttrValueOrString* aValue,
                                 bool aNotify)
@@ -3066,17 +3068,18 @@ HTMLInputElement::SetValueInternal(const
           }
         } else if (mType == NS_FORM_INPUT_RANGE) {
           nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
           if (frame) {
             frame->UpdateForValueChange();
           }
         }
         if (!mParserCreating) {
-          OnValueChanged(true);
+          OnValueChanged(/* aNotify = */ true,
+                         /* aWasInteractiveUserChange = */ false);
         }
         // else DoneCreatingElement calls us again once mParserCreating is false
       }
 
       if (mType == NS_FORM_INPUT_COLOR) {
         // Update color frame, to reflect color changes
         nsColorControlFrame* colorControlFrame = do_QueryFrame(GetPrimaryFrame());
         if (colorControlFrame) {
@@ -6060,16 +6063,17 @@ HTMLInputElement::SetDirectionIfAuto(boo
 }
 
 NS_IMETHODIMP
 HTMLInputElement::Reset()
 {
   // We should be able to reset all dirty flags regardless of the type.
   SetCheckedChanged(false);
   SetValueChanged(false);
+  mLastValueChangeWasInteractive = false;
 
   switch (GetValueMode()) {
     case VALUE_MODE_VALUE:
       return SetDefaultValueAsValue();
     case VALUE_MODE_DEFAULT_ON:
       DoSetChecked(DefaultChecked(), true, false);
       return NS_OK;
     case VALUE_MODE_FILENAME:
@@ -6912,19 +6916,20 @@ HTMLInputElement::SetCustomValidity(cons
   UpdateState(true);
 
   return NS_OK;
 }
 
 bool
 HTMLInputElement::IsTooLong()
 {
-  if (!MaxLengthApplies() ||
-      !HasAttr(kNameSpaceID_None, nsGkAtoms::maxlength) ||
-      !mValueChanged) {
+  if (!mValueChanged ||
+      !mLastValueChangeWasInteractive ||
+      !MaxLengthApplies() ||
+      !HasAttr(kNameSpaceID_None, nsGkAtoms::maxlength)) {
     return false;
   }
 
   int32_t maxLength = MaxLength();
 
   // Maxlength of -1 means parsing error.
   if (maxLength == -1) {
     return false;
@@ -7187,20 +7192,17 @@ HTMLInputElement::HasBadInput() const
     return false;
   }
   return false;
 }
 
 void
 HTMLInputElement::UpdateTooLongValidityState()
 {
-  // TODO: this code will be re-enabled with bug 613016 and bug 613019.
-#if 0
   SetValidityState(VALIDITY_STATE_TOO_LONG, IsTooLong());
-#endif
 }
 
 void
 HTMLInputElement::UpdateValueMissingValidityStateForRadio(bool aIgnoreSelf)
 {
   bool notify = !mParserCreating;
   nsCOMPtr<nsIDOMHTMLInputElement> selection = GetSelectedRadioButton();
 
@@ -7730,18 +7732,20 @@ HTMLInputElement::InitializeKeyboardEven
 {
   nsTextEditorState* state = GetEditorState();
   if (state) {
     state->InitializeKeyboardEventListeners();
   }
 }
 
 NS_IMETHODIMP_(void)
-HTMLInputElement::OnValueChanged(bool aNotify)
-{
+HTMLInputElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange)
+{
+  mLastValueChangeWasInteractive = aWasInteractiveUserChange;
+
   UpdateAllValidityStates(aNotify);
 
   if (HasDirAuto()) {
     SetDirectionIfAuto(true, aNotify);
   }
 }
 
 NS_IMETHODIMP_(bool)
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -224,17 +224,17 @@ public:
   NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override;
   NS_IMETHOD CreateEditor() override;
   NS_IMETHOD_(nsIContent*) GetRootEditorNode() override;
   NS_IMETHOD_(Element*) CreatePlaceholderNode() override;
   NS_IMETHOD_(Element*) GetPlaceholderNode() override;
   NS_IMETHOD_(void) UpdatePlaceholderVisibility(bool aNotify) override;
   NS_IMETHOD_(bool) GetPlaceholderVisibility() override;
   NS_IMETHOD_(void) InitializeKeyboardEventListeners() override;
-  NS_IMETHOD_(void) OnValueChanged(bool aNotify) override;
+  NS_IMETHOD_(void) OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) override;
   NS_IMETHOD_(bool) HasCachedSelection() override;
 
   void GetDisplayFileName(nsAString& aFileName) const;
 
   const nsTArray<OwningFileOrDirectory>& GetFilesOrDirectoriesInternal() const
   {
     return mFilesOrDirectories;
   }
@@ -1444,16 +1444,17 @@ protected:
   /**
    * The type of this input (<input type=...>) as an integer.
    * @see nsIFormControl.h (specifically NS_FORM_INPUT_*)
    */
   uint8_t                  mType;
   nsContentUtils::AutocompleteAttrState mAutocompleteAttrState;
   bool                     mDisabledChanged     : 1;
   bool                     mValueChanged        : 1;
+  bool                     mLastValueChangeWasInteractive : 1;
   bool                     mCheckedChanged      : 1;
   bool                     mChecked             : 1;
   bool                     mHandlingSelectEvent : 1;
   bool                     mShouldInitChecked   : 1;
   bool                     mParserCreating      : 1;
   bool                     mInInternalActivate  : 1;
   bool                     mCheckedIsToggled    : 1;
   bool                     mIndeterminate       : 1;
--- a/dom/html/HTMLTextAreaElement.cpp
+++ b/dom/html/HTMLTextAreaElement.cpp
@@ -50,16 +50,17 @@ NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER
 
 namespace mozilla {
 namespace dom {
 
 HTMLTextAreaElement::HTMLTextAreaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
                                          FromParser aFromParser)
   : nsGenericHTMLFormElementWithState(aNodeInfo),
     mValueChanged(false),
+    mLastValueChangeWasInteractive(false),
     mHandlingSelect(false),
     mDoneAddingChildren(!aFromParser),
     mInhibitStateRestoration(!!(aFromParser & FROM_PARSER_FRAGMENT)),
     mDisabledChanged(false),
     mCanShowInvalidUI(true),
     mCanShowValidUI(true),
     mState(this)
 {
@@ -1339,17 +1340,19 @@ HTMLTextAreaElement::SetCustomValidity(c
   UpdateState(true);
 
   return NS_OK;
 }
 
 bool
 HTMLTextAreaElement::IsTooLong()
 {
-  if (!HasAttr(kNameSpaceID_None, nsGkAtoms::maxlength) || !mValueChanged) {
+  if (!mValueChanged ||
+      !mLastValueChangeWasInteractive ||
+      !HasAttr(kNameSpaceID_None, nsGkAtoms::maxlength)) {
     return false;
   }
 
   int32_t maxLength = -1;
   GetMaxLength(&maxLength);
 
   // Maxlength of -1 means parsing error.
   if (maxLength == -1) {
@@ -1370,20 +1373,17 @@ HTMLTextAreaElement::IsValueMissing() co
   }
 
   return IsValueEmpty();
 }
 
 void
 HTMLTextAreaElement::UpdateTooLongValidityState()
 {
-  // TODO: this code will be re-enabled with bug 613016 and bug 613019.
-#if 0
   SetValidityState(VALIDITY_STATE_TOO_LONG, IsTooLong());
-#endif
 }
 
 void
 HTMLTextAreaElement::UpdateValueMissingValidityState()
 {
   SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing());
 }
 
@@ -1520,18 +1520,20 @@ HTMLTextAreaElement::GetTextEditorValue(
 
 NS_IMETHODIMP_(void)
 HTMLTextAreaElement::InitializeKeyboardEventListeners()
 {
   mState.InitializeKeyboardEventListeners();
 }
 
 NS_IMETHODIMP_(void)
-HTMLTextAreaElement::OnValueChanged(bool aNotify)
+HTMLTextAreaElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange)
 {
+  mLastValueChangeWasInteractive = aWasInteractiveUserChange;
+
   // Update the validity state
   bool validBefore = IsValid();
   UpdateTooLongValidityState();
   UpdateValueMissingValidityState();
 
   if (validBefore != IsValid()) {
     UpdateState(aNotify);
   }
--- a/dom/html/HTMLTextAreaElement.h
+++ b/dom/html/HTMLTextAreaElement.h
@@ -101,17 +101,17 @@ public:
   NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override;
   NS_IMETHOD CreateEditor() override;
   NS_IMETHOD_(nsIContent*) GetRootEditorNode() override;
   NS_IMETHOD_(Element*) CreatePlaceholderNode() override;
   NS_IMETHOD_(Element*) GetPlaceholderNode() override;
   NS_IMETHOD_(void) UpdatePlaceholderVisibility(bool aNotify) override;
   NS_IMETHOD_(bool) GetPlaceholderVisibility() override;
   NS_IMETHOD_(void) InitializeKeyboardEventListeners() override;
-  NS_IMETHOD_(void) OnValueChanged(bool aNotify) override;
+  NS_IMETHOD_(void) OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) override;
   NS_IMETHOD_(bool) HasCachedSelection() override;
 
   // nsIContent
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                                nsIContent* aBindingParent,
                                bool aCompileEventHandlers) override;
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true) override;
@@ -288,16 +288,18 @@ protected:
   // get rid of the compiler warning
   using nsGenericHTMLFormElementWithState::IsSingleLineTextControl;
 
   virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   nsCOMPtr<nsIControllers> mControllers;
   /** Whether or not the value has changed since its default value was given. */
   bool                     mValueChanged;
+  /** Whether or not the last change to the value was made interactively by the user. */
+  bool                     mLastValueChangeWasInteractive;
   /** Whether or not we are already handling select event. */
   bool                     mHandlingSelect;
   /** Whether or not we are done adding children (always true if not
       created by a parser */
   bool                     mDoneAddingChildren;
   /** Whether state restoration should be inhibited in DoneAddingChildren. */
   bool                     mInhibitStateRestoration;
   /** Whether our disabled state has changed from the default **/
--- a/dom/html/HTMLTrackElement.cpp
+++ b/dom/html/HTMLTrackElement.cpp
@@ -28,16 +28,17 @@
 #include "nsIDOMEventTarget.h"
 #include "nsIDOMHTMLMediaElement.h"
 #include "nsIHttpChannel.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsILoadGroup.h"
 #include "nsIObserver.h"
 #include "nsIStreamListener.h"
 #include "nsISupportsImpl.h"
+#include "nsISupportsPrimitives.h"
 #include "nsMappedAttributes.h"
 #include "nsNetUtil.h"
 #include "nsRuleData.h"
 #include "nsStyleConsts.h"
 #include "nsThreadUtils.h"
 #include "nsVideoFrame.h"
 
 static mozilla::LazyLogModule gTrackElementLog("nsTrackElement");
@@ -68,25 +69,82 @@ static constexpr nsAttrValue::EnumTable 
   { "metadata", static_cast<int16_t>(TextTrackKind::Metadata) },
   { 0 }
 };
 
 // Invalid values are treated as "metadata" in ParseAttribute, but if no value
 // at all is specified, it's treated as "subtitles" in GetKind
 static constexpr const nsAttrValue::EnumTable* kKindTableInvalidValueDefault = &kKindTable[4];
 
+class WindowDestroyObserver final : public nsIObserver
+{
+  NS_DECL_ISUPPORTS
+
+public:
+  explicit WindowDestroyObserver(HTMLTrackElement* aElement, uint64_t aWinID)
+    : mTrackElement(aElement)
+    , mInnerID(aWinID)
+  {
+    RegisterWindowDestroyObserver();
+  }
+  void RegisterWindowDestroyObserver()
+  {
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (obs) {
+      obs->AddObserver(this, "inner-window-destroyed", false);
+    }
+  }
+  void UnRegisterWindowDestroyObserver()
+  {
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (obs) {
+      obs->RemoveObserver(this, "inner-window-destroyed");
+    }
+    mTrackElement = nullptr;
+  }
+  NS_IMETHODIMP Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) override
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    if (strcmp(aTopic, "inner-window-destroyed") == 0) {
+      nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
+      NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
+      uint64_t innerID;
+      nsresult rv = wrapper->GetData(&innerID);
+      NS_ENSURE_SUCCESS(rv, rv);
+      if (innerID == mInnerID) {
+        if (mTrackElement) {
+          mTrackElement->NotifyShutdown();
+        }
+        UnRegisterWindowDestroyObserver();
+      }
+    }
+    return NS_OK;
+  }
+
+private:
+  ~WindowDestroyObserver() {};
+  HTMLTrackElement* mTrackElement;
+  uint64_t mInnerID;
+};
+NS_IMPL_ISUPPORTS(WindowDestroyObserver, nsIObserver);
+
 /** HTMLTrackElement */
 HTMLTrackElement::HTMLTrackElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
   : nsGenericHTMLElement(aNodeInfo)
   , mLoadResourceDispatched(false)
+  , mWindowDestroyObserver(nullptr)
 {
 }
 
 HTMLTrackElement::~HTMLTrackElement()
 {
+  if (mWindowDestroyObserver) {
+    mWindowDestroyObserver->UnRegisterWindowDestroyObserver();
+  }
+  NotifyShutdown();
 }
 
 NS_IMPL_ELEMENT_CLONE(HTMLTrackElement)
 
 NS_IMPL_ADDREF_INHERITED(HTMLTrackElement, Element)
 NS_IMPL_RELEASE_INHERITED(HTMLTrackElement, Element)
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLTrackElement, nsGenericHTMLElement,
@@ -258,16 +316,20 @@ HTMLTrackElement::LoadResource()
   NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
   channel->SetNotificationCallbacks(mListener);
 
   LOG(LogLevel::Debug, ("opening webvtt channel"));
   rv = channel->AsyncOpen2(mListener);
   NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
 
   mChannel = channel;
+  nsISupports* parentObject = OwnerDoc()->GetParentObject();
+  NS_ENSURE_TRUE_VOID(parentObject);
+  nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(parentObject);
+  mWindowDestroyObserver = new WindowDestroyObserver(this, window->WindowID());
 }
 
 nsresult
 HTMLTrackElement::BindToTree(nsIDocument* aDocument,
                              nsIContent* aParent,
                              nsIContent* aBindingParent,
                              bool aCompileEventHandlers)
 {
@@ -366,10 +428,20 @@ HTMLTrackElement::DispatchTrustedEvent(c
 }
 
 void
 HTMLTrackElement::DropChannel()
 {
   mChannel = nullptr;
 }
 
+void
+HTMLTrackElement::NotifyShutdown()
+{
+  if (mChannel) {
+    mChannel->Cancel(NS_BINDING_ABORTED);
+  }
+  mChannel = nullptr;
+  mListener = nullptr;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/html/HTMLTrackElement.h
+++ b/dom/html/HTMLTrackElement.h
@@ -19,16 +19,17 @@
 
 class nsIContent;
 class nsIDocument;
 
 namespace mozilla {
 namespace dom {
 
 class WebVTTListener;
+class WindowDestroyObserver;
 
 class HTMLTrackElement final : public nsGenericHTMLElement
 {
 public:
   explicit HTMLTrackElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
 
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
@@ -108,16 +109,18 @@ public:
   // Check enabling preference.
   static bool IsWebVTTEnabled();
 
   void DispatchTrackRunnable(const nsString& aEventName);
   void DispatchTrustedEvent(const nsAString& aName);
 
   void DropChannel();
 
+  void NotifyShutdown();
+
 protected:
   virtual ~HTMLTrackElement();
 
   virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
   void OnChannelRedirect(nsIChannel* aChannel, nsIChannel* aNewChannel,
                          uint32_t aFlags);
   // Open a new channel to the HTMLTrackElement's src attribute and call
   // mListener's LoadResource().
@@ -131,14 +134,16 @@ protected:
   RefPtr<HTMLMediaElement> mMediaParent;
   RefPtr<WebVTTListener> mListener;
 
   void CreateTextTrack();
 
 private:
   void DispatchLoadResource();
   bool mLoadResourceDispatched;
+
+  RefPtr<WindowDestroyObserver> mWindowDestroyObserver;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_HTMLTrackElement_h
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -2578,44 +2578,16 @@ nsGenericHTMLFormElement::IsLabelable() 
          type == NS_FORM_OUTPUT ||
          type == NS_FORM_SELECT ||
          type == NS_FORM_TEXTAREA;
 }
 
 //----------------------------------------------------------------------
 
 void
-nsGenericHTMLElement::Blur(mozilla::ErrorResult& aError)
-{
-  if (!ShouldBlur(this)) {
-    return;
-  }
-
-  nsIDocument* doc = GetComposedDoc();
-  if (!doc) {
-    return;
-  }
-
-  nsPIDOMWindowOuter* win = doc->GetWindow();
-  nsIFocusManager* fm = nsFocusManager::GetFocusManager();
-  if (win && fm) {
-    aError = fm->ClearFocus(win);
-  }
-}
-
-void
-nsGenericHTMLElement::Focus(ErrorResult& aError)
-{
-  nsIFocusManager* fm = nsFocusManager::GetFocusManager();
-  if (fm) {
-    aError = fm->SetFocus(this, 0);
-  }
-}
-
-void
 nsGenericHTMLElement::Click()
 {
   if (HandlingClick())
     return;
 
   // Strong in case the event kills it
   nsCOMPtr<nsIDocument> doc = GetComposedDoc();
 
--- a/dom/html/nsGenericHTMLElement.h
+++ b/dom/html/nsGenericHTMLElement.h
@@ -46,16 +46,18 @@ typedef nsMappedAttributeElement nsGener
 
 /**
  * A common superclass for HTML elements
  */
 class nsGenericHTMLElement : public nsGenericHTMLElementBase,
                              public nsIDOMHTMLElement
 {
 public:
+  using Element::SetTabIndex;
+  using Element::Focus;
   explicit nsGenericHTMLElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
     : nsGenericHTMLElementBase(aNodeInfo)
   {
     NS_ASSERTION(mNodeInfo->NamespaceID() == kNameSpaceID_XHTML,
                  "Unexpected namespace");
     AddStatesSilently(NS_EVENT_STATE_LTR);
     SetFlags(NODE_HAS_DIRECTION_LTR);
   }
@@ -98,30 +100,16 @@ public:
   {
     return GetBoolAttr(nsGkAtoms::hidden);
   }
   void SetHidden(bool aHidden, mozilla::ErrorResult& aError)
   {
     SetHTMLBoolAttr(nsGkAtoms::hidden, aHidden, aError);
   }
   virtual void Click();
-  virtual int32_t TabIndexDefault()
-  {
-    return -1;
-  }
-  int32_t TabIndex()
-  {
-    return GetIntAttr(nsGkAtoms::tabindex, TabIndexDefault());
-  }
-  void SetTabIndex(int32_t aTabIndex, mozilla::ErrorResult& aError)
-  {
-    SetHTMLIntAttr(nsGkAtoms::tabindex, aTabIndex, aError);
-  }
-  virtual void Focus(mozilla::ErrorResult& aError);
-  virtual void Blur(mozilla::ErrorResult& aError);
   void GetAccessKey(nsString& aAccessKey)
   {
     GetHTMLAttr(nsGkAtoms::accesskey, aAccessKey);
   }
   void SetAccessKey(const nsAString& aAccessKey, mozilla::ErrorResult& aError)
   {
     SetHTMLAttr(nsGkAtoms::accesskey, aAccessKey, aError);
   }
--- a/dom/html/nsITextControlElement.h
+++ b/dom/html/nsITextControlElement.h
@@ -164,17 +164,17 @@ public:
   /**
    * Returns the current expected placeholder visibility state.
    */
   NS_IMETHOD_(bool) GetPlaceholderVisibility() = 0;
 
   /**
    * Callback called whenever the value is changed.
    */
-  NS_IMETHOD_(void) OnValueChanged(bool aNotify) = 0;
+  NS_IMETHOD_(void) OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) = 0;
 
   static const int32_t DEFAULT_COLS = 20;
   static const int32_t DEFAULT_ROWS = 1;
   static const int32_t DEFAULT_ROWS_TEXTAREA = 2;
   static const int32_t DEFAULT_UNDO_CAP = 1000;
 
   // wrap can be one of these three values.  
   typedef enum {
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -973,17 +973,18 @@ nsTextInputListener::EditAction()
 
   // Make sure we know we were changed (do NOT set this to false if there are
   // no undo items; JS could change the value and we'd still need to save it)
   if (mSetValueChanged) {
     frame->SetValueChanged(true);
   }
 
   if (!mSettingValue) {
-    mTxtCtrlElement->OnValueChanged(true);
+    mTxtCtrlElement->OnValueChanged(/* aNotify = */ true,
+                                    /* aWasInteractiveUserChange = */ true);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsTextInputListener::BeforeEditAction()
 {
@@ -2177,17 +2178,18 @@ nsTextEditorState::SetValue(const nsAStr
       mBoundFrame->UpdateValueDisplay(true);
     }
   }
 
   // If we've reached the point where the root node has been created, we
   // can assume that it's safe to notify.
   ValueWasChanged(!!mRootNode);
 
-  mTextCtrlElement->OnValueChanged(!!mRootNode);
+  mTextCtrlElement->OnValueChanged(/* aNotify = */ !!mRootNode,
+                                   /* aWasInteractiveUserChange = */ false);
 
   return true;
 }
 
 void
 nsTextEditorState::InitializeKeyboardEventListeners()
 {
   //register key listeners
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -363,16 +363,17 @@ skip-if = buildapp == 'mulet' # TC: Bug 
 [test_bug607145.html]
 [test_bug610212.html]
 [test_bug610687.html]
 [test_bug611189.html]
 [test_bug612730.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # b2g(form control not selected/checked with synthesizeMouse, also fails on Android) b2g-debug(form control not selected/checked with synthesizeMouse, also fails on Android) b2g-desktop(form control not selected/checked with synthesizeMouse, also fails on Android)
 [test_bug613113.html]
 skip-if = buildapp == 'b2g' # b2g(bug 587671, need an invalidformsubmit observer) b2g-debug(bug 587671, need an invalidformsubmit observer) b2g-desktop(bug 587671, need an invalidformsubmit observer)
+[test_bug613019.html]
 [test_bug613722.html]
 [test_bug613979.html]
 [test_bug615595.html]
 [test_bug615833.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || os == 'mac' #TIMED_OUT # b2g(form control not selected/checked with synthesizeMouse, also fails on Android) b2g-debug(form control not selected/checked with synthesizeMouse, also fails on Android) b2g-desktop(form control not selected/checked with synthesizeMouse, also fails on Android) osx(bug 1275664)
 [test_bug617528.html]
 [test_bug618948.html]
 skip-if = buildapp == 'b2g' # b2g(bug 587671, need an invalidformsubmit observer) b2g-debug(bug 587671, need an invalidformsubmit observer) b2g-desktop(bug 587671, need an invalidformsubmit observer)
new file mode 100644
--- /dev/null
+++ b/dom/html/test/test_bug613019.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=613019
+-->
+<head>
+  <title>Test for Bug 613019</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=613019">Mozilla Bug 613019</a>
+<div id="content">
+  <input type="text" maxlength="2" style="width:200px" value="Test">
+  <textarea maxlength="2" style="width:200px">Test</textarea>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 613019 **/
+
+function testInteractivityOfValidityStates(elem) {
+  // verify that user interactivity is necessary for validity state to apply.
+  is(elem.value, "Test", "Element has incorrect starting value.");
+  is(elem.validity.tooLong, false, "Element should not be tooLong.");
+
+  elem.focus();
+
+  synthesizeKey("VK_BACK_SPACE", {});
+  is(elem.value, "Tes", "Element value was not changed correctly.");
+  is(elem.validity.tooLong, true, "Element should still be tooLong.");
+
+  synthesizeKey("VK_BACK_SPACE", {});
+  is(elem.value, "Te", "Element value was not changed correctly.");
+  is(elem.validity.tooLong, false, "Element should no longer be tooLong.");
+
+  elem.value = "Test";
+  is(elem.validity.tooLong, false,
+     "Element should not be tooLong after non-interactive value change.");
+}
+
+function test() {
+  window.getSelection().removeAllRanges();
+  testInteractivityOfValidityStates(document.querySelector("input[type=text]"));
+  testInteractivityOfValidityStates(document.querySelector("textarea"));
+  SimpleTest.finish();
+}
+
+window.onload = function() {
+  SimpleTest.waitForExplicitFinish();
+  setTimeout(test, 0);
+};
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -159,17 +159,17 @@
 #include "mozilla/widget/AudioSession.h"
 #endif
 
 #ifdef MOZ_X11
 #include "mozilla/X11Util.h"
 #endif
 
 #ifdef ACCESSIBILITY
-#include "nsIAccessibilityService.h"
+#include "nsAccessibilityService.h"
 #endif
 
 #ifndef MOZ_SIMPLEPUSH
 #include "mozilla/dom/PushNotifier.h"
 #endif
 
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/cellbroadcast/CellBroadcastIPCService.h"
@@ -2464,18 +2464,17 @@ ContentChild::RecvFlushMemory(const nsSt
 }
 
 bool
 ContentChild::RecvActivateA11y()
 {
 #ifdef ACCESSIBILITY
   // Start accessibility in content process if it's running in chrome
   // process.
-  nsCOMPtr<nsIAccessibilityService> accService =
-    services::GetAccessibilityService();
+  GetOrCreateAccService();
 #endif
   return true;
 }
 
 bool
 ContentChild::RecvGarbageCollect()
 {
   // Rebroadcast the "child-gc-request" so that workers will GC.
--- a/dom/media/WebVTTListener.cpp
+++ b/dom/media/WebVTTListener.cpp
@@ -84,24 +84,26 @@ WebVTTListener::AsyncOnChannelRedirect(n
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebVTTListener::OnStartRequest(nsIRequest* aRequest,
                                nsISupports* aContext)
 {
+  VTT_LOG("WebVTTListener::OnStartRequest\n");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebVTTListener::OnStopRequest(nsIRequest* aRequest,
                               nsISupports* aContext,
                               nsresult aStatus)
 {
+  VTT_LOG("WebVTTListener::OnStopRequest\n");
   if (NS_FAILED(aStatus)) {
     mElement->SetReadyState(TextTrackReadyState::FailedToLoad);
   }
   // Attempt to parse any final data the parser might still have.
   mParserWrapper->Flush();
   if (mElement->ReadyState() != TextTrackReadyState::FailedToLoad) {
     mElement->SetReadyState(TextTrackReadyState::Loaded);
   }
@@ -131,16 +133,17 @@ WebVTTListener::ParseChunk(nsIInputStrea
 
 NS_IMETHODIMP
 WebVTTListener::OnDataAvailable(nsIRequest* aRequest,
                                 nsISupports* aContext,
                                 nsIInputStream* aStream,
                                 uint64_t aOffset,
                                 uint32_t aCount)
 {
+  VTT_LOG("WebVTTListener::OnDataAvailable\n");
   uint32_t count = aCount;
   while (count > 0) {
     uint32_t read;
     nsresult rv = aStream->ReadSegments(ParseChunk, this, count, &read);
     NS_ENSURE_SUCCESS(rv, rv);
     if (!read) {
       return NS_ERROR_FAILURE;
     }
--- a/dom/media/platforms/wmf/DXVA2Manager.cpp
+++ b/dom/media/platforms/wmf/DXVA2Manager.cpp
@@ -299,17 +299,17 @@ D3D9DXVA2Manager::Init(nsACString& aFail
   }
 
   // Create D3D9DeviceEx. We pass null HWNDs here even though the documentation
   // suggests that one of them should not be. At this point in time Chromium
   // does the same thing for video acceleration.
   D3DPRESENT_PARAMETERS params = {0};
   params.BackBufferWidth = 1;
   params.BackBufferHeight = 1;
-  params.BackBufferFormat = D3DFMT_UNKNOWN;
+  params.BackBufferFormat = D3DFMT_A8R8G8B8;
   params.BackBufferCount = 1;
   params.SwapEffect = D3DSWAPEFFECT_DISCARD;
   params.hDeviceWindow = nullptr;
   params.Windowed = TRUE;
   params.Flags = D3DPRESENTFLAG_VIDEO;
 
   RefPtr<IDirect3DDevice9Ex> device;
   hr = d3d9Ex->CreateDeviceEx(D3DADAPTER_DEFAULT,
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -20,17 +20,17 @@ support-files =
   !/dom/media/test/320x240.ogv
   !/dom/media/test/r11025_s16_c1.wav
   !/dom/media/test/bug461281.ogg
   !/dom/media/test/seek.webm
   !/dom/media/test/gizmo.mp4
 
 [test_a_noOp.html]
 [test_dataChannel_basicAudio.html]
-skip-if = toolkit == 'gonk' || buildapp == 'mulet' # Bug 962984 for debug, bug 963244 for opt
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # Bug 962984 for debug, bug 963244 for opt
 [test_dataChannel_basicAudioVideo.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_dataChannel_basicAudioVideoNoBundle.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' # b2g(Bug 960442, video support for WebRTC is disabled on b2g) FAILS WHEN RUN MANUALLY ON AWS, android(Bug 1189784, timeouts on 4.3 emulator)
 [test_dataChannel_basicAudioVideoCombined.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_dataChannel_basicDataOnly.html]
 [test_dataChannel_basicVideo.html]
@@ -80,17 +80,17 @@ skip-if = (toolkit == 'gonk' || buildapp
 [test_getUserMedia_stopVideoAudioStreamWithFollowupVideoAudio.html]
 [test_getUserMedia_stopVideoStream.html]
 [test_getUserMedia_stopVideoStreamWithFollowupVideo.html]
 [test_getUserMedia_trackEnded.html]
 [test_getUserMedia_peerIdentity.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 1021776, too --ing slow on b2g)
 [test_peerConnection_addIceCandidate.html]
 [test_peerConnection_addtrack_removetrack_events.html]
-skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_basicAudio.html]
 skip-if = toolkit == 'gonk' # B2G emulator is too slow to handle a two-way audio call reliably
 [test_peerConnection_basicAudioNATSrflx.html]
 skip-if = toolkit == 'gonk' || toolkit == 'android' # B2G emulator is too slow to handle a two-way audio call reliably, websockets don't work on android (bug 1266217)
 [test_peerConnection_basicAudioNATRelay.html]
 skip-if = toolkit == 'gonk' || toolkit == 'android' # B2G emulator is too slow to handle a two-way audio call reliably, websockets don't work on android (bug 1266217)
 [test_peerConnection_basicAudioNATRelayTCP.html]
 skip-if = toolkit == 'gonk' || toolkit == 'android' # B2G emulator is too slow to handle a two-way audio call reliably, websockets don't work on android (bug 1266217)
@@ -199,19 +199,19 @@ skip-if = toolkit == 'gonk' || buildapp 
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html]
 [test_peerConnection_setRemoteAnswerInStable.html]
 [test_peerConnection_setRemoteOfferInHaveLocalOffer.html]
 [test_peerConnection_throwInCallbacks.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_toJSON.html]
 [test_peerConnection_trackDisabling_clones.html]
-skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_trackDisabling.html]
-skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_twoAudioStreams.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g (Bug 1059867), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_twoAudioTracksInOneStream.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g (Bug 1059867), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_twoAudioVideoStreams.html]
 # b2g(Bug 960442, video support for WebRTC is disabled on b2g), Bug 1171255 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator)
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (os == 'linux' && debug && e10s) || android_version == '18'
 [test_peerConnection_twoAudioVideoStreamsCombined.html]
@@ -222,23 +222,23 @@ skip-if = toolkit == 'gonk' || buildapp 
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18'
 [test_peerConnection_twoVideoTracksInOneStream.html]
 # b2g(Bug 960442, video support for WebRTC is disabled on b2g), Bug 1180000 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator)
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || os == "android"
 [test_peerConnection_addAudioTrackToExistingVideoStream.html]
 # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, android(Bug 1189784, timeouts on 4.3 emulator)
 skip-if = toolkit == 'gonk' || (android_version == '18' && debug)
 [test_peerConnection_addSecondAudioStream.html]
-skip-if = toolkit == 'gonk' # B2G emulator is too slow to finish a renegotiation test in under 5 minutes
+skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # emulator is too slow to finish a renegotiation test in under 5 minutes
 [test_peerConnection_answererAddSecondAudioStream.html]
-skip-if = toolkit == 'gonk' # B2G emulator is too slow to finish a renegotiation test in under 5 minutes
+skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # emulator is too slow to finish a renegotiation test in under 5 minutes
 [test_peerConnection_removeAudioTrack.html]
-skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, android(Bug 1189784, timeouts on 4.3 emulator)
+skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # emulator is too slow to finish a renegotiation test in under 5 minutes, android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_removeThenAddAudioTrack.html]
-skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, android(Bug 1189784, timeouts on 4.3 emulator)
+skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # emulator is too slow to finish a renegotiation test in under 5 minutes, android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_addSecondVideoStream.html]
 # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, Bug 1180000 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator)
 skip-if = toolkit == 'gonk' || android_version == '18'
 [test_peerConnection_removeVideoTrack.html]
 # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, Bug 1180000 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator)
 skip-if = toolkit == 'gonk' || (android_version == '18' && debug)
 [test_peerConnection_removeThenAddVideoTrack.html]
 # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, Bug 1180000 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator)
@@ -252,37 +252,37 @@ skip-if = toolkit == 'gonk' || (android_
 skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_addSecondVideoStreamNoBundle.html]
 # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, Bug 1180000 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator)
 skip-if = toolkit == 'gonk' || android_version == '18'
 [test_peerConnection_removeThenAddVideoTrackNoBundle.html]
 # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, Bug 1180000 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator)
 skip-if = toolkit == 'gonk' || android_version == '18'
 [test_peerConnection_addDataChannel.html]
-skip-if = toolkit == 'gonk' # B2G emulator seems to be so slow that DTLS cannot establish properly
+skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # emulator seems to be so slow that DTLS cannot establish properly, android(bug 1240256, intermittent ICE failures starting w/bug 1232082, possibly from timeout)
 [test_peerConnection_addDataChannelNoBundle.html]
 # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
 skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # b2g(emulator seems to be so slow that DTLS cannot establish properly), android(bug 1240256, intermittent ICE failures starting w/bug 1232082, possibly from timeout)
 [test_peerConnection_verifyAudioAfterRenegotiation.html]
-skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, android(Bug 1189784, timeouts on 4.3 emulator)
+skip-if = toolkit == 'gonk' || android_version == '18' # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_verifyVideoAfterRenegotiation.html]
 # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, Bug 1180000 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator)
 skip-if = toolkit == 'gonk' || android_version == '18'
 [test_peerConnection_audioRenegotiationInactiveAnswer.html]
 # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, Bug 1180000 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator)
 skip-if = toolkit == 'gonk' || android_version == '18'
 [test_peerConnection_videoRenegotiationInactiveAnswer.html]
 # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, Bug 1180000 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator)
 skip-if = toolkit == 'gonk' || android_version == '18'
 [test_peerConnection_webAudio.html]
 tags = webaudio webrtc
-skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' # b2g (Bug 1059867), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_localRollback.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
 [test_peerConnection_localReofferRollback.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
 [test_peerConnection_remoteRollback.html]
 skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # b2g (Bug 1059867), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_remoteReofferRollback.html]
-skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g (Bug 1059867), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_selftest.html]
 # Bug 1227781: Crash with bogus TURN server.
 [test_peerConnection_bug1227781.html]
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -1455,20 +1455,20 @@ PeerConnectionWrapper.prototype = {
                                                     : "packetsReceived"];
       info("Track " + track.id + " has " + nrPackets + " " +
            rtp.type + " RTP packets.");
       return nrPackets > 0;
     };
 
     info("Checking RTP packet flow for track " + track.id);
 
-    var retry = () => this._pc.getStats(track)
+    var retry = (delay) => this._pc.getStats(track)
       .then(stats => hasFlow(stats)? ok(true, "RTP flowing for track " + track.id) :
-                                     wait(200).then(retry));
-    return retry();
+            wait(delay).then(retry(1000)));
+    return retry(200);
   },
 
   /**
    * Wait for presence of video flow on all media elements and rtp flow on
    * all sending and receiving track involved in this test.
    *
    * @returns {Promise}
    *        A promise that resolves when media flows for all elements and tracks
--- a/dom/media/webrtc/MediaEngineWebRTC.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTC.cpp
@@ -120,19 +120,16 @@ MediaEngineWebRTC::MediaEngineWebRTC(Med
   }
 #else
 #ifdef MOZ_WIDGET_GONK
   AsyncLatencyLogger::Get()->AddRef();
 #endif
 #endif
   // XXX
   gFarendObserver = new AudioOutputObserver();
-
-  NS_NewNamedThread("AudioGUM", getter_AddRefs(mThread));
-  MOZ_ASSERT(mThread);
 }
 
 void
 MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
                                          nsTArray<RefPtr<MediaEngineVideoSource> >* aVSources)
 {
   // We spawn threads to handle gUM runnables, so we must protect the member vars
   MutexAutoLock lock(mMutex);
@@ -404,17 +401,17 @@ MediaEngineWebRTC::EnumerateAudioDevices
       if (SupportsDuplex()) {
         // The platform_supports_full_duplex.
 
         // For cubeb, it has state (the selected ID)
         // XXX just use the uniqueID for cubeb and support it everywhere, and get rid of this
         // XXX Small window where the device list/index could change!
         audioinput = new mozilla::AudioInputCubeb(mVoiceEngine, i);
       }
-      aSource = new MediaEngineWebRTCMicrophoneSource(mThread, mVoiceEngine, audioinput,
+      aSource = new MediaEngineWebRTCMicrophoneSource(mVoiceEngine, audioinput,
                                                       i, deviceName, uniqueId);
       mAudioSources.Put(uuid, aSource); // Hashtable takes ownership.
       aASources->AppendElement(aSource);
     }
   }
 }
 
 void
@@ -445,16 +442,11 @@ MediaEngineWebRTC::Shutdown()
     mVoiceEngine->SetTraceCallback(nullptr);
     webrtc::VoiceEngine::Delete(mVoiceEngine);
   }
 
   mVoiceEngine = nullptr;
 
   mozilla::camera::Shutdown();
   AudioInputCubeb::CleanupGlobalData();
-
-  if (mThread) {
-    mThread->Shutdown();
-    mThread = nullptr;
-  }
 }
 
 }
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -414,18 +414,17 @@ private:
   RefPtr<MediaEngineAudioSource> mAudioSource;
 };
 
 class MediaEngineWebRTCMicrophoneSource : public MediaEngineAudioSource,
                                           public webrtc::VoEMediaProcess
 {
   typedef MediaEngineAudioSource Super;
 public:
-  MediaEngineWebRTCMicrophoneSource(nsIThread* aThread,
-                                    webrtc::VoiceEngine* aVoiceEnginePtr,
+  MediaEngineWebRTCMicrophoneSource(webrtc::VoiceEngine* aVoiceEnginePtr,
                                     mozilla::AudioInput* aAudioInput,
                                     int aIndex,
                                     const char* name,
                                     const char* uuid);
 
   void GetName(nsAString& aName) const override;
   void GetUUID(nsACString& aUUID) const override;
 
@@ -538,17 +537,16 @@ private:
   // mMonitor protects mSources[] and mPrinicpalIds[] access/changes, and
   // transitions of mState from kStarted to kStopped (which are combined with
   // EndTrack()). mSources[] and mPrincipalHandles[] are accessed from webrtc
   // threads.
   Monitor mMonitor;
   nsTArray<RefPtr<SourceMediaStream>> mSources;
   nsTArray<PrincipalHandle> mPrincipalHandles; // Maps to mSources.
 
-  nsCOMPtr<nsIThread> mThread;
   int mCapIndex;
   int mChannel;
   TrackID mTrackID;
   bool mStarted;
 
   nsString mDeviceName;
   nsCString mDeviceUUID;
 
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -179,27 +179,25 @@ AudioOutputObserver::InsertFarEnd(const 
         mSaved = nullptr;
         mSamplesSaved = 0;
       }
     }
   }
 }
 
 MediaEngineWebRTCMicrophoneSource::MediaEngineWebRTCMicrophoneSource(
-    nsIThread* aThread,
     webrtc::VoiceEngine* aVoiceEnginePtr,
     mozilla::AudioInput* aAudioInput,
     int aIndex,
     const char* name,
     const char* uuid)
   : MediaEngineAudioSource(kReleased)
   , mVoiceEngine(aVoiceEnginePtr)
   , mAudioInput(aAudioInput)
   , mMonitor("WebRTCMic.Monitor")
-  , mThread(aThread)
   , mCapIndex(aIndex)
   , mChannel(-1)
   , mStarted(false)
   , mSampleFrequency(MediaEngine::DEFAULT_SAMPLE_RATE)
   , mPlayoutDelay(0)
   , mNullTransport(nullptr)
   , mSkipProcessing(false)
 {
@@ -611,21 +609,17 @@ MediaEngineWebRTCMicrophoneSource::Inser
     // XXX Bug 971528 - Support stereo capture in gUM
     MOZ_ASSERT(aChannels == 1,
         "GraphDriver only supports us stereo audio for now");
     channels.AppendElement(static_cast<T*>(buffer->Data()));
     segment->AppendFrames(buffer.forget(), channels, aFrames,
                          mPrincipalHandles[i]);
     segment->GetStartTime(insertTime);
 
-    RUN_ON_THREAD(mThread,
-                  WrapRunnable(mSources[i], &SourceMediaStream::AppendToTrack,
-                               mTrackID, segment,
-                               static_cast<AudioSegment*>(nullptr)),
-                  NS_DISPATCH_NORMAL);
+    mSources[i]->AppendToTrack(mTrackID, segment);
   }
 }
 
 // Called back on GraphDriver thread!
 // Note this can be called back after ::Shutdown()
 void
 MediaEngineWebRTCMicrophoneSource::NotifyInputData(MediaStreamGraph* aGraph,
                                                    const AudioDataValue* aBuffer,
--- a/dom/plugins/ipc/PluginInstanceParent.cpp
+++ b/dom/plugins/ipc/PluginInstanceParent.cpp
@@ -949,18 +949,19 @@ PluginInstanceParent::RecvShow(const NPR
     if (surface) {
         // Notify the cairo backend that this surface has changed behind
         // its back.
         gfxRect ur(updatedRect.left, updatedRect.top,
                    updatedRect.right - updatedRect.left,
                    updatedRect.bottom - updatedRect.top);
         surface->MarkDirty(ur);
 
+        bool isPlugin = true;
         RefPtr<gfx::SourceSurface> sourceSurface =
-            gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(nullptr, surface);
+            gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(nullptr, surface, isPlugin);
         RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(surface->GetSize(), sourceSurface);
 
         AutoTArray<ImageContainer::NonOwningImage,1> imageList;
         imageList.AppendElement(
             ImageContainer::NonOwningImage(image));
 
         ImageContainer *container = GetImageContainer();
         container->SetCurrentImages(imageList);
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -987,29 +987,29 @@ Promise::PerformMicroTaskCheckpoint()
   // On the main thread, we always use the main promise micro task queue.
   std::queue<nsCOMPtr<nsIRunnable>>& microtaskQueue =
     runtime->GetPromiseMicroTaskQueue();
 
   if (microtaskQueue.empty()) {
     return false;
   }
 
-  AutoSafeJSContext cx;
+  AutoSlowOperation aso;
 
   do {
     nsCOMPtr<nsIRunnable> runnable = microtaskQueue.front().forget();
     MOZ_ASSERT(runnable);
 
     // This function can re-enter, so we remove the element before calling.
     microtaskQueue.pop();
     nsresult rv = runnable->Run();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return false;
     }
-    JS_CheckForInterrupt(cx);
+    aso.CheckForInterrupt();
     runtime->AfterProcessMicrotask();
   } while (!microtaskQueue.empty());
 
   return true;
 }
 
 void
 Promise::PerformWorkerMicroTaskCheckpoint()
--- a/dom/security/test/cors/test_CrossSiteXHR.html
+++ b/dom/security/test/cors/test_CrossSiteXHR.html
@@ -754,28 +754,30 @@ function runTest() {
       is(res.didFail, true,
         "should have failed in test for " + test.toSource());
       is(res.status, 0, "wrong status in test for " + test.toSource());
       is(res.statusText, "", "wrong status text for " + test.toSource());
       is(res.responseXML, null,
          "wrong responseXML in test for " + test.toSource());
       is(res.responseText, "",
          "wrong responseText in test for " + test.toSource());
+      var expectedProgressCount = 0;
       if (!res.sendThrew) {
         if (test.username) {
+          expectedProgressCount = 1;
           is(res.events.join(","),
              "opening,rs1,sending,loadstart,rs4,error,loadend",
              "wrong events in test for " + test.toSource());
         } else {
           is(res.events.join(","),
              "opening,rs1,sending,loadstart,rs2,rs4,error,loadend",
              "wrong events in test for " + test.toSource());
         }
       }
-      is(res.progressEvents, 0,
+      is(res.progressEvents, expectedProgressCount,
          "wrong events in test for " + test.toSource());
       if (test.responseHeaders) {
         for (header in test.responseHeaders) {
           is(res.responseHeaders[header], null,
              "wrong response header (" + header + ") in test for " +
              test.toSource());
         }
       }
--- a/dom/svg/SVGAElement.cpp
+++ b/dom/svg/SVGAElement.cpp
@@ -189,16 +189,19 @@ SVGAElement::IsFocusableInternal(int32_t
 {
   nsCOMPtr<nsIURI> uri;
   if (IsLink(getter_AddRefs(uri))) {
     if (aTabIndex) {
       *aTabIndex = ((sTabFocusModel & eTabFocus_linksMask) == 0 ? -1 : 0);
     }
     return true;
   }
+  if (nsSVGElement::IsFocusableInternal(aTabIndex, aWithMouse)) {
+    return true;
+  }
 
   if (aTabIndex) {
     *aTabIndex = -1;
   }
 
   return false;
 }
 
--- a/dom/svg/nsSVGElement.cpp
+++ b/dom/svg/nsSVGElement.cpp
@@ -605,16 +605,19 @@ nsSVGElement::ParseAttribute(int32_t aNa
         rv = transformList->SetBaseValueString(aValue);
         if (NS_FAILED(rv)) {
           transformList->ClearBaseValue();
         } else {
           aResult.SetTo(transformList->GetBaseValue(), &aValue);
           didSetResult = true;
         }
         foundMatch = true;
+      } else if (aAttribute == nsGkAtoms::tabindex) {
+        didSetResult = aResult.ParseIntValue(aValue);
+        foundMatch = true;
       }
     }
 
     if (aAttribute == nsGkAtoms::_class) {
       mClassAttribute.SetBaseValue(aValue, this, false);
       aResult.ParseAtomArray(aValue);
       return true;
     }
@@ -1115,16 +1118,29 @@ nsSVGElement::GetViewportElement()
 }
 
 already_AddRefed<SVGAnimatedString>
 nsSVGElement::ClassName()
 {
   return mClassAttribute.ToDOMAnimatedString(this);
 }
 
+bool
+nsSVGElement::IsFocusableInternal(int32_t* aTabIndex, bool)
+{
+  int32_t index = TabIndex();
+
+  if (index == -1) {
+    return false;
+  }
+
+  *aTabIndex = index;
+  return true;
+}
+
 //------------------------------------------------------------------------
 // Helper class: MappedAttrParser, for parsing values of mapped attributes
 
 namespace {
 
 class MOZ_STACK_CLASS MappedAttrParser {
 public:
   MappedAttrParser(css::Loader* aLoader,
--- a/dom/svg/nsSVGElement.h
+++ b/dom/svg/nsSVGElement.h
@@ -312,16 +312,18 @@ public:
   virtual void ClearAnyCachedPath() {}
   virtual nsIDOMNode* AsDOMNode() final override { return this; }
   virtual bool IsTransformable() { return false; }
 
   // WebIDL
   mozilla::dom::SVGSVGElement* GetOwnerSVGElement();
   nsSVGElement* GetViewportElement();
   already_AddRefed<mozilla::dom::SVGAnimatedString> ClassName();
+  virtual bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) override;
+
 protected:
   virtual JSObject* WrapNode(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
 
 #ifdef DEBUG
   // We define BeforeSetAttr here and mark it final to ensure it is NOT used
   // by SVG elements.
   // This is because we're not currently passing the correct value for aValue to
   // BeforeSetAttr since it would involve allocating extra SVG value types.
--- a/dom/svg/test/mochitest.ini
+++ b/dom/svg/test/mochitest.ini
@@ -81,16 +81,17 @@ skip-if = android_version == '18' # bug 
 [test_SVGStringList.xhtml]
 [test_SVGStyleElement.xhtml]
 [test_SVGTransformListAddition.xhtml]
 [test_SVGTransformList.xhtml]
 [test_SVGUnitTypes.html]
 [test_SVGxxxListIndexing.xhtml]
 [test_SVGxxxList.xhtml]
 [test_switch.xhtml]
+[test_tabindex.html]
 [test_tearoff_with_cc.html]
 support-files = tearoff_with_cc_helper.html
 [test_text_2.html]
 [test_text_dirty.html]
 [test_text.html]
 [test_text_lengthAdjust.html]
 [test_text_scaled.html]
 [test_text_selection.html]
new file mode 100644
--- /dev/null
+++ b/dom/svg/test/test_tabindex.html
@@ -0,0 +1,68 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>Test for SVG tabIndex - Bug 778654</title>
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<svg xmlns="http://www.w3.org/2000/svg" overflow="visible">
+  <foreignObject id="f" x="0" y="0" width="200" height="60" tabindex="0">
+    <body xmlns="http://www.w3.org/1999/xhtml">
+      <p>Here is a paragraph that requires word wrap</p>
+    </body>
+  </foreignObject>
+  <rect id="r" x="0" y="70" width="100" height="100" fill="yellow" tabIndex="1"/>
+  <text id="t" x="0" y="200" tabindex="2">
+        This is SVG text
+  </text>
+</svg>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+function main()
+{
+  var f = document.getElementById('f');
+  var r = document.getElementById('r');
+  var t = document.getElementById('t');
+
+  try {
+    // Step 1: Checking by assigning tabIndex
+    is(f.tabIndex, 0, "foreignObject initial tabIndex");
+    f.tabIndex = 1;
+    is(f.tabIndex, 1, "foreignObject tabIndex is set to 1");
+
+    is(r.tabIndex, 1, "rect initial tabIndex");
+    r.tabIndex = 2;
+    is(r.tabIndex, 2, "rect tabIndex is set to 2");
+
+    is(t.tabIndex, 2, "text initial tabIndex");
+    t.tabIndex = 3;
+    is(t.tabIndex, 3, "text is set to 3");
+
+    // Step 2: Checking by triggering TAB event
+    is(document.activeElement.tabIndex, -1, "In the beginning, the active element tabindex is -1");
+
+    synthesizeKey("VK_TAB", {});
+    is(document.activeElement.tabIndex, 1, "The active element tabindex is 1");
+
+    synthesizeKey("VK_TAB", {});
+    is(document.activeElement.tabIndex, 2, "The active element tabindex is 2");
+
+    synthesizeKey("VK_TAB", {});
+    is(document.activeElement.tabIndex, 3, "The active element tabindex is 3");
+  } catch(e) {
+    ok(false, "Got unexpected exception" + e);
+  }
+
+  SimpleTest.finish();
+}
+
+window.addEventListener("load", main, false);
+</script>
+</pre>
+</body>
+</html>
--- a/dom/webidl/SVGElement.webidl
+++ b/dom/webidl/SVGElement.webidl
@@ -16,16 +16,21 @@ interface SVGElement : Element {
   [Constant]
   readonly attribute SVGAnimatedString className;
   [PutForwards=cssText, Constant]
   readonly attribute CSSStyleDeclaration style;
 
   readonly attribute SVGSVGElement? ownerSVGElement;
   readonly attribute SVGElement? viewportElement;
 
-           attribute EventHandler oncopy;
-           attribute EventHandler oncut;
-           attribute EventHandler onpaste;
+  attribute EventHandler oncopy;
+  attribute EventHandler oncut;
+  attribute EventHandler onpaste;
+
+  [SetterThrows, Pure]
+        attribute long tabIndex;
+  [Throws] void focus();
+  [Throws] void blur();
 };
 
 SVGElement implements GlobalEventHandlers;
 SVGElement implements TouchEventHandlers;
 SVGElement implements OnErrorEventHandlerForNodes;
--- a/dom/workers/test/bug1063538_worker.js
+++ b/dom/workers/test/bug1063538_worker.js
@@ -1,18 +1,25 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 var gJar = "jar:http://example.org/tests/dom/base/test/file_bug945152.jar!/data_big.txt";
 var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+var progressFired = false;
+
+xhr.onloadend = function(e) {
+  postMessage({type: 'finish', progressFired: progressFired });
+  self.close();
+};
 
 xhr.onprogress = function(e) {
+  if (e.loaded > 0) {
+    progressFired = true;
+  }
   xhr.abort();
-  postMessage({type: 'finish' });
-  self.close();
 };
 
 onmessage = function(e) {
   xhr.open("GET", gJar, true);
   xhr.send();
 }
--- a/dom/workers/test/test_bug1063538.html
+++ b/dom/workers/test/test_bug1063538.html
@@ -22,17 +22,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <pre id="test">
 <script type="application/javascript">
 
 function runTest() {
   var worker = new Worker("bug1063538_worker.js");
 
   worker.onmessage = function(e) {
     if (e.data.type == 'finish') {
-      ok(true, "Testing done.\n");
+      ok(e.data.progressFired, "Progress was fired.");
       SimpleTest.finish();
     }
   };
 
   worker.postMessage(true);
 }
 
 SimpleTest.waitForExplicitFinish();
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -168,17 +168,17 @@ XMLHttpRequestMainThread::XMLHttpRequest
     mFlagTimedOut(false), mFlagDeleted(false), mFlagSend(false),
     mUploadTransferred(0), mUploadTotal(0), mUploadComplete(true),
     mProgressSinceLastProgressEvent(false),
     mRequestSentTime(0), mTimeoutMilliseconds(0),
     mErrorLoad(false), mWaitingForOnStopRequest(false),
     mProgressTimerIsActive(false),
     mIsHtml(false),
     mWarnAboutSyncHtml(false),
-    mLoadLengthComputable(false), mLoadTotal(0),
+    mLoadTotal(0),
     mIsSystem(false),
     mIsAnon(false),
     mFirstStartRequestSeen(false),
     mInLoadProgressEvent(false),
     mResultJSON(JS::UndefinedValue()),
     mResultArrayBuffer(nullptr),
     mIsMappedArrayBuffer(false),
     mXPCOMifier(nullptr)
@@ -1013,38 +1013,37 @@ XMLHttpRequestMainThread::CloseRequest()
   }
 }
 
 void
 XMLHttpRequestMainThread::CloseRequestWithError(const ProgressEventType aType)
 {
   CloseRequest();
 
-  uint32_t responseLength = mResponseBody.Length();
   ResetResponse();
 
   // If we're in the destructor, don't risk dispatching an event.
   if (mFlagDeleted) {
     mFlagSyncLooping = false;
     return;
   }
 
   if (mState != State::unsent &&
       !(mState == State::opened && !mFlagSend) &&
       mState != State::done) {
     ChangeState(State::done, true);
 
     if (!mFlagSyncLooping) {
-      DispatchProgressEvent(this, aType, mLoadLengthComputable, responseLength,
-                            mLoadTotal);
       if (mUpload && !mUploadComplete) {
         mUploadComplete = true;
-        DispatchProgressEvent(mUpload, aType, true, mUploadTransferred,
-                              mUploadTotal);
+        DispatchProgressEvent(mUpload, ProgressEventType::progress, 0, 0);
+        DispatchProgressEvent(mUpload, aType, 0, 0);
       }
+      DispatchProgressEvent(this, ProgressEventType::progress, 0, 0);
+      DispatchProgressEvent(this, aType, 0, 0);
     }
   }
 
   // The ChangeState call above calls onreadystatechange handlers which
   // if they load a new url will cause XMLHttpRequestMainThread::Open to clear
   // the abort state bit. If this occurs we're not uninitialized (bug 361773).
   if (mFlagAborted) {
     ChangeState(State::unsent, false);  // IE seems to do it
@@ -1291,46 +1290,72 @@ XMLHttpRequestMainThread::FireReadystate
   event->SetTrusted(true);
   DispatchDOMEvent(nullptr, event, nullptr, nullptr);
   return NS_OK;
 }
 
 void
 XMLHttpRequestMainThread::DispatchProgressEvent(DOMEventTargetHelper* aTarget,
                                                 const ProgressEventType aType,
-                                                bool aLengthComputable,
                                                 int64_t aLoaded, int64_t aTotal)
 {
   NS_ASSERTION(aTarget, "null target");
 
   if (NS_FAILED(CheckInnerWindowCorrectness()) ||
       (!AllowUploadProgress() && aTarget == mUpload)) {
     return;
   }
 
+  // If blocked by CORS, zero-out the stats on progress events
+  // and never fire "progress" or "load" events at all.
+  if (IsDeniedCrossSiteCORSRequest()) {
+    if (aType == ProgressEventType::progress ||
+        aType == ProgressEventType::load) {
+      return;
+    }
+    aLoaded = 0;
+    aTotal = 0;
+  }
+
+  if (aType == ProgressEventType::progress) {
+    mInLoadProgressEvent = true;
+  }
+
   ProgressEventInit init;
   init.mBubbles = false;
   init.mCancelable = false;
-  init.mLengthComputable = aLengthComputable;
+  init.mLengthComputable = aTotal != 0; // XHR spec step 6.1
   init.mLoaded = aLoaded;
   init.mTotal = (aTotal == -1) ? 0 : aTotal;
 
   const nsAString& typeString = ProgressEventTypeStrings[(uint8_t)aType];
   RefPtr<ProgressEvent> event =
     ProgressEvent::Constructor(aTarget, typeString, init);
   event->SetTrusted(true);
 
   aTarget->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
 
+  if (aType == ProgressEventType::progress) {
+    mInLoadProgressEvent = false;
+
+    // clear chunked responses after every progress event
+    if (mResponseType == XMLHttpRequestResponseType::Moz_chunked_text ||
+        mResponseType == XMLHttpRequestResponseType::Moz_chunked_arraybuffer) {
+      mResponseBody.Truncate();
+      mResponseText.Truncate();
+      mResultArrayBuffer = nullptr;
+      mArrayBufferBuilder.reset();
+    }
+  }
+
   // If we're sending a load, error, timeout or abort event, then
   // also dispatch the subsequent loadend event.
   if (aType == ProgressEventType::load || aType == ProgressEventType::error ||
       aType == ProgressEventType::timeout || aType == ProgressEventType::abort) {
-    DispatchProgressEvent(aTarget, ProgressEventType::loadend,
-                          aLengthComputable, aLoaded, aTotal);
+    DispatchProgressEvent(aTarget, ProgressEventType::loadend, aLoaded, aTotal);
   }
 }
 
 already_AddRefed<nsIHttpChannel>
 XMLHttpRequestMainThread::GetCurrentHttpChannel()
 {
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
   return httpChannel.forget();
@@ -1343,16 +1368,23 @@ XMLHttpRequestMainThread::GetCurrentJARC
   return appChannel.forget();
 }
 
 bool
 XMLHttpRequestMainThread::IsSystemXHR() const
 {
   return mIsSystem || nsContentUtils::IsSystemPrincipal(mPrincipal);
 }
+ 
+bool
+XMLHttpRequestMainThread::InUploadPhase() const
+{
+  // We're in the upload phase while our state is State::opened.
+  return mState == State::opened;
+}
 
 NS_IMETHODIMP
 XMLHttpRequestMainThread::Open(const nsACString& aMethod, const nsACString& aUrl,
                                bool aAsync, const nsAString& aUsername,
                                const nsAString& aPassword, uint8_t optional_argc)
 {
   Optional<bool> async;
   if (!optional_argc) {
@@ -1675,17 +1707,19 @@ XMLHttpRequestMainThread::OnDataAvailabl
     ChangeState(State::loading);
     return request->Cancel(NS_OK);
   }
 
   mDataAvailable += totalRead;
 
   ChangeState(State::loading);
 
-  MaybeDispatchProgressEvents(false);
+  if (!mFlagSynchronous && !mProgressTimerIsActive) {
+    StartProgressEventTimer();
+  }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 XMLHttpRequestMainThread::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
 {
   PROFILER_LABEL("XMLHttpRequestMainThread", "OnStartRequest",
@@ -1722,30 +1756,32 @@ XMLHttpRequestMainThread::OnStartRequest
 
   nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
   NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
 
   nsresult status;
   request->GetStatus(&status);
   mErrorLoad = mErrorLoad || NS_FAILED(status);
 
+  // Upload phase is now over. If we were uploading anything,
+  // stop the timer and fire any final progress events.
   if (mUpload && !mUploadComplete && !mErrorLoad && !mFlagSynchronous) {
-    if (mProgressTimerIsActive) {
-      mProgressTimerIsActive = false;
-      mProgressNotifier->Cancel();
+    StopProgressEventTimer();
+
+    mUploadTransferred = mUploadTotal;
+
+    if (mProgressSinceLastProgressEvent) {
+      DispatchProgressEvent(mUpload, ProgressEventType::progress,
+                            mUploadTransferred, mUploadTotal);
+      mProgressSinceLastProgressEvent = false;
     }
-    if (mUploadTransferred < mUploadTotal) {
-      mUploadTransferred = mUploadTotal;
-      mProgressSinceLastProgressEvent = true;
-      mUploadLengthComputable = true;
-      MaybeDispatchProgressEvents(true);
-    }
+
     mUploadComplete = true;
     DispatchProgressEvent(mUpload, ProgressEventType::load,
-                          true, mUploadTotal, mUploadTotal);
+                          mUploadTotal, mUploadTotal);
   }
 
   mContext = ctxt;
   mFlagParseBody = true;
   ChangeState(State::headers_received);
 
   ResetResponse();
 
@@ -1919,20 +1955,18 @@ XMLHttpRequestMainThread::OnStartRequest
     // the spec requires the response document.referrer to be the empty string
     mResponseXML->SetReferrer(NS_LITERAL_CSTRING(""));
 
     mXMLParserStreamListener = listener;
     rv = mXMLParserStreamListener->OnStartRequest(request, ctxt);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  // We won't get any progress events anyway if we didn't have progress
-  // events when starting the request - so maybe no need to start timer here.
-  if (NS_SUCCEEDED(rv) && !mFlagSynchronous &&
-      HasListenersFor(nsGkAtoms::onprogress)) {
+  // Download phase beginning; start the progress event timer if necessary.
+  if (NS_SUCCEEDED(rv) && HasListenersFor(nsGkAtoms::onprogress)) {
     StartProgressEventTimer();
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 XMLHttpRequestMainThread::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status)
@@ -1965,26 +1999,24 @@ XMLHttpRequestMainThread::OnStopRequest(
   // Is this good enough here?
   if (mXMLParserStreamListener && mFlagParseBody) {
     mXMLParserStreamListener->OnStopRequest(request, ctxt, status);
   }
 
   mXMLParserStreamListener = nullptr;
   mContext = nullptr;
 
-  // If we're received data since the last progress event, make sure to fire
-  // an event for it, except in the HTML case, defer the last progress event
-  // until the parser is done.
-  if (!mIsHtml) {
-    MaybeDispatchProgressEvents(true);
-  }
-
   if (NS_SUCCEEDED(status) &&
+      (mResponseType == XMLHttpRequestResponseType::_empty ||
+       mResponseType == XMLHttpRequestResponseType::Text)) {
+    mLoadTotal = mResponseBody.Length();
+  } else if (NS_SUCCEEDED(status) &&
       (mResponseType == XMLHttpRequestResponseType::Blob ||
        mResponseType == XMLHttpRequestResponseType::Moz_blob)) {
+    ErrorResult rv;
     if (!mDOMBlob) {
       CreateDOMBlob(request);
     }
     if (mDOMBlob) {
       mResponseBlob = mDOMBlob;
       mDOMBlob = nullptr;
     } else {
       // mBlobSet can be null if the channel is non-file non-cacheable
@@ -1992,33 +2024,39 @@ XMLHttpRequestMainThread::OnStopRequest(
       if (!mBlobSet) {
         mBlobSet = new BlobSet();
       }
       // Smaller files may be written in cache map instead of separate files.
       // Also, no-store response cannot be written in persistent cache.
       nsAutoCString contentType;
       mChannel->GetContentType(contentType);
 
-      ErrorResult rv;
       mResponseBlob = mBlobSet->GetBlobInternal(GetOwner(), contentType, rv);
       mBlobSet = nullptr;
 
       if (NS_WARN_IF(rv.Failed())) {
         return rv.StealNSResult();
       }
     }
+
+    mLoadTotal = mResponseBlob->GetSize(rv);
+    if (NS_WARN_IF(rv.Failed())) {
+      status = rv.StealNSResult();
+    }
+
     NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
     NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty");
   } else if (NS_SUCCEEDED(status) &&
              ((mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
                !mIsMappedArrayBuffer) ||
               mResponseType == XMLHttpRequestResponseType::Moz_chunked_arraybuffer)) {
     // set the capacity down to the actual length, to realloc back
     // down to the actual size
-    if (!mArrayBufferBuilder.setCapacity(mArrayBufferBuilder.length())) {
+    mLoadTotal = mArrayBufferBuilder.length();
+    if (!mArrayBufferBuilder.setCapacity(mLoadTotal)) {
       // this should never happen!
       status = NS_ERROR_UNEXPECTED;
     }
   }
 
   nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
   NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
 
@@ -2040,67 +2078,96 @@ XMLHttpRequestMainThread::OnStopRequest(
   // If we're uninitialized at this point, we encountered an error
   // earlier and listeners have already been notified. Also we do
   // not want to do this if we already completed.
   if (mState == State::unsent || mState == State::done) {
     return NS_OK;
   }
 
   if (!mResponseXML) {
+    mFlagParseBody = false;
     ChangeStateToDone();
     return NS_OK;
   }
+
   if (mIsHtml) {
     NS_ASSERTION(!mFlagSyncLooping,
       "We weren't supposed to support HTML parsing with XHR!");
     nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(mResponseXML);
     EventListenerManager* manager =
       eventTarget->GetOrCreateListenerManager();
     manager->AddEventListenerByType(new nsXHRParseEndListener(this),
                                     kLiteralString_DOMContentLoaded,
                                     TrustedEventsAtSystemGroupBubble());
     return NS_OK;
+  } else {
+    mFlagParseBody = false;
   }
+
   // We might have been sent non-XML data. If that was the case,
   // we should null out the document member. The idea in this
   // check here is that if there is no document element it is not
   // an XML document. We might need a fancier check...
   if (!mResponseXML->GetRootElement()) {
     mResponseXML = nullptr;
   }
   ChangeStateToDone();
   return NS_OK;
 }
 
 void
+XMLHttpRequestMainThread::OnBodyParseEnd()
+{
+  mFlagParseBody = false;
+  ChangeStateToDone();
+}
+
+void
 XMLHttpRequestMainThread::ChangeStateToDone()
 {
-  if (mIsHtml) {
-    // In the HTML case, this has to be deferred, because the parser doesn't
-    // do it's job synchronously.
-    MaybeDispatchProgressEvents(true);
-  }
-
-  ChangeState(State::done, true);
+  StopProgressEventTimer();
+
+  MOZ_ASSERT(!mFlagParseBody,
+             "ChangeStateToDone() called before async HTML parsing is done.");
 
   mFlagSend = false;
 
   if (mTimeoutTimer) {
     mTimeoutTimer->Cancel();
   }
 
+  mLoadTotal = mLoadTransferred;
+
+  // Per spec, fire the last download progress event, if any,
+  // before readystatechange=4/done. (Note that 0-sized responses
+  // will have not sent a progress event yet, so one must be sent here).
+  if (!mFlagSynchronous &&
+      (!mLoadTransferred || mProgressSinceLastProgressEvent)) {
+    DispatchProgressEvent(this, ProgressEventType::progress,
+                          mLoadTransferred, mLoadTotal);
+    mProgressSinceLastProgressEvent = false;
+  }
+
+  // Per spec, fire readystatechange=4/done before final error events.
+  ChangeState(State::done, true);
+
+  // Per spec, if we failed in the upload phase, fire a final progress, error,
+  // and loadend event for the upload after readystatechange=4/done.
+  if (!mFlagSynchronous && mUpload && !mUploadComplete) {
+    DispatchProgressEvent(mUpload, ProgressEventType::progress, 0, 0);
+    DispatchProgressEvent(mUpload, ProgressEventType::error, 0, 0);
+  }
+
+  // Per spec, fire download's load/error and loadend events after
+  // readystatechange=4/done (and of course all upload events).
   DispatchProgressEvent(this,
-                        mErrorLoad ? ProgressEventType::error : ProgressEventType::load,
-                        !mErrorLoad,
-                        mLoadTransferred,
-                        mErrorLoad ? 0 : mLoadTransferred);
-  if (mErrorLoad && mUpload && !mUploadComplete) {
-    DispatchProgressEvent(mUpload, ProgressEventType::error, true,
-                          mUploadTransferred, mUploadTotal);
-  }
+                        mErrorLoad ? ProgressEventType::error :
+                                     ProgressEventType::load,
+                        mErrorLoad ? 0 : mLoadTransferred,
+                        mErrorLoad ? 0 : mLoadTotal);
 
   if (mErrorLoad) {
     // By nulling out channel here we make it so that Send() can test
     // for that and throw. Also calling the various status
     // methods/members will not throw.
     // This matches what IE does.
     mChannel = nullptr;
   }
@@ -2504,17 +2571,16 @@ XMLHttpRequestMainThread::SendInternal(c
     }
   }
 
   mUploadTransferred = 0;
   mUploadTotal = 0;
   // By default we don't have any upload, so mark upload complete.
   mUploadComplete = true;
   mErrorLoad = false;
-  mLoadLengthComputable = false;
   mLoadTotal = 0;
   if (aBody && httpChannel &&
       !method.LowerCaseEqualsLiteral("get") &&
       !method.LowerCaseEqualsLiteral("head")) {
 
     nsAutoCString charset;
     nsAutoCString defaultContentType;
     nsCOMPtr<nsIInputStream> postDataStream;
@@ -2805,20 +2871,17 @@ XMLHttpRequestMainThread::SendInternal(c
             suspendedDoc->SuppressEventHandling(nsIDocument::eEvents);
           }
           topWindow->SuspendTimeouts(1, false);
           resumeTimeoutRunnable = new nsResumeTimeoutsEvent(topInner);
         }
       }
     }
 
-    if (mProgressNotifier) {
-      mProgressTimerIsActive = false;
-      mProgressNotifier->Cancel();
-    }
+    StopProgressEventTimer();
 
     {
       nsAutoSyncOperation sync(suspendedDoc);
       nsIThread *thread = NS_GetCurrentThread();
       while (mFlagSyncLooping) {
         if (!NS_ProcessNextEvent(thread)) {
           rv = NS_ERROR_UNEXPECTED;
           break;
@@ -2834,27 +2897,26 @@ XMLHttpRequestMainThread::SendInternal(c
     if (resumeTimeoutRunnable) {
       NS_DispatchToCurrentThread(resumeTimeoutRunnable);
     }
   } else {
     // Now that we've successfully opened the channel, we can change state.  Note
     // that this needs to come after the AsyncOpen() and rv check, because this
     // can run script that would try to restart this request, and that could end
     // up doing our AsyncOpen on a null channel if the reentered AsyncOpen fails.
-    if (mProgressNotifier) {
-      mProgressTimerIsActive = false;
-      mProgressNotifier->Cancel();
-    }
-
+    StopProgressEventTimer();
+
+    // Upload phase beginning; start the progress event timer if necessary.
     if (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress)) {
       StartProgressEventTimer();
     }
-    DispatchProgressEvent(this, ProgressEventType::loadstart, false, 0, 0);
+    // Dispatch loadstart events
+    DispatchProgressEvent(this, ProgressEventType::loadstart, 0, 0);
     if (mUpload && !mUploadComplete) {
-      DispatchProgressEvent(mUpload, ProgressEventType::loadstart, true,
+      DispatchProgressEvent(mUpload, ProgressEventType::loadstart,
                             0, mUploadTotal);
     }
   }
 
   if (!mChannel) {
     // Per spec, silently fail on async request failures; throw for sync.
     if (mFlagSynchronous) {
       return NS_ERROR_FAILURE;
@@ -3116,20 +3178,18 @@ XMLHttpRequestMainThread::SetWithCredent
 }
 
 nsresult
 XMLHttpRequestMainThread::ChangeState(State aState, bool aBroadcast)
 {
   mState = aState;
   nsresult rv = NS_OK;
 
-  if (mProgressNotifier &&
-      aState != State::headers_received && aState != State::loading) {
-    mProgressTimerIsActive = false;
-    mProgressNotifier->Cancel();
+  if (aState != State::headers_received && aState != State::loading) {
+    StopProgressEventTimer();
   }
 
 
   if (aBroadcast && (!mFlagSynchronous ||
                      aState == State::opened ||
                      aState == State::done)) {
     rv = FireReadystatechangeEvent();
   }
@@ -3220,88 +3280,39 @@ XMLHttpRequestMainThread::OnRedirectVeri
 
   return result;
 }
 
 /////////////////////////////////////////////////////
 // nsIProgressEventSink methods:
 //
 
-void
-XMLHttpRequestMainThread::MaybeDispatchProgressEvents(bool aFinalProgress)
-{
-  if (aFinalProgress && mProgressTimerIsActive) {
-    mProgressTimerIsActive = false;
-    mProgressNotifier->Cancel();
-  }
-
-  if (mProgressTimerIsActive ||
-      !mProgressSinceLastProgressEvent ||
-      mErrorLoad ||
-      mFlagSynchronous) {
-    return;
-  }
-
-  if (!aFinalProgress) {
-    StartProgressEventTimer();
-  }
-
-  // We're in the upload phase while our state is State::opened.
-  if (mState == State::opened) {
-    if (mUpload && !mUploadComplete) {
-      DispatchProgressEvent(mUpload, ProgressEventType::progress,
-                            mUploadLengthComputable, mUploadTransferred,
-                            mUploadTotal);
-    }
-  } else {
-    if (aFinalProgress) {
-      mLoadTotal = mLoadTransferred;
-    }
-    mInLoadProgressEvent = true;
-    DispatchProgressEvent(this, ProgressEventType::progress,
-                          mLoadLengthComputable, mLoadTransferred,
-                          mLoadTotal);
-    mInLoadProgressEvent = false;
-    if (mResponseType == XMLHttpRequestResponseType::Moz_chunked_text ||
-        mResponseType == XMLHttpRequestResponseType::Moz_chunked_arraybuffer) {
-      mResponseBody.Truncate();
-      mResponseText.Truncate();
-      mResultArrayBuffer = nullptr;
-      mArrayBufferBuilder.reset();
-    }
-  }
-
-  mProgressSinceLastProgressEvent = false;
-}
-
 NS_IMETHODIMP
 XMLHttpRequestMainThread::OnProgress(nsIRequest *aRequest, nsISupports *aContext, int64_t aProgress, int64_t aProgressMax)
 {
-  // We're in the upload phase while our state is State::opened.
-  bool upload = mState == State::opened;
   // When uploading, OnProgress reports also headers in aProgress and aProgressMax.
   // So, try to remove the headers, if possible.
   bool lengthComputable = (aProgressMax != -1);
-  if (upload) {
+  if (InUploadPhase()) {
     int64_t loaded = aProgress;
     if (lengthComputable) {
       int64_t headerSize = aProgressMax - mUploadTotal;
       loaded -= headerSize;
     }
-    mUploadLengthComputable = lengthComputable;
     mUploadTransferred = loaded;
     mProgressSinceLastProgressEvent = true;
 
-    MaybeDispatchProgressEvents((mUploadTransferred == mUploadTotal));
+    if (!mFlagSynchronous && !mProgressTimerIsActive) {
+      StartProgressEventTimer();
+    }
   } else {
-    mLoadLengthComputable = lengthComputable;
     mLoadTotal = lengthComputable ? aProgressMax : 0;
     mLoadTransferred = aProgress;
-    // Don't dispatch progress events here. OnDataAvailable will take care
-    // of that.
+    // OnDataAvailable() handles mProgressSinceLastProgressEvent
+    // for the download phase.
   }
 
   if (mProgressEventSink) {
     mProgressEventSink->OnProgress(aRequest, aContext, aProgress,
                                    aProgressMax);
   }
 
   return NS_OK;
@@ -3485,17 +3496,43 @@ XMLHttpRequestMainThread::Notify(nsITime
   NS_WARNING("Unexpected timer!");
   return NS_ERROR_INVALID_POINTER;
 }
 
 void
 XMLHttpRequestMainThread::HandleProgressTimerCallback()
 {
   mProgressTimerIsActive = false;
-  MaybeDispatchProgressEvents(false);
+
+  if (!mProgressSinceLastProgressEvent || mErrorLoad) {
+    return;
+  }
+
+  if (InUploadPhase()) {
+    if (mUpload && !mUploadComplete) {
+      DispatchProgressEvent(mUpload, ProgressEventType::progress,
+                            mUploadTransferred, mUploadTotal);
+    }
+  } else {
+    DispatchProgressEvent(this, ProgressEventType::progress,
+                          mLoadTransferred, mLoadTotal);
+  }
+
+  mProgressSinceLastProgressEvent = false;
+
+  StartProgressEventTimer();
+}
+
+void
+XMLHttpRequestMainThread::StopProgressEventTimer()
+{
+  if (mProgressNotifier) {
+    mProgressTimerIsActive = false;
+    mProgressNotifier->Cancel();
+  }
 }
 
 void
 XMLHttpRequestMainThread::StartProgressEventTimer()
 {
   if (!mProgressNotifier) {
     mProgressNotifier = do_CreateInstance(NS_TIMER_CONTRACTID);
   }
--- a/dom/xhr/XMLHttpRequestMainThread.h
+++ b/dom/xhr/XMLHttpRequestMainThread.h
@@ -467,24 +467,18 @@ public:
                JS::MutableHandle<JS::Value> aRetval,
                ErrorResult& aRv) override;
 
   // This fires a trusted readystatechange event, which is not cancelable and
   // doesn't bubble.
   nsresult FireReadystatechangeEvent();
   void DispatchProgressEvent(DOMEventTargetHelper* aTarget,
                              const ProgressEventType aType,
-                             bool aLengthComputable,
                              int64_t aLoaded, int64_t aTotal);
 
-  // Dispatch the "progress" event on the XHR or XHR.upload object if we've
-  // received data since the last "progress" event. Also dispatches
-  // "uploadprogress" as needed.
-  void MaybeDispatchProgressEvents(bool aFinalProgress);
-
   // This is called by the factory constructor.
   nsresult Init();
 
   nsresult init(nsIPrincipal* principal,
                 nsPIDOMWindowInner* globalObject,
                 nsIURI* baseURI);
 
   void SetRequestObserver(nsIRequestObserver* aObserver);
@@ -531,20 +525,23 @@ protected:
   nsresult ChangeState(State aState, bool aBroadcast = true);
   already_AddRefed<nsILoadGroup> GetLoadGroup() const;
   nsIURI *GetBaseURI();
 
   already_AddRefed<nsIHttpChannel> GetCurrentHttpChannel();
   already_AddRefed<nsIJARChannel> GetCurrentJARChannel();
 
   bool IsSystemXHR() const;
+  bool InUploadPhase() const;
 
+  void OnBodyParseEnd();
   void ChangeStateToDone();
 
   void StartProgressEventTimer();
+  void StopProgressEventTimer();
 
   nsresult OnRedirectVerifyCallback(nsresult result);
 
   nsresult OpenInternal(const nsACString& aMethod,
                         const nsACString& aUrl,
                         const Optional<bool>& aAsync,
                         const Optional<nsAString>& aUsername,
                         const Optional<nsAString>& aPassword);
@@ -651,34 +648,32 @@ protected:
   // finishes downloading (or an error/abort has occurred during either phase).
   // Used to guard against the user trying to alter headers/etc when it's too
   // late, and ensure the XHR only handles one in-flight request at once.
   bool mFlagSend;
 
   RefPtr<XMLHttpRequestUpload> mUpload;
   int64_t mUploadTransferred;
   int64_t mUploadTotal;
-  bool mUploadLengthComputable;
   bool mUploadComplete;
   bool mProgressSinceLastProgressEvent;
 
   // Timeout support
   PRTime mRequestSentTime;
   uint32_t mTimeoutMilliseconds;
   nsCOMPtr<nsITimer> mTimeoutTimer;
   void StartTimeoutTimer();
   void HandleTimeoutCallback();
 
   bool mErrorLoad;
   bool mWaitingForOnStopRequest;
   bool mProgressTimerIsActive;
   bool mIsHtml;
   bool mWarnAboutMultipartHtml;
   bool mWarnAboutSyncHtml;
-  bool mLoadLengthComputable;
   int64_t mLoadTotal; // 0 if not known.
   // Amount of script-exposed (i.e. after undoing gzip compresion) data
   // received.
   uint64_t mDataAvailable;
   // Number of HTTP message body bytes received so far. This quantity is
   // in the same units as Content-Length and mLoadTotal, and hence counts
   // compressed bytes when the channel has gzip Content-Encoding. If the
   // channel does not have Content-Encoding, this will be the same as
@@ -800,17 +795,17 @@ private:
 class nsXHRParseEndListener : public nsIDOMEventListener
 {
 public:
   NS_DECL_ISUPPORTS
   NS_IMETHOD HandleEvent(nsIDOMEvent *event) override
   {
     nsCOMPtr<nsIXMLHttpRequest> xhr = do_QueryReferent(mXHR);
     if (xhr) {
-      static_cast<XMLHttpRequestMainThread*>(xhr.get())->ChangeStateToDone();
+      static_cast<XMLHttpRequestMainThread*>(xhr.get())->OnBodyParseEnd();
     }
     mXHR = nullptr;
     return NS_OK;
   }
   explicit nsXHRParseEndListener(nsIXMLHttpRequest* aXHR)
     : mXHR(do_GetWeakReference(aXHR)) {}
 private:
   virtual ~nsXHRParseEndListener() {}
--- a/dom/xhr/tests/test_xhr_progressevents.html
+++ b/dom/xhr/tests/test_xhr_progressevents.html
@@ -1,14 +1,14 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Test for XMLHttpRequest Progress Events</title>
   <script type="text/javascript" src="/MochiKit/packed.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>        
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body onload="gen.next();">
 <pre id=l></pre>
 <script type="application/javascript;version=1.7">
 SimpleTest.waitForExplicitFinish();
 
 var gen = runTests();
@@ -28,17 +28,17 @@ function getEvent(e) {
 
 function startsWith(a, b) {
   return a.substr(0, b.length) === b;
 }
 
 function updateProgress(e, data, testName) {
   var test = " while running " + testName;
   is(e.type, "progress", "event type" + test);
-  
+
   let response;
   if (data.nodata) {
     is(e.target.response, null, "response should be null" + test);
     response = null;
   }
   else if (data.text) {
     is(typeof e.target.response, "string", "response should be a string" + test);
     response = e.target.response;
@@ -69,17 +69,17 @@ function updateProgress(e, data, testNam
   ok(e.loaded - data.receivedBytes <= data.pendingBytes,
      "event.loaded didn't increase too much" + test);
 
   if (!data.nodata && !data.blob) {
     var newData;
     ok(startsWith(response, data.receivedResult),
        "response strictly grew" + test);
     newData = response.substr(data.receivedResult.length);
-  
+
     if (!data.encoded) {
       ok(newData.length > 0, "sanity check for progress" + test);
     }
     ok(startsWith(data.pendingResult, newData), "new data matches expected" + test);
   }
 
   is(e.lengthComputable, "total" in data, "lengthComputable" + test);
   if ("total" in data) {
@@ -194,30 +194,30 @@ function runTests() {
                       receivedBytes: 0,
                       total: test.total,
                       encoded: test.encoded,
                       nodata: responseType.nodata,
                       chunked: responseType.chunked,
                       text: responseType.text,
                       blob: responseType.blob,
                       file: test.file };
-  
+
         xhr.onreadystatechange = null;
         if (testState.file)
           xhr.open("GET", test.file);
         else
           xhr.open("POST", "progressserver.sjs?open&" + test.open);
         xhr.responseType = responseType.type;
         xhr.send("ready");
         xhr.onreadystatechange = getEvent;
 
         let e = yield undefined;
         is(e.type, "readystatechange", "should readystate to headers-received starting " + testState.name);
         is(xhr.readyState, xhr.HEADERS_RECEIVED, "should be in state HEADERS_RECEIVED starting " + testState.name);
-  
+
         e = yield undefined;
         is(e.type, "readystatechange", "should readystate to loading starting " + testState.name);
         is(xhr.readyState, xhr.LOADING, "should be in state LOADING starting " + testState.name);
         if (typeof testState.total == "undefined")
           delete testState.total;
       }
       if ("file" in test) {
         testState.pendingBytes = testState.total;
@@ -233,29 +233,29 @@ function runTests() {
         is(e.type, "readystatechange", "should readystate to done closing " + testState.name);
         is(xhr.readyState, xhr.DONE, "should be in state DONE closing " + testState.name);
         log("readystate to 4");
 
         if (responseType.chunked) {
           xhr.responseType;
           is(xhr.response, null, "chunked data has null response for " + testState.name);
         }
-      
+
         e = yield undefined;
         is(e.type, "load", "should fire load closing " + testState.name);
-        is(e.lengthComputable, true, "length should be computable during load closing " + testState.name);
+        is(e.lengthComputable, e.total != 0, "length should " + (e.total == 0 ? "not " : "") + "be computable during load closing " + testState.name);
         log("got load");
 
         if (responseType.chunked) {
           is(xhr.response, null, "chunked data has null response for " + testState.name);
         }
-      
+
         e = yield undefined;
         is(e.type, "loadend", "should fire loadend closing " + testState.name);
-        is(e.lengthComputable, true, "length should be computable during loadend closing " + testState.name);
+        is(e.lengthComputable, e.total != 0, "length should " + (e.total == 0 ? "not " : "") + "be computable during loadend closing " + testState.name);
         log("got loadend");
 
         // if we closed the connection using an explicit request, make sure that goes through before
         // running the next test in order to avoid reordered requests from closing the wrong
         // connection.
         if (xhrClose && xhrClose.readyState != xhrClose.DONE) {
           log("wait for closeConn to finish");
           xhrClose.onloadend = getEvent;
@@ -293,38 +293,38 @@ function runTests() {
           testState.pendingResult += "utf16" in test ? test.utf16 : test.data;
         }
         else {
           testState.pendingResult += test.data;
         }
         testState.pendingBytes = test.data.length;
         sendData(test.data);
       }
-  
+
       while(testState.pendingBytes) {
         log("waiting for more bytes: " + testState.pendingBytes);
         e = yield undefined;
         // Readystate can fire several times between each progress event.
         if (e.type === "readystatechange")
           continue;
-  
+
         updateProgress(e, testState, "data for " + testState.name + "[" + testState.index + "]");
         if (responseType.chunked) {
           testState.receivedResult = "";
         }
       }
 
       if (!testState.nodata && !testState.blob) {
         is(testState.pendingResult, "",
            "should have consumed the expected result");
       }
 
       log("done with this test");
     }
-  
+
     is(testState.name, "", "forgot to close last test");
   }
 
   SimpleTest.finish();
   yield undefined;
 }
 
 </script>
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -1692,51 +1692,24 @@ nsXULElement::GetParentTree(nsIDOMXULMul
 NS_IMETHODIMP
 nsXULElement::Focus()
 {
     ErrorResult rv;
     Focus(rv);
     return rv.StealNSResult();
 }
 
-void
-nsXULElement::Focus(ErrorResult& rv)
-{
-    nsIFocusManager* fm = nsFocusManager::GetFocusManager();
-    nsCOMPtr<nsIDOMElement> elem = do_QueryObject(this);
-    if (fm) {
-        rv = fm->SetFocus(this, 0);
-    }
-}
-
 NS_IMETHODIMP
 nsXULElement::Blur()
 {
     ErrorResult rv;
     Blur(rv);
     return rv.StealNSResult();
 }
 
-void
-nsXULElement::Blur(ErrorResult& rv)
-{
-    if (!ShouldBlur(this))
-      return;
-
-    nsIDocument* doc = GetComposedDoc();
-    if (!doc)
-      return;
-
-    nsPIDOMWindowOuter* win = doc->GetWindow();
-    nsIFocusManager* fm = nsFocusManager::GetFocusManager();
-    if (win && fm) {
-      rv = fm->ClearFocus(win);
-    }
-}
-
 NS_IMETHODIMP
 nsXULElement::Click()
 {
   return ClickWithInputSource(nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN, /* aIsTrusted = */ true);
 }
 
 void
 nsXULElement::Click(ErrorResult& rv)
--- a/dom/xul/nsXULElement.h
+++ b/dom/xul/nsXULElement.h
@@ -336,16 +336,18 @@ enum {
 ASSERT_NODE_FLAGS_SPACE(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + 3);
 
 #undef XUL_ELEMENT_FLAG_BIT
 
 class nsXULElement final : public nsStyledElement,
                            public nsIDOMXULElement
 {
 public:
+    using Element::Blur;
+    using Element::Focus;
     explicit nsXULElement(already_AddRefed<mozilla::dom::NodeInfo> aNodeInfo);
 
     static nsresult
     Create(nsXULPrototypeElement* aPrototype, nsIDocument* aDocument,
            bool aIsScriptable, bool aIsRoot, mozilla::dom::Element** aResult);
 
     NS_IMPL_FROMCONTENT(nsXULElement, kNameSpaceID_XUL)
 
@@ -556,18 +558,16 @@ public:
     {
         return BoolAttrIsTrue(nsGkAtoms::allowevents);
     }
     already_AddRefed<nsIRDFCompositeDataSource> GetDatabase();
     already_AddRefed<nsIXULTemplateBuilder> GetBuilder();
     already_AddRefed<nsIRDFResource> GetResource(mozilla::ErrorResult& rv);
     nsIControllers* GetControllers(mozilla::ErrorResult& rv);
     already_AddRefed<mozilla::dom::BoxObject> GetBoxObject(mozilla::ErrorResult& rv);
-    void Focus(mozilla::ErrorResult& rv);
-    void Blur(mozilla::ErrorResult& rv);
     void Click(mozilla::ErrorResult& rv);
     // The XPCOM DoCommand never fails, so it's OK for us.
     already_AddRefed<nsINodeList>
       GetElementsByAttribute(const nsAString& aAttribute,
                              const nsAString& aValue);
     already_AddRefed<nsINodeList>
       GetElementsByAttributeNS(const nsAString& aNamespaceURI,
                                const nsAString& aAttribute,
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -1032,16 +1032,19 @@ public:
                                                                       SurfaceFormat aFormat) const = 0;
 
   /**
    * Create a SourceSurface optimized for use with this DrawTarget from an
    * arbitrary SourceSurface type supported by this backend. This may return
    * aSourceSurface or some other existing surface.
    */
   virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const = 0;
+  virtual already_AddRefed<SourceSurface> OptimizeSourceSurfaceForUnknownAlpha(SourceSurface *aSurface) const {
+    return OptimizeSourceSurface(aSurface);
+  }
 
   /**
    * Create a SourceSurface for a type of NativeSurface. This may fail if the
    * draw target does not know how to deal with the type of NativeSurface passed
    * in. If this succeeds, the SourceSurface takes the ownersip of the NativeSurface.
    */
   virtual already_AddRefed<SourceSurface>
     CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const = 0;
--- a/gfx/2d/DrawTargetSkia.cpp
+++ b/gfx/2d/DrawTargetSkia.cpp
@@ -98,16 +98,41 @@ static void
 ReleaseTemporarySurface(void* aPixels, void* aContext)
 {
   DataSourceSurface* surf = static_cast<DataSourceSurface*>(aContext);
   if (surf) {
     surf->Release();
   }
 }
 
+static void
+WriteRGBXFormat(uint8_t* aData, const IntSize &aSize,
+                const int32_t aStride, SurfaceFormat aFormat)
+{
+  if (aFormat != SurfaceFormat::B8G8R8X8 || aSize.IsEmpty()) {
+    return;
+  }
+
+  int height = aSize.height;
+  int width = aSize.width * 4;
+
+  for (int row = 0; row < height; ++row) {
+    for (int column = 0; column < width; column += 4) {
+#ifdef IS_BIG_ENDIAN
+      aData[column] = 0xFF;
+#else
+      aData[column + 3] = 0xFF;
+#endif
+    }
+    aData += aStride;
+  }
+
+  return;
+}
+
 #ifdef DEBUG
 static bool
 VerifyRGBXFormat(uint8_t* aData, const IntSize &aSize, const int32_t aStride, SurfaceFormat aFormat)
 {
   if (aFormat != SurfaceFormat::B8G8R8X8 || aSize.IsEmpty()) {
     return true;
   }
   // We should've initialized the data to be opaque already
@@ -1372,53 +1397,86 @@ DrawTargetSkia::UsingSkiaGPU() const
 {
 #ifdef USE_SKIA_GPU
   return !!mGrContext;
 #else
   return false;
 #endif
 }
 
+#ifdef USE_SKIA_GPU
+already_AddRefed<SourceSurface>
+DrawTargetSkia::OptimizeGPUSourceSurface(SourceSurface *aSurface) const
+{
+  // Check if the underlying SkBitmap already has an associated GrTexture.
+  if (aSurface->GetType() == SurfaceType::SKIA &&
+      static_cast<SourceSurfaceSkia*>(aSurface)->GetBitmap().getTexture()) {
+    RefPtr<SourceSurface> surface(aSurface);
+    return surface.forget();
+  }
+
+  SkBitmap bitmap = GetBitmapForSurface(aSurface);
+
+  // Upload the SkBitmap to a GrTexture otherwise.
+  SkAutoTUnref<GrTexture> texture(
+      GrRefCachedBitmapTexture(mGrContext.get(), bitmap, GrTextureParams::ClampBilerp()));
+
+  if (texture) {
+    // Create a new SourceSurfaceSkia whose SkBitmap contains the GrTexture.
+    RefPtr<SourceSurfaceSkia> surface = new SourceSurfaceSkia();
+    if (surface->InitFromGrTexture(texture, aSurface->GetSize(), aSurface->GetFormat())) {
+      return surface.forget();
+    }
+  }
+
+  // The data was too big to fit in a GrTexture.
+  if (aSurface->GetType() == SurfaceType::SKIA) {
+    // It is already a Skia source surface, so just reuse it as-is.
+    RefPtr<SourceSurface> surface(aSurface);
+    return surface.forget();
+  }
+
+  // Wrap it in a Skia source surface so that can do tiled uploads on-demand.
+  RefPtr<SourceSurfaceSkia> surface = new SourceSurfaceSkia();
+  surface->InitFromBitmap(bitmap);
+  return surface.forget();
+}
+#endif
+
+already_AddRefed<SourceSurface>
+DrawTargetSkia::OptimizeSourceSurfaceForUnknownAlpha(SourceSurface *aSurface) const
+{
+#ifdef USE_SKIA_GPU
+  if (UsingSkiaGPU()) {
+    return OptimizeGPUSourceSurface(aSurface);
+  }
+#endif
+
+  if (aSurface->GetType() == SurfaceType::SKIA) {
+    RefPtr<SourceSurface> surface(aSurface);
+    return surface.forget();
+  }
+
+  RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface();
+
+  // For plugins, GDI can sometimes just write 0 to the alpha channel
+  // even for RGBX formats. In this case, we have to manually write
+  // the alpha channel to make Skia happy with RGBX and in case GDI
+  // writes some bad data. Luckily, this only happens on plugins.
+  WriteRGBXFormat(dataSurface->GetData(), dataSurface->GetSize(),
+                  dataSurface->Stride(), dataSurface->GetFormat());
+  return dataSurface.forget();
+}
+
 already_AddRefed<SourceSurface>
 DrawTargetSkia::OptimizeSourceSurface(SourceSurface *aSurface) const
 {
 #ifdef USE_SKIA_GPU
   if (UsingSkiaGPU()) {
-    // Check if the underlying SkBitmap already has an associated GrTexture.
-    if (aSurface->GetType() == SurfaceType::SKIA &&
-        static_cast<SourceSurfaceSkia*>(aSurface)->GetBitmap().getTexture()) {
-      RefPtr<SourceSurface> surface(aSurface);
-      return surface.forget();
-    }
-
-    SkBitmap bitmap = GetBitmapForSurface(aSurface);
-
-    // Upload the SkBitmap to a GrTexture otherwise.
-    SkAutoTUnref<GrTexture> texture(
-      GrRefCachedBitmapTexture(mGrContext.get(), bitmap, GrTextureParams::ClampBilerp()));
-
-    if (texture) {
-      // Create a new SourceSurfaceSkia whose SkBitmap contains the GrTexture.
-      RefPtr<SourceSurfaceSkia> surface = new SourceSurfaceSkia();
-      if (surface->InitFromGrTexture(texture, aSurface->GetSize(), aSurface->GetFormat())) {
-        return surface.forget();
-      }
-    }
-
-    // The data was too big to fit in a GrTexture.
-    if (aSurface->GetType() == SurfaceType::SKIA) {
-      // It is already a Skia source surface, so just reuse it as-is.
-      RefPtr<SourceSurface> surface(aSurface);
-      return surface.forget();
-    }
-
-    // Wrap it in a Skia source surface so that can do tiled uploads on-demand.
-    RefPtr<SourceSurfaceSkia> surface = new SourceSurfaceSkia();
-    surface->InitFromBitmap(bitmap);
-    return surface.forget();
+    return OptimizeGPUSourceSurface(aSurface);
   }
 #endif
 
   if (aSurface->GetType() == SurfaceType::SKIA) {
     RefPtr<SourceSurface> surface(aSurface);
     return surface.forget();
   }
 
--- a/gfx/2d/DrawTargetSkia.h
+++ b/gfx/2d/DrawTargetSkia.h
@@ -111,16 +111,17 @@ public:
                          const IntRect& aBounds = IntRect(),
                          bool aCopyBackground = false) override;
   virtual void PopLayer() override;
   virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData,
                                                             const IntSize &aSize,
                                                             int32_t aStride,
                                                             SurfaceFormat aFormat) const override;
   virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const override;
+  virtual already_AddRefed<SourceSurface> OptimizeSourceSurfaceForUnknownAlpha(SourceSurface *aSurface) const override;
   virtual already_AddRefed<SourceSurface>
     CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const override;
   virtual already_AddRefed<DrawTarget>
     CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const override;
   virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const override;
   virtual already_AddRefed<GradientStops> CreateGradientStops(GradientStop *aStops, uint32_t aNumStops, ExtendMode aExtendMode = ExtendMode::CLAMP) const override;
   virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override;
   virtual void SetTransform(const Matrix &aTransform) override;
@@ -136,16 +137,18 @@ public:
                          SurfaceFormat aFormat,
                          bool aCached);
   virtual bool
     InitWithGrContext(GrContext* aGrContext,
                       const IntSize &aSize,
                       SurfaceFormat aFormat) override {
     return InitWithGrContext(aGrContext, aSize, aFormat, false);
   }
+
+  already_AddRefed<SourceSurface> OptimizeGPUSourceSurface(SourceSurface *aSurface) const;
 #endif
 
   // Skia assumes that texture sizes fit in 16-bit signed integers.
   static size_t GetMaxSurfaceSize() {
     return 32767;
   }
 
   operator std::string() const {
--- a/gfx/thebes/DeviceManagerD3D11.cpp
+++ b/gfx/thebes/DeviceManagerD3D11.cpp
@@ -50,17 +50,16 @@ DeviceManagerD3D11::DeviceManagerD3D11()
 {
   // Set up the D3D11 feature levels we can ask for.
   if (IsWin8OrLater()) {
     mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_11_1);
   }
   mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_11_0);
   mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_10_1);
   mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_10_0);
-  mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_9_3);
 }
 
 static inline bool
 IsWARPStable()
 {
   // It seems like nvdxgiwrap makes a mess of WARP. See bug 1154703.
   if (!IsWin8OrLater() || GetModuleHandleA("nvdxgiwrap.dll")) {
     return false;
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -1001,17 +1001,19 @@ void SourceSurfaceDestroyed(void *aData)
 
 void
 gfxPlatform::ClearSourceSurfaceForSurface(gfxASurface *aSurface)
 {
   aSurface->SetData(&kSourceSurface, nullptr, nullptr);
 }
 
 /* static */ already_AddRefed<SourceSurface>
-gfxPlatform::GetSourceSurfaceForSurface(DrawTarget *aTarget, gfxASurface *aSurface)
+gfxPlatform::GetSourceSurfaceForSurface(DrawTarget *aTarget,
+                                        gfxASurface *aSurface,
+                                        bool aIsPlugin)
 {
   if (!aSurface->CairoSurface() || aSurface->CairoStatus()) {
     return nullptr;
   }
 
   if (!aTarget) {
     aTarget = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
   }
@@ -1059,17 +1061,19 @@ gfxPlatform::GetSourceSurfaceForSurface(
 
   // Currently no other DrawTarget types implement CreateSourceSurfaceFromNativeSurface
 
   if (!srcBuffer) {
     // If aSurface wraps data, we can create a SourceSurfaceRawData that wraps
     // the same data, then optimize it for aTarget:
     RefPtr<DataSourceSurface> surf = GetWrappedDataSourceSurface(aSurface);
     if (surf) {
-      srcBuffer = aTarget->OptimizeSourceSurface(surf);
+      srcBuffer = aIsPlugin ? aTarget->OptimizeSourceSurfaceForUnknownAlpha(surf)
+                            : aTarget->OptimizeSourceSurface(surf);
+
       if (srcBuffer == surf) {
         // GetWrappedDataSourceSurface returns a SourceSurface that holds a
         // strong reference to aSurface since it wraps aSurface's data and
         // needs it to stay alive. As a result we can't cache srcBuffer on
         // aSurface (below) since aSurface would then hold a strong reference
         // back to srcBuffer, creating a reference loop and a memory leak. Not
         // caching is fine since wrapping is cheap enough (no copying) so we
         // can just wrap again next time we're called.
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -205,19 +205,24 @@ public:
      * The returned surface keeps a reference to aTarget, so it is OK to keep the
      * surface, even if aTarget changes.
      * aTarget should not keep a reference to the returned surface because that
      * will cause a cycle.
      *
      * This function is static so that it can be accessed from
      * PluginInstanceChild (where we can't call gfxPlatform::GetPlatform()
      * because the prefs service can only be accessed from the main process).
+     *
+     * aIsPlugin is used to tell the backend that they can optimize this surface
+     * specifically because it's used for a plugin. This is mostly for Skia.
      */
     static already_AddRefed<SourceSurface>
-      GetSourceSurfaceForSurface(mozilla::gfx::DrawTarget *aTarget, gfxASurface *aSurface);
+      GetSourceSurfaceForSurface(mozilla::gfx::DrawTarget *aTarget,
+                                 gfxASurface *aSurface,
+                                 bool aIsPlugin = false);
 
     static void ClearSourceSurfaceForSurface(gfxASurface *aSurface);
 
     static already_AddRefed<DataSourceSurface>
         GetWrappedDataSourceSurface(gfxASurface *aSurface);
 
     virtual already_AddRefed<mozilla::gfx::ScaledFont>
       GetScaledFontForFont(mozilla::gfx::DrawTarget* aTarget, gfxFont *aFont);
--- a/image/ImageCacheKey.cpp
+++ b/image/ImageCacheKey.cpp
@@ -40,67 +40,79 @@ BlobSerial(ImageURL* aURI)
   if (NS_SUCCEEDED(NS_GetBlobForBlobURISpec(spec, getter_AddRefs(blob))) &&
       blob) {
     return Some(blob->GetSerialNumber());
   }
 
   return Nothing();
 }
 
-ImageCacheKey::ImageCacheKey(nsIURI* aURI, nsIDocument* aDocument)
+ImageCacheKey::ImageCacheKey(nsIURI* aURI,
+                             const PrincipalOriginAttributes& aAttrs,
+                             nsIDocument* aDocument)
   : mURI(new ImageURL(aURI))
+  , mOriginAttributes(aAttrs)
   , mControlledDocument(GetControlledDocumentToken(aDocument))
   , mIsChrome(URISchemeIs(mURI, "chrome"))
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (URISchemeIs(mURI, "blob")) {
     mBlobSerial = BlobSerial(mURI);
   }
 
-  mHash = ComputeHash(mURI, mBlobSerial, mControlledDocument);
+  mHash = ComputeHash(mURI, mBlobSerial, mOriginAttributes, mControlledDocument);
 }
 
-ImageCacheKey::ImageCacheKey(ImageURL* aURI, nsIDocument* aDocument)
+ImageCacheKey::ImageCacheKey(ImageURL* aURI,
+                             const PrincipalOriginAttributes& aAttrs,
+                             nsIDocument* aDocument)
   : mURI(aURI)
+  , mOriginAttributes(aAttrs)
   , mControlledDocument(GetControlledDocumentToken(aDocument))
   , mIsChrome(URISchemeIs(mURI, "chrome"))
 {
   MOZ_ASSERT(aURI);
 
   if (URISchemeIs(mURI, "blob")) {
     mBlobSerial = BlobSerial(mURI);
   }
 
-  mHash = ComputeHash(mURI, mBlobSerial, mControlledDocument);
+  mHash = ComputeHash(mURI, mBlobSerial, mOriginAttributes, mControlledDocument);
 }
 
 ImageCacheKey::ImageCacheKey(const ImageCacheKey& aOther)
   : mURI(aOther.mURI)
   , mBlobSerial(aOther.mBlobSerial)
+  , mOriginAttributes(aOther.mOriginAttributes)
   , mControlledDocument(aOther.mControlledDocument)
   , mHash(aOther.mHash)
   , mIsChrome(aOther.mIsChrome)
 { }
 
 ImageCacheKey::ImageCacheKey(ImageCacheKey&& aOther)
   : mURI(Move(aOther.mURI))
   , mBlobSerial(Move(aOther.mBlobSerial))
+  , mOriginAttributes(aOther.mOriginAttributes)
   , mControlledDocument(aOther.mControlledDocument)
   , mHash(aOther.mHash)
   , mIsChrome(aOther.mIsChrome)
 { }
 
 bool
 ImageCacheKey::operator==(const ImageCacheKey& aOther) const
 {
   // Don't share the image cache between a controlled document and anything else.
   if (mControlledDocument != aOther.mControlledDocument) {
     return false;
   }
+  // The origin attributes always have to match.
+  if (mOriginAttributes != aOther.mOriginAttributes) {
+    return false;
+  }
   if (mBlobSerial || aOther.mBlobSerial) {
     // If at least one of us has a blob serial, just compare the blob serial and
     // the ref portion of the URIs.
     return mBlobSerial == aOther.mBlobSerial &&
            mURI->HasSameRef(*aOther.mURI);
   }
 
   // For non-blob URIs, compare the URIs.
@@ -111,37 +123,41 @@ const char*
 ImageCacheKey::Spec() const
 {
   return mURI->Spec();
 }
 
 /* static */ uint32_t
 ImageCacheKey::ComputeHash(ImageURL* aURI,
                            const Maybe<uint64_t>& aBlobSerial,
+                           const PrincipalOriginAttributes& aAttrs,
                            void* aControlledDocument)
 {
   // Since we frequently call Hash() several times in a row on the same
   // ImageCacheKey, as an optimization we compute our hash once and store it.
 
   nsPrintfCString ptr("%p", aControlledDocument);
+  nsAutoCString suffix;
+  aAttrs.CreateSuffix(suffix);
+
   if (aBlobSerial) {
     // For blob URIs, we hash the serial number of the underlying blob, so that
     // different blob URIs which point to the same blob share a cache entry. We
     // also include the ref portion of the URI to support -moz-samplesize, which
     // requires us to create different Image objects even if the source data is
     // the same.
     nsAutoCString ref;
     aURI->GetRef(ref);
-    return HashGeneric(*aBlobSerial, HashString(ref + ptr));
+    return HashGeneric(*aBlobSerial, HashString(ref + suffix + ptr));
   }
 
   // For non-blob URIs, we hash the URI spec.
   nsAutoCString spec;
   aURI->GetSpec(spec);
-  return HashString(spec + ptr);
+  return HashString(spec + suffix + ptr);
 }
 
 /* static */ void*
 ImageCacheKey::GetControlledDocumentToken(nsIDocument* aDocument)
 {
   // For non-controlled documents, we just return null.  For controlled
   // documents, we cast the pointer into a void* to avoid dereferencing
   // it (since we only use it for comparisons), and return it.
--- a/image/ImageCacheKey.h
+++ b/image/ImageCacheKey.h
@@ -5,16 +5,17 @@
 
 /**
  * ImageCacheKey is the key type for the image cache (see imgLoader.h).
  */
 
 #ifndef mozilla_image_src_ImageCacheKey_h
 #define mozilla_image_src_ImageCacheKey_h
 
+#include "mozilla/BasePrincipal.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/RefPtr.h"
 
 class nsIDocument;
 class nsIURI;
 
 namespace mozilla {
 namespace image {
@@ -27,18 +28,20 @@ class ImageURL;
  * We key the cache on the initial URI (before any redirects), with some
  * canonicalization applied. See ComputeHash() for the details.
  * Controlled documents do not share their cache entries with
  * non-controlled documents, or other controlled documents.
  */
 class ImageCacheKey final
 {
 public:
-  ImageCacheKey(nsIURI* aURI, nsIDocument* aDocument);
-  ImageCacheKey(ImageURL* aURI, nsIDocument* aDocument);
+  ImageCacheKey(nsIURI* aURI, const PrincipalOriginAttributes& aAttrs,
+                nsIDocument* aDocument);
+  ImageCacheKey(ImageURL* aURI, const PrincipalOriginAttributes& aAttrs,
+                nsIDocument* aDocument);
 
   ImageCacheKey(const ImageCacheKey& aOther);
   ImageCacheKey(ImageCacheKey&& aOther);
 
   bool operator==(const ImageCacheKey& aOther) const;
   uint32_t Hash() const { return mHash; }
 
   /// A weak pointer to the URI spec for this cache entry. For logging only.
@@ -49,21 +52,23 @@ public:
 
   /// A token indicating which service worker controlled document this entry
   /// belongs to, if any.
   void* ControlledDocument() const { return mControlledDocument; }
 
 private:
   static uint32_t ComputeHash(ImageURL* aURI,
                               const Maybe<uint64_t>& aBlobSerial,
+                              const PrincipalOriginAttributes& aAttrs,
                               void* aControlledDocument);
   static void* GetControlledDocumentToken(nsIDocument* aDocument);
 
   RefPtr<ImageURL> mURI;
   Maybe<uint64_t> mBlobSerial;
+  PrincipalOriginAttributes mOriginAttributes;
   void* mControlledDocument;
   uint32_t mHash;
   bool mIsChrome;
 };
 
 } // namespace image
 } // namespace mozilla
 
--- a/image/imgFrame.cpp
+++ b/image/imgFrame.cpp
@@ -17,17 +17,16 @@
 #include "gfxAlphaRecovery.h"
 
 #include "GeckoProfiler.h"
 #include "MainThreadUtils.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/gfx/Tools.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryReporting.h"
-#include "mozilla/Telemetry.h"
 #include "nsMargin.h"
 #include "nsThreadUtils.h"
 
 
 namespace mozilla {
 
 using namespace gfx;
 
@@ -429,69 +428,21 @@ imgFrame::Optimize()
   }
 
   // XXX(seth): It's currently unclear if there's any reason why we can't
   // optimize non-premult surfaces. We should look into removing this.
   if (mNonPremult) {
     return NS_OK;
   }
 
-#ifdef ANDROID
-  SurfaceFormat optFormat = gfxPlatform::GetPlatform()
-    ->Optimal2DFormatForContent(gfxContentType::COLOR);
-
-  if (mFormat != SurfaceFormat::B8G8R8A8 &&
-      optFormat == SurfaceFormat::R5G6B5_UINT16) {
-    Telemetry::Accumulate(Telemetry::IMAGE_OPTIMIZE_TO_565_USED, true);
-
-    RefPtr<VolatileBuffer> buf =
-      AllocateBufferForImage(mFrameRect.Size(), optFormat);
-    if (!buf) {
-      return NS_OK;
-    }
-
-    RefPtr<DataSourceSurface> surf =
-      CreateLockedSurface(buf, mFrameRect.Size(), optFormat);
-    if (!surf) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    DataSourceSurface::MappedSurface mapping;
-    if (!surf->Map(DataSourceSurface::MapType::WRITE, &mapping)) {
-      gfxCriticalError() << "imgFrame::Optimize failed to map surface";
-      return NS_ERROR_FAILURE;
-    }
-
-    RefPtr<DrawTarget> target =
-      Factory::CreateDrawTargetForData(BackendType::CAIRO,
-                                       mapping.mData,
-                                       mFrameRect.Size(),
-                                       mapping.mStride,
-                                       optFormat);
-
-    if (!target) {
-      gfxWarning() << "imgFrame::Optimize failed in CreateDrawTargetForData";
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-    Rect rect(0, 0, mFrameRect.width, mFrameRect.height);
-    target->DrawSurface(mImageSurface, rect, rect);
-    target->Flush();
-    surf->Unmap();
-
-    mImageSurface = surf;
-    mVBuf = buf;
-    mFormat = optFormat;
-  }
-#else
   mOptSurface = gfxPlatform::GetPlatform()
     ->ScreenReferenceDrawTarget()->OptimizeSourceSurface(mImageSurface);
   if (mOptSurface == mImageSurface) {
     mOptSurface = nullptr;
   }
-#endif
 
   if (mOptSurface) {
     mVBuf = nullptr;
     mVBufPtr = nullptr;
     mImageSurface = nullptr;
   }
 
 #ifdef MOZ_WIDGET_ANDROID
@@ -830,21 +781,23 @@ imgFrame::UnlockImageData()
   if (mLockCount == 1 && !mPalettedImageData) {
     // We can't safely optimize off-main-thread, so create a runnable to do it.
     if (!NS_IsMainThread()) {
       nsCOMPtr<nsIRunnable> runnable = new UnlockImageDataRunnable(this);
       NS_DispatchToMainThread(runnable);
       return NS_OK;
     }
 
-    // Convert the data surface to a GPU surface or a single color if possible.
-    // This will also release mImageSurface if possible.
+    // Convert our data surface to a GPU surface if possible. We'll also try to
+    // release mImageSurface.
     Optimize();
 
-    // Allow the OS to release our data surface.
+    // Allow the OS to release our data surface. Note that mImageSurface also
+    // keeps our volatile buffer alive, so this doesn't actually work unless we
+    // released mImageSurface in Optimize().
     mVBufPtr = nullptr;
   }
 
   mLockCount--;
 
   return NS_OK;
 }
 
--- a/image/imgLoader.cpp
+++ b/image/imgLoader.cpp
@@ -1347,17 +1347,26 @@ imgLoader::ClearCache(bool chrome)
 NS_IMETHODIMP
 imgLoader::FindEntryProperties(nsIURI* uri,
                                nsIDOMDocument* aDOMDoc,
                                nsIProperties** _retval)
 {
   *_retval = nullptr;
 
   nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDOMDoc);
-  ImageCacheKey key(uri, doc);
+
+  PrincipalOriginAttributes attrs;
+  if (doc) {
+    nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
+    if (principal) {
+      attrs = BasePrincipal::Cast(principal)->OriginAttributesRef();
+    }
+  }
+
+  ImageCacheKey key(uri, attrs, doc);
   imgCacheTable& cache = GetCache(key);
 
   RefPtr<imgCacheEntry> entry;
   if (cache.Get(key, getter_AddRefs(entry)) && entry) {
     if (mCacheTracker && entry->HasNoProxies()) {
       mCacheTracker->MarkUsed(entry);
     }
 
@@ -2105,17 +2114,21 @@ imgLoader::LoadImage(nsIURI* aURI,
   }
 
   RefPtr<imgCacheEntry> entry;
 
   // Look in the cache for our URI, and then validate it.
   // XXX For now ignore aCacheKey. We will need it in the future
   // for correctly dealing with image load requests that are a result
   // of post data.
-  ImageCacheKey key(aURI, aLoadingDocument);
+  PrincipalOriginAttributes attrs;
+  if (aLoadingPrincipal) {
+    attrs = BasePrincipal::Cast(aLoadingPrincipal)->OriginAttributesRef();
+  }
+  ImageCacheKey key(aURI, attrs, aLoadingDocument);
   imgCacheTable& cache = GetCache(key);
 
   if (cache.Get(key, getter_AddRefs(entry)) && entry) {
     if (ValidateEntry(entry, aURI, aInitialDocumentURI, aReferrerURI,
                       aReferrerPolicy, aLoadGroup, aObserver, aLoadingDocument,
                       requestFlags, aContentPolicyType, true, _retval,
                       aLoadingPrincipal, corsmode)) {
       request = entry->GetRequest();
@@ -2309,17 +2322,26 @@ imgLoader::LoadImageWithChannel(nsIChann
 
   MOZ_ASSERT(NS_UsePrivateBrowsing(channel) == mRespectPrivacy);
 
   RefPtr<imgRequest> request;
 
   nsCOMPtr<nsIURI> uri;
   channel->GetURI(getter_AddRefs(uri));
   nsCOMPtr<nsIDocument> doc = do_QueryInterface(aCX);
-  ImageCacheKey key(uri, doc);
+
+  NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
+  nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
+
+  PrincipalOriginAttributes attrs;
+  if (loadInfo) {
+    attrs.InheritFromNecko(loadInfo->GetOriginAttributes());
+  }
+
+  ImageCacheKey key(uri, attrs, doc);
 
   nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
   channel->GetLoadFlags(&requestFlags);
 
   RefPtr<imgCacheEntry> entry;
 
   if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) {
     RemoveFromCache(key);
@@ -2411,17 +2433,17 @@ imgLoader::LoadImageWithChannel(nsIChann
     // We use originalURI here to fulfil the imgIRequest contract on GetURI.
     nsCOMPtr<nsIURI> originalURI;
     channel->GetOriginalURI(getter_AddRefs(originalURI));
 
     // XXX(seth): We should be able to just use |key| here, except that |key| is
     // constructed above with the *current URI* and not the *original URI*. I'm
     // pretty sure this is a bug, and it's preventing us from ever getting a
     // cache hit in LoadImageWithChannel when redirects are involved.
-    ImageCacheKey originalURIKey(originalURI, doc);
+    ImageCacheKey originalURIKey(originalURI, attrs, doc);
 
     // Default to doing a principal check because we don't know who
     // started that load and whether their principal ended up being
     // inherited on the channel.
     NewRequestAndEntry(/* aForcePrincipalCheckForCacheEntry = */ true,
                        this, originalURIKey,
                        getter_AddRefs(request),
                        getter_AddRefs(entry));
--- a/ipc/glue/IPCMessageUtils.cpp
+++ b/ipc/glue/IPCMessageUtils.cpp
@@ -7,17 +7,17 @@
 #include "IPCMessageUtils.h"
 #include "mozilla/CheckedInt.h"
 
 namespace IPC {
 
 bool
 ByteLengthIsValid(uint32_t aNumElements, size_t aElementSize, int* aByteLength)
 {
-  auto length = CheckedInt<int>(aNumElements) * aElementSize;
+  auto length = mozilla::CheckedInt<int>(aNumElements) * aElementSize;
   if (!length.isValid()) {
     return false;
   }
   *aByteLength = length.value();
   return true;
 }
 
 } // namespace IPC
--- a/ipc/glue/IPCStreamUtils.cpp
+++ b/ipc/glue/IPCStreamUtils.cpp
@@ -5,21 +5,25 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "IPCStreamUtils.h"
 
 #include "nsIIPCSerializableInputStream.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/dom/PContentChild.h"
+#include "mozilla/dom/PContentParent.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/ipc/FileDescriptorSetChild.h"
 #include "mozilla/ipc/FileDescriptorSetParent.h"
+#include "mozilla/ipc/InputStreamUtils.h"
 #include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/PBackgroundParent.h"
 #include "mozilla/ipc/SendStream.h"
+#include "mozilla/unused.h"
 #include "nsIAsyncInputStream.h"
 
 namespace mozilla {
 namespace ipc {
 
 namespace {
 
 // These serialization and cleanup functions could be externally exposed.  For
@@ -340,17 +344,17 @@ AutoIPCStream::~AutoIPCStream()
   if (mValue && IsSet()) {
     CleanupIPCStream(*mValue, mTaken);
   } else {
     CleanupIPCStream(*mOptionalValue, mTaken);
   }
 }
 
 void
-AutoIPCStream::Serialize(nsIInputStream* aStream, PContentChild* aManager)
+AutoIPCStream::Serialize(nsIInputStream* aStream, dom::PContentChild* aManager)
 {
   MOZ_ASSERT(aStream);
   MOZ_ASSERT(aManager);
   MOZ_ASSERT(mValue || mOptionalValue);
   MOZ_ASSERT(!mTaken);
   MOZ_ASSERT(!IsSet());
 
   if (mValue) {
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -26,17 +26,17 @@ using mozilla::Move;
 
 // Undo the damage done by mozzconf.h
 #undef compress
 
 // Logging seems to be somewhat broken on b2g.
 #ifdef MOZ_B2G
 #define IPC_LOG(...)
 #else
-static LazyLogModule sLogModule("ipc");
+static mozilla::LazyLogModule sLogModule("ipc");
 #define IPC_LOG(...) MOZ_LOG(sLogModule, LogLevel::Debug, (__VA_ARGS__))
 #endif
 
 /*
  * IPC design:
  *
  * There are three kinds of messages: async, sync, and intr. Sync and intr
  * messages are blocking. Only intr and high-priority sync messages can nest.
@@ -92,16 +92,17 @@ static LazyLogModule sLogModule("ipc");
  * message, we may dispatch an async message. This causes some additional
  * complexity. One issue is that replies can be received out of order. It's also
  * more difficult to determine whether one message is nested inside
  * another. Consequently, intr handling uses mOutOfTurnReplies and
  * mRemoteStackDepthGuess, which are not needed for sync messages.
  */
 
 using namespace mozilla;
+using namespace mozilla::ipc;
 using namespace std;
 
 using mozilla::dom::AutoNoJSAPI;
 using mozilla::dom::ScriptSettingsInitialized;
 using mozilla::MonitorAutoLock;
 using mozilla::MonitorAutoUnlock;
 
 #define IPC_ASSERT(_cond, ...)                                      \
--- a/ipc/glue/ProtocolUtils.cpp
+++ b/ipc/glue/ProtocolUtils.cpp
@@ -5,20 +5,23 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "base/process_util.h"
 
 #ifdef OS_POSIX
 #include <errno.h>
 #endif
 
+#include "mozilla/ipc/ProtocolUtils.h"
+
+#include "mozilla/dom/ContentParent.h"
 #include "mozilla/ipc/MessageChannel.h"
-#include "mozilla/ipc/ProtocolUtils.h"
 #include "mozilla/ipc/Transport.h"
 #include "mozilla/StaticMutex.h"
+#include "nsPrintfCString.h"
 
 #if defined(MOZ_SANDBOX) && defined(XP_WIN)
 #define TARGET_SANDBOX_EXPORTS
 #include "mozilla/sandboxTarget.h"
 #endif
 
 #if defined(MOZ_CRASHREPORTER) && defined(XP_WIN)
 #include "aclapi.h"
--- a/js/src/asmjs/WasmInstance.cpp
+++ b/js/src/asmjs/WasmInstance.cpp
@@ -396,17 +396,17 @@ Instance::~Instance()
 
 void
 Instance::tracePrivate(JSTracer* trc)
 {
     // This method is only called from WasmInstanceObject so the only reason why
     // TraceEdge is called is so that the pointer can be updated during a moving
     // GC. TraceWeakEdge may sound better, but it is less efficient given that
     // we know object_ is already marked.
-    MOZ_ASSERT(!IsAboutToBeFinalized(&object_));
+    MOZ_ASSERT(!gc::IsAboutToBeFinalized(&object_));
     TraceEdge(trc, &object_, "wasm instance object");
 
     for (const FuncImport& fi : metadata().funcImports)
         TraceNullableEdge(trc, &funcImportTls(fi).obj, "wasm import");
 
     for (const SharedTable& table : tables_)
         table->trace(trc);
 
--- a/js/src/asmjs/WasmJS.cpp
+++ b/js/src/asmjs/WasmJS.cpp
@@ -851,17 +851,17 @@ WasmTableObject::finalize(FreeOp* fop, J
         tableObj.table().Release();
 }
 
 /* static */ void
 WasmTableObject::trace(JSTracer* trc, JSObject* obj)
 {
     WasmTableObject& tableObj = obj->as<WasmTableObject>();
     if (!tableObj.isNewborn())
-        tableObj.table().trace(trc);
+        tableObj.table().tracePrivate(trc);
 }
 
 /* static */ WasmTableObject*
 WasmTableObject::create(JSContext* cx, uint32_t length)
 {
     RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmTable).toObject());
 
     AutoSetNewObjectMetadata metadata(cx);
@@ -872,17 +872,17 @@ WasmTableObject::create(JSContext* cx, u
     MOZ_ASSERT(obj->isNewborn());
 
     TableDesc desc;
     desc.kind = TableKind::AnyFunction;
     desc.external = true;
     desc.initial = length;
     desc.maximum = length;
 
-    SharedTable table = Table::create(cx, desc);
+    SharedTable table = Table::create(cx, desc, obj);
     if (!table)
         return nullptr;
 
     obj->initReservedSlot(TABLE_SLOT, PrivateValue(table.forget().take()));
 
     MOZ_ASSERT(!obj->isNewborn());
     return obj;
 }
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -584,17 +584,17 @@ Module::instantiateTable(JSContext* cx, 
                 MOZ_ASSERT(tableDesc.kind == TableKind::AnyFunction);
 
                 tableObj.set(WasmTableObject::create(cx, tableDesc.initial));
                 if (!tableObj)
                     return false;
 
                 table = &tableObj->table();
             } else {
-                table = Table::create(cx, tableDesc);
+                table = Table::create(cx, tableDesc, /* HandleWasmTableObject = */ nullptr);
                 if (!table)
                     return false;
             }
 
             if (!tables->emplaceBack(table)) {
                 ReportOutOfMemory(cx);
                 return false;
             }
--- a/js/src/asmjs/WasmTable.cpp
+++ b/js/src/asmjs/WasmTable.cpp
@@ -16,38 +16,40 @@
  * limitations under the License.
  */
 
 #include "asmjs/WasmTable.h"
 
 #include "jscntxt.h"
 
 #include "asmjs/WasmInstance.h"
+#include "asmjs/WasmJS.h"
 
 using namespace js;
 using namespace js::wasm;
 
 /* static */ SharedTable
-Table::create(JSContext* cx, const TableDesc& desc)
+Table::create(JSContext* cx, const TableDesc& desc, HandleWasmTableObject maybeObject)
 {
     SharedTable table = cx->new_<Table>();
     if (!table)
         return nullptr;
 
     // The raw element type of a Table depends on whether it is external: an
     // external table can contain functions from multiple instances and thus
     // must store an additional instance pointer in each element.
     void* array;
     if (desc.external)
         array = cx->pod_calloc<ExternalTableElem>(desc.initial);
     else
         array = cx->pod_calloc<void*>(desc.initial);
     if (!array)
         return nullptr;
 
+    table->maybeObject_.set(maybeObject);
     table->array_.reset((uint8_t*)array);
     table->kind_ = desc.kind;
     table->length_ = desc.initial;
     table->initialized_ = false;
     table->external_ = desc.external;
     return table;
 }
 
@@ -68,26 +70,50 @@ Table::init(Instance& instance)
     } else {
         void** array = internalArray();
         for (uint32_t i = 0; i < length_; i++)
             array[i] = code;
     }
 }
 
 void
-Table::trace(JSTracer* trc)
+Table::tracePrivate(JSTracer* trc)
 {
+    // If this table has a WasmTableObject, then this method is only called by
+    // WasmTableObject's trace hook so maybeObject_ must already be marked.
+    // TraceEdge is called so that the pointer can be updated during a moving
+    // GC. TraceWeakEdge may sound better, but it is less efficient given that
+    // we know object_ is already marked.
+    if (maybeObject_) {
+        MOZ_ASSERT(!gc::IsAboutToBeFinalized(&maybeObject_));
+        TraceEdge(trc, &maybeObject_, "wasm table object");
+    }
+
     if (!initialized_ || !external_)
         return;
 
     ExternalTableElem* array = externalArray();
     for (uint32_t i = 0; i < length_; i++)
         array[i].tls->instance->trace(trc);
 }
 
+void
+Table::trace(JSTracer* trc)
+{
+    // The trace hook of WasmTableObject will call Table::tracePrivate at
+    // which point we can mark the rest of the children. If there is no
+    // WasmTableObject, call Table::tracePrivate directly. Redirecting through
+    // the WasmTableObject avoids marking the entire Table on each incoming
+    // edge (once per dependent Instance).
+    if (maybeObject_)
+        TraceEdge(trc, &maybeObject_, "wasm table object");
+    else
+        tracePrivate(trc);
+}
+
 void**
 Table::internalArray() const
 {
     MOZ_ASSERT(initialized_);
     MOZ_ASSERT(!external_);
     return (void**)array_.get();
 }
 
--- a/js/src/asmjs/WasmTable.h
+++ b/js/src/asmjs/WasmTable.h
@@ -25,24 +25,31 @@ namespace js {
 namespace wasm {
 
 // A Table is an indexable array of opaque values. Tables are first-class
 // stateful objects exposed to WebAssembly. asm.js also uses Tables to represent
 // its homogeneous function-pointer tables.
 
 class Table : public ShareableBase<Table>
 {
-    UniquePtr<uint8_t[], JS::FreePolicy> array_;
-    TableKind kind_;
-    uint32_t length_;
-    bool initialized_;
-    bool external_;
+    typedef UniquePtr<uint8_t[], JS::FreePolicy> UniqueByteArray;
+
+    ReadBarrieredWasmTableObject maybeObject_;
+    UniqueByteArray              array_;
+    TableKind                    kind_;
+    uint32_t                     length_;
+    bool                         initialized_;
+    bool                         external_;
+
+    void tracePrivate(JSTracer* trc);
+    friend class js::WasmTableObject;
 
   public:
-    static RefPtr<Table> create(JSContext* cx, const TableDesc& desc);
+    static RefPtr<Table> create(JSContext* cx, const TableDesc& desc,
+                                HandleWasmTableObject maybeObject);
     void trace(JSTracer* trc);
 
     // These accessors may be used before initialization.
 
     bool external() const { return external_; }
     bool isTypedFunction() const { return kind_ == TableKind::TypedFunction; }
     uint32_t length() const { return length_; }
     uint8_t* base() const { return array_.get(); }
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -537,23 +537,23 @@ intl_availableLocales(JSContext* cx, Cou
     if (!locales)
         return false;
 
 #if ENABLE_INTL_API
     uint32_t count = countAvailable();
     RootedValue t(cx, BooleanValue(true));
     for (uint32_t i = 0; i < count; i++) {
         const char* locale = getAvailable(i);
-        ScopedJSFreePtr<char> lang(JS_strdup(cx, locale));
+        auto lang = DuplicateString(cx, locale);
         if (!lang)
             return false;
         char* p;
-        while ((p = strchr(lang, '_')))
+        while ((p = strchr(lang.get(), '_')))
             *p = '-';
-        RootedAtom a(cx, Atomize(cx, lang, strlen(lang)));
+        RootedAtom a(cx, Atomize(cx, lang.get(), strlen(lang.get())));
         if (!a)
             return false;
         if (!DefineProperty(cx, locales, a->asPropertyName(), t, nullptr, nullptr,
                             JSPROP_ENUMERATE))
         {
             return false;
         }
     }
@@ -603,36 +603,31 @@ equal(JSAutoByteString& s1, const char* 
 static const char*
 icuLocale(const char* locale)
 {
     if (equal(locale, "und"))
         return ""; // ICU root locale
     return locale;
 }
 
-// Simple RAII for ICU objects. MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE
-// unfortunately doesn't work because of namespace incompatibilities
-// (TypeSpecificDelete cannot be in icu and mozilla at the same time)
-// and because ICU declares both UNumberFormat and UDateTimePatternGenerator
-// as void*.
-template <typename T>
+// Simple RAII for ICU objects.  Unfortunately, ICU's C++ API is uniformly
+// unstable, so we can't use its smart pointers for this.
+template <typename T, void (Delete)(T*)>
 class ScopedICUObject
 {
     T* ptr_;
-    void (* deleter_)(T*);
 
   public:
-    ScopedICUObject(T* ptr, void (*deleter)(T*))
-      : ptr_(ptr),
-        deleter_(deleter)
+    explicit ScopedICUObject(T* ptr)
+      : ptr_(ptr)
     {}
 
     ~ScopedICUObject() {
         if (ptr_)
-            deleter_(ptr_);
+            Delete(ptr_);
     }
 
     // In cases where an object should be deleted on abnormal exits,
     // but returned to the caller if everything goes well, call forget()
     // to transfer the object just before returning.
     T* forget() {
         T* tmp = ptr_;
         ptr_ = nullptr;
@@ -774,25 +769,27 @@ collator_finalize(FreeOp* fop, JSObject*
     const Value& slot = obj->as<NativeObject>().getReservedSlot(UCOLLATOR_SLOT);
     if (!slot.isUndefined()) {
         if (UCollator* coll = static_cast<UCollator*>(slot.toPrivate()))
             ucol_close(coll);
     }
 }
 
 static JSObject*
-InitCollatorClass(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
+CreateCollatorPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
 {
     RootedFunction ctor(cx, global->createConstructor(cx, &Collator, cx->names().Collator, 0));
     if (!ctor)
         return nullptr;
 
-    RootedObject proto(cx, global->as<GlobalObject>().getOrCreateCollatorPrototype(cx));
+    RootedNativeObject proto(cx, global->createBlankPrototype(cx, &CollatorClass));
     if (!proto)
         return nullptr;
+    proto->setReservedSlot(UCOLLATOR_SLOT, PrivateValue(nullptr));
+
     if (!LinkConstructorAndPrototype(cx, ctor, proto))
         return nullptr;
 
     // 10.2.2
     if (!JS_DefineFunctions(cx, ctor, collator_static_methods))
         return nullptr;
 
     // 10.3.2 and 10.3.3
@@ -822,28 +819,17 @@ InitCollatorClass(JSContext* cx, HandleO
     if (!IntlInitialize(cx, proto, cx->names().InitializeCollator, UndefinedHandleValue, options))
         return nullptr;
 
     // 8.1
     RootedValue ctorValue(cx, ObjectValue(*ctor));
     if (!DefineProperty(cx, Intl, cx->names().Collator, ctorValue, nullptr, nullptr, 0))
         return nullptr;
 
-    return ctor;
-}
-
-bool
-GlobalObject::initCollatorProto(JSContext* cx, Handle<GlobalObject*> global)
-{
-    RootedNativeObject proto(cx, global->createBlankPrototype(cx, &CollatorClass));
-    if (!proto)
-        return false;
-    proto->setReservedSlot(UCOLLATOR_SLOT, PrivateValue(nullptr));
-    global->setReservedSlot(COLLATOR_PROTO, ObjectValue(*proto));
-    return true;
+    return proto;
 }
 
 bool
 js::intl_Collator_availableLocales(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 0);
 
@@ -865,17 +851,17 @@ js::intl_availableCollations(JSContext* 
     if (!locale)
         return false;
     UErrorCode status = U_ZERO_ERROR;
     UEnumeration* values = ucol_getKeywordValuesForLocale("co", locale.ptr(), false, &status);
     if (U_FAILURE(status)) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
         return false;
     }
-    ScopedICUObject<UEnumeration> toClose(values, uenum_close);
+    ScopedICUObject<UEnumeration, uenum_close> toClose(values);
 
     uint32_t count = uenum_count(values, &status);
     if (U_FAILURE(status)) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
         return false;
     }
 
     RootedObject collations(cx, NewDenseEmptyArray(cx));
@@ -1273,26 +1259,28 @@ numberFormat_finalize(FreeOp* fop, JSObj
     const Value& slot = obj->as<NativeObject>().getReservedSlot(UNUMBER_FORMAT_SLOT);
     if (!slot.isUndefined()) {
         if (UNumberFormat* nf = static_cast<UNumberFormat*>(slot.toPrivate()))
             unum_close(nf);
     }
 }
 
 static JSObject*
-InitNumberFormatClass(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
+CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
 {
     RootedFunction ctor(cx);
     ctor = global->createConstructor(cx, &NumberFormat, cx->names().NumberFormat, 0);
     if (!ctor)
         return nullptr;
 
-    RootedObject proto(cx, global->as<GlobalObject>().getOrCreateNumberFormatPrototype(cx));
+    RootedNativeObject proto(cx, global->createBlankPrototype(cx, &NumberFormatClass));
     if (!proto)
         return nullptr;
+    proto->setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
+
     if (!LinkConstructorAndPrototype(cx, ctor, proto))
         return nullptr;
 
     // 11.2.2
     if (!JS_DefineFunctions(cx, ctor, numberFormat_static_methods))
         return nullptr;
 
     // 11.3.2 and 11.3.3
@@ -1328,28 +1316,17 @@ InitNumberFormatClass(JSContext* cx, Han
         return nullptr;
     }
 
     // 8.1
     RootedValue ctorValue(cx, ObjectValue(*ctor));
     if (!DefineProperty(cx, Intl, cx->names().NumberFormat, ctorValue, nullptr, nullptr, 0))
         return nullptr;
 
-    return ctor;
-}
-
-bool
-GlobalObject::initNumberFormatProto(JSContext* cx, Handle<GlobalObject*> global)
-{
-    RootedNativeObject proto(cx, global->createBlankPrototype(cx, &NumberFormatClass));
-    if (!proto)
-        return false;
-    proto->setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
-    global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*proto));
-    return true;
+    return proto;
 }
 
 bool
 js::intl_NumberFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 0);
 
@@ -1507,17 +1484,17 @@ NewUNumberFormat(JSContext* cx, HandleOb
     uUseGrouping = value.toBoolean();
 
     UErrorCode status = U_ZERO_ERROR;
     UNumberFormat* nf = unum_open(uStyle, nullptr, 0, icuLocale(locale.ptr()), nullptr, &status);
     if (U_FAILURE(status)) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
         return nullptr;
     }
-    ScopedICUObject<UNumberFormat> toClose(nf, unum_close);
+    ScopedICUObject<UNumberFormat, unum_close> toClose(nf);
 
     if (uCurrency) {
         unum_setTextAttribute(nf, UNUM_CURRENCY_CODE, uCurrency, 3, &status);
         if (U_FAILURE(status)) {
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
             return nullptr;
         }
     }
@@ -1746,26 +1723,28 @@ dateTimeFormat_finalize(FreeOp* fop, JSO
     const Value& slot = obj->as<NativeObject>().getReservedSlot(UDATE_FORMAT_SLOT);
     if (!slot.isUndefined()) {
         if (UDateFormat* df = static_cast<UDateFormat*>(slot.toPrivate()))
             udat_close(df);
     }
 }
 
 static JSObject*
-InitDateTimeFormatClass(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
+CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
 {
     RootedFunction ctor(cx);
     ctor = global->createConstructor(cx, &DateTimeFormat, cx->names().DateTimeFormat, 0);
     if (!ctor)
         return nullptr;
 
-    RootedObject proto(cx, global->as<GlobalObject>().getOrCreateDateTimeFormatPrototype(cx));
+    RootedNativeObject proto(cx, global->createBlankPrototype(cx, &DateTimeFormatClass));
     if (!proto)
         return nullptr;
+    proto->setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(nullptr));
+
     if (!LinkConstructorAndPrototype(cx, ctor, proto))
         return nullptr;
 
     // 12.2.2
     if (!JS_DefineFunctions(cx, ctor, dateTimeFormat_static_methods))
         return nullptr;
 
     // 12.3.2 and 12.3.3
@@ -1816,28 +1795,17 @@ InitDateTimeFormatClass(JSContext* cx, H
         return nullptr;
     }
 
     // 8.1
     RootedValue ctorValue(cx, ObjectValue(*ctor));
     if (!DefineProperty(cx, Intl, cx->names().DateTimeFormat, ctorValue, nullptr, nullptr, 0))
         return nullptr;
 
-    return ctor;
-}
-
-bool
-GlobalObject::initDateTimeFormatProto(JSContext* cx, Handle<GlobalObject*> global)
-{
-    RootedNativeObject proto(cx, global->createBlankPrototype(cx, &DateTimeFormatClass));
-    if (!proto)
-        return false;
-    proto->setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(nullptr));
-    global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*proto));
-    return true;
+    return proto;
 }
 
 bool
 js::intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 0);
 
@@ -1875,46 +1843,54 @@ js::intl_availableCalendars(JSContext* c
 
     RootedObject calendars(cx, NewDenseEmptyArray(cx));
     if (!calendars)
         return false;
     uint32_t index = 0;
 
     // We need the default calendar for the locale as the first result.
     UErrorCode status = U_ZERO_ERROR;
-    UCalendar* cal = ucal_open(nullptr, 0, locale.ptr(), UCAL_DEFAULT, &status);
-    const char* calendar = ucal_getType(cal, &status);
-    if (U_FAILURE(status)) {
-        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
-        return false;
+    RootedString jscalendar(cx);
+    {
+        UCalendar* cal = ucal_open(nullptr, 0, locale.ptr(), UCAL_DEFAULT, &status);
+
+        // This correctly handles nullptr |cal| when opening failed.
+        ScopedICUObject<UCalendar, ucal_close> closeCalendar(cal);
+
+        const char* calendar = ucal_getType(cal, &status);
+        if (U_FAILURE(status)) {
+            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+            return false;
+        }
+
+        jscalendar = JS_NewStringCopyZ(cx, bcp47CalendarName(calendar));
+        if (!jscalendar)
+            return false;
     }
-    ucal_close(cal);
-    RootedString jscalendar(cx, JS_NewStringCopyZ(cx, bcp47CalendarName(calendar)));
-    if (!jscalendar)
-        return false;
+
     RootedValue element(cx, StringValue(jscalendar));
     if (!DefineElement(cx, calendars, index++, element))
         return false;
 
     // Now get the calendars that "would make a difference", i.e., not the default.
     UEnumeration* values = ucal_getKeywordValuesForLocale("ca", locale.ptr(), false, &status);
     if (U_FAILURE(status)) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
         return false;
     }
-    ScopedICUObject<UEnumeration> toClose(values, uenum_close);
+    ScopedICUObject<UEnumeration, uenum_close> toClose(values);
 
     uint32_t count = uenum_count(values, &status);
     if (U_FAILURE(status)) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
         return false;
     }
 
     for (; count > 0; count--) {
-        calendar = uenum_next(values, nullptr, &status);
+        const char* calendar = uenum_next(values, nullptr, &status);
         if (U_FAILURE(status)) {
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
             return false;
         }
 
         jscalendar = JS_NewStringCopyZ(cx, bcp47CalendarName(calendar));
         if (!jscalendar)
             return false;
@@ -1951,17 +1927,17 @@ js::intl_patternForSkeleton(JSContext* c
     uint32_t skeletonLen = u_strlen(Char16ToUChar(skeletonChars.start().get()));
 
     UErrorCode status = U_ZERO_ERROR;
     UDateTimePatternGenerator* gen = udatpg_open(icuLocale(locale.ptr()), &status);
     if (U_FAILURE(status)) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
         return false;
     }
-    ScopedICUObject<UDateTimePatternGenerator> toClose(gen, udatpg_close);
+    ScopedICUObject<UDateTimePatternGenerator, udatpg_close> toClose(gen);
 
     int32_t size = udatpg_getBestPattern(gen, Char16ToUChar(skeletonChars.start().get()),
                                          skeletonLen, nullptr, 0, &status);
     if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
         return false;
     }
     ScopedJSFreePtr<UChar> pattern(cx->pod_malloc<UChar>(size + 1));
@@ -2384,56 +2360,76 @@ static const JSFunctionSpec intl_static_
     JS_SELF_HOSTED_FN("getCanonicalLocales", "Intl_getCanonicalLocales", 1, 0),
     JS_FS_END
 };
 
 /**
  * Initializes the Intl Object and its standard built-in properties.
  * Spec: ECMAScript Internationalization API Specification, 8.0, 8.1
  */
+bool
+GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global)
+{
+    RootedObject proto(cx, global->getOrCreateObjectPrototype(cx));
+    if (!proto)
+        return false;
+
+    // The |Intl| object is just a plain object with some "static" function
+    // properties and some constructor properties.
+    RootedObject intl(cx, NewObjectWithGivenProto(cx, &IntlClass, proto, SingletonObject));
+    if (!intl)
+        return false;
+
+    // Add the static functions.
+    if (!JS_DefineFunctions(cx, intl, intl_static_methods))
+        return false;
+
+    // Add the constructor properties, computing and returning the relevant
+    // prototype objects needed below.
+    RootedObject collatorProto(cx, CreateCollatorPrototype(cx, intl, global));
+    if (!collatorProto)
+        return false;
+    RootedObject dateTimeFormatProto(cx, CreateDateTimeFormatPrototype(cx, intl, global));
+    if (!dateTimeFormatProto)
+        return false;
+    RootedObject numberFormatProto(cx, CreateNumberFormatPrototype(cx, intl, global));
+    if (!numberFormatProto)
+        return false;
+
+    // The |Intl| object is fully set up now, so define the global property.
+    RootedValue intlValue(cx, ObjectValue(*intl));
+    if (!DefineProperty(cx, global, cx->names().Intl, intlValue, nullptr, nullptr,
+                        JSPROP_RESOLVING))
+    {
+        return false;
+    }
+
+    // Now that the |Intl| object is successfully added, we can OOM-safely fill
+    // in all relevant reserved global slots.
+
+    // Cache the various prototypes, for use in creating instances of these
+    // objects with the proper [[Prototype]] as "the original value of
+    // |Intl.Collator.prototype|" and similar.  For builtin classes like
+    // |String.prototype| we have |JSProto_*| that enables
+    // |getPrototype(JSProto_*)|, but that has global-object-property-related
+    // baggage we don't need or want, so we use one-off reserved slots.
+    global->setReservedSlot(COLLATOR_PROTO, ObjectValue(*collatorProto));
+    global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*dateTimeFormatProto));
+    global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*numberFormatProto));
+
+    // Also cache |Intl| to implement spec language that conditions behavior
+    // based on values being equal to "the standard built-in |Intl| object".
+    // Use |setConstructor| to correspond with |JSProto_Intl|.
+    //
+    // XXX We should possibly do a one-off reserved slot like above.
+    global->setConstructor(JSProto_Intl, ObjectValue(*intl));
+    return true;
+}
+
 JSObject*
 js::InitIntlClass(JSContext* cx, HandleObject obj)
 {
-    MOZ_ASSERT(obj->is<GlobalObject>());
-    Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
-
-    // The constructors above need to be able to determine whether they've been
-    // called with this being "the standard built-in Intl object". The global
-    // object reserves slots to track standard built-in objects, but doesn't
-    // normally keep references to non-constructors. This makes sure there is one.
-    RootedObject Intl(cx, global->getOrCreateIntlObject(cx));
-    if (!Intl)
-        return nullptr;
-
-    RootedValue IntlValue(cx, ObjectValue(*Intl));
-    if (!DefineProperty(cx, global, cx->names().Intl, IntlValue, nullptr, nullptr,
-                        JSPROP_RESOLVING))
-    {
-        return nullptr;
-    }
-
-    if (!JS_DefineFunctions(cx, Intl, intl_static_methods))
+    Handle<GlobalObject*> global = obj.as<GlobalObject>();
+    if (!GlobalObject::initIntlObject(cx, global))
         return nullptr;
 
-    if (!InitCollatorClass(cx, Intl, global))
-        return nullptr;
-    if (!InitNumberFormatClass(cx, Intl, global))
-        return nullptr;
-    if (!InitDateTimeFormatClass(cx, Intl, global))
-        return nullptr;
-
-    global->setConstructor(JSProto_Intl, ObjectValue(*Intl));
-
-    return Intl;
+    return &global->getConstructor(JSProto_Intl).toObject();
 }
-
-bool
-GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global)
-{
-    RootedObject Intl(cx);
-    RootedObject proto(cx, global->getOrCreateObjectPrototype(cx));
-    Intl = NewObjectWithGivenProto(cx, &IntlClass, proto, SingletonObject);
-    if (!Intl)
-        return false;
-
-    global->setConstructor(JSProto_Intl, ObjectValue(*Intl));
-    return true;
-}
--- a/js/src/gc/Barrier.h
+++ b/js/src/gc/Barrier.h
@@ -904,16 +904,17 @@ class ArrayBufferObject;
 class NestedScopeObject;
 class DebugScopeObject;
 class GlobalObject;
 class ScriptSourceObject;
 class Shape;
 class BaseShape;
 class UnownedBaseShape;
 class WasmInstanceObject;
+class WasmTableObject;
 namespace jit {
 class JitCode;
 } // namespace jit
 
 typedef PreBarriered<JSObject*> PreBarrieredObject;
 typedef PreBarriered<JSScript*> PreBarrieredScript;
 typedef PreBarriered<jit::JitCode*> PreBarrieredJitCode;
 typedef PreBarriered<JSString*> PreBarrieredString;
@@ -956,14 +957,15 @@ typedef ReadBarriered<JSObject*> ReadBar
 typedef ReadBarriered<JSFunction*> ReadBarrieredFunction;
 typedef ReadBarriered<JSScript*> ReadBarrieredScript;
 typedef ReadBarriered<ScriptSourceObject*> ReadBarrieredScriptSourceObject;
 typedef ReadBarriered<Shape*> ReadBarrieredShape;
 typedef ReadBarriered<jit::JitCode*> ReadBarrieredJitCode;
 typedef ReadBarriered<ObjectGroup*> ReadBarrieredObjectGroup;
 typedef ReadBarriered<JS::Symbol*> ReadBarrieredSymbol;
 typedef ReadBarriered<WasmInstanceObject*> ReadBarrieredWasmInstanceObject;
+typedef ReadBarriered<WasmTableObject*> ReadBarrieredWasmTableObject;
 
 typedef ReadBarriered<Value> ReadBarrieredValue;
 
 } /* namespace js */
 
 #endif /* gc_Barrier_h */
--- a/js/src/gc/Policy.h
+++ b/js/src/gc/Policy.h
@@ -103,16 +103,17 @@ class JitCode;
     D(js::ScopeObject*) \
     D(js::ScriptSourceObject*) \
     D(js::Shape*) \
     D(js::SharedArrayBufferObject*) \
     D(js::StructTypeDescr*) \
     D(js::UnownedBaseShape*) \
     D(js::WasmInstanceObject*) \
     D(js::WasmMemoryObject*) \
+    D(js::WasmTableObject*) \
     D(js::jit::JitCode*)
 
 // Expand the given macro D for each internal tagged GC pointer type.
 #define FOR_EACH_INTERNAL_TAGGED_GC_POINTER_TYPE(D) \
     D(js::TaggedProto)
 
 // Expand the macro D for every GC reference type that we know about.
 #define FOR_EACH_GC_POINTER_TYPE(D) \
--- a/js/src/jit-test/tests/wasm/table-gc.js
+++ b/js/src/jit-test/tests/wasm/table-gc.js
@@ -14,17 +14,18 @@ const Table = WebAssembly.Table;
 const textToBinary = str => wasmTextToBinary(str, 'new-format');
 const evalText = (str, imports) => new Instance(new Module(textToBinary(str)), imports);
 
 var caller = `(type $v2i (func (result i32))) (func $call (param $i i32) (result i32) (call_indirect $v2i (get_local $i))) (export "call" $call)`
 var callee = i => `(func $f${i} (type $v2i) (result i32) (i32.const ${i}))`;
 
 // A table should not hold exported functions alive and exported functions
 // should not hold their originating table alive. Live exported functions should
-// hold instances alive. Nothing should hold the export object alive.
+// hold instances alive and instances hold imported tables alive. Nothing
+// should hold the export object alive.
 resetFinalizeCount();
 var i = evalText(`(module (table (resizable 2)) (export "tbl" table) (elem (i32.const 0) $f0) ${callee(0)} ${caller})`);
 var e = i.exports;
 var t = e.tbl;
 var f = t.get(0);
 assertEq(f(), e.call(0));
 assertErrorMessage(() => e.call(1), Error, /bad wasm indirect call/);
 assertErrorMessage(() => e.call(2), Error, /out-of-range/);
@@ -43,20 +44,20 @@ f.edge = makeFinalizeObserver();
 gc();
 assertEq(finalizeCount(), 1);
 i.exports = null;
 e = null;
 gc();
 assertEq(finalizeCount(), 2);
 t = null;
 gc();
-assertEq(finalizeCount(), 3);
+assertEq(finalizeCount(), 2);
 i = null;
 gc();
-assertEq(finalizeCount(), 3);
+assertEq(finalizeCount(), 2);
 assertEq(f(), 0);
 f = null;
 gc();
 assertEq(finalizeCount(), 5);
 
 // A table should hold the instance of any of its elements alive.
 resetFinalizeCount();
 var i = evalText(`(module (table (resizable 1)) (export "tbl" table) (elem (i32.const 0) $f0) ${callee(0)} ${caller})`);
@@ -191,17 +192,17 @@ assertEq(finalizeCount(), 5);
 // there are no outstanding references.
 resetFinalizeCount();
 const N = 10;
 var tbl = new Table({initial:N, element:"anyfunc"});
 tbl.edge = makeFinalizeObserver();
 function runTest() {
     tbl = null;
     gc();
-    assertEq(finalizeCount(), 1);
+    assertEq(finalizeCount(), 0);
     return 100;
 }
 var i = evalText(
     `(module
         (import "a" "b" (result i32))
         (func $f (param i32) (result i32) (call_import 0))
         (export "f" $f)
     )`,
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -598,17 +598,17 @@ IonBuilder::analyzeNewLoopTypes(MBasicBl
                     MDefinition* oldDef = oldEntryRp->getOperand(slot);
                     if (!oldDef->isPhi()) {
                         MOZ_ASSERT(oldDef->block()->id() < oldEntry->id());
                         MOZ_ASSERT(oldDef == entry->getSlot(slot));
                         continue;
                     }
                     MPhi* oldPhi = oldDef->toPhi();
                     MPhi* newPhi = entry->getSlot(slot)->toPhi();
-                    if (!newPhi->addBackedgeType(oldPhi->type(), oldPhi->resultTypeSet()))
+                    if (!newPhi->addBackedgeType(alloc(), oldPhi->type(), oldPhi->resultTypeSet()))
                         return false;
                 }
             }
 
             // Update the most recent header for this loop encountered, in case
             // new types flow to the phis and the loop is processed at least
             // three times.
             loopHeaders_[i].header = entry;
@@ -639,27 +639,27 @@ IonBuilder::analyzeNewLoopTypes(MBasicBl
 
         if (*last == JSOP_POS)
             last = earlier;
 
         if (CodeSpec[*last].format & JOF_TYPESET) {
             TemporaryTypeSet* typeSet = bytecodeTypes(last);
             if (!typeSet->empty()) {
                 MIRType type = typeSet->getKnownMIRType();
-                if (!phi->addBackedgeType(type, typeSet))
+                if (!phi->addBackedgeType(alloc(), type, typeSet))
                     return false;
             }
         } else if (*last == JSOP_GETLOCAL || *last == JSOP_GETARG) {
             uint32_t slot = (*last == JSOP_GETLOCAL)
                             ? info().localSlot(GET_LOCALNO(last))
                             : info().argSlotUnchecked(GET_ARGNO(last));
             if (slot < info().firstStackSlot()) {
                 MPhi* otherPhi = entry->getSlot(slot)->toPhi();
                 if (otherPhi->hasBackedgeType()) {
-                    if (!phi->addBackedgeType(otherPhi->type(), otherPhi->resultTypeSet()))
+                    if (!phi->addBackedgeType(alloc(), otherPhi->type(), otherPhi->resultTypeSet()))
                         return false;
                 }
             }
         } else {
             MIRType type = MIRType::None;
             switch (*last) {
               case JSOP_VOID:
               case JSOP_UNDEFINED:
@@ -721,17 +721,17 @@ IonBuilder::analyzeNewLoopTypes(MBasicBl
               case JSOP_MOD:
               case JSOP_NEG:
                 type = inspector->expectedResultType(last);
                 break;
               default:
                 break;
             }
             if (type != MIRType::None) {
-                if (!phi->addBackedgeType(type, nullptr))
+                if (!phi->addBackedgeType(alloc(), type, nullptr))
                     return false;
             }
         }
     }
     return true;
 }
 
 bool
@@ -2448,17 +2448,17 @@ IonBuilder::finishLoop(CFGState& state, 
     MOZ_ASSERT(current);
 
     MOZ_ASSERT(loopDepth_);
     loopDepth_--;
     MOZ_ASSERT_IF(successor, successor->loopDepth() == loopDepth_);
 
     // Compute phis in the loop header and propagate them throughout the loop,
     // including the successor.
-    AbortReason r = state.loop.entry->setBackedge(current);
+    AbortReason r = state.loop.entry->setBackedge(alloc(), current);
     if (r == AbortReason_Alloc)
         return ControlStatus_Error;
     if (r == AbortReason_Disable) {
         // If there are types for variables on the backedge that were not
         // present at the original loop header, then uses of the variables'
         // phis may have generated incorrect nodes. The new types have been
         // incorporated into the header phis, so remove all blocks for the
         // loop body and restart with the new types.
@@ -7977,17 +7977,17 @@ IonBuilder::newPendingLoopHeader(MBasicB
 
             // Extract typeset from value.
             LifoAlloc* lifoAlloc = alloc().lifoAlloc();
             TemporaryTypeSet* typeSet =
                 lifoAlloc->new_<TemporaryTypeSet>(lifoAlloc, existingType);
             if (!typeSet)
                 return nullptr;
             MIRType type = typeSet->getKnownMIRType();
-            if (!phi->addBackedgeType(type, typeSet))
+            if (!phi->addBackedgeType(alloc(), type, typeSet))
                 return nullptr;
         }
     }
 
     return block;
 }
 
 MTest*
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -1075,17 +1075,17 @@ class IonBuilder
     TemporaryTypeSet* bytecodeTypes(jsbytecode* pc);
 
     // Use one of the below methods for updating the current block, rather than
     // updating |current| directly. setCurrent() should only be used in cases
     // where the block cannot have phis whose type needs to be computed.
 
     MOZ_MUST_USE bool setCurrentAndSpecializePhis(MBasicBlock* block) {
         if (block) {
-            if (!block->specializePhis())
+            if (!block->specializePhis(alloc()))
                 return false;
         }
         setCurrent(block);
         return true;
     }
 
     void setCurrent(MBasicBlock* block) {
         current = block;
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -2448,56 +2448,55 @@ MPhi::congruentTo(const MDefinition* ins
     // For now, consider phis in different blocks incongruent.
     if (ins->block() != block())
         return false;
 
     return congruentIfOperandsEqual(ins);
 }
 
 static inline TemporaryTypeSet*
-MakeMIRTypeSet(MIRType type)
+MakeMIRTypeSet(TempAllocator& alloc, MIRType type)
 {
     MOZ_ASSERT(type != MIRType::Value);
     TypeSet::Type ntype = type == MIRType::Object
                           ? TypeSet::AnyObjectType()
                           : TypeSet::PrimitiveType(ValueTypeFromMIRType(type));
-    LifoAlloc* alloc = GetJitContext()->temp->lifoAlloc();
-    return alloc->new_<TemporaryTypeSet>(alloc, ntype);
+    return alloc.lifoAlloc()->new_<TemporaryTypeSet>(alloc.lifoAlloc(), ntype);
 }
 
 bool
-jit::MergeTypes(MIRType* ptype, TemporaryTypeSet** ptypeSet,
+jit::MergeTypes(TempAllocator& alloc, MIRType* ptype, TemporaryTypeSet** ptypeSet,
                 MIRType newType, TemporaryTypeSet* newTypeSet)
 {
     if (newTypeSet && newTypeSet->empty())
         return true;
+    LifoAlloc::AutoFallibleScope fallibleAllocator(alloc.lifoAlloc());
     if (newType != *ptype) {
         if (IsTypeRepresentableAsDouble(newType) && IsTypeRepresentableAsDouble(*ptype)) {
             *ptype = MIRType::Double;
         } else if (*ptype != MIRType::Value) {
             if (!*ptypeSet) {
-                *ptypeSet = MakeMIRTypeSet(*ptype);
+                *ptypeSet = MakeMIRTypeSet(alloc, *ptype);
                 if (!*ptypeSet)
                     return false;
             }
             *ptype = MIRType::Value;
         } else if (*ptypeSet && (*ptypeSet)->empty()) {
             *ptype = newType;
         }
     }
     if (*ptypeSet) {
-        LifoAlloc* alloc = GetJitContext()->temp->lifoAlloc();
         if (!newTypeSet && newType != MIRType::Value) {
-            newTypeSet = MakeMIRTypeSet(newType);
+            newTypeSet = MakeMIRTypeSet(alloc, newType);
             if (!newTypeSet)
                 return false;
         }
         if (newTypeSet) {
             if (!newTypeSet->isSubset(*ptypeSet)) {
-                *ptypeSet = TypeSet::unionSets(*ptypeSet, newTypeSet, alloc);
+                *ptypeSet = TypeSet::unionSets(*ptypeSet, newTypeSet, alloc.lifoAlloc());
                 if (!*ptypeSet)
                     return false;
             }
         } else {
             *ptypeSet = nullptr;
         }
     }
     return true;
@@ -2588,17 +2587,17 @@ jit::CanStoreUnboxedType(TempAllocator& 
 
 static bool
 CanStoreUnboxedType(TempAllocator& alloc, JSValueType unboxedType, MDefinition* value)
 {
     return CanStoreUnboxedType(alloc, unboxedType, value->type(), value->resultTypeSet());
 }
 
 bool
-MPhi::specializeType()
+MPhi::specializeType(TempAllocator& alloc)
 {
 #ifdef DEBUG
     MOZ_ASSERT(!specialized_);
     specialized_ = true;
 #endif
 
     MOZ_ASSERT(!inputs_.empty());
 
@@ -2613,35 +2612,35 @@ MPhi::specializeType()
         start = 1;
     }
 
     MIRType resultType = this->type();
     TemporaryTypeSet* resultTypeSet = this->resultTypeSet();
 
     for (size_t i = start; i < inputs_.length(); i++) {
         MDefinition* def = getOperand(i);
-        if (!MergeTypes(&resultType, &resultTypeSet, def->type(), def->resultTypeSet()))
+        if (!MergeTypes(alloc, &resultType, &resultTypeSet, def->type(), def->resultTypeSet()))
             return false;
     }
 
     setResultType(resultType);
     setResultTypeSet(resultTypeSet);
     return true;
 }
 
 bool
-MPhi::addBackedgeType(MIRType type, TemporaryTypeSet* typeSet)
+MPhi::addBackedgeType(TempAllocator& alloc, MIRType type, TemporaryTypeSet* typeSet)
 {
     MOZ_ASSERT(!specialized_);
 
     if (hasBackedgeType_) {
         MIRType resultType = this->type();
         TemporaryTypeSet* resultTypeSet = this->resultTypeSet();
 
-        if (!MergeTypes(&resultType, &resultTypeSet, type, typeSet))
+        if (!MergeTypes(alloc, &resultType, &resultTypeSet, type, typeSet))
             return false;
 
         setResultType(resultType);
         setResultTypeSet(resultTypeSet);
     } else {
         setResultType(type);
         setResultTypeSet(typeSet);
         hasBackedgeType_ = true;
@@ -2668,22 +2667,22 @@ MPhi::typeIncludes(MDefinition* def)
         return this->type() == MIRType::Value
             && (!this->resultTypeSet() || this->resultTypeSet()->unknown());
     }
 
     return this->mightBeType(def->type());
 }
 
 bool
-MPhi::checkForTypeChange(MDefinition* ins, bool* ptypeChange)
+MPhi::checkForTypeChange(TempAllocator& alloc, MDefinition* ins, bool* ptypeChange)
 {
     MIRType resultType = this->type();
     TemporaryTypeSet* resultTypeSet = this->resultTypeSet();
 
-    if (!MergeTypes(&resultType, &resultTypeSet, ins->type(), ins->resultTypeSet()))
+    if (!MergeTypes(alloc, &resultType, &resultTypeSet, ins->type(), ins->resultTypeSet()))
         return false;
 
     if (resultType != this->type() || resultTypeSet != this->resultTypeSet()) {
         *ptypeChange = true;
         setResultType(resultType);
         setResultTypeSet(resultTypeSet);
     }
     return true;
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -3094,17 +3094,17 @@ class MThrow
 // Fabricate a type set containing only the type of the specified object.
 TemporaryTypeSet*
 MakeSingletonTypeSet(CompilerConstraintList* constraints, JSObject* obj);
 
 TemporaryTypeSet*
 MakeSingletonTypeSet(CompilerConstraintList* constraints, ObjectGroup* obj);
 
 MOZ_MUST_USE bool
-MergeTypes(MIRType* ptype, TemporaryTypeSet** ptypeSet,
+MergeTypes(TempAllocator& alloc, MIRType* ptype, TemporaryTypeSet** ptypeSet,
            MIRType newType, TemporaryTypeSet* newTypeSet);
 
 bool
 TypeSetIncludes(TypeSet* types, MIRType input, TypeSet* inputTypes);
 
 bool
 EqualTypes(MIRType type1, TemporaryTypeSet* typeset1,
            MIRType type2, TemporaryTypeSet* typeset2);
@@ -7375,17 +7375,17 @@ class MPhi final
     }
     bool triedToSpecialize() const {
         return triedToSpecialize_;
     }
     void specialize(MIRType type) {
         triedToSpecialize_ = true;
         setResultType(type);
     }
-    bool specializeType();
+    bool specializeType(TempAllocator& alloc);
 
 #ifdef DEBUG
     // Assert that this is a phi in a loop header with a unique predecessor and
     // a unique backedge.
     void assertLoopPhi() const;
 #else
     void assertLoopPhi() const {}
 #endif
@@ -7404,17 +7404,18 @@ class MPhi final
         return getOperand(1);
     }
 
     // Whether this phi's type already includes information for def.
     bool typeIncludes(MDefinition* def);
 
     // Add types for this phi which speculate about new inputs that may come in
     // via a loop backedge.
-    MOZ_MUST_USE bool addBackedgeType(MIRType type, TemporaryTypeSet* typeSet);
+    MOZ_MUST_USE bool addBackedgeType(TempAllocator& alloc, MIRType type,
+                                      TemporaryTypeSet* typeSet);
 
     // Initializes the operands vector to the given capacity,
     // permitting use of addInput() instead of addInputSlow().
     MOZ_MUST_USE bool reserveLength(size_t length) {
         return inputs_.reserve(length);
     }
 
     // Use only if capacity has been reserved by reserveLength
@@ -7432,17 +7433,17 @@ class MPhi final
     // we know the inputs fits in the vector's inline storage.
     void addInlineInput(MDefinition* ins) {
         MOZ_ASSERT(inputs_.length() < InputVector::InlineLength);
         MOZ_ALWAYS_TRUE(addInputSlow(ins));
     }
 
     // Update the type of this phi after adding |ins| as an input. Set
     // |*ptypeChange| to true if the type changed.
-    bool checkForTypeChange(MDefinition* ins, bool* ptypeChange);
+    bool checkForTypeChange(TempAllocator& alloc, MDefinition* ins, bool* ptypeChange);
 
     MDefinition* foldsTo(TempAllocator& alloc) override;
     MDefinition* foldsTernary();
     MDefinition* foldsFilterTypeSet();
 
     bool congruentTo(const MDefinition* ins) const override;
 
     bool isIterator() const {
--- a/js/src/jit/MIRGraph.cpp
+++ b/js/src/jit/MIRGraph.cpp
@@ -1278,30 +1278,30 @@ MBasicBlock::assertUsesAreNotWithin(MUse
     for (; use != end; use++) {
         MOZ_ASSERT_IF(use->consumer()->isDefinition(),
                       use->consumer()->toDefinition()->block()->id() < id());
     }
 #endif
 }
 
 AbortReason
-MBasicBlock::setBackedge(MBasicBlock* pred)
+MBasicBlock::setBackedge(TempAllocator& alloc, MBasicBlock* pred)
 {
     // Predecessors must be finished, and at the correct stack depth.
     MOZ_ASSERT(hasLastIns());
     MOZ_ASSERT(pred->hasLastIns());
     MOZ_ASSERT(pred->stackDepth() == entryResumePoint()->stackDepth());
 
     // We must be a pending loop header
     MOZ_ASSERT(kind_ == PENDING_LOOP_HEADER);
 
     bool hadTypeChange = false;
 
     // Add exit definitions to each corresponding phi at the entry.
-    if (!inheritPhisFromBackedge(pred, &hadTypeChange))
+    if (!inheritPhisFromBackedge(alloc, pred, &hadTypeChange))
         return AbortReason_Alloc;
 
     if (hadTypeChange) {
         for (MPhiIterator phi = phisBegin(); phi != phisEnd(); phi++)
             phi->removeOperand(phi->numOperands() - 1);
         return AbortReason_Disable;
     }
 
@@ -1547,17 +1547,17 @@ MBasicBlock::inheritPhis(MBasicBlock* he
         // If the entryDef is the same as exitDef, then we must propagate the
         // phi down to this successor. This chance was missed as part of
         // setBackedge() because exits are not captured in resume points.
         setSlot(slot, phi);
     }
 }
 
 bool
-MBasicBlock::inheritPhisFromBackedge(MBasicBlock* backedge, bool* hadTypeChange)
+MBasicBlock::inheritPhisFromBackedge(TempAllocator& alloc, MBasicBlock* backedge, bool* hadTypeChange)
 {
     // We must be a pending loop header
     MOZ_ASSERT(kind_ == PENDING_LOOP_HEADER);
 
     size_t stackDepth = entryResumePoint()->stackDepth();
     for (size_t slot = 0; slot < stackDepth; slot++) {
         // Get the value stack-slot of the back edge.
         MDefinition* exitDef = backedge->getSlot(slot);
@@ -1588,31 +1588,31 @@ MBasicBlock::inheritPhisFromBackedge(MBa
             // onto phis.
             exitDef = entryDef->getOperand(0);
         }
 
         bool typeChange = false;
 
         if (!entryDef->addInputSlow(exitDef))
             return false;
-        if (!entryDef->checkForTypeChange(exitDef, &typeChange))
+        if (!entryDef->checkForTypeChange(alloc, exitDef, &typeChange))
             return false;
         *hadTypeChange |= typeChange;
         setSlot(slot, entryDef);
     }
 
     return true;
 }
 
 bool
-MBasicBlock::specializePhis()
+MBasicBlock::specializePhis(TempAllocator& alloc)
 {
     for (MPhiIterator iter = phisBegin(); iter != phisEnd(); iter++) {
         MPhi* phi = *iter;
-        if (!phi->specializeType())
+        if (!phi->specializeType(alloc))
             return false;
     }
     return true;
 }
 
 MTest*
 MBasicBlock::immediateDominatorBranch(BranchDirection* pdirection)
 {
--- a/js/src/jit/MIRGraph.h
+++ b/js/src/jit/MIRGraph.h
@@ -250,36 +250,37 @@ class MBasicBlock : public TempObject, p
     void removePredecessorWithoutPhiOperands(MBasicBlock* pred, size_t predIndex);
 
     // Resets all the dominator info so that it can be recomputed.
     void clearDominatorInfo();
 
     // Sets a back edge. This places phi nodes and rewrites instructions within
     // the current loop as necessary. If the backedge introduces new types for
     // phis at the loop header, returns a disabling abort.
-    MOZ_MUST_USE AbortReason setBackedge(MBasicBlock* block);
+    MOZ_MUST_USE AbortReason setBackedge(TempAllocator& alloc, MBasicBlock* block);
     MOZ_MUST_USE bool setBackedgeAsmJS(MBasicBlock* block);
 
     // Resets a LOOP_HEADER block to a NORMAL block.  This is needed when
     // optimizations remove the backedge.
     void clearLoopHeader();
 
     // Sets a block to a LOOP_HEADER block, with newBackedge as its backedge.
     // This is needed when optimizations remove the normal entry to a loop
     // with multiple entries.
     void setLoopHeader(MBasicBlock* newBackedge);
 
     // Propagates phis placed in a loop header down to this successor block.
     void inheritPhis(MBasicBlock* header);
 
     // Propagates backedge slots into phis operands of the loop header.
-    MOZ_MUST_USE bool inheritPhisFromBackedge(MBasicBlock* backedge, bool* hadTypeChange);
+    MOZ_MUST_USE bool inheritPhisFromBackedge(TempAllocator& alloc, MBasicBlock* backedge,
+                                              bool* hadTypeChange);
 
     // Compute the types for phis in this block according to their inputs.
-    MOZ_MUST_USE bool specializePhis();
+    MOZ_MUST_USE bool specializePhis(TempAllocator& alloc);
 
     void insertBefore(MInstruction* at, MInstruction* ins);
     void insertAfter(MInstruction* at, MInstruction* ins);
 
     void insertAtEnd(MInstruction* ins);
 
     // Add an instruction to this block, from elsewhere in the graph.
     void addFromElsewhere(MInstruction* ins);
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -3888,19 +3888,16 @@ GCRuntime::beginMarkPhase(JS::gcreason::
             /* Unmark everything in the zones being collected. */
             zone->arenas.unmarkAll();
         }
 
         for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
             /* Unmark all weak maps in the zones being collected. */
             WeakMapBase::unmarkZone(zone);
         }
-
-        if (isFull)
-            UnmarkScriptData(rt, lock);
     }
 
     markRuntime(gcmarker, MarkRuntime, lock);
 
     gcstats::AutoPhase ap2(stats, gcstats::PHASE_MARK_ROOTS);
 
     if (isIncremental) {
         gcstats::AutoPhase ap3(stats, gcstats::PHASE_BUFFER_GRAY_ROOTS);
@@ -5416,18 +5413,17 @@ GCRuntime::endSweepPhase(bool destroying
         gcstats::AutoPhase ap(stats, gcstats::PHASE_DESTROY);
 
         /*
          * Sweep script filenames after sweeping functions in the generic loop
          * above. In this way when a scripted function's finalizer destroys the
          * script and calls rt->destroyScriptHook, the hook can still access the
          * script's filename. See bug 323267.
          */
-        if (isFull)
-            SweepScriptData(rt, lock);
+        SweepScriptData(rt, lock);
 
         /* Clear out any small pools that we're hanging on to. */
         if (jit::JitRuntime* jitRuntime = rt->jitRuntime()) {
             jitRuntime->execAlloc().purge();
             jitRuntime->backedgeExecAlloc().purge();
         }
     }
 
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -581,19 +581,16 @@ FindScopeObjectIndex(JSScript* script, N
     for (unsigned i = 0; i < length; ++i) {
         if (vector[i] == &scope)
             return i;
     }
 
     MOZ_CRASH("Scope not found");
 }
 
-static bool
-SaveSharedScriptData(ExclusiveContext*, Handle<JSScript*>, SharedScriptData*, uint32_t);
-
 enum XDRClassKind {
     CK_BlockObject  = 0,
     CK_WithObject   = 1,
     CK_RegexpObject = 2,
     CK_JSFunction   = 3,
     CK_JSObject     = 4
 };
 
@@ -875,17 +872,16 @@ js::XDRScript(XDRState<mode>* xdr, Handl
         if (!JSScript::partiallyInit(cx, script, nconsts, nobjects, ntrynotes,
                                      nblockscopes, nyieldoffsets, nTypeSets))
         {
             return false;
         }
 
         MOZ_ASSERT(!script->mainOffset());
         script->mainOffset_ = prologueLength;
-        script->setLength(length);
         script->funLength_ = funLength;
 
         scriptp.set(script);
 
         if (scriptBits & (1 << Strict))
             script->strict_ = true;
         if (scriptBits & (1 << ExplicitUseStrict))
             script->explicitUseStrict_ = true;
@@ -949,50 +945,47 @@ js::XDRScript(XDRState<mode>* xdr, Handl
     }
 
     if (mode == XDR_DECODE) {
         script->lineno_ = lineno;
         script->column_ = column;
         script->nslots_ = nslots;
     }
 
-    jsbytecode* code = script->code();
-    SharedScriptData* ssd;
+    auto scriptDataGuard = mozilla::MakeScopeExit([&] {
+        if (mode == XDR_DECODE)
+            script->freeScriptData();
+    });
+
     if (mode == XDR_DECODE) {
-        ssd = SharedScriptData::new_(cx, length, nsrcnotes, natoms);
-        if (!ssd)
+        if (!script->createScriptData(cx, length, nsrcnotes, natoms))
             return false;
-        code = ssd->data;
-        if (natoms != 0) {
-            script->natoms_ = natoms;
-            script->atoms = ssd->atoms();
-        }
     }
 
+    jsbytecode* code = script->code();
     if (!xdr->codeBytes(code, length) || !xdr->codeBytes(code + length, nsrcnotes)) {
-        if (mode == XDR_DECODE)
-            js_free(ssd);
         return false;
     }
 
     for (i = 0; i != natoms; ++i) {
         if (mode == XDR_DECODE) {
             RootedAtom tmp(cx);
             if (!XDRAtom(xdr, &tmp))
                 return false;
-            script->atoms[i].init(tmp);
+            script->atoms()[i].init(tmp);
         } else {
-            RootedAtom tmp(cx, script->atoms[i]);
+            RootedAtom tmp(cx, script->atoms()[i]);
             if (!XDRAtom(xdr, &tmp))
                 return false;
         }
     }
 
+    scriptDataGuard.release();
     if (mode == XDR_DECODE) {
-        if (!SaveSharedScriptData(cx, script, ssd, nsrcnotes))
+        if (!script->shareScriptData(cx))
             return false;
     }
 
     if (nconsts) {
         GCPtrValue* vector = script->consts()->vector;
         RootedValue val(cx);
         for (i = 0; i != nconsts; ++i) {
             if (mode == XDR_ENCODE)
@@ -2411,149 +2404,139 @@ ScriptSource::setSourceMapURL(ExclusiveC
 /*
  * Shared script data management.
  */
 
 SharedScriptData*
 js::SharedScriptData::new_(ExclusiveContext* cx, uint32_t codeLength,
                            uint32_t srcnotesLength, uint32_t natoms)
 {
-    /*
-     * Ensure the atoms are aligned, as some architectures don't allow unaligned
-     * access.
-     */
-    const uint32_t pointerSize = sizeof(JSAtom*);
-    const uint32_t pointerMask = pointerSize - 1;
-    const uint32_t dataOffset = offsetof(SharedScriptData, data);
-    uint32_t baseLength = codeLength + srcnotesLength;
-    uint32_t padding = (pointerSize - ((baseLength + dataOffset) & pointerMask)) & pointerMask;
-    uint32_t length = baseLength + padding + pointerSize * natoms;
-
-    SharedScriptData* entry = reinterpret_cast<SharedScriptData*>(
-            cx->zone()->pod_malloc<uint8_t>(length + dataOffset));
+    uint32_t dataLength = natoms * sizeof(GCPtrAtom) + codeLength + srcnotesLength;
+    uint32_t allocLength = offsetof(SharedScriptData, data_) + dataLength;
+    auto entry = reinterpret_cast<SharedScriptData*>(cx->zone()->pod_malloc<uint8_t>(allocLength));
     if (!entry) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
-    entry->length = length;
-    entry->natoms = natoms;
-    entry->marked = false;
-    memset(entry->data + baseLength, 0, padding);
+    entry->refCount_ = 0;
+    entry->dataLength_ = dataLength;
+    entry->natoms_ = natoms;
+    entry->codeLength_ = codeLength;
 
     /*
      * Call constructors to initialize the storage that will be accessed as a
      * GCPtrAtom array via atoms().
      */
     GCPtrAtom* atoms = entry->atoms();
-    MOZ_ASSERT(reinterpret_cast<uintptr_t>(atoms) % sizeof(JSAtom*) == 0);
+    MOZ_ASSERT(reinterpret_cast<uintptr_t>(atoms) % sizeof(GCPtrAtom*) == 0);
     for (unsigned i = 0; i < natoms; ++i)
         new (&atoms[i]) GCPtrAtom();
 
     return entry;
 }
 
+bool
+JSScript::createScriptData(ExclusiveContext* cx, uint32_t codeLength, uint32_t srcnotesLength,
+                           uint32_t natoms)
+{
+    MOZ_ASSERT(!scriptData());
+    SharedScriptData* ssd = SharedScriptData::new_(cx, codeLength, srcnotesLength, natoms);
+    if (!ssd)
+        return false;
+
+    setScriptData(ssd);
+    return true;
+}
+
+void
+JSScript::freeScriptData()
+{
+    MOZ_ASSERT(scriptData_->refCount() == 1);
+    scriptData_->decRefCount();
+    scriptData_ = nullptr;
+}
+
+void
+JSScript::setScriptData(js::SharedScriptData* data)
+{
+    MOZ_ASSERT(!scriptData_);
+    scriptData_ = data;
+    scriptData_->incRefCount();
+}
+
 /*
  * Takes ownership of its *ssd parameter and either adds it into the runtime's
  * ScriptDataTable or frees it if a matching entry already exists.
  *
  * Sets the |code| and |atoms| fields on the given JSScript.
  */
-static bool
-SaveSharedScriptData(ExclusiveContext* cx, Handle<JSScript*> script, SharedScriptData* ssd,
-                     uint32_t nsrcnotes)
+bool
+JSScript::shareScriptData(ExclusiveContext* cx)
 {
-    MOZ_ASSERT(script != nullptr);
-    MOZ_ASSERT(ssd != nullptr);
+    SharedScriptData* ssd = scriptData();
+    MOZ_ASSERT(ssd);
+    MOZ_ASSERT(ssd->refCount() == 1);
 
     AutoLockForExclusiveAccess lock(cx);
 
     ScriptBytecodeHasher::Lookup l(ssd);
 
     ScriptDataTable::AddPtr p = cx->scriptDataTable(lock).lookupForAdd(l);
     if (p) {
-        js_free(ssd);
-        ssd = *p;
+        MOZ_ASSERT(ssd != *p);
+        freeScriptData();
+        setScriptData(*p);
     } else {
         if (!cx->scriptDataTable(lock).add(p, ssd)) {
-            script->setCode(nullptr);
-            script->atoms = nullptr;
-            js_free(ssd);
+            freeScriptData();
             ReportOutOfMemory(cx);
             return false;
         }
+
+        // Being in the table counts as a reference on the script data.
+        scriptData()->incRefCount();
     }
 
-    /*
-     * During the IGC we need to ensure that bytecode is marked whenever it is
-     * accessed even if the bytecode was already in the table: at this point
-     * old scripts or exceptions pointing to the bytecode may no longer be
-     * reachable. This is effectively a read barrier.
-     */
-    if (cx->isJSContext()) {
-        JSContext* ncx = cx->asJSContext();
-        if (JS::IsIncrementalGCInProgress(ncx) && ncx->gc.isFullGc())
-            ssd->marked = true;
-    }
-
-    script->setCode(ssd->data);
-    script->atoms = ssd->atoms();
+    MOZ_ASSERT(scriptData()->refCount() >= 2);
     return true;
 }
 
-static inline void
-MarkScriptData(JSRuntime* rt, const jsbytecode* bytecode)
-{
-    /*
-     * As an invariant, a ScriptBytecodeEntry should not be 'marked' outside of
-     * a GC. Since SweepScriptBytecodes is only called during a full gc,
-     * to preserve this invariant, only mark during a full gc.
-     */
-    if (rt->gc.isFullGc())
-        SharedScriptData::fromBytecode(bytecode)->marked = true;
-}
-
-void
-js::UnmarkScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock)
-{
-    MOZ_ASSERT(rt->gc.isFullGc());
-    ScriptDataTable& table = rt->scriptDataTable(lock);
-    for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) {
-        SharedScriptData* entry = e.front();
-        entry->marked = false;
-    }
-}
-
 void
 js::SweepScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock)
 {
-    MOZ_ASSERT(rt->gc.isFullGc());
+    // Entries are removed from the table when their reference count is one,
+    // i.e. when the only reference to them is from the table entry.
+
     ScriptDataTable& table = rt->scriptDataTable(lock);
 
-    if (rt->keepAtoms())
-        return;
-
     for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) {
-        SharedScriptData* entry = e.front();
-        if (!entry->marked) {
-            js_free(entry);
+        SharedScriptData* scriptData = e.front();
+        if (scriptData->refCount() == 1) {
+            scriptData->decRefCount();
             e.removeFront();
         }
     }
 }
 
 void
 js::FreeScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock)
 {
     ScriptDataTable& table = rt->scriptDataTable(lock);
     if (!table.initialized())
         return;
 
-    for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront())
+    for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) {
+#ifdef DEBUG
+        SharedScriptData* scriptData = e.front();
+        fprintf(stderr, "ERROR: GC found live SharedScriptData %p with ref count %d at shutdown\n",
+                scriptData, scriptData->refCount());
+#endif
         js_free(e.front());
+    }
 
     table.clear();
 }
 
 /*
  * JSScript::data and SharedScriptData::data have complex,
  * manually-controlled, memory layouts.
  *
@@ -2828,24 +2811,23 @@ JSScript::partiallyInit(ExclusiveContext
 JSScript::fullyInitTrivial(ExclusiveContext* cx, Handle<JSScript*> script)
 {
     if (!Bindings::initTrivialForScript(cx, script))
         return false;
 
     if (!partiallyInit(cx, script, 0, 0, 0, 0, 0, 0))
         return false;
 
-    SharedScriptData* ssd = SharedScriptData::new_(cx, 1, 1, 0);
-    if (!ssd)
+    if (!script->createScriptData(cx, 1, 1, 0))
         return false;
 
-    ssd->data[0] = JSOP_RETRVAL;
-    ssd->data[1] = SRC_NULL;
-    script->setLength(1);
-    return SaveSharedScriptData(cx, script, ssd, 1);
+    jsbytecode* code = script->code();
+    code[0] = JSOP_RETRVAL;
+    code[1] = SRC_NULL;
+    return script->shareScriptData(cx);
 }
 
 /* static */ void
 JSScript::linkToFunctionFromEmitter(js::ExclusiveContext* cx, JS::Handle<JSScript*> script,
                                     js::frontend::FunctionBox* funbox)
 {
     script->funHasExtensibleScope_ = funbox->hasExtensibleScope();
     script->funNeedsDeclEnvObject_ = funbox->needsDeclEnvObject();
@@ -2921,29 +2903,26 @@ JSScript::fullyInitFromEmitter(Exclusive
         return false;
     }
 
     MOZ_ASSERT(script->mainOffset() == 0);
     script->mainOffset_ = prologueLength;
 
     script->lineno_ = bce->firstLine;
 
-    script->setLength(prologueLength + mainLength);
-    script->natoms_ = natoms;
-    SharedScriptData* ssd = SharedScriptData::new_(cx, script->length(), nsrcnotes, natoms);
-    if (!ssd)
+    if (!script->createScriptData(cx, prologueLength + mainLength, nsrcnotes, natoms))
         return false;
 
-    jsbytecode* code = ssd->data;
+    jsbytecode* code = script->code();
     PodCopy<jsbytecode>(code, bce->prologue.code.begin(), prologueLength);
     PodCopy<jsbytecode>(code + prologueLength, bce->main.code.begin(), mainLength);
     bce->copySrcNotes((jssrcnote*)(code + script->length()), nsrcnotes);
-    InitAtomMap(bce->atomIndices.getMap(), ssd->atoms());
-
-    if (!SaveSharedScriptData(cx, script, ssd, nsrcnotes))
+    InitAtomMap(bce->atomIndices.getMap(), script->atoms());
+
+    if (!script->shareScriptData(cx))
         return false;
 
     if (bce->constList.length() != 0)
         bce->constList.finish(script->consts());
     if (bce->objectList.length != 0)
         bce->objectList.finish(script->objects());
     if (bce->tryNoteList.length() != 0)
         bce->tryNoteList.finish(script->trynotes());
@@ -3147,16 +3126,19 @@ JSScript::finalize(FreeOp* fop)
     destroyScriptCounts(fop);
     destroyDebugScript(fop);
 
     if (data) {
         JS_POISON(data, 0xdb, computedSizeOfData());
         fop->free_(data);
     }
 
+    if (scriptData_)
+        scriptData_->decRefCount();
+
     fop->runtime()->contextFromMainThread()->caches.lazyScriptCache.remove(this);
 
     // In most cases, our LazyScript's script pointer will reference this
     // script, and thus be nulled out by normal weakref processing. However, if
     // we unlazified the LazyScript during incremental sweeping, it will have a
     // completely different JSScript.
     MOZ_ASSERT_IF(lazyScript && !IsAboutToBeFinalizedUnbarriered(&lazyScript),
                   !lazyScript->hasScript() || lazyScript->maybeScriptUnbarriered() != this);
@@ -3557,23 +3539,20 @@ js::detail::CopyScript(JSContext* cx, Ha
     /* This assignment must occur before all the Rebase calls. */
     dst->data = data.forget();
     dst->dataSize_ = size;
     MOZ_ASSERT(bool(dst->data) == bool(src->data));
     if (dst->data)
         memcpy(dst->data, src->data, size);
 
     /* Script filenames, bytecodes and atoms are runtime-wide. */
-    dst->setCode(src->code());
-    dst->atoms = src->atoms;
-
-    dst->setLength(src->length());
+    dst->setScriptData(src->scriptData());
+
     dst->lineno_ = src->lineno();
     dst->mainOffset_ = src->mainOffset();
-    dst->natoms_ = src->natoms();
     dst->funLength_ = src->funLength();
     dst->nTypeSets_ = src->nTypeSets();
     dst->nslots_ = src->nslots();
     if (src->argumentsHasVarBinding()) {
         dst->setArgumentsHasVarBinding();
         if (src->analyzedArgsUsage())
             dst->setNeedsArgsObj(src->needsArgsObj());
     }
@@ -3910,31 +3889,37 @@ JSScript::hasBreakpointsAt(jsbytecode* p
     BreakpointSite* site = getBreakpointSite(pc);
     if (!site)
         return false;
 
     return site->enabledCount > 0;
 }
 
 void
+SharedScriptData::traceChildren(JSTracer* trc)
+{
+    MOZ_ASSERT(refCount() != 0);
+    for (uint32_t i = 0; i < natoms(); ++i)
+        TraceNullableEdge(trc, &atoms()[i], "atom");
+}
+
+void
 JSScript::traceChildren(JSTracer* trc)
 {
     // NOTE: this JSScript may be partially initialized at this point.  E.g. we
     // may have created it and partially initialized it with
     // JSScript::Create(), but not yet finished initializing it with
     // fullyInitFromEmitter() or fullyInitTrivial().
 
     MOZ_ASSERT_IF(trc->isMarkingTracer() &&
                   static_cast<GCMarker*>(trc)->shouldCheckCompartments(),
                   zone()->isCollecting());
 
-    if (atoms) {
-        for (uint32_t i = 0; i < natoms(); ++i)
-            TraceNullableEdge(trc, &atoms[i], "atom");
-    }
+    if (scriptData())
+        scriptData()->traceChildren(trc);
 
     if (hasObjects()) {
         ObjectArray* objarray = objects();
         TraceRange(trc, objarray->length, objarray->vector, "objects");
     }
 
     if (hasConsts()) {
         ConstArray* constarray = consts();
@@ -3947,23 +3932,19 @@ JSScript::traceChildren(JSTracer* trc)
     TraceNullableEdge(trc, &function_, "function");
     TraceNullableEdge(trc, &module_, "module");
 
     TraceNullableEdge(trc, &enclosingStaticScope_, "enclosingStaticScope");
 
     if (maybeLazyScript())
         TraceManuallyBarrieredEdge(trc, &lazyScript, "lazyScript");
 
-    if (trc->isMarkingTracer()) {
+    if (trc->isMarkingTracer())
         compartment()->mark();
 
-        if (code())
-            MarkScriptData(trc->runtime(), code());
-    }
-
     bindings.trace(trc);
 
     jit::TraceJitScripts(trc, this);
 }
 
 void
 LazyScript::finalize(FreeOp* fop)
 {
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -929,16 +929,106 @@ XDRLazyScript(XDRState<mode>* xdr, Handl
 
 /*
  * Code any constant value.
  */
 template<XDRMode mode>
 bool
 XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp);
 
+/*
+ * Common data that can be shared between many scripts in a single runtime.
+ */
+class SharedScriptData
+{
+    // This class is reference counted as follows: each pointer from a JSScript
+    // counts as one reference plus there may be one reference from the shared
+    // script data table.
+    mozilla::Atomic<uint32_t> refCount_;
+
+    uint32_t dataLength_;
+    uint32_t natoms_;
+    uint32_t codeLength_;
+    uintptr_t data_[1];
+
+  public:
+    static SharedScriptData* new_(ExclusiveContext* cx, uint32_t codeLength,
+                                  uint32_t srcnotesLength, uint32_t natoms);
+
+    uint32_t refCount() const {
+        return refCount_;
+    }
+    void incRefCount() {
+        refCount_++;
+    }
+    void decRefCount() {
+        MOZ_ASSERT(refCount_ != 0);
+        refCount_--;
+        if (refCount_ == 0)
+            js_free(this);
+    }
+
+    uint32_t dataLength() const {
+        return dataLength_;
+    }
+    uint8_t* data() {
+        return reinterpret_cast<uint8_t*>(data_);
+    }
+
+    uint32_t natoms() const {
+        return natoms_;
+    }
+    GCPtrAtom* atoms() {
+        if (!natoms_)
+            return nullptr;
+        return reinterpret_cast<GCPtrAtom*>(data());
+    }
+
+    uint32_t codeLength() const {
+        return codeLength_;
+    }
+    jsbytecode* code() {
+        return reinterpret_cast<jsbytecode*>(data() + natoms_ * sizeof(GCPtrAtom));
+    }
+
+    void traceChildren(JSTracer* trc);
+
+  private:
+    SharedScriptData() = delete;
+    SharedScriptData(const SharedScriptData&) = delete;
+    SharedScriptData& operator=(const SharedScriptData&) = delete;
+};
+
+struct ScriptBytecodeHasher
+{
+    struct Lookup
+    {
+        const uint8_t* data;
+        uint32_t length;
+
+        explicit Lookup(SharedScriptData* ssd) : data(ssd->data()), length(ssd->dataLength()) {}
+    };
+    static HashNumber hash(const Lookup& l) { return mozilla::HashBytes(l.data, l.length); }
+    static bool match(SharedScriptData* entry, const Lookup& lookup) {
+        if (entry->dataLength() != lookup.length)
+            return false;
+        return mozilla::PodEqual<uint8_t>(entry->data(), lookup.data, lookup.length);
+    }
+};
+
+typedef HashSet<SharedScriptData*,
+                ScriptBytecodeHasher,
+                SystemAllocPolicy> ScriptDataTable;
+
+extern void
+SweepScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock);
+
+extern void
+FreeScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock);
+
 } /* namespace js */
 
 class JSScript : public js::gc::TenuredCell
 {
     template <js::XDRMode mode>
     friend
     bool
     js::XDRScript(js::XDRState<mode>* xdr, js::HandleObject enclosingScope,
@@ -975,23 +1065,21 @@ class JSScript : public js::gc::TenuredC
 
     js::Shape* callObjShape() const {
         return bindings.callObjShape();
     }
 
     // Word-sized fields.
 
   private:
-    jsbytecode*     code_;     /* bytecodes and their immediate operands */
+    js::SharedScriptData* scriptData_;
   public:
     uint8_t*        data;      /* pointer to variable-length data array (see
                                    comment above Create() for details) */
 
-    js::GCPtrAtom* atoms;      /* maps immediate index to literal struct */
-
     JSCompartment*  compartment_;
 
   private:
     /* Persistent type information retained across GCs. */
     js::TypeScript* types_;
 
     // This script's ScriptSourceObject, or a CCW thereof.
     //
@@ -1021,26 +1109,24 @@ class JSScript : public js::gc::TenuredC
      * Pointer to either baseline->method()->raw() or ion->method()->raw(), or
      * nullptr if there's no Baseline or Ion script.
      */
     uint8_t* baselineOrIonRaw;
     uint8_t* baselineOrIonSkipArgCheck;
 
     // 32-bit fields.
 
-    uint32_t        length_;    /* length of code vector */
     uint32_t        dataSize_;  /* size of the used part of the data array */
 
     uint32_t        lineno_;    /* base line number of script */
     uint32_t        column_;    /* base column of script, optionally set */
 
     uint32_t        mainOffset_;/* offset of main entry point from code, after
                                    predef'ing prologue */
 
-    uint32_t        natoms_;    /* length of atoms array */
     uint32_t        nslots_;    /* vars plus maximum stack depth */
 
     /* Range of characters in scriptSource which contains this script's source. */
     uint32_t        sourceStart_;
     uint32_t        sourceEnd_;
 
     // Number of times the script has been called or has had backedges taken.
     // When running in ion, also increased for any inlined scripts. Reset if
@@ -1195,17 +1281,17 @@ class JSScript : public js::gc::TenuredC
 
     bool isDerivedClassConstructor_:1;
     bool isDefaultClassConstructor_:1;
 
     // Add padding so JSScript is gc::Cell aligned. Make padding protected
     // instead of private to suppress -Wunused-private-field compiler warnings.
   protected:
 #if JS_BITS_PER_WORD == 32
-    // No padding currently required.
+    uint32_t padding_;
 #endif
 
     //
     // End of fields.  Start methods.
     //
 
   public:
     static JSScript* Create(js::ExclusiveContext* cx,
@@ -1243,27 +1329,31 @@ class JSScript : public js::gc::TenuredC
   public:
     inline JSPrincipals* principals();
 
     JSCompartment* compartment() const { return compartment_; }
     JSCompartment* maybeCompartment() const { return compartment(); }
 
     void setVersion(JSVersion v) { version = v; }
 
+    js::SharedScriptData* scriptData() {
+        return scriptData_;
+    }
+
     // Script bytecode is immutable after creation.
     jsbytecode* code() const {
-        return code_;
+        if (!scriptData_)
+            return nullptr;
+        return scriptData_->code();
     }
     size_t length() const {
-        return length_;
+        MOZ_ASSERT(scriptData_);
+        return scriptData_->codeLength();
     }
 
-    void setCode(jsbytecode* code) { code_ = code; }
-    void setLength(size_t length) { length_ = length; }
-
     jsbytecode* codeEnd() const { return code() + length(); }
 
     jsbytecode* lastPC() const {
         jsbytecode* pc = codeEnd() - js::JSOP_RETRVAL_LENGTH;
         MOZ_ASSERT(*pc == JSOP_RETRVAL);
         return pc;
     }
 
@@ -1709,16 +1799,22 @@ class JSScript : public js::gc::TenuredC
 
     // Switch the script over from the off-thread compartment's static
     // global lexical scope to the main thread compartment's.
     void fixEnclosingStaticGlobalLexicalScope();
 
   private:
     bool makeTypes(JSContext* cx);
 
+    bool createScriptData(js::ExclusiveContext* cx, uint32_t codeLength, uint32_t srcnotesLength,
+                          uint32_t natoms);
+    bool shareScriptData(js::ExclusiveContext* cx);
+    void freeScriptData();
+    void setScriptData(js::SharedScriptData* data);
+
   public:
     uint32_t getWarmUpCount() const { return warmUpCount; }
     uint32_t incWarmUpCounter(uint32_t amount = 1) { return warmUpCount += amount; }
     uint32_t* addressOfWarmUpCounter() { return reinterpret_cast<uint32_t*>(&warmUpCount); }
     static size_t offsetOfWarmUpCounter() { return offsetof(JSScript, warmUpCount); }
     void resetWarmUpCounter() { incWarmUpResetCounter(); warmUpCount = 0; }
 
     uint16_t getWarmUpResetCount() const { return warmUpResetCount; }
@@ -1802,21 +1898,28 @@ class JSScript : public js::gc::TenuredC
 
     js::YieldOffsetArray& yieldOffsets() {
         MOZ_ASSERT(hasYieldOffsets());
         return *reinterpret_cast<js::YieldOffsetArray*>(data + yieldOffsetsOffset());
     }
 
     bool hasLoops();
 
-    size_t natoms() const { return natoms_; }
+    size_t natoms() const {
+        MOZ_ASSERT(scriptData_);
+        return scriptData_->natoms();
+    }
+    js::GCPtrAtom* atoms() const {
+        MOZ_ASSERT(scriptData_);
+        return scriptData_->atoms();
+    }
 
     js::GCPtrAtom& getAtom(size_t index) const {
         MOZ_ASSERT(index < natoms());
-        return atoms[index];
+        return atoms()[index];
     }
 
     js::GCPtrAtom& getAtom(jsbytecode* pc) const {
         MOZ_ASSERT(containsPC(pc) && containsPC(pc + sizeof(uint32_t)));
         return getAtom(GET_UINT32_INDEX(pc));
     }
 
     js::PropertyName* getName(size_t index) {
@@ -2406,71 +2509,16 @@ class LazyScript : public gc::TenuredCel
     uint64_t packedFields() const {
         return packedFields_;
     }
 };
 
 /* If this fails, add/remove padding within LazyScript. */
 JS_STATIC_ASSERT(sizeof(LazyScript) % js::gc::CellSize == 0);
 
-struct SharedScriptData
-{
-    uint32_t length;
-    uint32_t natoms;
-    mozilla::Atomic<bool, mozilla::ReleaseAcquire> marked;
-    jsbytecode data[1];
-
-    static SharedScriptData* new_(ExclusiveContext* cx, uint32_t codeLength,
-                                  uint32_t srcnotesLength, uint32_t natoms);
-
-    GCPtrAtom* atoms() {
-        if (!natoms)
-            return nullptr;
-        return reinterpret_cast<GCPtrAtom*>(data + length - sizeof(JSAtom*) * natoms);
-    }
-
-    static SharedScriptData* fromBytecode(const jsbytecode* bytecode) {
-        return (SharedScriptData*)(bytecode - offsetof(SharedScriptData, data));
-    }
-
-  private:
-    SharedScriptData() = delete;
-    SharedScriptData(const SharedScriptData&) = delete;
-};
-
-struct ScriptBytecodeHasher
-{
-    struct Lookup
-    {
-        jsbytecode*         code;
-        uint32_t            length;
-
-        explicit Lookup(SharedScriptData* ssd) : code(ssd->data), length(ssd->length) {}
-    };
-    static HashNumber hash(const Lookup& l) { return mozilla::HashBytes(l.code, l.length); }
-    static bool match(SharedScriptData* entry, const Lookup& lookup) {
-        if (entry->length != lookup.length)
-            return false;
-        return mozilla::PodEqual<jsbytecode>(entry->data, lookup.code, lookup.length);
-    }
-};
-
-typedef HashSet<SharedScriptData*,
-                ScriptBytecodeHasher,
-                SystemAllocPolicy> ScriptDataTable;
-
-extern void
-UnmarkScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock);
-
-extern void
-SweepScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock);
-
-extern void
-FreeScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock);
-
 struct ScriptAndCounts
 {
     /* This structure is stored and marked from the JSRuntime. */
     JSScript* script;
     ScriptCounts scriptCounts;
 
     inline explicit ScriptAndCounts(JSScript* script);
     inline ScriptAndCounts(ScriptAndCounts&& sac);
--- a/js/src/tests/browser.js
+++ b/js/src/tests/browser.js
@@ -8,48 +8,147 @@
 //       nested shell.js/browser.js.  Second, can you instead add it to
 //       shell.js?  Our goal is to unify these two files for readability, and
 //       the plan is to empty out this file into that one over time.  Third,
 //       supposing you must add to this file, please add it to this IIFE for
 //       better modularity/resilience against tests that must do particularly
 //       bizarre things that might break the harness.
 
 (function(global) {
+  /**********************************************************************
+   * CACHED PRIMORDIAL FUNCTIONALITY (before a test might overwrite it) *
+   **********************************************************************/
+
+  var ReflectApply = global.Reflect.apply;
+
+  // BEWARE: ObjectGetOwnPropertyDescriptor is only safe to use if its result
+  //         is inspected using own-property-examining functionality.  Directly
+  //         accessing properties on a returned descriptor without first
+  //         verifying the property's existence can invoke user-modifiable
+  //         behavior.
+  var ObjectGetOwnPropertyDescriptor = global.Object.getOwnPropertyDescriptor;
+
+  var document = global.document;
+  var documentBody = global.document.body;
+  var documentDocumentElement = global.document.documentElement;
+  var DocumentCreateElement = global.document.createElement;
+  var ElementInnerHTMLSetter =
+    ObjectGetOwnPropertyDescriptor(global.Element.prototype, "innerHTML").set;
+  var HTMLIFramePrototypeContentWindowGetter =
+    ObjectGetOwnPropertyDescriptor(global.HTMLIFrameElement.prototype, "contentWindow").get;
+  var HTMLIFramePrototypeRemove = global.HTMLIFrameElement.prototype.remove;
+  var NodePrototypeAppendChild = global.Node.prototype.appendChild;
+  var NodePrototypeTextContentSetter =
+    ObjectGetOwnPropertyDescriptor(global.Node.prototype, "textContent").set;
+
+  // Cached DOM nodes used by the test harness itself.  (We assume the test
+  // doesn't misbehave in a way that actively interferes with what the test
+  // harness runner observes, e.g. navigating the page to a different location.
+  // Short of running every test in a worker -- which has its own problems --
+  // there's no way to isolate a test from the page to that extent.)
+  var printOutputContainer =
+    global.document.getElementById("jsreftest-print-output-container");
+
+  /****************************
+   * GENERAL HELPER FUNCTIONS *
+   ****************************/
+
+  function AppendChild(elt, kid) {
+    ReflectApply(NodePrototypeAppendChild, elt, [kid]);
+  }
+
+  function CreateElement(name) {
+    return ReflectApply(DocumentCreateElement, document, [name]);
+  }
+
+  function HTMLSetAttribute(element, name, value) {
+    ReflectApply(HTMLElementPrototypeSetAttribute, element, [name, value]);
+  }
+
+  function SetTextContent(element, text) {
+    ReflectApply(NodePrototypeTextContentSetter, element, [text]);
+  }
+
   /****************************
    * UTILITY FUNCTION EXPORTS *
    ****************************/
 
   var newGlobal = global.newGlobal;
   if (typeof newGlobal !== "function") {
     newGlobal = function newGlobal() {
-      var iframe = global.document.createElement("iframe");
-      global.document.documentElement.appendChild(iframe);
-      var win = iframe.contentWindow;
-      iframe.remove();
+      var iframe = CreateElement("iframe");
+      AppendChild(documentDocumentElement, iframe);
+      var win =
+        ReflectApply(HTMLIFramePrototypeContentWindowGetter, iframe, []);
+      ReflectApply(HTMLIFramePrototypeRemove, iframe, []);
+
       // Shim in "evaluate"
       win.evaluate = win.eval;
       return win;
     };
     global.newGlobal = newGlobal;
   }
 
-  // This function is *only* used in this file!  Ultimately it should only be
-  // used by other exports in this IIFE, but for now just export it so that
-  // functions not exported within this IIFE (but still in this file) can use
-  // it.
-  function DocumentWrite(s) {
-    try {
-      var msgDiv = global.document.createElement('div');
-      msgDiv.innerHTML = s;
-      global.document.body.appendChild(msgDiv);
-    } catch (e) {
-      global.document.write(s + '<br>\n');
+  // This function is *only* used by shell.js's for-browsers |print()| function!
+  // It's only defined/exported here because it needs CreateElement and friends,
+  // only defined here, and we're not yet ready to move them to shell.js.
+  function AddPrintOutput(s) {
+    var msgDiv = CreateElement("div");
+    SetTextContent(msgDiv, s);
+    AppendChild(printOutputContainer, msgDiv);
+  }
+  global.AddPrintOutput = AddPrintOutput;
+
+  /*************************************************************************
+   * HARNESS-CENTRIC EXPORTS (we should generally work to eliminate these) *
+   *************************************************************************/
+
+  // This overwrites shell.js's version that merely prints the given string.
+  function writeHeaderToLog(string) {
+    string = String(string);
+
+    // First dump to the console.
+    dump(string + "\n");
+
+    // Then output to the page.
+    var h2 = CreateElement("h2");
+    SetTextContent(h2, string);
+    AppendChild(printOutputContainer, h2);
+  }
+  global.writeHeaderToLog = writeHeaderToLog;
+
+  // XXX This function overwrites one in shell.js.  We should define the
+  //     separate versions in a single location.  Also the dependence on
+  //     |global.{PASSED,FAILED}| is very silly.
+  function writeFormattedResult(expect, actual, string, passed) {
+    // XXX remove this?  it's unneeded in the shell version
+    string = String(string);
+
+    dump(string + "\n");
+
+    var font = CreateElement("font");
+    if (passed) {
+      HTMLSetAttribute(font, "color", "#009900");
+      SetTextContent(font, " \u00A0" + global.PASSED);
+    } else {
+      HTMLSetAttribute(font, "color", "#aa0000");
+      SetTextContent(font, "\u00A0" + global.FAILED + expect);
     }
+
+    var b = CreateElement("b");
+    AppendChild(b, font);
+
+    var tt = CreateElement("tt");
+    SetTextContent(tt, string);
+    AppendChild(tt, b);
+
+    AppendChild(printOutputContainer, tt);
+    AppendChild(printOutputContainer, CreateElement("br"));
   }
-  global.DocumentWrite = DocumentWrite;
+  global.writeFormattedResult = writeFormattedResult;
 })(this);
 
 
 var gPageCompleted;
 var GLOBAL = this + '';
 
 // Variables local to jstests harness.
 var jstestsTestPassesUnlessItThrows = false;
@@ -62,93 +161,24 @@ var jstestsOptions;
  *
  * Overrides the same-named function in shell.js.
  */
 function testPassesUnlessItThrows() {
   jstestsTestPassesUnlessItThrows = true;
 }
 
 /*
- * Requests to load the given JavaScript file before the file containing the
- * test case.
- */
-function include(file) {
-  outputscripttag(file, {language: "type", mimetype: "text/javascript"});
-}
-
-/*
  * Sets a restore function which restores the standard built-in ECMAScript
  * properties after a destructive test case, and which will be called after
  * the test case terminates.
  */
 function setRestoreFunction(restore) {
   jstestsRestoreFunction = restore;
 }
 
-function htmlesc(str) {
-  if (str == '<')
-    return '&lt;';
-  if (str == '>')
-    return '&gt;';
-  if (str == '&')
-    return '&amp;';
-  return str;
-}
-
-function print() {
-  var s = 'TEST-INFO | ';
-  var a;
-  for (var i = 0; i < arguments.length; i++)
-  {
-    a = arguments[i];
-    s += String(a) + ' ';
-  }
-
-  if (typeof dump == 'function')
-  {
-    dump( s + '\n');
-  }
-
-  s = s.replace(/[<>&]/g, htmlesc);
-
-  DocumentWrite(s);
-}
-
-function writeHeaderToLog( string ) {
-  string = String(string);
-
-  if (typeof dump == 'function')
-  {
-    dump( string + '\n');
-  }
-
-  string = string.replace(/[<>&]/g, htmlesc);
-
-  DocumentWrite( "<h2>" + string + "</h2>" );
-}
-
-function writeFormattedResult( expect, actual, string, passed ) {
-  string = String(string);
-
-  if (typeof dump == 'function')
-  {
-    dump( string + '\n');
-  }
-
-  string = string.replace(/[<>&]/g, htmlesc);
-
-  var s = "<tt>"+ string ;
-  s += "<b>" ;
-  s += ( passed ) ? "<font color=#009900> &nbsp;" + PASSED
-    : "<font color=#aa0000>&nbsp;" +  FAILED + expect;
-
-  DocumentWrite( s + "</font></b></tt><br>" );
-  return passed;
-}
-
 window.onerror = function (msg, page, line)
 {
   jstestsTestPassesUnlessItThrows = false;
 
   // Restore options in case a test case used this common variable name.
   options = jstestsOptions;
 
   // Restore the ECMAScript environment after potentially destructive tests.
@@ -189,20 +219,16 @@ function gc()
     SpecialPowers.forceGC();
   }
   catch(ex)
   {
     print('gc: ' + ex);
   }
 }
 
-function quit()
-{
-}
-
 function options(aOptionName)
 {
   // return value of options() is a comma delimited list
   // of the previously set values
 
   var value = '';
   for (var optionName in options.currvalues)
   {
@@ -271,21 +297,16 @@ function optionsInit() {
     }
     else
     {
       options.initvalues[optionName] = true;
     }
   }
 }
 
-function gczeal(z)
-{
-  SpecialPowers.setGCZeal(z);
-}
-
 function jsTestDriverBrowserInit()
 {
 
   if (typeof dump != 'function')
   {
     dump = print;
   }
 
--- a/js/src/tests/ecma/ExecutionContexts/10.1.4-1.js
+++ b/js/src/tests/ecma/ExecutionContexts/10.1.4-1.js
@@ -63,15 +63,14 @@ function test() {
     gTestcases[gTc].passed = writeTestCaseResult(
       gTestcases[gTc].expect,
       gTestcases[gTc].actual,
       gTestcases[gTc].description +" = "+
       gTestcases[gTc].actual );
 
     gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value ";
   }
-  stopTest();
   return ( gTestcases );
 }
 
 function MyObject() {
   this.eval = new Function( "x", "return(Math.pow(Number(x),2))" );
 }
--- a/js/src/tests/ecma/ExecutionContexts/10.1.4-10.js
+++ b/js/src/tests/ecma/ExecutionContexts/10.1.4-10.js
@@ -61,11 +61,10 @@ function test() {
     gTestcases[gTc].passed = writeTestCaseResult(
       gTestcases[gTc].expect,
       gTestcases[gTc].actual,
       gTestcases[gTc].description +" = "+
       gTestcases[gTc].actual );
 
     gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value ";
   }
-  stopTest();
   return ( gTestcases );
 }
--- a/js/src/tests/ecma/ExecutionContexts/10.1.4-2.js
+++ b/js/src/tests/ecma/ExecutionContexts/10.1.4-2.js
@@ -65,15 +65,14 @@ function test() {
     gTestcases[gTc].passed = writeTestCaseResult(
       gTestcases[gTc].expect,
       gTestcases[gTc].actual,
       gTestcases[gTc].description +" = "+
       gTestcases[gTc].actual );
 
     gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value ";
   }
-  stopTest();
   return ( gTestcases );
 }
 
 function MyObject() {
   this.eval = new Function( "x", "return(Math.pow(Number(x),2))" );
 }
--- a/js/src/tests/ecma/ExecutionContexts/10.1.4-3.js
+++ b/js/src/tests/ecma/ExecutionContexts/10.1.4-3.js
@@ -63,15 +63,14 @@ function test() {
     gTestcases[gTc].passed = writeTestCaseResult(
       gTestcases[gTc].expect,
       gTestcases[gTc].actual,
       gTestcases[gTc].description +" = "+
       gTestcases[gTc].actual );
 
     gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value ";
   }
-  stopTest();
   return ( gTestcases );
 }
 
 function MyObject() {
   this.eval = new Function( "x", "return(Math.pow(Number(x),2))" );
 }
--- a/js/src/tests/ecma/ExecutionContexts/10.1.4-4.js
+++ b/js/src/tests/ecma/ExecutionContexts/10.1.4-4.js
@@ -65,15 +65,14 @@ function test() {
     gTestcases[gTc].passed = writeTestCaseResult(
       gTestcases[gTc].expect,
       gTestcases[gTc].actual,
       gTestcases[gTc].description +" = "+
       gTestcases[gTc].actual );
 
     gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value ";
   }
-  stopTest();
   return ( gTestcases );
 }
 
 function MyObject() {
   this.eval = new Function( "x", "return(Math.pow(Number(x),2))" );
 }
--- a/js/src/tests/ecma/ExecutionContexts/10.1.4-5.js
+++ b/js/src/tests/ecma/ExecutionContexts/10.1.4-5.js
@@ -65,14 +65,13 @@ function test() {
     gTestcases[gTc].passed = writeTestCaseResult(
       gTestcases[gTc].expect,
       gTestcases[gTc].actual,
       gTestcases[gTc].description +" = "+
       gTestcases[gTc].actual );
 
     gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value ";
   }
-  stopTest();
   return ( gTestcases );
 }
 function MyObject() {
   this.eval = new Function( "x", "return(Math.pow(Number(x),2))" );
 }
--- a/js/src/tests/ecma/ExecutionContexts/10.1.4-7.js
+++ b/js/src/tests/ecma/ExecutionContexts/10.1.4-7.js
@@ -64,15 +64,14 @@ function test() {
     gTestcases[gTc].passed = writeTestCaseResult(
       gTestcases[gTc].expect,
       gTestcases[gTc].actual,
       gTestcases[gTc].description +" = "+
       gTestcases[gTc].actual );
 
     gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value ";
   }
-  stopTest();
   return ( gTestcases );
 }
 
 function MyObject() {
   this.eval = new Function( "x", "return(Math.pow(Number(x),2))" );
 }
--- a/js/src/tests/ecma/ExecutionContexts/10.1.4-8.js
+++ b/js/src/tests/ecma/ExecutionContexts/10.1.4-8.js
@@ -65,15 +65,14 @@ function test() {
     gTestcases[gTc].passed = writeTestCaseResult(
       gTestcases[gTc].expect,
       gTestcases[gTc].actual,
       gTestcases[gTc].description +" = "+
       gTestcases[gTc].actual );
 
     gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value ";
   }
-  stopTest();
   return ( gTestcases );
 }
 
 function MyObject() {
   this.eval = new Function( "x", "return(Math.pow(Number(x),2))" );
 }
--- a/js/src/tests/ecma/ExecutionContexts/10.1.5-3.js
+++ b/js/src/tests/ecma/ExecutionContexts/10.1.5-3.js
@@ -86,11 +86,10 @@ function test() {
     gTestcases[gTc].passed = writeTestCaseResult(
       gTestcases[gTc].expect,
       gTestcases[gTc].actual,
       gTestcases[gTc].description +" = "+
       gTestcases[gTc].actual );
 
     gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value ";
   }
-  stopTest();
   return ( gTestcases );
 }
--- a/js/src/tests/ecma/LexicalConventions/7.3-1.js
+++ b/js/src/tests/ecma/LexicalConventions/7.3-1.js
@@ -48,11 +48,10 @@ function test() {
     gTestcases[gTc].passed = writeTestCaseResult(
       gTestcases[gTc].expect,
       gTestcases[gTc].actual,
       gTestcases[gTc].description +":  "+
       gTestcases[gTc].actual );
 
     gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : " ignored chars after line terminator of single-line comment";
   }
-  stopTest();
   return ( gTestcases );
 }
--- a/js/src/tests/ecma/extensions/10.1.4-9.js
+++ b/js/src/tests/ecma/extensions/10.1.4-9.js
@@ -63,14 +63,13 @@ function test() {
     gTestcases[gTc].passed = writeTestCaseResult(
       gTestcases[gTc].expect,
       gTestcases[gTc].actual,
       gTestcases[gTc].description +" = "+
       gTestcases[gTc].actual );
 
     gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value ";
   }
-  stopTest();
   return ( gTestcases );
 }
 function MyObject( n ) {
   this.__proto__ = Number.prototype;
 }
--- a/js/src/tests/jsreftest.html
+++ b/js/src/tests/jsreftest.html
@@ -4,16 +4,20 @@
     <!--
       This test driver is to be invoked using 
       jsreftest.html?test=path-to-test-js
 
       It will load the associated test javascript file 
       using the default script language attributes, then execute the
       test.
     -->
-    <script type="text/javascript" src="shell.js">
-    </script>
-    <script type="text/javascript" src="browser.js">
-    </script>
   </head>
   <body>
+    <!--
+      print() appends div-element children to this, so this div must appear
+      before all script elements.
+    -->
+    <div id="jsreftest-print-output-container"></div>
+
+    <script type="text/javascript" src="shell.js"></script>
+    <script type="text/javascript" src="browser.js"></script>
   </body>
 </html>
--- a/js/src/tests/shell.js
+++ b/js/src/tests/shell.js
@@ -13,24 +13,36 @@
   /**********************************************************************
    * CACHED PRIMORDIAL FUNCTIONALITY (before a test might overwrite it) *
    **********************************************************************/
 
   var undefined; // sigh
 
   var Error = global.Error;
   var Number = global.Number;
+  var String = global.String;
   var TypeError = global.TypeError;
 
   var ArrayIsArray = global.Array.isArray;
   var ObjectCreate = global.Object.create;
   var ObjectDefineProperty = global.Object.defineProperty;
   var ReflectApply = global.Reflect.apply;
   var StringPrototypeEndsWith = global.String.prototype.endsWith;
 
+  var runningInBrowser = typeof global.window !== "undefined";
+  if (runningInBrowser) {
+    // Certain cached functionality only exists (and is only needed) when
+    // running in the browser.  Segregate that caching here.
+
+    var SpecialPowersSetGCZeal =
+      global.SpecialPowers ? global.SpecialPowers.setGCZeal : undefined;
+  }
+
+  var runningInShell = typeof window === "undefined";
+
   /****************************
    * GENERAL HELPER FUNCTIONS *
    ****************************/
 
   // We could use Array.prototype.pop, but we don't so it's clear exactly what
   // dependencies this function has on test-modifiable behavior (i.e. none).
   function ArrayPop(arr) {
     assertEq(ArrayIsArray(arr), true,
@@ -137,34 +149,98 @@
     throw new Error(fullmsg);
   }
   global.assertThrowsInstanceOf = assertThrowsInstanceOf;
 
   /****************************
    * UTILITY FUNCTION EXPORTS *
    ****************************/
 
-  // Eventually this polyfill should be defined here, not in browser.js.  For
-  // now tolerate more-resilient code depending on less-resilient code.
-  assertEq(typeof global.print, "function",
-           "print function is pre-existing, either provided by the shell or " +
-           "the already-executed top-level browser.js");
+  var dump = global.dump;
+  if (typeof global.dump === "function") {
+    // A presumptively-functional |dump| exists, so no need to do anything.
+  } else {
+    // We don't have |dump|.  Try to simulate the desired effect another way.
+    if (runningInBrowser) {
+      // We can't actually print to the console: |global.print| invokes browser
+      // printing functionality here (it's overwritten just below), and
+      // |global.dump| isn't a function that'll dump to the console (presumably
+      // because the preference to enable |dump| wasn't set).  Just make it a
+      // no-op.
+      dump = function() {};
+    } else {
+      // |print| prints to stdout: make |dump| do likewise.
+      dump = global.print;
+    }
+    global.dump = dump;
+  }
+
+  var print;
+  if (runningInBrowser) {
+    // We're executing in a browser.  Using |global.print| would invoke browser
+    // printing functionality: not what tests want!  Instead, use a print
+    // function that syncs up with the test harness and console.
+    print = function print() {
+      var s = "TEST-INFO | ";
+      for (var i = 0; i < arguments.length; i++)
+        s += String(arguments[i]) + " ";
+
+      // Dump the string to the console for developers and the harness.
+      dump(s + "\n");
+
+      // AddPrintOutput doesn't require HTML special characters be escaped.
+      global.AddPrintOutput(s);
+    };
+
+    global.print = print;
+  } else {
+    // We're executing in a shell, and |global.print| is the desired function.
+    print = global.print;
+  }
+
+  var quit = global.quit;
+  if (typeof quit !== "function") {
+    // XXX There's something very strange about quit() in browser runs being a
+    //     function that doesn't quit at all (!).  We should get rid of quit()
+    //     as an integral part of tests in favor of something else.
+    quit = function quit() {};
+    global.quit = quit;
+  }
+
+  var gczeal = global.gczeal;
+  if (typeof gczeal !== "function") {
+    if (typeof SpecialPowersSetGCZeal === "function") {
+      gczeal = function gczeal(z) {
+        SpecialPowersSetGCZeal(z);
+      };
+    } else {
+      gczeal = function() {}; // no-op if not available
+    }
+
+    global.gczeal = gczeal;
+  }
 
   /******************************************************
    * TEST METADATA EXPORTS (these are of dubious value) *
    ******************************************************/
 
   global.SECTION = "";
   global.VERSION = "";
   global.BUGNUMBER = "";
 
   /*************************************************************************
    * HARNESS-CENTRIC EXPORTS (we should generally work to eliminate these) *
    *************************************************************************/
 
+  var PASSED = " PASSED! ";
+  global.PASSED = PASSED;
+
+  var FAILED = " FAILED! ";
+  global.FAILED = FAILED;
+
   /** Set up test environment. */
   function startTest() {
     if (global.BUGNUMBER)
       global.print("BUGNUMBER: " + global.BUGNUMBER);
   }
   global.startTest = startTest;
 
   var callStack = [];
@@ -213,16 +289,24 @@
   function currentFunc() {
     if (callStack.length == 0)
       return "top level script";
 
     return callStack[callStack.length - 1];
   }
   global.currentFunc = currentFunc;
 
+  // XXX This function is *only* used in harness functions and really shouldn't
+  //     be exported.
+  var writeFormattedResult =
+    function writeFormattedResult(expect, actual, string, passed) {
+      print((passed ? PASSED : FAILED) + string + ' expected: ' + expect);
+    };
+  global.writeFormattedResult = writeFormattedResult;
+
   /*****************************************************
    * RHINO-SPECIFIC EXPORTS (are these used any more?) *
    *****************************************************/
 
   function inRhino() {
     return typeof global.defineClass === "function";
   }
   global.inRhino = inRhino;
@@ -237,35 +321,31 @@
     var cx = GetContext();
     cx.setOptimizationLevel(i);
   }
   global.OptLevel = OptLevel;
 })(this);
 
 
 var STATUS = "STATUS: ";
-var SECT_PREFIX = 'Section ';
-var SECT_SUFFIX = ' of test - ';
 
 var gDelayTestDriverEnd = false;
 
 var gTestcases = new Array();
 var gTc = gTestcases.length;
 var summary = '';
 var description = '';
 var expected = '';
 var actual = '';
 var msg = '';
 
 /*
  * constant strings
  */
 var GLOBAL = this + '';
-var PASSED = " PASSED! ";
-var FAILED = " FAILED! ";
 
 var DESCRIPTION;