merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 09 Aug 2016 15:44:51 +0200
changeset 398604 6cf0089510fad8deb866136f5b92bbced9498447
parent 398603 0813514a828bf931605bd22596d8fe876c1bb368 (current diff)
parent 398507 7bbd0953ab0b4d9ce9dab06e86a0f245b568b72c (diff)
child 398605 e6bd806cd94fceb012d89452abc013a0e7b90145
child 398609 3d8a310ac7c01a961c725ae44f45d26d80fc78b8
child 398621 613fec9a571e48c64918ebd0a8153096300ff992
child 398626 4d9747a41aa7e23c67164b4be88cb5ec887e3152
child 398672 575f6cc36d09f74a8e6af7873b8c184ae4e83aa0
child 398684 110678b15eff5d4a7c2cba7879bac6229237ba2e
child 398686 99f20b0c58d01780b427936cc216403535e6b46c
child 398703 19920bbdd56fdb9526e47d964df758515c6606e0
child 398713 e598f18c0d765090a7c83d433cb5fc169914c0e4
child 398746 cae2ee91f62d2b774cdbce4c3886472694f3f5a5
child 398762 0a1e0b3fa9bb0b267a9cc25fd0c1a949c2e0c143
child 398763 36731b8a4770b5a3874766850983f7a1beaf2647
child 398766 4369adb8b653c0c30160807c3e33f41667bebcfd
child 398779 4dd7d0ccddf9d06268d2061efe525226b5cf7c2b
child 398824 611414038a418082e7f741f8a12292c60ea1652c
child 398828 a3fcb88e90a48724a744e3b07dda7e1b33c6f583
child 398831 086d533edeb95bd49f345e1bb4128aefb88920c9
child 398844 74267a84971ef4b324e44f4e25f63d302a2dbeec
child 398845 9ad5336c62148db198e77f14486568a52725befc
child 398854 52484c6c91bfa6f6601522224ea41e3a6361fac0
child 398862 c6b246ff0815fb6d6508ff25baf3c14c670e654e
child 398873 0e3462cb58b4a915410dd8ef4861da6c6a2044ac
child 398874 dbf838910ae92a8742da0082d038cf4e42038462
child 398877 4854608587635b59d2f387b00551c1f88b590609
child 398878 90a352c0bcf8e9d3b14f62a27c76160272cd45bb
child 398891 5fcc575040072d952bcdd28dc9d9eb399ea2a2a9
child 398893 c739f1bd1d0d692c79063f2b3e72d7ab6341650f
child 398921 d241c96d3d5a0d1d4295e576ef9c1b1da2f21b68
child 398924 d63050dc71fc57fb3ab07d0d230644b0f4655118
child 398925 dfd373e8d0bd83a0416fe9fbdacc7764acb10787
child 398970 4516db9e1e1a17b78dc2a085dc397d0a585edc1d
child 398973 304c526d4ae0b2b91804131bad8c9b31e0c68195
child 398974 8590ade93167bee77faa21a4a725d7b76d9460ae
child 398975 dbd2f355e325c04b8382af62c7e3e75e62054a87
child 398992 a8ca7f128bb67778738cbb673148d04274faac96
child 398994 0567aa4d8716426764a4b9e1f479c5d983444b3d
child 399011 558308c7c69273b97db4465b21a0e8cce62e0c79
child 399015 86b657ffb8f496fc41922a75c70d6d8b2cbbe419
child 399017 98a35eb4da897149310141f8ffc71f53d42376b3
child 399029 c0be6e19ac281a8bfd39c7afd8fb6ce80aa979f1
child 399034 ebc8d8826cc46136d52d441a705a2939fd85160f
child 399060 639de48e57d30750f5996e7b352268db0c4cd5cc
child 399061 0e3763fbb688c839eeb9523bec7220928fac71ee
child 399062 e5f09bc5d6071e11682e6bd1be4fc069427a8dc7
child 399064 ab3587c84f721783f18ae128ec910b8971e8b3ab
child 399076 06e083e4d63788324bef400f35f4512a00a2cf6f
child 399091 40ba9e84d450ec0e869182c91b050df5c2564367
child 399092 19641d3832626313063fbaaa66ce31bc765c0407
child 399095 70f0dd1539ad142951e954f878d3bb85f57b6709
child 399096 f057c94084b189e822254bedfdbf2078c7179194
child 399101 cea56cb231f4a348c474640b8ed4242b3d197c1f
child 399186 599d65518eb52c8b833dbc7bd886a3b8f3dce6ab
child 399226 35beb02dbd3ecff0a5bffe50fbb3d2482762577e
child 399236 b5111de5c92931217e3a658c36fde65f789b03fb
child 399260 42404b7d229f02ce71f8fe9bdc6b62232b18218f
child 399261 24cea11332f965d6c81bd33d379225bf87823e9f
child 399267 e2f03a177c0a9685df0733258d58467a641ec149
child 399275 0ba16569cd12432c7b834a2298718210b3c17537
child 399363 9a97b8f866a71ddfe7bf1b04cda5c37ec2f6dd9b
child 399417 84e8d90301abc4389e1dc98657385e56c56d5d1f
child 399430 d15d758d149762171948177f8897567cfd7476e2
child 399431 e56ee0f5f66314dc9f1cf4d278c4a3be91954549
child 399482 944384214d6029d8a0bbad85f2a578cac3a50e0b
child 399486 cb071062ceb0666293ae1dc096edd516f48558ba
child 399514 bddaee635e00828cf40c1bcd7ebe0b5134245e2e
child 399560 0a1d1f717271984d10b7f7b0d5a3a243a34f523b
child 399602 dfa034f2ee42e3ec77bea3508fd439a332f4619a
child 399654 89517176e6a7849b1a9cf64d6de8d005c3f1de36
child 399664 62f860e704d86bbd5251b4cb81a3283c25a81ac0
child 399670 bc3394433bff6b25c4a2f28f4eb316116b0c35de
child 399679 b21224ccce04a228b9da8c71d8c6395479f3e2a5
child 399706 e99b742e2fdcda3ec1e1165d39c57426425c69ad
child 399715 7cbd6b64c417d8177cb4888f23dee953da6f20f8
child 399736 8c7ca039bd4b3558a8e08a8dd3bcaf5d2d5f3584
child 399798 645b642f822f5abfcaadcb8ecbef6edebbefaf26
child 399802 0c9305c229188400cd713142b4b6badfb80a4ab9
child 399805 d8d84227dd174752442804d47d3c70c63dca14e7
child 399883 03124cbde3b4cc54432f9c2383a7feb300877fae
child 399893 7e73163df241c8761f3ef991713d831d1132eac1
child 399928 ac42d941be454a736c45c3d3f9655e2454fad6ef
child 400258 f67be2658dac72574ef9a1a151cd683d34d1ebe4
child 400320 7b569409cd481ddfc9bef3ed544094d3c440363f
child 400553 829103cec4aec622a494bd916064c80d15ce935c
child 400570 a53d1b7e1884f41d2a31d4ed5cc7e0ffa3a3ad47
child 400572 e1e85421be5562bcd91dfad4c50d598e35fe4d81
child 400574 dc6fcc128337b1cd1d98ba5bce0406f14c7d9bfb
child 400586 16a98279db0a125b55dc3ca8fc51b8140a8d7324
child 400633 c2ec19b1e169b783a296c94aebb2821cde3baf8c
child 400942 fdda025c6e9d177b031404c2531a6eeeda7bb322
child 401167 f386a6a7fbf52d8ba3a63587b9bc7223d0472b8d
child 401485 f6044595d9e8210e95553298593731f74d7293ef
child 401558 7cac677b2ebef766f80377a308172c46527c09f6
child 401958 230bb61332f2bbcf7e36b8258570599e57ca78a8
child 402710 83e70a929e071dcebb325c942bb06f74e796e405
push id25578
push userbmo:jnicol@mozilla.com
push dateTue, 09 Aug 2016 14:31:52 +0000
reviewersmerge
milestone51.0a1
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;