Merge m-c to autoland. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 10 Oct 2018 12:32:05 -0400
changeset 499000 6eb2ec7487c13cbfc024ed3ee2ba857ac87e1e89
parent 498999 84e2040e592add53094c718a6d766d9559ab2498 (current diff)
parent 498965 1a9cbc785296806682eb165e0307b05b8f45b7e7 (diff)
child 499001 56b783db0fd3052b6344d55962e46bc692bc5383
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to autoland. a=merge
accessible/android/DocAccessibleWrap.cpp
accessible/android/ProxyAccessibleWrap.cpp
accessible/android/ProxyAccessibleWrap.h
accessible/android/RootAccessibleWrap.cpp
python/mozbuild/mozbuild/mach_commands.py
testing/talos/talos/startup_test/tresize/addon/bootstrap.js
testing/talos/talos/startup_test/tresize/addon/chrome.manifest
testing/talos/talos/startup_test/tresize/addon/content/Profiler.js
testing/talos/talos/startup_test/tresize/addon/content/framescript.js
testing/talos/talos/startup_test/tresize/addon/content/initialize_browser.js
testing/talos/talos/startup_test/tresize/addon/content/tresize-test.html
testing/talos/talos/startup_test/tresize/addon/content/tresize.js
testing/talos/talos/startup_test/tresize/addon/install.rdf
toolkit/themes/shared/in-content/check.svg
widget/android/bindings/AccessibilityEvent-classes.txt
--- a/accessible/android/AccessibleWrap.cpp
+++ b/accessible/android/AccessibleWrap.cpp
@@ -1,509 +1,23 @@
 /* -*- 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 "AccessibleWrap.h"
 
-#include "Accessible-inl.h"
-#include "DocAccessibleWrap.h"
-#include "IDSet.h"
-#include "JavaBuiltins.h"
-#include "SessionAccessibility.h"
-#include "nsAccessibilityService.h"
-#include "nsIPersistentProperties2.h"
-#include "nsIStringBundle.h"
-#include "nsAccUtils.h"
-
-#define ROLE_STRINGS_URL "chrome://global/locale/AccessFu.properties"
-
 using namespace mozilla::a11y;
 
-// IDs should be a positive 32bit integer.
-IDSet sIDSet(31UL);
-
 //-----------------------------------------------------
 // construction
 //-----------------------------------------------------
 AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc)
   : Accessible(aContent, aDoc)
 {
-  if (aDoc) {
-    mID = AcquireID();
-    DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(aDoc);
-    doc->AddID(mID, this);
-  }
 }
 
 //-----------------------------------------------------
 // destruction
 //-----------------------------------------------------
-AccessibleWrap::~AccessibleWrap() {}
-
-nsresult
-AccessibleWrap::HandleAccEvent(AccEvent* aEvent)
-{
-  nsresult rv = Accessible::HandleAccEvent(aEvent);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (IPCAccessibilityActive()) {
-    return NS_OK;
-  }
-
-  auto accessible = static_cast<AccessibleWrap*>(aEvent->GetAccessible());
-  NS_ENSURE_TRUE(accessible, NS_ERROR_FAILURE);
-
-  // The accessible can become defunct if we have an xpcom event listener
-  // which decides it would be fun to change the DOM and flush layout.
-  if (accessible->IsDefunct() || !accessible->IsBoundToParent()) {
-    return NS_OK;
-  }
-
-  if (DocAccessible* doc = accessible->Document()) {
-    if (!nsCoreUtils::IsContentDocument(doc->DocumentNode())) {
-      return NS_OK;
-    }
-  }
-
-  SessionAccessibility* sessionAcc =
-    SessionAccessibility::GetInstanceFor(accessible);
-  if (!sessionAcc) {
-    return NS_OK;
-  }
-
-  switch (aEvent->GetEventType()) {
-    case nsIAccessibleEvent::EVENT_FOCUS:
-      sessionAcc->SendFocusEvent(accessible);
-      break;
-    case nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED: {
-      AccVCChangeEvent* vcEvent = downcast_accEvent(aEvent);
-      auto newPosition = static_cast<AccessibleWrap*>(vcEvent->NewAccessible());
-      auto oldPosition = static_cast<AccessibleWrap*>(vcEvent->OldAccessible());
-
-      if (sessionAcc && newPosition) {
-        if (oldPosition != newPosition) {
-          if (vcEvent->Reason() == nsIAccessiblePivot::REASON_POINT) {
-            sessionAcc->SendHoverEnterEvent(newPosition);
-          } else {
-            sessionAcc->SendAccessibilityFocusedEvent(newPosition);
-          }
-        }
-
-        if (vcEvent->BoundaryType() != nsIAccessiblePivot::NO_BOUNDARY) {
-          sessionAcc->SendTextTraversedEvent(
-            newPosition, vcEvent->NewStartOffset(), vcEvent->NewEndOffset());
-        }
-      }
-      break;
-    }
-    case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
-      AccCaretMoveEvent* event = downcast_accEvent(aEvent);
-      sessionAcc->SendTextSelectionChangedEvent(accessible,
-                                                event->GetCaretOffset());
-      break;
-    }
-    case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
-    case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
-      AccTextChangeEvent* event = downcast_accEvent(aEvent);
-      sessionAcc->SendTextChangedEvent(accessible,
-                                       event->ModifiedText(),
-                                       event->GetStartOffset(),
-                                       event->GetLength(),
-                                       event->IsTextInserted(),
-                                       event->IsFromUserInput());
-      break;
-    }
-    case nsIAccessibleEvent::EVENT_STATE_CHANGE: {
-      AccStateChangeEvent* event = downcast_accEvent(aEvent);
-      auto state = event->GetState();
-      if (state & states::CHECKED) {
-        sessionAcc->SendClickedEvent(accessible);
-      }
-
-      if (state & states::SELECTED) {
-        sessionAcc->SendSelectedEvent(accessible);
-      }
-
-      if (state & states::BUSY) {
-        sessionAcc->SendWindowStateChangedEvent(accessible);
-      }
-      break;
-    }
-    case nsIAccessibleEvent::EVENT_SCROLLING: {
-      AccScrollingEvent* event = downcast_accEvent(aEvent);
-      sessionAcc->SendScrollingEvent(accessible,
-                                     event->ScrollX(),
-                                     event->ScrollY(),
-                                     event->MaxScrollX(),
-                                     event->MaxScrollY());
-      break;
-    }
-    case nsIAccessibleEvent::EVENT_SHOW:
-    case nsIAccessibleEvent::EVENT_HIDE: {
-      AccMutationEvent* event = downcast_accEvent(aEvent);
-      auto parent = static_cast<AccessibleWrap*>(event->Parent());
-      sessionAcc->SendWindowContentChangedEvent(parent);
-      break;
-    }
-    default:
-      break;
-  }
-
-  return NS_OK;
-}
-
-void
-AccessibleWrap::Shutdown()
-{
-  if (mDoc) {
-    if (mID > 0) {
-      if (auto doc = static_cast<DocAccessibleWrap*>(mDoc.get())) {
-        doc->RemoveID(mID);
-      }
-      ReleaseID(mID);
-      mID = 0;
-    }
-  }
-
-  Accessible::Shutdown();
-}
-
-int32_t
-AccessibleWrap::AcquireID()
-{
-  return sIDSet.GetID();
-}
-
-void
-AccessibleWrap::ReleaseID(int32_t aID)
-{
-  sIDSet.ReleaseID(aID);
-}
-
-void
-AccessibleWrap::SetTextContents(const nsAString& aText) {
-  if (IsHyperText()) {
-    AsHyperText()->ReplaceText(aText);
-  }
-}
-
-void
-AccessibleWrap::GetTextContents(nsAString& aText) {
-  // For now it is a simple wrapper for getting entire range of TextSubstring.
-  // In the future this may be smarter and retrieve a flattened string.
-  if (IsHyperText()) {
-    AsHyperText()->TextSubstring(0, -1, aText);
-  }
-}
-
-bool
-AccessibleWrap::GetSelectionBounds(int32_t* aStartOffset, int32_t* aEndOffset) {
-  if (IsHyperText()) {
-    return AsHyperText()->SelectionBoundsAt(0, aStartOffset, aEndOffset);
-  }
-
-  return false;
-}
-
-mozilla::java::GeckoBundle::LocalRef
-AccessibleWrap::CreateBundle(int32_t aParentID,
-                             role aRole,
-                             uint64_t aState,
-                             const nsString& aName,
-                             const nsString& aTextValue,
-                             const nsString& aDOMNodeID,
-                             const nsIntRect& aBounds,
-                             double aCurVal,
-                             double aMinVal,
-                             double aMaxVal,
-                             double aStep,
-                             nsIPersistentProperties* aAttributes,
-                             const nsTArray<int32_t>& aChildren) const
+AccessibleWrap::~AccessibleWrap()
 {
-  GECKOBUNDLE_START(nodeInfo);
-  GECKOBUNDLE_PUT(nodeInfo, "id", java::sdk::Integer::ValueOf(VirtualViewID()));
-  GECKOBUNDLE_PUT(nodeInfo, "parentId", java::sdk::Integer::ValueOf(aParentID));
-  uint64_t flags = GetFlags(aRole, aState);
-  GECKOBUNDLE_PUT(nodeInfo, "flags", java::sdk::Integer::ValueOf(flags));
-
-  nsAutoString geckoRole;
-  nsAutoString roleDescription;
-  nsAutoString className;
-  GetAndroidRoleAndClass(aRole, geckoRole, roleDescription, className);
-  if (VirtualViewID() == kNoID) {
-    className.AssignLiteral("android.webkit.WebView");
-    roleDescription.AssignLiteral("");
-  }
-  GECKOBUNDLE_PUT(
-    nodeInfo, "roleDescription", jni::StringParam(roleDescription));
-  GECKOBUNDLE_PUT(nodeInfo, "geckoRole", jni::StringParam(geckoRole));
-  GECKOBUNDLE_PUT(nodeInfo, "className", jni::StringParam(className));
-
-  if (!aTextValue.IsEmpty() &&
-      (flags & java::SessionAccessibility::FLAG_EDITABLE)) {
-    GECKOBUNDLE_PUT(nodeInfo, "hint", jni::StringParam(aName));
-    GECKOBUNDLE_PUT(nodeInfo, "text", jni::StringParam(aTextValue));
-  } else {
-    GECKOBUNDLE_PUT(nodeInfo, "text", jni::StringParam(aName));
-  }
-
-  if (!aDOMNodeID.IsEmpty()) {
-    GECKOBUNDLE_PUT(
-      nodeInfo, "viewIdResourceName", jni::StringParam(aDOMNodeID));
-  }
-
-  const int32_t data[4] = {
-    aBounds.x, aBounds.y, aBounds.x + aBounds.width, aBounds.y + aBounds.height
-  };
-  GECKOBUNDLE_PUT(nodeInfo, "bounds", jni::IntArray::New(data, 4));
-
-  if (HasNumericValue()) {
-    GECKOBUNDLE_START(rangeInfo);
-    if (aMaxVal == 1 && aMinVal == 0) {
-      GECKOBUNDLE_PUT(
-        rangeInfo, "type", java::sdk::Integer::ValueOf(2)); // percent
-    } else if (std::round(aStep) != aStep) {
-      GECKOBUNDLE_PUT(
-        rangeInfo, "type", java::sdk::Integer::ValueOf(1)); // float
-    } else {
-      GECKOBUNDLE_PUT(
-        rangeInfo, "type", java::sdk::Integer::ValueOf(0)); // integer
-    }
-
-    if (!IsNaN(aCurVal)) {
-      GECKOBUNDLE_PUT(rangeInfo, "current", java::sdk::Double::New(aCurVal));
-    }
-    if (!IsNaN(aMinVal)) {
-      GECKOBUNDLE_PUT(rangeInfo, "min", java::sdk::Double::New(aMinVal));
-    }
-    if (!IsNaN(aMaxVal)) {
-      GECKOBUNDLE_PUT(rangeInfo, "max", java::sdk::Double::New(aMaxVal));
-    }
-
-    GECKOBUNDLE_FINISH(rangeInfo);
-    GECKOBUNDLE_PUT(nodeInfo, "rangeInfo", rangeInfo);
-  }
-
-  nsString inputType;
-  nsAccUtils::GetAccAttr(aAttributes, nsGkAtoms::textInputType, inputType);
-  if (!inputType.IsEmpty()) {
-    GECKOBUNDLE_PUT(nodeInfo, "inputType", jni::StringParam(inputType));
-  }
-
-  nsString posinset;
-  nsresult rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("posinset"), posinset);
-  if (NS_SUCCEEDED(rv)) {
-    int32_t rowIndex;
-    if (sscanf(NS_ConvertUTF16toUTF8(posinset).get(), "%d", &rowIndex) > 0) {
-      GECKOBUNDLE_START(collectionItemInfo);
-      GECKOBUNDLE_PUT(
-        collectionItemInfo, "rowIndex", java::sdk::Integer::ValueOf(rowIndex));
-      GECKOBUNDLE_PUT(
-        collectionItemInfo, "columnIndex", java::sdk::Integer::ValueOf(0));
-      GECKOBUNDLE_PUT(
-        collectionItemInfo, "rowSpan", java::sdk::Integer::ValueOf(1));
-      GECKOBUNDLE_PUT(
-        collectionItemInfo, "columnSpan", java::sdk::Integer::ValueOf(1));
-      GECKOBUNDLE_FINISH(collectionItemInfo);
-
-      GECKOBUNDLE_PUT(nodeInfo, "collectionItemInfo", collectionItemInfo);
-    }
-  }
-
-  nsString colSize;
-  rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("child-item-count"),
-                                      colSize);
-  if (NS_SUCCEEDED(rv)) {
-    int32_t rowCount;
-    if (sscanf(NS_ConvertUTF16toUTF8(colSize).get(), "%d", &rowCount) > 0) {
-      GECKOBUNDLE_START(collectionInfo);
-      GECKOBUNDLE_PUT(
-        collectionInfo, "rowCount", java::sdk::Integer::ValueOf(rowCount));
-      GECKOBUNDLE_PUT(
-        collectionInfo, "columnCount", java::sdk::Integer::ValueOf(1));
-
-      nsString unused;
-      rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("hierarchical"),
-                                          unused);
-      if (NS_SUCCEEDED(rv)) {
-        GECKOBUNDLE_PUT(
-          collectionInfo, "isHierarchical", java::sdk::Boolean::TRUE());
-      }
-
-      if (IsSelect()) {
-        int32_t selectionMode = (aState & states::MULTISELECTABLE) ? 2 : 1;
-        GECKOBUNDLE_PUT(collectionInfo,
-                        "selectionMode",
-                        java::sdk::Integer::ValueOf(selectionMode));
-      }
-      GECKOBUNDLE_FINISH(collectionInfo);
-
-      GECKOBUNDLE_PUT(nodeInfo, "collectionInfo", collectionInfo);
-    }
-  }
-
-  GECKOBUNDLE_PUT(nodeInfo,
-                  "children",
-                  jni::IntArray::New(aChildren.Elements(), aChildren.Length()));
-  GECKOBUNDLE_FINISH(nodeInfo);
-
-  return nodeInfo;
 }
-
-uint64_t
-AccessibleWrap::GetFlags(role aRole, uint64_t aState)
-{
-  uint64_t flags = 0;
-  if (aState & states::CHECKABLE) {
-    flags |= java::SessionAccessibility::FLAG_CHECKABLE;
-  }
-
-  if (aState & states::CHECKED) {
-    flags |= java::SessionAccessibility::FLAG_CHECKED;
-  }
-
-  if (aState & states::INVALID) {
-    flags |= java::SessionAccessibility::FLAG_CONTENT_INVALID;
-  }
-
-  if (aState & states::EDITABLE) {
-    flags |= java::SessionAccessibility::FLAG_EDITABLE;
-  }
-
-  if (aState & states::SENSITIVE) {
-    flags |= java::SessionAccessibility::FLAG_CLICKABLE;
-  }
-
-  if (aState & states::ENABLED) {
-    flags |= java::SessionAccessibility::FLAG_ENABLED;
-  }
-
-  if (aState & states::FOCUSABLE) {
-    flags |= java::SessionAccessibility::FLAG_FOCUSABLE;
-  }
-
-  if (aState & states::FOCUSED) {
-    flags |= java::SessionAccessibility::FLAG_FOCUSED;
-  }
-
-  if (aState & states::MULTI_LINE) {
-    flags |= java::SessionAccessibility::FLAG_MULTI_LINE;
-  }
-
-  if (aState & states::SELECTABLE) {
-    flags |= java::SessionAccessibility::FLAG_SELECTABLE;
-  }
-
-  if (aState & states::SELECTED) {
-    flags |= java::SessionAccessibility::FLAG_SELECTED;
-  }
-
-  if ((aState & (states::INVISIBLE | states::OFFSCREEN)) == 0) {
-    flags |= java::SessionAccessibility::FLAG_VISIBLE_TO_USER;
-  }
-
-  if (aRole == roles::PASSWORD_TEXT) {
-    flags |= java::SessionAccessibility::FLAG_PASSWORD;
-  }
-
-  return flags;
-}
-
-void
-AccessibleWrap::GetAndroidRoleAndClass(role aRole,
-                                       nsAString& aGeckoRole,
-                                       nsAString& aRoleDescription,
-                                       nsAString& aClassStr)
-{
-  nsresult rv = NS_OK;
-
-  nsCOMPtr<nsIStringBundleService> sbs =
-    do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Failed to get string bundle service");
-    return;
-  }
-
-  nsCOMPtr<nsIStringBundle> bundle;
-  rv = sbs->CreateBundle(ROLE_STRINGS_URL, getter_AddRefs(bundle));
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Failed to get string bundle");
-    return;
-  }
-
-#define ROLE(geckoRole,                                                        \
-             stringRole,                                                       \
-             atkRole,                                                          \
-             macRole,                                                          \
-             msaaRole,                                                         \
-             ia2Role,                                                          \
-             androidClass,                                                     \
-             nameRule)                                                         \
-  case roles::geckoRole:                                                       \
-    rv = bundle->GetStringFromName(stringRole, aRoleDescription);              \
-    if (NS_FAILED(rv))                                                         \
-      aRoleDescription.AssignLiteral("");                                      \
-    aGeckoRole.AssignLiteral(stringRole);                                      \
-    aClassStr.AssignLiteral(androidClass);                                     \
-    break;
-
-  switch (aRole) {
-#include "RoleMap.h"
-    default:
-      aRoleDescription.AssignLiteral("");
-      aGeckoRole.AssignLiteral("nothing");
-      aClassStr.AssignLiteral("android.view.View");
-      return;
-  }
-
-#undef ROLE
-}
-
-void
-AccessibleWrap::DOMNodeID(nsString& aDOMNodeID)
-{
-  if (mContent) {
-    nsAtom* id = mContent->GetID();
-    if (id) {
-      id->ToString(aDOMNodeID);
-    }
-  }
-}
-
-mozilla::java::GeckoBundle::LocalRef
-AccessibleWrap::ToBundle()
-{
-  AccessibleWrap* parent = static_cast<AccessibleWrap*>(Parent());
-
-  nsAutoString name;
-  Name(name);
-
-  nsAutoString value;
-  Value(value);
-
-  nsAutoString viewIdResourceName;
-  DOMNodeID(viewIdResourceName);
-
-  nsCOMPtr<nsIPersistentProperties> attributes = Attributes();
-
-  auto childCount = ChildCount();
-  nsTArray<int32_t> children(childCount);
-  for (uint32_t i = 0; i < childCount; i++) {
-    auto child = static_cast<AccessibleWrap*>(GetChildAt(i));
-    children.AppendElement(child->VirtualViewID());
-  }
-
-  return CreateBundle(parent ? parent->VirtualViewID() : 0,
-                      Role(),
-                      State(),
-                      name,
-                      value,
-                      viewIdResourceName,
-                      Bounds(),
-                      CurValue(),
-                      MinValue(),
-                      MaxValue(),
-                      Step(),
-                      attributes,
-                      children);
-}
--- a/accessible/android/AccessibleWrap.h
+++ b/accessible/android/AccessibleWrap.h
@@ -1,80 +1,25 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_a11y_AccessibleWrap_h_
 #define mozilla_a11y_AccessibleWrap_h_
 
+#include "nsCOMPtr.h"
 #include "Accessible.h"
-#include "GeneratedJNIWrappers.h"
-#include "mozilla/a11y/ProxyAccessible.h"
-#include "nsCOMPtr.h"
 
 namespace mozilla {
 namespace a11y {
 
 class AccessibleWrap : public Accessible
 {
-public:
+public: // construction, destruction
   AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc);
   virtual ~AccessibleWrap();
-
-  virtual nsresult HandleAccEvent(AccEvent* aEvent) override;
-  virtual void Shutdown() override;
-
-  int32_t VirtualViewID() const { return mID; }
-
-  virtual void SetTextContents(const nsAString& aText);
-
-  virtual void GetTextContents(nsAString& aText);
-
-  virtual bool GetSelectionBounds(int32_t* aStartOffset, int32_t* aEndOffset);
-
-  virtual mozilla::java::GeckoBundle::LocalRef ToBundle();
-
-  static const int32_t kNoID = -1;
-
-protected:
-  mozilla::java::GeckoBundle::LocalRef CreateBundle(
-    int32_t aParentID,
-    role aRole,
-    uint64_t aState,
-    const nsString& aName,
-    const nsString& aTextValue,
-    const nsString& aDOMNodeID,
-    const nsIntRect& aBounds,
-    double aCurVal,
-    double aMinVal,
-    double aMaxVal,
-    double aStep,
-    nsIPersistentProperties* aAttributes,
-    const nsTArray<int32_t>& aChildren) const;
-
-  // IDs should be a positive 32bit integer.
-  static int32_t AcquireID();
-  static void ReleaseID(int32_t aID);
-
-  int32_t mID;
-
-private:
-  void DOMNodeID(nsString& aDOMNodeID);
-
-  static void GetAndroidRoleAndClass(role aRole,
-                                     nsAString& aGeckoRole,
-                                     nsAString& aRoleDescription,
-                                     nsAString& aClassStr);
-
-  static uint64_t GetFlags(role aRole, uint64_t aState);
 };
 
-static inline AccessibleWrap*
-WrapperFor(const ProxyAccessible* aProxy)
-{
-  return reinterpret_cast<AccessibleWrap*>(aProxy->GetWrapper());
-}
-
 } // namespace a11y
 } // namespace mozilla
 
 #endif
deleted file mode 100644
--- a/accessible/android/DocAccessibleWrap.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "DocAccessibleWrap.h"
-#include "nsIDocShell.h"
-
-using namespace mozilla::a11y;
-
-////////////////////////////////////////////////////////////////////////////////
-// DocAccessibleWrap
-////////////////////////////////////////////////////////////////////////////////
-
-DocAccessibleWrap::DocAccessibleWrap(nsIDocument* aDocument,
-                                     nsIPresShell* aPresShell)
-  : DocAccessible(aDocument, aPresShell)
-{
-  nsCOMPtr<nsIDocShellTreeItem> treeItem(aDocument->GetDocShell());
-
-  nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
-  treeItem->GetParent(getter_AddRefs(parentTreeItem));
-
-  if (treeItem->ItemType() == nsIDocShellTreeItem::typeContent &&
-      (!parentTreeItem ||
-       parentTreeItem->ItemType() == nsIDocShellTreeItem::typeChrome)) {
-    // The top-level content document gets this special ID.
-    mID = kNoID;
-  } else {
-    mID = AcquireID();
-  }
-}
-
-DocAccessibleWrap::~DocAccessibleWrap() {}
-
-AccessibleWrap*
-DocAccessibleWrap::GetAccessibleByID(int32_t aID) const
-{
-  if (AccessibleWrap* acc = mIDToAccessibleMap.Get(aID)) {
-    return acc;
-  }
-
-  // If the ID is not in the hash table, check the IDs of the child docs.
-  for (uint32_t i = 0; i < ChildDocumentCount(); i++) {
-    auto childDoc = reinterpret_cast<AccessibleWrap*>(GetChildDocumentAt(i));
-    if (childDoc->VirtualViewID() == aID) {
-      return childDoc;
-    }
-  }
-
-  return nullptr;
-}
--- a/accessible/android/DocAccessibleWrap.h
+++ b/accessible/android/DocAccessibleWrap.h
@@ -6,35 +6,14 @@
 #ifndef mozilla_a11y_DocAccessibleWrap_h__
 #define mozilla_a11y_DocAccessibleWrap_h__
 
 #include "DocAccessible.h"
 
 namespace mozilla {
 namespace a11y {
 
-class DocAccessibleWrap : public DocAccessible
-{
-public:
-  DocAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell);
-  virtual ~DocAccessibleWrap();
-
-  /**
-   * Manage the mapping from id to Accessible.
-   */
-  void AddID(uint32_t aID, AccessibleWrap* aAcc)
-  {
-    mIDToAccessibleMap.Put(aID, aAcc);
-  }
-  void RemoveID(uint32_t aID) { mIDToAccessibleMap.Remove(aID); }
-  AccessibleWrap* GetAccessibleByID(int32_t aID) const;
-
-protected:
-  /*
-   * This provides a mapping from 32 bit id to accessible objects.
-   */
-  nsDataHashtable<nsUint32HashKey, AccessibleWrap*> mIDToAccessibleMap;
-};
+typedef DocAccessible DocAccessibleWrap;
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/accessible/android/Platform.cpp
+++ b/accessible/android/Platform.cpp
@@ -1,200 +1,89 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Platform.h"
-#include "ProxyAccessibleWrap.h"
-#include "SessionAccessibility.h"
-#include "mozilla/a11y/ProxyAccessible.h"
-#include "nsIAccessibleEvent.h"
-#include "nsIAccessiblePivot.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 void
 a11y::PlatformInit()
 {
 }
 
 void
 a11y::PlatformShutdown()
 {
 }
 
 void
-a11y::ProxyCreated(ProxyAccessible* aProxy, uint32_t aInterfaces)
+a11y::ProxyCreated(ProxyAccessible*, uint32_t)
 {
-  AccessibleWrap* wrapper = nullptr;
-  if (aProxy->IsDoc()) {
-    wrapper = new DocProxyAccessibleWrap(aProxy->AsDoc());
-  } else {
-    wrapper = new ProxyAccessibleWrap(aProxy);
-  }
-
-  wrapper->AddRef();
-  aProxy->SetWrapper(reinterpret_cast<uintptr_t>(wrapper));
-}
-
-void
-a11y::ProxyDestroyed(ProxyAccessible* aProxy)
-{
-  AccessibleWrap* wrapper =
-    reinterpret_cast<AccessibleWrap*>(aProxy->GetWrapper());
-
-  // If aProxy is a document that was created, but
-  // RecvPDocAccessibleConstructor failed then aProxy->GetWrapper() will be
-  // null.
-  if (!wrapper) {
-    return;
-  }
-
-  wrapper->Shutdown();
-  aProxy->SetWrapper(0);
-  wrapper->Release();
 }
 
 void
-a11y::ProxyEvent(ProxyAccessible* aTarget, uint32_t aEventType)
+a11y::ProxyDestroyed(ProxyAccessible*)
 {
-  SessionAccessibility* sessionAcc =
-    SessionAccessibility::GetInstanceFor(aTarget);
-  if (!sessionAcc) {
-    return;
-  }
-
-  switch (aEventType) {
-    case nsIAccessibleEvent::EVENT_FOCUS:
-      sessionAcc->SendFocusEvent(WrapperFor(aTarget));
-      break;
-  }
 }
 
 void
-a11y::ProxyStateChangeEvent(ProxyAccessible* aTarget,
-                            uint64_t aState,
-                            bool aEnabled)
+a11y::ProxyEvent(ProxyAccessible*, uint32_t)
 {
-  SessionAccessibility* sessionAcc =
-    SessionAccessibility::GetInstanceFor(aTarget);
-
-  if (!sessionAcc) {
-    return;
-  }
+}
 
-  if (aState & states::CHECKED) {
-    sessionAcc->SendClickedEvent(WrapperFor(aTarget));
-  }
-
-  if (aState & states::SELECTED) {
-    sessionAcc->SendSelectedEvent(WrapperFor(aTarget));
-  }
-
-  if (aState & states::BUSY) {
-    sessionAcc->SendWindowStateChangedEvent(WrapperFor(aTarget));
-  }
+void
+a11y::ProxyStateChangeEvent(ProxyAccessible*, uint64_t, bool)
+{
 }
 
 void
 a11y::ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset)
 {
-  SessionAccessibility* sessionAcc =
-    SessionAccessibility::GetInstanceFor(aTarget);
-
-  if (sessionAcc) {
-    sessionAcc->SendTextSelectionChangedEvent(WrapperFor(aTarget), aOffset);
-  }
 }
 
 void
-a11y::ProxyTextChangeEvent(ProxyAccessible* aTarget,
-                           const nsString& aStr,
-                           int32_t aStart,
-                           uint32_t aLen,
-                           bool aIsInsert,
-                           bool aFromUser)
+a11y::ProxyTextChangeEvent(ProxyAccessible*,
+                           const nsString&,
+                           int32_t,
+                           uint32_t,
+                           bool,
+                           bool)
 {
-  SessionAccessibility* sessionAcc =
-    SessionAccessibility::GetInstanceFor(aTarget);
-
-  if (sessionAcc) {
-    sessionAcc->SendTextChangedEvent(
-      WrapperFor(aTarget), aStr, aStart, aLen, aIsInsert, aFromUser);
-  }
 }
 
 void
-a11y::ProxyShowHideEvent(ProxyAccessible* aTarget,
-                         ProxyAccessible* aParent,
-                         bool aInsert,
-                         bool aFromUser)
+a11y::ProxyShowHideEvent(ProxyAccessible*, ProxyAccessible*, bool, bool)
 {
-  SessionAccessibility* sessionAcc =
-    SessionAccessibility::GetInstanceFor(aTarget);
-  if (sessionAcc) {
-    sessionAcc->SendWindowContentChangedEvent(WrapperFor(aParent));
-  }
 }
 
 void
 a11y::ProxySelectionEvent(ProxyAccessible*, ProxyAccessible*, uint32_t)
 {
 }
 
 void
-a11y::ProxyVirtualCursorChangeEvent(ProxyAccessible* aTarget,
-                                    ProxyAccessible* aOldPosition,
-                                    int32_t aOldStartOffset,
-                                    int32_t aOldEndOffset,
-                                    ProxyAccessible* aNewPosition,
-                                    int32_t aNewStartOffset,
-                                    int32_t aNewEndOffset,
-                                    int16_t aReason,
-                                    int16_t aBoundaryType,
-                                    bool aFromUser)
+a11y::ProxyVirtualCursorChangeEvent(ProxyAccessible*,
+                                    ProxyAccessible*,
+                                    int32_t,
+                                    int32_t,
+                                    ProxyAccessible*,
+                                    int32_t,
+                                    int32_t,
+                                    int16_t,
+                                    int16_t,
+                                    bool)
 {
-  if (!aNewPosition) {
-    return;
-  }
-
-  SessionAccessibility* sessionAcc =
-    SessionAccessibility::GetInstanceFor(aTarget);
-
-  if (!sessionAcc) {
-    return;
-  }
-
-  if (aOldPosition != aNewPosition) {
-    if (aReason == nsIAccessiblePivot::REASON_POINT) {
-      sessionAcc->SendHoverEnterEvent(WrapperFor(aNewPosition));
-    } else {
-      sessionAcc->SendAccessibilityFocusedEvent(WrapperFor(aNewPosition));
-    }
-  }
-
-  if (aBoundaryType != nsIAccessiblePivot::NO_BOUNDARY) {
-    sessionAcc->SendTextTraversedEvent(
-      WrapperFor(aNewPosition), aNewStartOffset, aNewEndOffset);
-  }
 }
 
 void
-a11y::ProxyScrollingEvent(ProxyAccessible* aTarget,
-                          uint32_t aEventType,
-                          uint32_t aScrollX,
-                          uint32_t aScrollY,
-                          uint32_t aMaxScrollX,
-                          uint32_t aMaxScrollY)
+a11y::ProxyScrollingEvent(ProxyAccessible*,
+                          uint32_t,
+                          uint32_t,
+                          uint32_t,
+                          uint32_t,
+                          uint32_t)
 {
-  if (aEventType == nsIAccessibleEvent::EVENT_SCROLLING) {
-    SessionAccessibility* sessionAcc =
-      SessionAccessibility::GetInstanceFor(aTarget);
-
-    if (sessionAcc) {
-      sessionAcc->SendScrollingEvent(
-        WrapperFor(aTarget), aScrollX, aScrollY, aMaxScrollX, aMaxScrollY);
-    }
-  }
 }
deleted file mode 100644
--- a/accessible/android/ProxyAccessibleWrap.cpp
+++ /dev/null
@@ -1,147 +0,0 @@
-/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * 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 "ProxyAccessibleWrap.h"
-#include "nsPersistentProperties.h"
-
-using namespace mozilla::a11y;
-
-ProxyAccessibleWrap::ProxyAccessibleWrap(ProxyAccessible* aProxy)
-  : AccessibleWrap(nullptr, nullptr)
-{
-  mType = eProxyType;
-  mBits.proxy = aProxy;
-
-  if (aProxy->mHasValue) {
-    mStateFlags |= eHasNumericValue;
-  }
-
-  if (aProxy->mIsSelection) {
-    mGenericTypes |= eSelect;
-  }
-
-  if (aProxy->mIsHyperText) {
-    mGenericTypes |= eHyperText;
-  }
-
-  auto doc = reinterpret_cast<DocProxyAccessibleWrap*>(
-    Proxy()->Document()->GetWrapper());
-  if (doc) {
-    mID = AcquireID();
-    doc->AddID(mID, this);
-  }
-}
-
-void
-ProxyAccessibleWrap::Shutdown()
-{
-  auto doc = reinterpret_cast<DocProxyAccessibleWrap*>(
-    Proxy()->Document()->GetWrapper());
-  if (mID && doc) {
-    doc->RemoveID(mID);
-    ReleaseID(mID);
-    mID = 0;
-  }
-
-  mBits.proxy = nullptr;
-  mStateFlags |= eIsDefunct;
-}
-
-// Accessible
-
-already_AddRefed<nsIPersistentProperties>
-ProxyAccessibleWrap::Attributes()
-{
-  RefPtr<nsPersistentProperties> attributes = new nsPersistentProperties();
-  nsAutoString unused;
-  AutoTArray<Attribute, 10> attrs;
-  Proxy()->Attributes(&attrs);
-  for (size_t i = 0; i < attrs.Length(); i++) {
-    attributes->SetStringProperty(
-      attrs.ElementAt(i).Name(), attrs.ElementAt(i).Value(), unused);
-  }
-
-  return attributes.forget();
-}
-
-uint32_t
-ProxyAccessibleWrap::ChildCount() const
-{
-  return Proxy()->ChildrenCount();
-}
-
-void
-ProxyAccessibleWrap::ScrollTo(uint32_t aHow) const
-{
-  Proxy()->ScrollTo(aHow);
-}
-
-// Other
-
-void
-ProxyAccessibleWrap::SetTextContents(const nsAString& aText)
-{
-  Proxy()->ReplaceText(PromiseFlatString(aText));
-}
-
-void
-ProxyAccessibleWrap::GetTextContents(nsAString& aText)
-{
-  nsAutoString text;
-  Proxy()->TextSubstring(0, -1, text);
-  aText.Assign(text);
-}
-
-bool
-ProxyAccessibleWrap::GetSelectionBounds(int32_t* aStartOffset,
-                                        int32_t* aEndOffset)
-{
-  nsAutoString unused;
-  return Proxy()->SelectionBoundsAt(0, unused, aStartOffset, aEndOffset);
-}
-
-mozilla::java::GeckoBundle::LocalRef
-ProxyAccessibleWrap::ToBundle()
-{
-  ProxyAccessible* proxy = Proxy();
-  if (!proxy) {
-    return nullptr;
-  }
-
-  int32_t parentID = proxy->Parent() ?
-    WrapperFor(proxy->Parent())->VirtualViewID() : 0;
-
-  nsAutoString name;
-  proxy->Name(name);
-
-  nsAutoString value;
-  proxy->Value(value);
-
-  nsAutoString viewIdResourceName;
-  proxy->DOMNodeID(viewIdResourceName);
-
-  nsCOMPtr<nsIPersistentProperties> attributes = Attributes();
-
-  auto childCount = proxy->ChildrenCount();
-  nsTArray<int32_t> children(childCount);
-  for (uint32_t i = 0; i < childCount; i++) {
-    auto child = WrapperFor(proxy->ChildAt(i));
-    children.AppendElement(child->VirtualViewID());
-  }
-
-  return CreateBundle(parentID,
-                      proxy->Role(),
-                      proxy->State(),
-                      name,
-                      value,
-                      viewIdResourceName,
-                      proxy->Bounds(),
-                      proxy->CurValue(),
-                      proxy->MinValue(),
-                      proxy->MaxValue(),
-                      proxy->Step(),
-                      attributes,
-                      children);
-}
deleted file mode 100644
--- a/accessible/android/ProxyAccessibleWrap.h
+++ /dev/null
@@ -1,123 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. *
- */
-
-#ifndef MOZILLA_A11Y_ProxyAccessibleWrap_h
-#define MOZILLA_A11Y_ProxyAccessibleWrap_h
-
-#include "AccessibleWrap.h"
-#include "DocAccessibleParent.h"
-
-namespace mozilla {
-namespace a11y {
-
-/**
- * A wrapper for Accessible proxies. The public methods here should be overriden
- * from AccessibleWrap or its super classes.
- * This gives us an abstraction layer so SessionAccessibility doesn't have
- * to distinguish between a local or remote accessibles.
- * NOTE: This shouldn't be regarded as a full Accessible implementation.
- */
-class ProxyAccessibleWrap : public AccessibleWrap
-{
-public:
-  explicit ProxyAccessibleWrap(ProxyAccessible* aProxy);
-
-  virtual void Shutdown() override;
-
-  // Accessible
-
-  virtual already_AddRefed<nsIPersistentProperties> Attributes() override;
-
-  virtual uint32_t ChildCount() const override;
-
-  virtual void ScrollTo(uint32_t aHow) const override;
-
-  // AccessibleWrap
-
-  virtual void SetTextContents(const nsAString& aText) override;
-
-  virtual void GetTextContents(nsAString& aText) override;
-
-  virtual bool GetSelectionBounds(int32_t* aStartOffset, int32_t* aEndOffset) override;
-
-  virtual mozilla::java::GeckoBundle::LocalRef ToBundle() override;
-};
-
-class DocProxyAccessibleWrap : public ProxyAccessibleWrap
-{
-public:
-  explicit DocProxyAccessibleWrap(DocAccessibleParent* aProxy)
-    : ProxyAccessibleWrap(aProxy)
-  {
-    mGenericTypes |= eDocument;
-
-    if (auto parent = ParentDocument()) {
-      mID = AcquireID();
-      parent->AddID(mID, this);
-    } else {
-      // top level
-      mID = kNoID;
-    }
-  }
-
-  virtual void Shutdown() override
-  {
-    if (mID) {
-      auto parent = ParentDocument();
-      if (parent) {
-        MOZ_ASSERT(mID != kNoID, "A non root accessible always has a parent");
-        parent->RemoveID(mID);
-        ReleaseID(mID);
-      }
-    }
-    mID = 0;
-    mBits.proxy = nullptr;
-    mStateFlags |= eIsDefunct;
-  }
-
-  DocProxyAccessibleWrap* ParentDocument()
-  {
-    DocAccessibleParent* proxy = static_cast<DocAccessibleParent*>(Proxy());
-    MOZ_ASSERT(proxy);
-    if (DocAccessibleParent* parent = proxy->ParentDoc()) {
-      return reinterpret_cast<DocProxyAccessibleWrap*>(parent->GetWrapper());
-    }
-
-    return nullptr;
-  }
-
-  DocProxyAccessibleWrap* GetChildDocumentAt(uint32_t aIndex)
-  {
-    auto doc = Proxy()->AsDoc();
-    if (doc && doc->ChildDocCount() > aIndex) {
-      return reinterpret_cast<DocProxyAccessibleWrap*>(
-        doc->ChildDocAt(aIndex)->GetWrapper());
-    }
-
-    return nullptr;
-  }
-
-  void AddID(uint32_t aID, AccessibleWrap* aAcc)
-  {
-    mIDToAccessibleMap.Put(aID, aAcc);
-  }
-  void RemoveID(uint32_t aID) { mIDToAccessibleMap.Remove(aID); }
-  AccessibleWrap* GetAccessibleByID(uint32_t aID) const
-  {
-    return mIDToAccessibleMap.Get(aID);
-  }
-
-private:
-  /*
-   * This provides a mapping from 32 bit id to accessible objects.
-   */
-  nsDataHashtable<nsUint32HashKey, AccessibleWrap*> mIDToAccessibleMap;
-};
-}
-}
-
-#endif
deleted file mode 100644
--- a/accessible/android/RootAccessibleWrap.cpp
+++ /dev/null
@@ -1,92 +0,0 @@
-/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * 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 "RootAccessibleWrap.h"
-
-#include "AccessibleOrProxy.h"
-#include "DocAccessibleParent.h"
-#include "ProxyAccessibleWrap.h"
-#include "SessionAccessibility.h"
-
-using namespace mozilla::a11y;
-
-RootAccessibleWrap::RootAccessibleWrap(nsIDocument* aDoc,
-                                       nsIPresShell* aPresShell)
-  : RootAccessible(aDoc, aPresShell)
-{
-}
-
-RootAccessibleWrap::~RootAccessibleWrap() {}
-
-AccessibleWrap*
-RootAccessibleWrap::GetContentAccessible()
-{
-  if (ProxyAccessible* proxy = GetPrimaryRemoteTopLevelContentDoc()) {
-    return WrapperFor(proxy);
-  }
-
-  if (ChildDocumentCount()) {
-    return GetChildDocumentAt(0);
-  }
-
-  return nullptr;
-}
-
-AccessibleWrap*
-RootAccessibleWrap::FindAccessibleById(int32_t aID)
-{
-  AccessibleWrap* contentAcc = GetContentAccessible();
-
-  if (!contentAcc) {
-    return nullptr;
-  }
-
-  if (aID == AccessibleWrap::kNoID) {
-    return contentAcc;
-  }
-
-  if (contentAcc->IsProxy()) {
-    return FindAccessibleById(static_cast<DocProxyAccessibleWrap*>(contentAcc),
-                              aID);
-  }
-
-  return FindAccessibleById(
-    static_cast<DocAccessibleWrap*>(contentAcc->AsDoc()), aID);
-}
-
-AccessibleWrap*
-RootAccessibleWrap::FindAccessibleById(DocProxyAccessibleWrap* aDoc,
-                                       int32_t aID)
-{
-  AccessibleWrap* acc = aDoc->GetAccessibleByID(aID);
-  uint32_t index = 0;
-  while (!acc) {
-    auto child =
-      static_cast<DocProxyAccessibleWrap*>(aDoc->GetChildDocumentAt(index++));
-    if (!child) {
-      break;
-    }
-    acc = FindAccessibleById(child, aID);
-  }
-
-  return acc;
-}
-
-AccessibleWrap*
-RootAccessibleWrap::FindAccessibleById(DocAccessibleWrap* aDoc, int32_t aID)
-{
-  AccessibleWrap* acc = aDoc->GetAccessibleByID(aID);
-  uint32_t index = 0;
-  while (!acc) {
-    auto child =
-      static_cast<DocAccessibleWrap*>(aDoc->GetChildDocumentAt(index++));
-    if (!child) {
-      break;
-    }
-    acc = FindAccessibleById(child, aID);
-  }
-
-  return acc;
-}
--- a/accessible/android/RootAccessibleWrap.h
+++ b/accessible/android/RootAccessibleWrap.h
@@ -6,32 +6,14 @@
 #ifndef mozilla_a11y_RootAccessibleWrap_h__
 #define mozilla_a11y_RootAccessibleWrap_h__
 
 #include "RootAccessible.h"
 
 namespace mozilla {
 namespace a11y {
 
-class DocProxyAccessibleWrap;
-
-class RootAccessibleWrap : public RootAccessible
-{
-public:
-  RootAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell);
-  virtual ~RootAccessibleWrap();
-
-  AccessibleWrap* GetContentAccessible();
-
-  AccessibleWrap* FindAccessibleById(int32_t aID);
-
-  // Recursively searches for the accessible ID within the document tree.
-  AccessibleWrap* FindAccessibleById(DocAccessibleWrap* aDocument, int32_t aID);
-
-  // Recursively searches for the accessible ID within the proxy document tree.
-  AccessibleWrap* FindAccessibleById(DocProxyAccessibleWrap* aDocument,
-                                     int32_t aID);
-};
+typedef RootAccessible RootAccessibleWrap;
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/accessible/android/SessionAccessibility.cpp
+++ b/accessible/android/SessionAccessibility.cpp
@@ -1,22 +1,16 @@
 /* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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 "SessionAccessibility.h"
 #include "AndroidUiThread.h"
 #include "nsThreadUtils.h"
-#include "AccessibilityEvent.h"
-#include "HyperTextAccessible.h"
-#include "JavaBuiltins.h"
-#include "RootAccessibleWrap.h"
-#include "nsAccessibilityService.h"
-#include "nsViewManager.h"
 
 #ifdef DEBUG
 #include <android/log.h>
 #define AALOG(args...)                                                         \
   __android_log_print(ANDROID_LOG_INFO, "GeckoAccessibilityNative", ##args)
 #else
 #define AALOG(args...)                                                         \
   do {                                                                         \
@@ -24,30 +18,16 @@
 #endif
 
 template<>
 const char nsWindow::NativePtr<mozilla::a11y::SessionAccessibility>::sName[] =
   "SessionAccessibility";
 
 using namespace mozilla::a11y;
 
-class Settings final
-  : public mozilla::java::SessionAccessibility::Settings::Natives<Settings>
-{
-public:
-  static void ToggleNativeAccessibility(bool aEnable)
-  {
-    if (aEnable) {
-      GetOrCreateAccService();
-    } else {
-      MaybeShutdownAccService(nsAccessibilityService::ePlatformAPI);
-    }
-  }
-};
-
 void
 SessionAccessibility::SetAttached(bool aAttached,
                                   already_AddRefed<Runnable> aRunnable)
 {
   if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
     uiThread->Dispatch(NS_NewRunnableFunction(
       "SessionAccessibility::Attach",
       [aAttached,
@@ -56,264 +36,8 @@ SessionAccessibility::SetAttached(bool a
        runnable = RefPtr<Runnable>(aRunnable)] {
         sa->SetAttached(aAttached);
         if (runnable) {
           runnable->Run();
         }
       }));
   }
 }
-
-void
-SessionAccessibility::Init()
-{
-  java::SessionAccessibility::NativeProvider::Natives<
-    SessionAccessibility>::Init();
-  Settings::Init();
-}
-
-mozilla::jni::Object::LocalRef
-SessionAccessibility::GetNodeInfo(int32_t aID)
-{
-  java::GeckoBundle::GlobalRef ret = nullptr;
-  RefPtr<SessionAccessibility> self(this);
-  nsAppShell::SyncRunEvent([this, self, aID, &ret] {
-    if (RootAccessibleWrap* rootAcc = GetRoot()) {
-      AccessibleWrap* acc = rootAcc->FindAccessibleById(aID);
-      if (acc) {
-        ret = acc->ToBundle();
-      } else {
-        AALOG("oops, nothing for %d", aID);
-      }
-    }
-  });
-
-  return mozilla::jni::Object::Ref::From(ret);
-}
-
-RootAccessibleWrap*
-SessionAccessibility::GetRoot()
-{
-  if (!mWindow) {
-    return nullptr;
-  }
-
-  return static_cast<RootAccessibleWrap*>(mWindow->GetRootAccessible());
-}
-
-void
-SessionAccessibility::SetText(int32_t aID, jni::String::Param aText)
-{
-  if (RootAccessibleWrap* rootAcc = GetRoot()) {
-    AccessibleWrap* acc = rootAcc->FindAccessibleById(aID);
-    if (!acc) {
-      return;
-    }
-
-    acc->SetTextContents(aText->ToString());
-  }
-}
-
-SessionAccessibility*
-SessionAccessibility::GetInstanceFor(ProxyAccessible* aAccessible)
-{
-  Accessible* outerDoc = aAccessible->OuterDocOfRemoteBrowser();
-  if (!outerDoc) {
-    return nullptr;
-  }
-
-  return GetInstanceFor(outerDoc);
-}
-
-SessionAccessibility*
-SessionAccessibility::GetInstanceFor(Accessible* aAccessible)
-{
-  RootAccessible* rootAcc = aAccessible->RootAccessible();
-  nsIPresShell* shell = rootAcc->PresShell();
-  nsViewManager* vm = shell->GetViewManager();
-  if (!vm) {
-    return nullptr;
-  }
-
-  nsCOMPtr<nsIWidget> rootWidget;
-  vm->GetRootWidget(getter_AddRefs(rootWidget));
-  // `rootWidget` can be one of several types. Here we make sure it is an
-  // android nsWindow that implemented NS_NATIVE_WIDGET to return itself.
-  if (rootWidget &&
-      rootWidget->WindowType() == nsWindowType::eWindowType_toplevel &&
-      rootWidget->GetNativeData(NS_NATIVE_WIDGET) == rootWidget) {
-    return static_cast<nsWindow*>(rootWidget.get())->GetSessionAccessibility();
-  }
-
-  return nullptr;
-}
-
-void
-SessionAccessibility::SendAccessibilityFocusedEvent(AccessibleWrap* aAccessible)
-{
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::TYPE_VIEW_ACCESSIBILITY_FOCUSED,
-    aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
-  aAccessible->ScrollTo(nsIAccessibleScrollType::SCROLL_TYPE_ANYWHERE);
-}
-
-void
-SessionAccessibility::SendHoverEnterEvent(AccessibleWrap* aAccessible)
-{
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::TYPE_VIEW_HOVER_ENTER,
-    aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
-}
-
-void
-SessionAccessibility::SendFocusEvent(AccessibleWrap* aAccessible)
-{
-  // Suppress focus events from about:blank pages.
-  // This is important for tests.
-  if (aAccessible->IsDoc() && aAccessible->ChildCount() == 0) {
-    return;
-  }
-
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::TYPE_VIEW_FOCUSED,
-    aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
-}
-
-void
-SessionAccessibility::SendScrollingEvent(AccessibleWrap* aAccessible,
-                                         int32_t aScrollX,
-                                         int32_t aScrollY,
-                                         int32_t aMaxScrollX,
-                                         int32_t aMaxScrollY)
-{
-  int32_t virtualViewId = aAccessible->VirtualViewID();
-
-  if (virtualViewId != AccessibleWrap::kNoID) {
-    // XXX: Support scrolling in subframes
-    return;
-  }
-
-  GECKOBUNDLE_START(eventInfo);
-  GECKOBUNDLE_PUT(eventInfo, "scrollX", java::sdk::Integer::ValueOf(aScrollX));
-  GECKOBUNDLE_PUT(eventInfo, "scrollY", java::sdk::Integer::ValueOf(aScrollY));
-  GECKOBUNDLE_PUT(eventInfo, "maxScrollX", java::sdk::Integer::ValueOf(aMaxScrollX));
-  GECKOBUNDLE_PUT(eventInfo, "maxScrollY", java::sdk::Integer::ValueOf(aMaxScrollY));
-  GECKOBUNDLE_FINISH(eventInfo);
-
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::TYPE_VIEW_SCROLLED, virtualViewId,
-    eventInfo, aAccessible->ToBundle());
-
-  SendWindowContentChangedEvent(aAccessible);
-}
-
-void
-SessionAccessibility::SendWindowContentChangedEvent(AccessibleWrap* aAccessible)
-{
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::TYPE_WINDOW_CONTENT_CHANGED,
-    aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
-}
-
-void
-SessionAccessibility::SendWindowStateChangedEvent(AccessibleWrap* aAccessible)
-{
-  // Suppress window state changed events from about:blank pages.
-  // This is important for tests.
-  if (aAccessible->IsDoc() && aAccessible->ChildCount() == 0) {
-    return;
-  }
-
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::TYPE_WINDOW_STATE_CHANGED,
-    aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
-}
-
-void
-SessionAccessibility::SendTextSelectionChangedEvent(AccessibleWrap* aAccessible,
-                                                    int32_t aCaretOffset)
-{
-  int32_t fromIndex = aCaretOffset;
-  int32_t startSel = -1;
-  int32_t endSel = -1;
-  if (aAccessible->GetSelectionBounds(&startSel, &endSel)) {
-    fromIndex = startSel == aCaretOffset ? endSel : startSel;
-  }
-
-  GECKOBUNDLE_START(eventInfo);
-  GECKOBUNDLE_PUT(eventInfo, "fromIndex", java::sdk::Integer::ValueOf(fromIndex));
-  GECKOBUNDLE_PUT(eventInfo, "toIndex", java::sdk::Integer::ValueOf(aCaretOffset));
-  GECKOBUNDLE_FINISH(eventInfo);
-
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::TYPE_VIEW_TEXT_SELECTION_CHANGED,
-    aAccessible->VirtualViewID(), eventInfo, aAccessible->ToBundle());
-}
-
-void
-SessionAccessibility::SendTextChangedEvent(AccessibleWrap* aAccessible,
-                                           const nsString& aStr,
-                                           int32_t aStart,
-                                           uint32_t aLen,
-                                           bool aIsInsert,
-                                           bool aFromUser)
-{
-  if (!aFromUser) {
-    // Only dispatch text change events from users, for now.
-    return;
-  }
-
-  nsAutoString text;
-  aAccessible->GetTextContents(text);
-  nsAutoString beforeText(text);
-  if (aIsInsert) {
-    beforeText.Cut(aStart, aLen);
-  } else {
-    beforeText.Insert(aStr, aStart);
-  }
-
-  GECKOBUNDLE_START(eventInfo);
-  GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text));
-  GECKOBUNDLE_PUT(eventInfo, "beforeText", jni::StringParam(beforeText));
-  GECKOBUNDLE_PUT(eventInfo, "addedCount", java::sdk::Integer::ValueOf(aIsInsert ? aLen : 0));
-  GECKOBUNDLE_PUT(eventInfo, "removedCount", java::sdk::Integer::ValueOf(aIsInsert ? 0 : aLen));
-  GECKOBUNDLE_FINISH(eventInfo);
-
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::TYPE_VIEW_TEXT_CHANGED,
-    aAccessible->VirtualViewID(), eventInfo, aAccessible->ToBundle());
-}
-
-void
-SessionAccessibility::SendTextTraversedEvent(AccessibleWrap* aAccessible,
-                                             int32_t aStartOffset,
-                                             int32_t aEndOffset)
-{
-  nsAutoString text;
-  aAccessible->GetTextContents(text);
-
-  GECKOBUNDLE_START(eventInfo);
-  GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text));
-  GECKOBUNDLE_PUT(eventInfo, "fromIndex", java::sdk::Integer::ValueOf(aStartOffset));
-  GECKOBUNDLE_PUT(eventInfo, "toIndex", java::sdk::Integer::ValueOf(aEndOffset));
-  GECKOBUNDLE_FINISH(eventInfo);
-
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::
-      TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
-    aAccessible->VirtualViewID(), eventInfo, aAccessible->ToBundle());
-}
-
-void
-SessionAccessibility::SendClickedEvent(AccessibleWrap* aAccessible)
-{
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::TYPE_VIEW_CLICKED,
-    aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
-}
-
-void
-SessionAccessibility::SendSelectedEvent(AccessibleWrap* aAccessible)
-{
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::TYPE_VIEW_SELECTED,
-    aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
-}
--- a/accessible/android/SessionAccessibility.h
+++ b/accessible/android/SessionAccessibility.h
@@ -2,51 +2,21 @@
 /* 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_SessionAccessibility_h_
 #define mozilla_a11y_SessionAccessibility_h_
 
 #include "GeneratedJNINatives.h"
-#include "GeneratedJNIWrappers.h"
-#include "nsAppShell.h"
-#include "nsThreadUtils.h"
 #include "nsWindow.h"
 
-#define GECKOBUNDLE_START(name)                                                \
-  nsTArray<jni::String::LocalRef> _##name##_keys;                              \
-  nsTArray<jni::Object::LocalRef> _##name##_values;
-
-#define GECKOBUNDLE_PUT(name, key, value)                                      \
-  _##name##_keys.AppendElement(jni::StringParam(NS_LITERAL_STRING(key)));      \
-  _##name##_values.AppendElement(value);
-
-#define GECKOBUNDLE_FINISH(name)                                               \
-  MOZ_ASSERT(_##name##_keys.Length() == _##name##_values.Length());            \
-  auto _##name##_jkeys =                                                       \
-    jni::ObjectArray::New<jni::String>(_##name##_keys.Length());               \
-  auto _##name##_jvalues =                                                     \
-    jni::ObjectArray::New<jni::Object>(_##name##_values.Length());             \
-  for (size_t i = 0;                                                           \
-       i < _##name##_keys.Length() && i < _##name##_values.Length();           \
-       i++) {                                                                  \
-    _##name##_jkeys->SetElement(i, _##name##_keys.ElementAt(i));               \
-    _##name##_jvalues->SetElement(i, _##name##_values.ElementAt(i));           \
-  }                                                                            \
-  auto name =                                                                  \
-    mozilla::java::GeckoBundle::New(_##name##_jkeys, _##name##_jvalues);
-
 namespace mozilla {
 namespace a11y {
 
-class AccessibleWrap;
-class ProxyAccessible;
-class RootAccessibleWrap;
-
 class SessionAccessibility final
   : public java::SessionAccessibility::NativeProvider::Natives<SessionAccessibility>
 {
 public:
   typedef java::SessionAccessibility::NativeProvider::Natives<SessionAccessibility> Base;
 
   SessionAccessibility(
     nsWindow::NativePtr<SessionAccessibility>* aPtr,
@@ -63,59 +33,26 @@ public:
     SetAttached(false, std::move(aDisposer));
   }
 
   const java::SessionAccessibility::NativeProvider::Ref& GetJavaAccessibility()
   {
     return mSessionAccessibility;
   }
 
-  static void Init();
-  static SessionAccessibility* GetInstanceFor(ProxyAccessible* aAccessible);
-  static SessionAccessibility* GetInstanceFor(Accessible* aAccessible);
-
   // Native implementations
   using Base::AttachNative;
   using Base::DisposeNative;
-  jni::Object::LocalRef GetNodeInfo(int32_t aID);
-  void SetText(int32_t aID, jni::String::Param aText);
-  void StartNativeAccessibility();
 
-  // Event methods
-  void SendFocusEvent(AccessibleWrap* aAccessible);
-  void SendScrollingEvent(AccessibleWrap* aAccessible,
-                          int32_t aScrollX,
-                          int32_t aScrollY,
-                          int32_t aMaxScrollX,
-                          int32_t aMaxScrollY);
-  void SendAccessibilityFocusedEvent(AccessibleWrap* aAccessible);
-  void SendHoverEnterEvent(AccessibleWrap* aAccessible);
-  void SendTextSelectionChangedEvent(AccessibleWrap* aAccessible,
-                                     int32_t aCaretOffset);
-  void SendTextTraversedEvent(AccessibleWrap* aAccessible,
-                              int32_t aStartOffset,
-                              int32_t aEndOffset);
-  void SendTextChangedEvent(AccessibleWrap* aAccessible,
-                            const nsString& aStr,
-                            int32_t aStart,
-                            uint32_t aLen,
-                            bool aIsInsert,
-                            bool aFromUser);
-  void SendSelectedEvent(AccessibleWrap* aAccessible);
-  void SendClickedEvent(AccessibleWrap* aAccessible);
-  void SendWindowContentChangedEvent(AccessibleWrap* aAccessible);
-  void SendWindowStateChangedEvent(AccessibleWrap* aAccessible);
-
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SessionAccessibility)
+  NS_INLINE_DECL_REFCOUNTING(SessionAccessibility)
 
 private:
   ~SessionAccessibility() {}
 
   void SetAttached(bool aAttached, already_AddRefed<Runnable> aRunnable);
-  RootAccessibleWrap* GetRoot();
 
   nsWindow::WindowPtr<SessionAccessibility> mWindow; // Parent only
   java::SessionAccessibility::NativeProvider::GlobalRef mSessionAccessibility;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
--- a/accessible/android/moz.build
+++ b/accessible/android/moz.build
@@ -6,20 +6,17 @@
 
 EXPORTS.mozilla.a11y += ['AccessibleWrap.h',
     'HyperTextAccessibleWrap.h',
     'SessionAccessibility.h',
 ]
 
 SOURCES += [
     'AccessibleWrap.cpp',
-    'DocAccessibleWrap.cpp',
     'Platform.cpp',
-    'ProxyAccessibleWrap.cpp',
-    'RootAccessibleWrap.cpp',
     'SessionAccessibility.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/accessible/base',
     '/accessible/generic',
     '/accessible/html',
     '/accessible/ipc',
--- a/accessible/generic/Accessible.h
+++ b/accessible/generic/Accessible.h
@@ -550,17 +550,17 @@ public:
   /**
    * Focus the accessible.
    */
   virtual void TakeFocus() const;
 
   /**
    * Scroll the accessible into view.
    */
-  virtual void ScrollTo(uint32_t aHow) const;
+  void ScrollTo(uint32_t aHow) const;
 
   /**
    * Scroll the accessible to the given point.
    */
   void ScrollToPoint(uint32_t aCoordinateType, int32_t aX, int32_t aY);
 
   /**
    * Get a pointer to accessibility interface for this node, which is specific
--- a/accessible/ipc/ProxyAccessibleBase.h
+++ b/accessible/ipc/ProxyAccessibleBase.h
@@ -163,24 +163,23 @@ protected:
     , mWrapper(0)
     , mID(aID)
     , mRole(aRole)
     , mOuterDoc(false)
     , mIsDoc(false)
     , mHasValue(aInterfaces & Interfaces::VALUE)
     , mIsHyperLink(aInterfaces & Interfaces::HYPERLINK)
     , mIsHyperText(aInterfaces & Interfaces::HYPERTEXT)
-    , mIsSelection(aInterfaces & Interfaces::SELECTION)
   {
   }
 
   explicit ProxyAccessibleBase(DocAccessibleParent* aThisAsDoc) :
     mParent(kNoParent), mDoc(aThisAsDoc), mWrapper(0), mID(0),
     mRole(roles::DOCUMENT), mOuterDoc(false), mIsDoc(true), mHasValue(false),
-    mIsHyperLink(false), mIsHyperText(false), mIsSelection(false)
+    mIsHyperLink(false), mIsHyperText(false)
   {}
 
 protected:
   void SetParent(Derived* aParent);
 
 private:
   uintptr_t mParent;
   static const uintptr_t kNoParent = UINTPTR_MAX;
@@ -200,17 +199,16 @@ protected:
 private:
   bool mOuterDoc : 1;
 
 public:
   const bool mIsDoc: 1;
   const bool mHasValue: 1;
   const bool mIsHyperLink: 1;
   const bool mIsHyperText: 1;
-  const bool mIsSelection: 1;
 };
 
 extern template class ProxyAccessibleBase<ProxyAccessible>;
 
 }
 }
 
 #endif
--- a/accessible/jsat/AccessFu.jsm
+++ b/accessible/jsat/AccessFu.jsm
@@ -14,29 +14,29 @@ ChromeUtils.defineModuleGetter(this, "Re
 if (Utils.MozBuildApp === "mobile/android") {
   ChromeUtils.import("resource://gre/modules/Messaging.jsm");
 }
 
 const GECKOVIEW_MESSAGE = {
   ACTIVATE: "GeckoView:AccessibilityActivate",
   BY_GRANULARITY: "GeckoView:AccessibilityByGranularity",
   CLIPBOARD: "GeckoView:AccessibilityClipboard",
-  CURSOR_TO_FOCUSED: "GeckoView:AccessibilityCursorToFocused",
   EXPLORE_BY_TOUCH: "GeckoView:AccessibilityExploreByTouch",
   LONG_PRESS: "GeckoView:AccessibilityLongPress",
   NEXT: "GeckoView:AccessibilityNext",
   PREVIOUS: "GeckoView:AccessibilityPrevious",
   SCROLL_BACKWARD: "GeckoView:AccessibilityScrollBackward",
   SCROLL_FORWARD: "GeckoView:AccessibilityScrollForward",
   SELECT: "GeckoView:AccessibilitySelect",
   SET_SELECTION: "GeckoView:AccessibilitySetSelection",
   VIEW_FOCUSED: "GeckoView:AccessibilityViewFocused",
 };
 
 const ACCESSFU_MESSAGE = {
+  PRESENT: "AccessFu:Present",
   DOSCROLL: "AccessFu:DoScroll",
 };
 
 const FRAME_SCRIPT = "chrome://global/content/accessibility/content-script.js";
 
 var AccessFu = {
   /**
    * A lazy getter for event handler that binds the scope to AccessFu object.
@@ -52,16 +52,21 @@ var AccessFu = {
    */
   enable: function enable() {
     if (this._enabled) {
       return;
     }
     this._enabled = true;
 
     ChromeUtils.import("resource://gre/modules/accessibility/Utils.jsm");
+    ChromeUtils.import("resource://gre/modules/accessibility/Presentation.jsm");
+
+    // Check for output notification
+    this._notifyOutputPref =
+      new PrefCache("accessibility.accessfu.notify_output");
 
     Services.obs.addObserver(this, "remote-browser-shown");
     Services.obs.addObserver(this, "inprocess-browser-shown");
     Services.ww.registerNotification(this);
 
     for (let win of Services.wm.getEnumerator(null)) {
       this._attachWindow(win);
     }
@@ -82,30 +87,35 @@ var AccessFu = {
     Services.obs.removeObserver(this, "remote-browser-shown");
     Services.obs.removeObserver(this, "inprocess-browser-shown");
     Services.ww.unregisterNotification(this);
 
     for (let win of Services.wm.getEnumerator(null)) {
       this._detachWindow(win);
     }
 
+    delete this._notifyOutputPref;
+
     if (this.doneCallback) {
       this.doneCallback();
       delete this.doneCallback;
     }
 
     Logger.info("AccessFu:Disabled");
   },
 
   receiveMessage: function receiveMessage(aMessage) {
     Logger.debug(() => {
       return ["Recieved", aMessage.name, JSON.stringify(aMessage.json)];
     });
 
     switch (aMessage.name) {
+      case ACCESSFU_MESSAGE.PRESENT:
+        this._output(aMessage.json, aMessage.target);
+        break;
       case ACCESSFU_MESSAGE.DOSCROLL:
         this.Input.doScroll(aMessage.json, aMessage.target);
         break;
     }
   },
 
   _attachWindow: function _attachWindow(win) {
     let wtype = win.document.documentElement.getAttribute("windowtype");
@@ -140,16 +150,48 @@ var AccessFu = {
     win.removeEventListener("TabSelect", this);
     if (win.WindowEventDispatcher) {
       // desktop mochitests don't have this.
       win.WindowEventDispatcher.unregisterListener(this,
         Object.values(GECKOVIEW_MESSAGE));
     }
   },
 
+  _output: function _output(aPresentationData, aBrowser) {
+    if (!aPresentationData) {
+      // Either no android events to send or a string used for testing only.
+      return;
+    }
+
+    if (!Utils.isAliveAndVisible(Utils.AccService.getAccessibleFor(aBrowser))) {
+      return;
+    }
+
+    let win = aBrowser.ownerGlobal;
+
+    for (let evt of aPresentationData) {
+      if (typeof evt == "string") {
+        continue;
+      }
+
+      if (win.WindowEventDispatcher) {
+        // desktop mochitests don't have this.
+        win.WindowEventDispatcher.sendRequest({
+          ...evt,
+          type: "GeckoView:AccessibilityEvent"
+        });
+      }
+    }
+
+    if (this._notifyOutputPref.value) {
+      Services.obs.notifyObservers(null, "accessibility-output",
+                                   JSON.stringify(aPresentationData));
+    }
+  },
+
   onEvent(event, data, callback) {
     switch (event) {
       case GECKOVIEW_MESSAGE.SETTINGS:
         if (data.enabled) {
           this._enable();
         } else {
           this._disable();
         }
@@ -172,18 +214,21 @@ var AccessFu = {
         // XXX: Advertize long press on supported objects and implement action
         break;
       case GECKOVIEW_MESSAGE.SCROLL_FORWARD:
         this.Input.androidScroll("forward");
         break;
       case GECKOVIEW_MESSAGE.SCROLL_BACKWARD:
         this.Input.androidScroll("backward");
         break;
-      case GECKOVIEW_MESSAGE.CURSOR_TO_FOCUSED:
-        this.autoMove({ moveToFocused: true });
+      case GECKOVIEW_MESSAGE.VIEW_FOCUSED:
+        this._focused = data.gainFocus;
+        if (this._focused) {
+          this.autoMove({ forcePresent: true, noOpIfOnScreen: true });
+        }
         break;
       case GECKOVIEW_MESSAGE.BY_GRANULARITY:
         this.Input.moveByGranularity(data);
         break;
       case GECKOVIEW_MESSAGE.EXPLORE_BY_TOUCH:
         this.Input.moveToPoint("Simple", ...data.coordinates);
         break;
       case GECKOVIEW_MESSAGE.SET_SELECTION:
@@ -231,16 +276,20 @@ var AccessFu = {
     }
   },
 
   autoMove: function autoMove(aOptions) {
     const mm = Utils.getMessageManager();
     mm.sendAsyncMessage("AccessFu:AutoMove", aOptions);
   },
 
+  announce: function announce(aAnnouncement) {
+    this._output(Presentation.announce(aAnnouncement), Utils.getCurrentBrowser());
+  },
+
   // So we don't enable/disable twice
   _enabled: false,
 
   // Layerview is focused
   _focused: false,
 
   /**
    * Adjusts the given bounds that are defined in device display pixels
--- a/accessible/jsat/ContentControl.jsm
+++ b/accessible/jsat/ContentControl.jsm
@@ -9,16 +9,18 @@ ChromeUtils.defineModuleGetter(this, "Lo
 ChromeUtils.defineModuleGetter(this, "Roles",
   "resource://gre/modules/accessibility/Constants.jsm");
 ChromeUtils.defineModuleGetter(this, "States",
   "resource://gre/modules/accessibility/Constants.jsm");
 ChromeUtils.defineModuleGetter(this, "TraversalRules",
   "resource://gre/modules/accessibility/Traversal.jsm");
 ChromeUtils.defineModuleGetter(this, "TraversalHelper",
   "resource://gre/modules/accessibility/Traversal.jsm");
+ChromeUtils.defineModuleGetter(this, "Presentation",
+  "resource://gre/modules/accessibility/Presentation.jsm");
 
 var EXPORTED_SYMBOLS = ["ContentControl"];
 
 const MOVEMENT_GRANULARITY_CHARACTER = 1;
 const MOVEMENT_GRANULARITY_WORD = 2;
 const MOVEMENT_GRANULARITY_LINE = 4;
 
 const CLIPBOARD_COPY = 0x4000;
@@ -148,16 +150,19 @@ this.ContentControl.prototype = {
         // new position.
         this.sendToChild(vc, aMessage, { action: childAction }, true);
       }
     } else if (!this._childMessageSenders.has(aMessage.target) &&
                origin !== "top") {
       // We failed to move, and the message is not from a parent, so forward
       // to it.
       this.sendToParent(aMessage);
+    } else {
+      this._contentScope.get().sendAsyncMessage("AccessFu:Present",
+        Presentation.noMove(action));
     }
   },
 
   handleMoveToPoint: function cc_handleMoveToPoint(aMessage) {
     let [x, y] = [aMessage.json.x, aMessage.json.y];
     let rule = TraversalRules[aMessage.json.rule];
 
     this.vc.moveToPoint(rule, x, y, true);
@@ -218,28 +223,39 @@ this.ContentControl.prototype = {
 
         for (let eventType of ["mousedown", "mouseup"]) {
           let evt = this.document.createEvent("MouseEvents");
           evt.initMouseEvent(eventType, true, true, this.window,
             x, y, 0, 0, 0, false, false, false, false, 0, null);
           node.dispatchEvent(evt);
         }
       }
+
+      // Action invoked will be presented on checked/selected state change.
+      if (!Utils.getState(aAccessible).contains(States.CHECKABLE) &&
+          !Utils.getState(aAccessible).contains(States.SELECTABLE)) {
+        this._contentScope.get().sendAsyncMessage("AccessFu:Present",
+          Presentation.actionInvoked());
+      }
     };
 
     let focusedAcc = Utils.AccService.getAccessibleFor(
       this.document.activeElement);
     if (focusedAcc && this.vc.position === focusedAcc
         && focusedAcc.role === Roles.ENTRY) {
       let accText = focusedAcc.QueryInterface(Ci.nsIAccessibleText);
+      let oldOffset = accText.caretOffset;
       let newOffset = aMessage.json.offset;
+      let text = accText.getText(0, accText.characterCount);
+
       if (newOffset >= 0 && newOffset <= accText.characterCount) {
         accText.caretOffset = newOffset;
       }
 
+      this.presentCaretChange(text, oldOffset, accText.caretOffset);
       return;
     }
 
     // recursively find a descendant that is activatable.
     let getActivatableDescendant = (aAccessible) => {
       if (aAccessible.actionCount > 0) {
         return aAccessible;
       }
@@ -378,16 +394,25 @@ this.ContentControl.prototype = {
           if (startSel != endSel) {
             editText.cutText(startSel, endSel);
           }
           break;
       }
     }
   },
 
+  presentCaretChange: function cc_presentCaretChange(
+    aText, aOldOffset, aNewOffset) {
+    if (aOldOffset !== aNewOffset) {
+      let msg = Presentation.textSelectionChanged(aText, aNewOffset, aNewOffset,
+        aOldOffset, aOldOffset, true);
+      this._contentScope.get().sendAsyncMessage("AccessFu:Present", msg);
+    }
+  },
+
   getChildCursor: function cc_getChildCursor(aAccessible) {
     let acc = aAccessible || this.vc.position;
     if (Utils.isAliveAndVisible(acc) && acc.role === Roles.INTERNAL_FRAME) {
       let domNode = acc.DOMNode;
       let mm = this._childMessageSenders.get(domNode, null);
       if (!mm) {
         mm = Utils.getMessageManager(domNode);
         mm.addWeakMessageListener("AccessFu:MoveCursor", this);
@@ -426,37 +451,48 @@ this.ContentControl.prototype = {
   sendToParent: function cc_sendToParent(aMessage) {
     // XXX: This is a silly way to make a deep copy
     let newJSON = JSON.parse(JSON.stringify(aMessage.json));
     newJSON.origin = "child";
     aMessage.target.sendAsyncMessage(aMessage.name, newJSON);
   },
 
   /**
-   * Move cursor.
+   * Move cursor and/or present its location.
    * aOptions could have any of these fields:
    * - delay: in ms, before actual move is performed. Another autoMove call
    *    would cancel it. Useful if we want to wait for a possible trailing
    *    focus move. Default 0.
    * - noOpIfOnScreen: if accessible is alive and visible, don't do anything.
+   * - forcePresent: present cursor location, whether we move or don't.
    * - moveToFocused: if there is a focused accessible move to that. This takes
    *    precedence over given anchor.
    * - moveMethod: pivot move method to use, default is 'moveNext',
    */
   autoMove: function cc_autoMove(aAnchor, aOptions = {}) {
     this.cancelAutoMove();
 
     let moveFunc = () => {
       let vc = this.vc;
       let acc = aAnchor;
       let rule = aOptions.onScreenOnly ?
         TraversalRules.SimpleOnScreen : TraversalRules.Simple;
+      let forcePresentFunc = () => {
+        if (aOptions.forcePresent) {
+          this._contentScope.get().sendAsyncMessage(
+            "AccessFu:Present", Presentation.pivotChanged(
+              vc.position, null, vc.startOffset, vc.endOffset,
+              Ci.nsIAccessiblePivot.REASON_NONE,
+              Ci.nsIAccessiblePivot.NO_BOUNDARY));
+        }
+      };
 
       if (aOptions.noOpIfOnScreen &&
         Utils.isAliveAndVisible(vc.position, true)) {
+        forcePresentFunc();
         return;
       }
 
       if (aOptions.moveToFocused) {
         acc = Utils.AccService.getAccessibleFor(
           this.document.activeElement) || acc;
       }
 
@@ -468,24 +504,29 @@ this.ContentControl.prototype = {
         moved = vc[moveFirstOrLast ? "moveNext" : moveMethod](rule, acc, true,
                                                               true);
       }
       if (moveFirstOrLast && !moved) {
         // We move to first/last after no anchor move happened or succeeded.
         moved = vc[moveMethod](rule, true);
       }
 
-      this.sendToChild(vc, {
+      let sentToChild = this.sendToChild(vc, {
         name: "AccessFu:AutoMove",
         json: {
           moveMethod: aOptions.moveMethod,
           moveToFocused: aOptions.moveToFocused,
           noOpIfOnScreen: true,
+          forcePresent: true
         }
       }, null, true);
+
+      if (!moved && !sentToChild) {
+        forcePresentFunc();
+      }
     };
 
     if (aOptions.delay) {
       this._autoMove = this.window.setTimeout(moveFunc, aOptions.delay);
     } else {
       moveFunc();
     }
   },
--- a/accessible/jsat/EventManager.jsm
+++ b/accessible/jsat/EventManager.jsm
@@ -5,73 +5,125 @@
 "use strict";
 
 ChromeUtils.defineModuleGetter(this, "Services",
   "resource://gre/modules/Services.jsm");
 ChromeUtils.defineModuleGetter(this, "Utils",
   "resource://gre/modules/accessibility/Utils.jsm");
 ChromeUtils.defineModuleGetter(this, "Logger",
   "resource://gre/modules/accessibility/Utils.jsm");
+ChromeUtils.defineModuleGetter(this, "Presentation",
+  "resource://gre/modules/accessibility/Presentation.jsm");
 ChromeUtils.defineModuleGetter(this, "Roles",
   "resource://gre/modules/accessibility/Constants.jsm");
 ChromeUtils.defineModuleGetter(this, "Events",
   "resource://gre/modules/accessibility/Constants.jsm");
 ChromeUtils.defineModuleGetter(this, "States",
   "resource://gre/modules/accessibility/Constants.jsm");
+ChromeUtils.defineModuleGetter(this, "clearTimeout",
+  "resource://gre/modules/Timer.jsm");
+ChromeUtils.defineModuleGetter(this, "setTimeout",
+  "resource://gre/modules/Timer.jsm");
 
 var EXPORTED_SYMBOLS = ["EventManager"];
 
 function EventManager(aContentScope) {
   this.contentScope = aContentScope;
   this.addEventListener = this.contentScope.addEventListener.bind(
     this.contentScope);
   this.removeEventListener = this.contentScope.removeEventListener.bind(
     this.contentScope);
   this.sendMsgFunc = this.contentScope.sendAsyncMessage.bind(
     this.contentScope);
+  this.webProgress = this.contentScope.docShell.
+    QueryInterface(Ci.nsIInterfaceRequestor).
+    getInterface(Ci.nsIWebProgress);
 }
 
 this.EventManager.prototype = {
   start: function start() {
     try {
       if (!this._started) {
         Logger.debug("EventManager.start");
 
         this._started = true;
 
         AccessibilityEventObserver.addListener(this);
 
+        this.webProgress.addProgressListener(this,
+          (Ci.nsIWebProgress.NOTIFY_STATE_ALL |
+           Ci.nsIWebProgress.NOTIFY_LOCATION));
+        this.addEventListener("wheel", this, true);
+        this.addEventListener("scroll", this, true);
+        this.addEventListener("resize", this, true);
         this._preDialogPosition = new WeakMap();
       }
+      this.present(Presentation.tabStateChanged(null, "newtab"));
+
     } catch (x) {
       Logger.logException(x, "Failed to start EventManager");
     }
   },
 
   // XXX: Stop is not called when the tab is closed (|TabClose| event is too
   // late). It is only called when the AccessFu is disabled explicitly.
   stop: function stop() {
     if (!this._started) {
       return;
     }
     Logger.debug("EventManager.stop");
     AccessibilityEventObserver.removeListener(this);
     try {
       this._preDialogPosition = new WeakMap();
+      this.webProgress.removeProgressListener(this);
+      this.removeEventListener("wheel", this, true);
+      this.removeEventListener("scroll", this, true);
+      this.removeEventListener("resize", this, true);
     } catch (x) {
       // contentScope is dead.
     } finally {
       this._started = false;
     }
   },
 
   get contentControl() {
     return this.contentScope._jsat_contentControl;
   },
 
+  handleEvent: function handleEvent(aEvent) {
+    Logger.debug(() => {
+      return ["DOMEvent", aEvent.type];
+    });
+
+    // The target could be an element, document or window
+    const win = aEvent.target.ownerGlobal;
+    try {
+      switch (aEvent.type) {
+        case "wheel":
+        {
+          let delta = aEvent.deltaX || aEvent.deltaY;
+          this.contentControl.autoMove(
+           null,
+           { moveMethod: delta > 0 ? "moveNext" : "movePrevious",
+             onScreenOnly: true, noOpIfOnScreen: true, delay: 500 });
+          break;
+        }
+        case "scroll":
+          this.present(Presentation.viewportScrolled(win));
+        case "resize":
+        {
+          this.present(Presentation.viewportChanged(win));
+          break;
+        }
+      }
+    } catch (x) {
+      Logger.logException(x, "Error handling DOM event");
+    }
+  },
+
   handleAccEvent: function handleAccEvent(aEvent) {
     Logger.debug(() => {
       return ["A11yEvent", Logger.eventToString(aEvent),
               Logger.accessibleToString(aEvent.accessible)];
     });
 
     // Don't bother with non-content events in firefox.
     if (Utils.MozBuildApp == "browser" &&
@@ -99,47 +151,358 @@ this.EventManager.prototype = {
         if (position && position.role == Roles.INTERNAL_FRAME) {
           break;
         }
 
         // Blur to document if new position is not explicitly focused.
         if (!position || !Utils.getState(position).contains(States.FOCUSED)) {
           aEvent.accessibleDocument.takeFocus();
         }
+
+        this.present(
+          Presentation.pivotChanged(position, event.oldAccessible,
+                                    event.newStartOffset, event.newEndOffset,
+                                    event.reason, event.boundaryType));
+
+        break;
+      }
+      case Events.STATE_CHANGE:
+      {
+        const event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent);
+        const state = Utils.getState(event);
+        if (state.contains(States.CHECKED)) {
+          this.present(Presentation.checked(aEvent.accessible));
+        } else if (state.contains(States.SELECTED)) {
+          this.present(Presentation.selected(aEvent.accessible));
+        }
         break;
       }
       case Events.NAME_CHANGE:
       {
-        // XXX: Port to Android
+        let acc = aEvent.accessible;
+        if (acc === this.contentControl.vc.position) {
+          this.present(Presentation.nameChanged(acc));
+        } else {
+          let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
+            ["text", "all"]);
+          if (liveRegion) {
+            this.present(Presentation.nameChanged(acc, isPolite));
+          }
+        }
         break;
       }
       case Events.SCROLLING_START:
       {
         this.contentControl.autoMove(aEvent.accessible);
         break;
       }
+      case Events.TEXT_CARET_MOVED:
+      {
+        let acc = aEvent.accessible.QueryInterface(Ci.nsIAccessibleText);
+        let caretOffset = aEvent.
+          QueryInterface(Ci.nsIAccessibleCaretMoveEvent).caretOffset;
+
+        // We could get a caret move in an accessible that is not focused,
+        // it doesn't mean we are not on any editable accessible. just not
+        // on this one..
+        let state = Utils.getState(acc);
+        if (state.contains(States.FOCUSED) && state.contains(States.EDITABLE)) {
+          let fromIndex = caretOffset;
+          if (acc.selectionCount) {
+            const [startSel, endSel] = Utils.getTextSelection(acc);
+            fromIndex = startSel == caretOffset ? endSel : startSel;
+          }
+          this.present(Presentation.textSelectionChanged(
+            acc.getText(0, -1), fromIndex, caretOffset, 0, 0,
+            aEvent.isFromUserInput));
+        }
+        break;
+      }
       case Events.SHOW:
       {
-        // XXX: Port to Android
+        this._handleShow(aEvent);
         break;
       }
       case Events.HIDE:
       {
-        // XXX: Port to Android
+        let evt = aEvent.QueryInterface(Ci.nsIAccessibleHideEvent);
+        this._handleHide(evt);
+        break;
+      }
+      case Events.TEXT_INSERTED:
+      case Events.TEXT_REMOVED:
+      {
+        let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
+          ["text", "all"]);
+        if (aEvent.isFromUserInput || liveRegion) {
+          // Handle all text mutations coming from the user or if they happen
+          // on a live region.
+          this._handleText(aEvent, liveRegion, isPolite);
+        }
+        break;
+      }
+      case Events.FOCUS:
+      {
+        // Put vc where the focus is at
+        let acc = aEvent.accessible;
+        if (![Roles.CHROME_WINDOW,
+             Roles.DOCUMENT,
+             Roles.APPLICATION].includes(acc.role)) {
+          this.contentControl.autoMove(acc);
+        }
+
+        this.present(Presentation.focused(acc));
+
+       if (Utils.inTest) {
+        this.sendMsgFunc("AccessFu:Focused");
+       }
+       break;
+      }
+      case Events.DOCUMENT_LOAD_COMPLETE:
+      {
+        let position = this.contentControl.vc.position;
+        // Check if position is in the subtree of the DOCUMENT_LOAD_COMPLETE
+        // event's dialog accessible or accessible document
+        let subtreeRoot = aEvent.accessible.role === Roles.DIALOG ?
+          aEvent.accessible : aEvent.accessibleDocument;
+        if (aEvent.accessible === aEvent.accessibleDocument ||
+            (position && Utils.isInSubtree(position, subtreeRoot))) {
+          // Do not automove into the document if the virtual cursor is already
+          // positioned inside it.
+          break;
+        }
+        this._preDialogPosition.set(aEvent.accessible.DOMNode, position);
+        this.contentControl.autoMove(aEvent.accessible, { delay: 500 });
         break;
       }
+      case Events.TEXT_VALUE_CHANGE:
+        // We handle this events in TEXT_INSERTED/TEXT_REMOVED.
+        break;
       case Events.VALUE_CHANGE:
       {
-        // XXX: Port to Android
-        break;
+        let position = this.contentControl.vc.position;
+        let target = aEvent.accessible;
+        if (position === target ||
+            Utils.getEmbeddedControl(position) === target) {
+          this.present(Presentation.valueChanged(target));
+        } else {
+          let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
+            ["text", "all"]);
+          if (liveRegion) {
+            this.present(Presentation.valueChanged(target, isPolite));
+          }
+        }
+      }
+    }
+  },
+
+  _handleShow: function _handleShow(aEvent) {
+    let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
+      ["additions", "all"]);
+    // Only handle show if it is a relevant live region.
+    if (!liveRegion) {
+      return;
+    }
+    // Show for text is handled by the EVENT_TEXT_INSERTED handler.
+    if (aEvent.accessible.role === Roles.TEXT_LEAF) {
+      return;
+    }
+    this._dequeueLiveEvent(Events.HIDE, liveRegion);
+    this.present(Presentation.liveRegion(liveRegion, isPolite, false));
+  },
+
+  _handleHide: function _handleHide(aEvent) {
+    let {liveRegion, isPolite} = this._handleLiveRegion(
+      aEvent, ["removals", "all"]);
+    let acc = aEvent.accessible;
+    if (liveRegion) {
+      // Hide for text is handled by the EVENT_TEXT_REMOVED handler.
+      if (acc.role === Roles.TEXT_LEAF) {
+        return;
+      }
+      this._queueLiveEvent(Events.HIDE, liveRegion, isPolite);
+    } else {
+      let vc = Utils.getVirtualCursor(this.contentScope.content.document);
+      if (vc.position &&
+        (Utils.getState(vc.position).contains(States.DEFUNCT) ||
+          Utils.isInSubtree(vc.position, acc))) {
+        let position = this._preDialogPosition.get(aEvent.accessible.DOMNode) ||
+          aEvent.targetPrevSibling || aEvent.targetParent;
+        if (!position) {
+          try {
+            position = acc.previousSibling;
+          } catch (x) {
+            // Accessible is unattached from the accessible tree.
+            position = acc.parent;
+          }
+        }
+        this.contentControl.autoMove(position,
+          { moveToFocused: true, delay: 500 });
       }
     }
   },
 
-  QueryInterface: ChromeUtils.generateQI([Ci.nsISupportsWeakReference, Ci.nsIObserver])
+  _handleText: function _handleText(aEvent, aLiveRegion, aIsPolite) {
+    let event = aEvent.QueryInterface(Ci.nsIAccessibleTextChangeEvent);
+    let isInserted = event.isInserted;
+    let txtIface = aEvent.accessible.QueryInterface(Ci.nsIAccessibleText);
+
+    let text = "";
+    try {
+      text = txtIface.getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT);
+    } catch (x) {
+      // XXX we might have gotten an exception with of a
+      // zero-length text. If we did, ignore it (bug #749810).
+      if (txtIface.characterCount) {
+        throw x;
+      }
+    }
+    // If there are embedded objects in the text, ignore them.
+    // Assuming changes to the descendants would already be handled by the
+    // show/hide event.
+    let modifiedText = event.modifiedText.replace(/\uFFFC/g, "");
+    if (modifiedText != event.modifiedText && !modifiedText.trim()) {
+      return;
+    }
+
+    if (aLiveRegion) {
+      if (aEvent.eventType === Events.TEXT_REMOVED) {
+        this._queueLiveEvent(Events.TEXT_REMOVED, aLiveRegion, aIsPolite,
+          modifiedText);
+      } else {
+        this._dequeueLiveEvent(Events.TEXT_REMOVED, aLiveRegion);
+        this.present(Presentation.liveRegion(aLiveRegion, aIsPolite, false,
+          modifiedText));
+      }
+    } else {
+      this.present(Presentation.textChanged(aEvent.accessible, isInserted,
+        event.start, event.length, text, modifiedText));
+    }
+  },
+
+  _handleLiveRegion: function _handleLiveRegion(aEvent, aRelevant) {
+    if (aEvent.isFromUserInput) {
+      return {};
+    }
+    let parseLiveAttrs = function parseLiveAttrs(aAccessible) {
+      let attrs = Utils.getAttributes(aAccessible);
+      if (attrs["container-live"]) {
+        return {
+          live: attrs["container-live"],
+          relevant: attrs["container-relevant"] || "additions text",
+          busy: attrs["container-busy"],
+          atomic: attrs["container-atomic"],
+          memberOf: attrs["member-of"]
+        };
+      }
+      return null;
+    };
+    // XXX live attributes are not set for hidden accessibles yet. Need to
+    // climb up the tree to check for them.
+    let getLiveAttributes = function getLiveAttributes(aEvent) {
+      let liveAttrs = parseLiveAttrs(aEvent.accessible);
+      if (liveAttrs) {
+        return liveAttrs;
+      }
+      let parent = aEvent.targetParent;
+      while (parent) {
+        liveAttrs = parseLiveAttrs(parent);
+        if (liveAttrs) {
+          return liveAttrs;
+        }
+        parent = parent.parent;
+      }
+      return {};
+    };
+    let {live, relevant, /* busy, atomic, memberOf */ } = getLiveAttributes(aEvent);
+    // If container-live is not present or is set to |off| ignore the event.
+    if (!live || live === "off") {
+      return {};
+    }
+    // XXX: support busy and atomic.
+
+    // Determine if the type of the mutation is relevant. Default is additions
+    // and text.
+    let isRelevant = Utils.matchAttributeValue(relevant, aRelevant);
+    if (!isRelevant) {
+      return {};
+    }
+    return {
+      liveRegion: aEvent.accessible,
+      isPolite: live === "polite"
+    };
+  },
+
+  _dequeueLiveEvent: function _dequeueLiveEvent(aEventType, aLiveRegion) {
+    let domNode = aLiveRegion.DOMNode;
+    if (this._liveEventQueue && this._liveEventQueue.has(domNode)) {
+      let queue = this._liveEventQueue.get(domNode);
+      let nextEvent = queue[0];
+      if (nextEvent.eventType === aEventType) {
+        clearTimeout(nextEvent.timeoutID);
+        queue.shift();
+        if (queue.length === 0) {
+          this._liveEventQueue.delete(domNode);
+        }
+      }
+    }
+  },
+
+  _queueLiveEvent: function _queueLiveEvent(aEventType, aLiveRegion, aIsPolite, aModifiedText) {
+    if (!this._liveEventQueue) {
+      this._liveEventQueue = new WeakMap();
+    }
+    let eventHandler = {
+      eventType: aEventType,
+      timeoutID: setTimeout(this.present.bind(this),
+        20, // Wait for a possible EVENT_SHOW or EVENT_TEXT_INSERTED event.
+        Presentation.liveRegion(aLiveRegion, aIsPolite, true, aModifiedText))
+    };
+
+    let domNode = aLiveRegion.DOMNode;
+    if (this._liveEventQueue.has(domNode)) {
+      this._liveEventQueue.get(domNode).push(eventHandler);
+    } else {
+      this._liveEventQueue.set(domNode, [eventHandler]);
+    }
+  },
+
+  present: function present(aPresentationData) {
+    if (aPresentationData && aPresentationData.length > 0) {
+      this.sendMsgFunc("AccessFu:Present", aPresentationData);
+    }
+  },
+
+  onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+    let tabstate = "";
+
+    let loadingState = Ci.nsIWebProgressListener.STATE_TRANSFERRING |
+      Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
+    let loadedState = Ci.nsIWebProgressListener.STATE_STOP |
+      Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+
+    if ((aStateFlags & loadingState) == loadingState) {
+      tabstate = "loading";
+    } else if ((aStateFlags & loadedState) == loadedState &&
+               !aWebProgress.isLoadingDocument) {
+      tabstate = "loaded";
+    }
+
+    if (tabstate) {
+      let docAcc = Utils.AccService.getAccessibleFor(aWebProgress.DOMWindow.document);
+      this.present(Presentation.tabStateChanged(docAcc, tabstate));
+    }
+  },
+
+  onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+    let docAcc = Utils.AccService.getAccessibleFor(aWebProgress.DOMWindow.document);
+    this.present(Presentation.tabStateChanged(docAcc, "newdoc"));
+  },
+
+  QueryInterface: ChromeUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference, Ci.nsIObserver])
 };
 
 const AccessibilityEventObserver = {
 
   /**
    * A WeakMap containing [content, EventManager] pairs.
    */
   eventManagers: new WeakMap(),
new file mode 100644
--- /dev/null
+++ b/accessible/jsat/OutputGenerator.jsm
@@ -0,0 +1,824 @@
+/* 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/. */
+
+/* exported UtteranceGenerator */
+
+"use strict";
+
+const INCLUDE_DESC = 0x01;
+const INCLUDE_NAME = 0x02;
+const INCLUDE_VALUE = 0x04;
+const NAME_FROM_SUBTREE_RULE = 0x10;
+const IGNORE_EXPLICIT_NAME = 0x20;
+
+const OUTPUT_DESC_FIRST = 0;
+const OUTPUT_DESC_LAST = 1;
+
+ChromeUtils.defineModuleGetter(this, "Utils", // jshint ignore:line
+  "resource://gre/modules/accessibility/Utils.jsm");
+ChromeUtils.defineModuleGetter(this, "PrefCache", // jshint ignore:line
+  "resource://gre/modules/accessibility/Utils.jsm");
+ChromeUtils.defineModuleGetter(this, "Logger", // jshint ignore:line
+  "resource://gre/modules/accessibility/Utils.jsm");
+ChromeUtils.defineModuleGetter(this, "Roles", // jshint ignore:line
+  "resource://gre/modules/accessibility/Constants.jsm");
+ChromeUtils.defineModuleGetter(this, "States", // jshint ignore:line
+  "resource://gre/modules/accessibility/Constants.jsm");
+
+var EXPORTED_SYMBOLS = ["UtteranceGenerator"]; // jshint ignore:line
+
+var OutputGenerator = {
+
+  defaultOutputOrder: OUTPUT_DESC_LAST,
+
+  /**
+   * Generates output for a PivotContext.
+   * @param {PivotContext} aContext object that generates and caches
+   *    context information for a given accessible and its relationship with
+   *    another accessible.
+   * @return {Object} An array of speech data. Depending on the utterance order,
+   *    the data describes the context for an accessible object either
+   *    starting from the accessible's ancestry or accessible's subtree.
+   */
+  genForContext: function genForContext(aContext) {
+    let output = [];
+    let self = this;
+    let addOutput = function addOutput(aAccessible) {
+      output.push.apply(output, self.genForObject(aAccessible, aContext));
+    };
+    let ignoreSubtree = function ignoreSubtree(aAccessible) {
+      let roleString = Utils.AccService.getStringRole(aAccessible.role);
+      let nameRule = self.roleRuleMap[roleString] || 0;
+      // Ignore subtree if the name is explicit and the role's name rule is the
+      // NAME_FROM_SUBTREE_RULE.
+      return (((nameRule & INCLUDE_VALUE) && aAccessible.value) ||
+              ((nameRule & NAME_FROM_SUBTREE_RULE) &&
+               (Utils.getAttributes(aAccessible)["explicit-name"] === "true" &&
+               !(nameRule & IGNORE_EXPLICIT_NAME))));
+    };
+
+    let contextStart = this._getContextStart(aContext);
+
+    if (this.outputOrder === OUTPUT_DESC_FIRST) {
+      contextStart.forEach(addOutput);
+      addOutput(aContext.accessible);
+      for (let node of aContext.subtreeGenerator(true, ignoreSubtree)) {
+        addOutput(node);
+      }
+    } else {
+      for (let node of aContext.subtreeGenerator(false, ignoreSubtree)) {
+        addOutput(node);
+      }
+      addOutput(aContext.accessible);
+
+      // If there are any documents in new ancestry, find a first one and place
+      // it in the beginning of the utterance.
+      let doc, docIndex = contextStart.findIndex(
+        ancestor => ancestor.role === Roles.DOCUMENT);
+
+      if (docIndex > -1) {
+        doc = contextStart.splice(docIndex, 1)[0];
+      }
+
+      contextStart.reverse().forEach(addOutput);
+      if (doc) {
+        output.unshift.apply(output, self.genForObject(doc, aContext));
+      }
+    }
+
+    return output;
+  },
+
+
+  /**
+   * Generates output for an object.
+   * @param {nsIAccessible} aAccessible accessible object to generate output
+   *    for.
+   * @param {PivotContext} aContext object that generates and caches
+   *    context information for a given accessible and its relationship with
+   *    another accessible.
+   * @return {Array} A 2 element array of speech data. The first element
+   *    describes the object and its state. The second element is the object's
+   *    name. Whether the object's description or it's role is included is
+   *    determined by {@link roleRuleMap}.
+   */
+  genForObject: function genForObject(aAccessible, aContext) {
+    let roleString = Utils.AccService.getStringRole(aAccessible.role);
+    let func = this.objectOutputFunctions[
+      OutputGenerator._getOutputName(roleString)] ||
+      this.objectOutputFunctions.defaultFunc;
+
+    let flags = this.roleRuleMap[roleString] || 0;
+
+    if (aAccessible.childCount === 0) {
+      flags |= INCLUDE_NAME;
+    }
+
+    return func.apply(this, [aAccessible, roleString,
+                             Utils.getState(aAccessible), flags, aContext]);
+  },
+
+  /**
+   * Generates output for an action performed.
+   * @param {nsIAccessible} aAccessible accessible object that the action was
+   *    invoked in.
+   * @param {string} aActionName the name of the action, one of the keys in
+   *    {@link gActionMap}.
+   * @return {Array} A one element array with action data.
+   */
+  genForAction: function genForAction(aObject, aActionName) {}, // jshint ignore:line
+
+  /**
+   * Generates output for an announcement.
+   * @param {string} aAnnouncement unlocalized announcement.
+   * @return {Array} An announcement speech data to be localized.
+   */
+  genForAnnouncement: function genForAnnouncement(aAnnouncement) {}, // jshint ignore:line
+
+  /**
+   * Generates output for a tab state change.
+   * @param {nsIAccessible} aAccessible accessible object of the tab's attached
+   *    document.
+   * @param {string} aTabState the tab state name, see
+   *    {@link Presenter.tabStateChanged}.
+   * @return {Array} The tab state utterace.
+   */
+  genForTabStateChange: function genForTabStateChange(aObject, aTabState) {}, // jshint ignore:line
+
+  /**
+   * Generates output for announcing entering and leaving editing mode.
+   * @param {aIsEditing} boolean true if we are in editing mode
+   * @return {Array} The mode utterance
+   */
+  genForEditingMode: function genForEditingMode(aIsEditing) {}, // jshint ignore:line
+
+  _getContextStart: function getContextStart(aContext) {}, // jshint ignore:line
+
+  /**
+   * Adds an accessible name and description to the output if available.
+   * @param {Array} aOutput Output array.
+   * @param {nsIAccessible} aAccessible current accessible object.
+   * @param {Number} aFlags output flags.
+   */
+  _addName: function _addName(aOutput, aAccessible, aFlags) {
+    let name;
+    if ((Utils.getAttributes(aAccessible)["explicit-name"] === "true" &&
+         !(aFlags & IGNORE_EXPLICIT_NAME)) || (aFlags & INCLUDE_NAME)) {
+      name = aAccessible.name;
+    }
+
+    let description = aAccessible.description;
+    if (description) {
+      // Compare against the calculated name unconditionally, regardless of name rule,
+      // so we can make sure we don't speak duplicated descriptions
+      let tmpName = name || aAccessible.name;
+      if (tmpName && (description !== tmpName)) {
+        if (name) {
+          name = this.outputOrder === OUTPUT_DESC_FIRST ?
+            description + " - " + name :
+            name + " - " + description;
+        } else {
+          name = description;
+        }
+      }
+    }
+
+    if (!name || !name.trim()) {
+      return;
+    }
+    aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? "push" : "unshift"](name);
+  },
+
+  /**
+   * Adds a landmark role to the output if available.
+   * @param {Array} aOutput Output array.
+   * @param {nsIAccessible} aAccessible current accessible object.
+   */
+  _addLandmark: function _addLandmark(aOutput, aAccessible) {
+    let landmarkName = Utils.getLandmarkName(aAccessible);
+    if (!landmarkName) {
+      return;
+    }
+    aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? "unshift" : "push"]({
+      string: landmarkName
+    });
+  },
+
+  /**
+   * Adds math roles to the output, for a MathML accessible.
+   * @param {Array} aOutput Output array.
+   * @param {nsIAccessible} aAccessible current accessible object.
+   * @param {String} aRoleStr aAccessible's role string.
+   */
+  _addMathRoles: function _addMathRoles(aOutput, aAccessible, aRoleStr) {
+    // First, determine the actual role to use (e.g. mathmlfraction).
+    let roleStr = aRoleStr;
+    switch (aAccessible.role) {
+      case Roles.MATHML_CELL:
+      case Roles.MATHML_ENCLOSED:
+      case Roles.MATHML_LABELED_ROW:
+      case Roles.MATHML_ROOT:
+      case Roles.MATHML_SQUARE_ROOT:
+      case Roles.MATHML_TABLE:
+      case Roles.MATHML_TABLE_ROW:
+        // Use the default role string.
+        break;
+      case Roles.MATHML_MULTISCRIPTS:
+      case Roles.MATHML_OVER:
+      case Roles.MATHML_SUB:
+      case Roles.MATHML_SUB_SUP:
+      case Roles.MATHML_SUP:
+      case Roles.MATHML_UNDER:
+      case Roles.MATHML_UNDER_OVER:
+        // For scripted accessibles, use the string 'mathmlscripted'.
+        roleStr = "mathmlscripted";
+        break;
+      case Roles.MATHML_FRACTION:
+        // From a semantic point of view, the only important point is to
+        // distinguish between fractions that have a bar and those that do not.
+        // Per the MathML 3 spec, the latter happens iff the linethickness
+        // attribute is of the form [zero-float][optional-unit]. In that case,
+        // we use the string 'mathmlfractionwithoutbar'.
+        let linethickness = Utils.getAttributes(aAccessible).linethickness;
+        if (linethickness) {
+            let numberMatch = linethickness.match(/^(?:\d|\.)+/);
+            if (numberMatch && !parseFloat(numberMatch[0])) {
+                roleStr += "withoutbar";
+            }
+        }
+        break;
+      default:
+        // Otherwise, do not output the actual role.
+        roleStr = null;
+        break;
+    }
+
+    // Get the math role based on the position in the parent accessible
+    // (e.g. numerator for the first child of a mathmlfraction).
+    let mathRole = Utils.getMathRole(aAccessible);
+    if (mathRole) {
+      aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? "push" : "unshift"]({
+        string: this._getOutputName(mathRole)});
+    }
+    if (roleStr) {
+      aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? "push" : "unshift"]({
+        string: this._getOutputName(roleStr)});
+    }
+  },
+
+  /**
+   * Adds MathML menclose notations to the output.
+   * @param {Array} aOutput Output array.
+   * @param {nsIAccessible} aAccessible current accessible object.
+   */
+  _addMencloseNotations: function _addMencloseNotations(aOutput, aAccessible) {
+    let notations = Utils.getAttributes(aAccessible).notation || "longdiv";
+    aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? "push" : "unshift"].apply(
+      aOutput, notations.split(" ").map(notation => {
+        return { string: this._getOutputName("notation-" + notation) };
+      }));
+  },
+
+  /**
+   * Adds an entry type attribute to the description if available.
+   * @param {Array} aOutput Output array.
+   * @param {nsIAccessible} aAccessible current accessible object.
+   * @param {String} aRoleStr aAccessible's role string.
+   */
+  _addType: function _addType(aOutput, aAccessible, aRoleStr) {
+    if (aRoleStr !== "entry") {
+      return;
+    }
+
+    let typeName = Utils.getAttributes(aAccessible)["text-input-type"];
+    // Ignore the the input type="text" case.
+    if (!typeName || typeName === "text") {
+      return;
+    }
+    aOutput.push({string: "textInputType_" + typeName});
+  },
+
+  _addState: function _addState(aOutput, aState, aRoleStr) {}, // jshint ignore:line
+
+  _addRole: function _addRole(aOutput, aAccessible, aRoleStr) {}, // jshint ignore:line
+
+  get outputOrder() {
+    if (!this._utteranceOrder) {
+      this._utteranceOrder = new PrefCache("accessibility.accessfu.utterance");
+    }
+    return typeof this._utteranceOrder.value === "number" ?
+      this._utteranceOrder.value : this.defaultOutputOrder;
+  },
+
+  _getOutputName: function _getOutputName(aName) {
+    return aName.replace(/\s/g, "");
+  },
+
+  roleRuleMap: {
+    "menubar": INCLUDE_DESC,
+    "scrollbar": INCLUDE_DESC,
+    "grip": INCLUDE_DESC,
+    "alert": INCLUDE_DESC | INCLUDE_NAME,
+    "menupopup": INCLUDE_DESC,
+    "menuitem": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "tooltip": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "columnheader": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "rowheader": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "column": NAME_FROM_SUBTREE_RULE,
+    "row": NAME_FROM_SUBTREE_RULE,
+    "cell": INCLUDE_DESC | INCLUDE_NAME,
+    "application": INCLUDE_NAME,
+    "document": INCLUDE_NAME | NAME_FROM_SUBTREE_RULE, // don't use the subtree of entire document
+    "grouping": INCLUDE_DESC | INCLUDE_NAME,
+    "toolbar": INCLUDE_DESC,
+    "table": INCLUDE_DESC | INCLUDE_NAME,
+    "link": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "helpballoon": NAME_FROM_SUBTREE_RULE,
+    "list": INCLUDE_DESC | INCLUDE_NAME,
+    "listitem": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "outline": INCLUDE_DESC,
+    "outlineitem": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "pagetab": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "graphic": INCLUDE_DESC,
+    "switch": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "pushbutton": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "checkbutton": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "radiobutton": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "buttondropdown": NAME_FROM_SUBTREE_RULE,
+    "combobox": INCLUDE_DESC | INCLUDE_VALUE,
+    "droplist": INCLUDE_DESC,
+    "progressbar": INCLUDE_DESC | INCLUDE_VALUE,
+    "slider": INCLUDE_DESC | INCLUDE_VALUE,
+    "spinbutton": INCLUDE_DESC | INCLUDE_VALUE,
+    "diagram": INCLUDE_DESC,
+    "animation": INCLUDE_DESC,
+    "equation": INCLUDE_DESC,
+    "buttonmenu": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "buttondropdowngrid": NAME_FROM_SUBTREE_RULE,
+    "pagetablist": INCLUDE_DESC,
+    "canvas": INCLUDE_DESC,
+    "check menu item": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "label": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "password text": INCLUDE_DESC,
+    "popup menu": INCLUDE_DESC,
+    "radio menu item": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "table column header": NAME_FROM_SUBTREE_RULE,
+    "table row header": NAME_FROM_SUBTREE_RULE,
+    "tear off menu item": NAME_FROM_SUBTREE_RULE,
+    "toggle button": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "parent menuitem": NAME_FROM_SUBTREE_RULE,
+    "header": INCLUDE_DESC,
+    "footer": INCLUDE_DESC,
+    "entry": INCLUDE_DESC | INCLUDE_NAME | INCLUDE_VALUE,
+    "caption": INCLUDE_DESC,
+    "document frame": INCLUDE_DESC,
+    "heading": INCLUDE_DESC,
+    "calendar": INCLUDE_DESC | INCLUDE_NAME,
+    "combobox option": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "listbox option": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "listbox rich option": NAME_FROM_SUBTREE_RULE,
+    "gridcell": NAME_FROM_SUBTREE_RULE,
+    "check rich option": NAME_FROM_SUBTREE_RULE,
+    "term": NAME_FROM_SUBTREE_RULE,
+    "definition": NAME_FROM_SUBTREE_RULE,
+    "key": NAME_FROM_SUBTREE_RULE,
+    "image map": INCLUDE_DESC,
+    "option": INCLUDE_DESC,
+    "listbox": INCLUDE_DESC,
+    "definitionlist": INCLUDE_DESC | INCLUDE_NAME,
+    "dialog": INCLUDE_DESC | INCLUDE_NAME,
+    "chrome window": IGNORE_EXPLICIT_NAME,
+    "app root": IGNORE_EXPLICIT_NAME,
+    "statusbar": NAME_FROM_SUBTREE_RULE,
+    "mathml table": INCLUDE_DESC | INCLUDE_NAME,
+    "mathml labeled row": NAME_FROM_SUBTREE_RULE,
+    "mathml table row": NAME_FROM_SUBTREE_RULE,
+    "mathml cell": INCLUDE_DESC | INCLUDE_NAME,
+    "mathml fraction": INCLUDE_DESC,
+    "mathml square root": INCLUDE_DESC,
+    "mathml root": INCLUDE_DESC,
+    "mathml enclosed": INCLUDE_DESC,
+    "mathml sub": INCLUDE_DESC,
+    "mathml sup": INCLUDE_DESC,
+    "mathml sub sup": INCLUDE_DESC,
+    "mathml under": INCLUDE_DESC,
+    "mathml over": INCLUDE_DESC,
+    "mathml under over": INCLUDE_DESC,
+    "mathml multiscripts": INCLUDE_DESC,
+    "mathml identifier": INCLUDE_DESC,
+    "mathml number": INCLUDE_DESC,
+    "mathml operator": INCLUDE_DESC,
+    "mathml text": INCLUDE_DESC,
+    "mathml string literal": INCLUDE_DESC,
+    "mathml row": INCLUDE_DESC,
+    "mathml style": INCLUDE_DESC,
+    "mathml error": INCLUDE_DESC },
+
+  mathmlRolesSet: new Set([
+    Roles.MATHML_MATH,
+    Roles.MATHML_IDENTIFIER,
+    Roles.MATHML_NUMBER,
+    Roles.MATHML_OPERATOR,
+    Roles.MATHML_TEXT,
+    Roles.MATHML_STRING_LITERAL,
+    Roles.MATHML_GLYPH,
+    Roles.MATHML_ROW,
+    Roles.MATHML_FRACTION,
+    Roles.MATHML_SQUARE_ROOT,
+    Roles.MATHML_ROOT,
+    Roles.MATHML_FENCED,
+    Roles.MATHML_ENCLOSED,
+    Roles.MATHML_STYLE,
+    Roles.MATHML_SUB,
+    Roles.MATHML_SUP,
+    Roles.MATHML_SUB_SUP,
+    Roles.MATHML_UNDER,
+    Roles.MATHML_OVER,
+    Roles.MATHML_UNDER_OVER,
+    Roles.MATHML_MULTISCRIPTS,
+    Roles.MATHML_TABLE,
+    Roles.LABELED_ROW,
+    Roles.MATHML_TABLE_ROW,
+    Roles.MATHML_CELL,
+    Roles.MATHML_ACTION,
+    Roles.MATHML_ERROR,
+    Roles.MATHML_STACK,
+    Roles.MATHML_LONG_DIVISION,
+    Roles.MATHML_STACK_GROUP,
+    Roles.MATHML_STACK_ROW,
+    Roles.MATHML_STACK_CARRIES,
+    Roles.MATHML_STACK_CARRY,
+    Roles.MATHML_STACK_LINE
+  ]),
+
+  objectOutputFunctions: {
+    _generateBaseOutput:
+      function _generateBaseOutput(aAccessible, aRoleStr, aState, aFlags) {
+        let output = [];
+
+        if (aFlags & INCLUDE_DESC) {
+          this._addState(output, aState, aRoleStr);
+          this._addType(output, aAccessible, aRoleStr);
+          this._addRole(output, aAccessible, aRoleStr);
+        }
+
+        if (aFlags & INCLUDE_VALUE && aAccessible.value.trim()) {
+          output[this.outputOrder === OUTPUT_DESC_FIRST ? "push" : "unshift"](
+            aAccessible.value);
+        }
+
+        this._addName(output, aAccessible, aFlags);
+        this._addLandmark(output, aAccessible);
+
+        return output;
+      },
+
+    label: function label(aAccessible, aRoleStr, aState, aFlags, aContext) {
+      if (aContext.isNestedControl ||
+          aContext.accessible == Utils.getEmbeddedControl(aAccessible)) {
+        // If we are on a nested control, or a nesting label,
+        // we don't need the context.
+        return [];
+      }
+
+      return this.objectOutputFunctions.defaultFunc.apply(this, arguments);
+    },
+
+    entry: function entry(aAccessible, aRoleStr, aState, aFlags) {
+      let rolestr = aState.contains(States.MULTI_LINE) ? "textarea" : "entry";
+      return this.objectOutputFunctions.defaultFunc.apply(
+        this, [aAccessible, rolestr, aState, aFlags]);
+    },
+
+    pagetab: function pagetab(aAccessible, aRoleStr, aState, aFlags) {
+      let itemno = {};
+      let itemof = {};
+      aAccessible.groupPosition({}, itemof, itemno);
+      let output = [];
+      this._addState(output, aState);
+      this._addRole(output, aAccessible, aRoleStr);
+      output.push({
+        string: "objItemOfN",
+        args: [itemno.value, itemof.value]
+      });
+
+      this._addName(output, aAccessible, aFlags);
+      this._addLandmark(output, aAccessible);
+
+      return output;
+    },
+
+    table: function table(aAccessible, aRoleStr, aState, aFlags) {
+      let output = [];
+      let table;
+      try {
+        table = aAccessible.QueryInterface(Ci.nsIAccessibleTable);
+      } catch (x) {
+        Logger.logException(x);
+        return output;
+      } finally {
+        // Check if it's a layout table, and bail out if true.
+        // We don't want to speak any table information for layout tables.
+        if (table.isProbablyForLayout()) {
+          return output;
+        }
+        this._addRole(output, aAccessible, aRoleStr);
+        output.push.call(output, {
+          string: this._getOutputName("tblColumnInfo"),
+          count: table.columnCount
+        }, {
+          string: this._getOutputName("tblRowInfo"),
+          count: table.rowCount
+        });
+        this._addName(output, aAccessible, aFlags);
+        this._addLandmark(output, aAccessible);
+        return output;
+      }
+    },
+
+    gridcell: function gridcell(aAccessible, aRoleStr, aState, aFlags) {
+      let output = [];
+      this._addState(output, aState);
+      this._addName(output, aAccessible, aFlags);
+      this._addLandmark(output, aAccessible);
+      return output;
+    },
+
+    // Use the table output functions for MathML tabular elements.
+    mathmltable: function mathmltable() {
+      return this.objectOutputFunctions.table.apply(this, arguments);
+    },
+
+    mathmlcell: function mathmlcell() {
+      return this.objectOutputFunctions.cell.apply(this, arguments);
+    },
+
+    mathmlenclosed: function mathmlenclosed(aAccessible, aRoleStr, aState,
+                                            aFlags, aContext) {
+      let output = this.objectOutputFunctions.defaultFunc.
+        apply(this, [aAccessible, aRoleStr, aState, aFlags, aContext]);
+      this._addMencloseNotations(output, aAccessible);
+      return output;
+    }
+  }
+};
+
+/**
+ * Generates speech utterances from objects, actions and state changes.
+ * An utterance is an array of speech data.
+ *
+ * It should not be assumed that flattening an utterance array would create a
+ * gramatically correct sentence. For example, {@link genForObject} might
+ * return: ['graphic', 'Welcome to my home page'].
+ * Each string element in an utterance should be gramatically correct in itself.
+ * Another example from {@link genForObject}: ['list item 2 of 5', 'Alabama'].
+ *
+ * An utterance is ordered from the least to the most important. Speaking the
+ * last string usually makes sense, but speaking the first often won't.
+ * For example {@link genForAction} might return ['button', 'clicked'] for a
+ * clicked event. Speaking only 'clicked' makes sense. Speaking 'button' does
+ * not.
+ */
+var UtteranceGenerator = {  // jshint ignore:line
+  __proto__: OutputGenerator, // jshint ignore:line
+
+  gActionMap: {
+    jump: "jumpAction",
+    press: "pressAction",
+    check: "checkAction",
+    uncheck: "uncheckAction",
+    on: "onAction",
+    off: "offAction",
+    select: "selectAction",
+    unselect: "unselectAction",
+    open: "openAction",
+    close: "closeAction",
+    switch: "switchAction",
+    click: "clickAction",
+    collapse: "collapseAction",
+    expand: "expandAction",
+    activate: "activateAction",
+    cycle: "cycleAction"
+  },
+
+  // TODO: May become more verbose in the future.
+  genForAction: function genForAction(aObject, aActionName) {
+    return [{string: this.gActionMap[aActionName]}];
+  },
+
+  genForLiveRegion:
+    function genForLiveRegion(aContext, aIsHide, aModifiedText) {
+      let utterance = [];
+      if (aIsHide) {
+        utterance.push({string: "hidden"});
+      }
+      return utterance.concat(aModifiedText || this.genForContext(aContext));
+    },
+
+  genForAnnouncement: function genForAnnouncement(aAnnouncement) {
+    return [{
+      string: aAnnouncement
+    }];
+  },
+
+  genForTabStateChange: function genForTabStateChange(aObject, aTabState) {
+    switch (aTabState) {
+      case "newtab":
+        return [{string: "tabNew"}];
+      case "loading":
+        return [{string: "tabLoading"}];
+      case "loaded":
+        return [aObject.name, {string: "tabLoaded"}];
+      case "loadstopped":
+        return [{string: "tabLoadStopped"}];
+      case "reload":
+        return [{string: "tabReload"}];
+      default:
+        return [];
+    }
+  },
+
+  genForEditingMode: function genForEditingMode(aIsEditing) {
+    return [{string: aIsEditing ? "editingMode" : "navigationMode"}];
+  },
+
+  objectOutputFunctions: {
+
+    __proto__: OutputGenerator.objectOutputFunctions, // jshint ignore:line
+
+    defaultFunc: function defaultFunc() {
+      return this.objectOutputFunctions._generateBaseOutput.apply(
+        this, arguments);
+    },
+
+    heading: function heading(aAccessible, aRoleStr, aState, aFlags) {
+      let level = {};
+      aAccessible.groupPosition(level, {}, {});
+      let utterance = [{string: "headingLevel", args: [level.value]}];
+
+      this._addName(utterance, aAccessible, aFlags);
+      this._addLandmark(utterance, aAccessible);
+
+      return utterance;
+    },
+
+    listitem: function listitem(aAccessible, aRoleStr, aState, aFlags) {
+      let itemno = {};
+      let itemof = {};
+      aAccessible.groupPosition({}, itemof, itemno);
+      let utterance = [];
+      if (itemno.value == 1) {
+        // Start of list
+        utterance.push({string: "listStart"});
+      } else if (itemno.value == itemof.value) {
+        // last item
+        utterance.push({string: "listEnd"});
+      }
+
+      this._addName(utterance, aAccessible, aFlags);
+      this._addLandmark(utterance, aAccessible);
+
+      return utterance;
+    },
+
+    list: function list(aAccessible, aRoleStr, aState, aFlags) {
+      return this._getListUtterance(aAccessible, aRoleStr, aFlags,
+        aAccessible.childCount);
+    },
+
+    definitionlist:
+      function definitionlist(aAccessible, aRoleStr, aState, aFlags) {
+        return this._getListUtterance(aAccessible, aRoleStr, aFlags,
+          aAccessible.childCount / 2);
+      },
+
+    application: function application(aAccessible, aRoleStr, aState, aFlags) {
+      // Don't utter location of applications, it gets tiring.
+      if (aAccessible.name != aAccessible.DOMNode.location) {
+        return this.objectOutputFunctions.defaultFunc.apply(this,
+          [aAccessible, aRoleStr, aState, aFlags]);
+      }
+
+      return [];
+    },
+
+    cell: function cell(aAccessible, aRoleStr, aState, aFlags, aContext) {
+      let utterance = [];
+      let cell = aContext.getCellInfo(aAccessible);
+      if (cell) {
+        let addCellChanged =
+          function addCellChanged(aUtterance, aChanged, aString, aIndex) {
+            if (aChanged) {
+              aUtterance.push({string: aString, args: [aIndex + 1]});
+            }
+          };
+        let addExtent = function addExtent(aUtterance, aExtent, aString) {
+          if (aExtent > 1) {
+            aUtterance.push({string: aString, args: [aExtent]});
+          }
+        };
+        let addHeaders = function addHeaders(aUtterance, aHeaders) {
+          if (aHeaders.length > 0) {
+            aUtterance.push.apply(aUtterance, aHeaders);
+          }
+        };
+
+        addCellChanged(utterance, cell.columnChanged, "columnInfo",
+          cell.columnIndex);
+        addCellChanged(utterance, cell.rowChanged, "rowInfo", cell.rowIndex);
+
+        addExtent(utterance, cell.columnExtent, "spansColumns");
+        addExtent(utterance, cell.rowExtent, "spansRows");
+
+        addHeaders(utterance, cell.columnHeaders);
+        addHeaders(utterance, cell.rowHeaders);
+      }
+
+      this._addName(utterance, aAccessible, aFlags);
+      this._addLandmark(utterance, aAccessible);
+
+      return utterance;
+    },
+
+    columnheader: function columnheader() {
+      return this.objectOutputFunctions.cell.apply(this, arguments);
+    },
+
+    rowheader: function rowheader() {
+      return this.objectOutputFunctions.cell.apply(this, arguments);
+    },
+
+    statictext: function statictext(aAccessible) {
+      if (Utils.isListItemDecorator(aAccessible, true)) {
+        return [];
+      }
+
+      return this.objectOutputFunctions.defaultFunc.apply(this, arguments);
+    }
+  },
+
+  _getContextStart: function _getContextStart(aContext) {
+    return aContext.newAncestry;
+  },
+
+  _addRole: function _addRole(aOutput, aAccessible, aRoleStr) {
+    if (this.mathmlRolesSet.has(aAccessible.role)) {
+      this._addMathRoles(aOutput, aAccessible, aRoleStr);
+    } else {
+      aOutput.push({string: this._getOutputName(aRoleStr)});
+    }
+  },
+
+  /**
+   * Add localized state information to output data.
+   * Note: We do not expose checked and selected states, we let TalkBack do it for us
+   * there. This is because we expose the checked information on the node info itself.
+   */
+  _addState: function _addState(aOutput, aState, aRoleStr) {
+    if (aState.contains(States.UNAVAILABLE)) {
+      aOutput.push({string: "stateUnavailable"});
+    }
+
+    if (aState.contains(States.READONLY)) {
+      aOutput.push({string: "stateReadonly"});
+    }
+
+    if (aState.contains(States.PRESSED)) {
+      aOutput.push({string: "statePressed"});
+    }
+
+    if (aState.contains(States.EXPANDABLE)) {
+      let statetr = aState.contains(States.EXPANDED) ?
+        "stateExpanded" : "stateCollapsed";
+      aOutput.push({string: statetr});
+    }
+
+    if (aState.contains(States.REQUIRED)) {
+      aOutput.push({string: "stateRequired"});
+    }
+
+    if (aState.contains(States.TRAVERSED)) {
+      aOutput.push({string: "stateTraversed"});
+    }
+
+    if (aState.contains(States.HASPOPUP)) {
+      aOutput.push({string: "stateHasPopup"});
+    }
+  },
+
+  _getListUtterance:
+    function _getListUtterance(aAccessible, aRoleStr, aFlags, aItemCount) {
+      let utterance = [];
+      this._addRole(utterance, aAccessible, aRoleStr);
+      utterance.push({
+        string: this._getOutputName("listItemsCount"),
+        count: aItemCount
+      });
+
+      this._addName(utterance, aAccessible, aFlags);
+      this._addLandmark(utterance, aAccessible);
+
+      return utterance;
+    }
+};
new file mode 100644
--- /dev/null
+++ b/accessible/jsat/Presentation.jsm
@@ -0,0 +1,332 @@
+/* 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/. */
+
+/* exported Presentation */
+
+"use strict";
+
+ChromeUtils.import("resource://gre/modules/accessibility/Utils.jsm");
+ChromeUtils.defineModuleGetter(this, "PivotContext", // jshint ignore:line
+  "resource://gre/modules/accessibility/Utils.jsm");
+ChromeUtils.defineModuleGetter(this, "UtteranceGenerator", // jshint ignore:line
+  "resource://gre/modules/accessibility/OutputGenerator.jsm");
+ChromeUtils.defineModuleGetter(this, "States", // jshint ignore:line
+  "resource://gre/modules/accessibility/Constants.jsm");
+ChromeUtils.defineModuleGetter(this, "Roles", // jshint ignore:line
+  "resource://gre/modules/accessibility/Constants.jsm");
+ChromeUtils.defineModuleGetter(this, "AndroidEvents", // jshint ignore:line
+  "resource://gre/modules/accessibility/Constants.jsm");
+
+var EXPORTED_SYMBOLS = ["Presentation"]; // jshint ignore:line
+
+const EDIT_TEXT_ROLES = new Set([
+  Roles.SPINBUTTON, Roles.PASSWORD_TEXT,
+  Roles.AUTOCOMPLETE, Roles.ENTRY, Roles.EDITCOMBOBOX]);
+
+class AndroidPresentor {
+  constructor() {
+    this.type = "Android";
+    this.displayedAccessibles = new WeakMap();
+  }
+
+  /**
+   * The virtual cursor's position changed.
+   * @param {PivotContext} aContext the context object for the new pivot
+   *   position.
+   * @param {int} aReason the reason for the pivot change.
+   *   See nsIAccessiblePivot.
+   * @param {bool} aBoundaryType the boundary type for the text movement
+   * or NO_BOUNDARY if it was not a text movement. See nsIAccessiblePivot.
+   */
+  pivotChanged(aPosition, aOldPosition, aStartOffset, aEndOffset, aReason, aBoundaryType) {
+    let context = new PivotContext(
+      aPosition, aOldPosition, aStartOffset, aEndOffset);
+    if (!context.accessible) {
+      return null;
+    }
+
+    let androidEvents = [];
+
+    const isExploreByTouch = aReason == Ci.nsIAccessiblePivot.REASON_POINT;
+
+    if (isExploreByTouch) {
+      // This isn't really used by TalkBack so this is a half-hearted attempt
+      // for now.
+      androidEvents.push({eventType: AndroidEvents.VIEW_HOVER_EXIT, text: []});
+    }
+
+    if (aPosition != aOldPosition) {
+      let info = this._infoFromContext(context);
+      let eventType = isExploreByTouch ?
+        AndroidEvents.VIEW_HOVER_ENTER :
+        AndroidEvents.VIEW_ACCESSIBILITY_FOCUSED;
+      androidEvents.push({...info, eventType});
+
+      try {
+        context.accessibleForBounds.scrollTo(
+          Ci.nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE);
+      } catch (e) {}
+    }
+
+    if (aBoundaryType != Ci.nsIAccessiblePivot.NO_BOUNDARY) {
+      const adjustedText = context.textAndAdjustedOffsets;
+
+      androidEvents.push({
+        eventType: AndroidEvents.VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
+        text: [adjustedText.text],
+        fromIndex: adjustedText.startOffset,
+        toIndex: adjustedText.endOffset
+      });
+
+      aPosition.QueryInterface(Ci.nsIAccessibleText).scrollSubstringTo(
+        aStartOffset, aEndOffset,
+        Ci.nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE);
+    }
+
+    if (context.accessible) {
+      this.displayedAccessibles.set(context.accessible.document.window, context);
+    }
+
+    return androidEvents;
+  }
+
+  focused(aObject) {
+    let info = this._infoFromContext(
+      new PivotContext(aObject, null, -1, -1, true, false));
+    return [{ eventType: AndroidEvents.VIEW_FOCUSED, ...info }];
+  }
+
+  /**
+   * An object's check action has been invoked.
+   * Note: Checkable objects use TalkBack's text derived from the event state, so we don't
+   * populate the text here.
+   * @param {nsIAccessible} aAccessible the object that has been invoked.
+   */
+  checked(aAccessible) {
+    return [{
+      eventType: AndroidEvents.VIEW_CLICKED,
+      checked: Utils.getState(aAccessible).contains(States.CHECKED)
+    }];
+  }
+
+  /**
+   * An object's select action has been invoked.
+   * @param {nsIAccessible} aAccessible the object that has been invoked.
+   */
+  selected(aAccessible) {
+    return [{
+      eventType: AndroidEvents.VIEW_SELECTED,
+      selected: Utils.getState(aAccessible).contains(States.SELECTED)
+    }];
+  }
+
+  /**
+   * An object's action has been invoked.
+   */
+  actionInvoked() {
+    return [{ eventType: AndroidEvents.VIEW_CLICKED }];
+  }
+
+  /**
+   * Text has changed, either by the user or by the system. TODO.
+   */
+  textChanged(aAccessible, aIsInserted, aStart, aLength, aText, aModifiedText) {
+    let androidEvent = {
+      eventType: AndroidEvents.VIEW_TEXT_CHANGED,
+      text: [aText],
+      fromIndex: aStart,
+      removedCount: 0,
+      addedCount: 0
+    };
+
+    if (aIsInserted) {
+      androidEvent.addedCount = aLength;
+      androidEvent.beforeText =
+        aText.substring(0, aStart) + aText.substring(aStart + aLength);
+    } else {
+      androidEvent.removedCount = aLength;
+      androidEvent.beforeText =
+        aText.substring(0, aStart) + aModifiedText + aText.substring(aStart);
+    }
+
+    return [androidEvent];
+  }
+
+  /**
+   * Text selection has changed. TODO.
+   */
+  textSelectionChanged(aText, aStart, aEnd, aOldStart, aOldEnd, aIsFromUserInput) {
+    let androidEvents = [];
+
+    if (aIsFromUserInput) {
+      let [from, to] = aOldStart < aStart ?
+        [aOldStart, aStart] : [aStart, aOldStart];
+      androidEvents.push({
+        eventType: AndroidEvents.VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
+        text: [aText],
+        fromIndex: from,
+        toIndex: to
+      });
+    } else {
+      androidEvents.push({
+        eventType: AndroidEvents.VIEW_TEXT_SELECTION_CHANGED,
+        text: [aText],
+        fromIndex: aStart,
+        toIndex: aEnd,
+        itemCount: aText.length
+      });
+    }
+
+    return androidEvents;
+  }
+
+  /**
+   * Selection has changed.
+   * XXX: Implement android event?
+   * @param {nsIAccessible} aObject the object that has been selected.
+   */
+  selectionChanged(aObject) {
+    return ["todo.selection-changed"];
+  }
+
+  /**
+   * Name has changed.
+   * XXX: Implement android event?
+   * @param {nsIAccessible} aAccessible the object whose value has changed.
+   */
+  nameChanged(aAccessible) {
+    return ["todo.name-changed"];
+  }
+
+  /**
+   * Value has changed.
+   * XXX: Implement android event?
+   * @param {nsIAccessible} aAccessible the object whose value has changed.
+   */
+  valueChanged(aAccessible) {
+    return ["todo.value-changed"];
+  }
+
+  /**
+   * The tab, or the tab's document state has changed.
+   * @param {nsIAccessible} aDocObj the tab document accessible that has had its
+   *    state changed, or null if the tab has no associated document yet.
+   * @param {string} aPageState the state name for the tab, valid states are:
+   *    'newtab', 'loading', 'newdoc', 'loaded', 'stopped', and 'reload'.
+   */
+  tabStateChanged(aDocObj, aPageState) {
+    return this.announce(
+      UtteranceGenerator.genForTabStateChange(aDocObj, aPageState));
+  }
+
+  /**
+   * The viewport has changed because of scroll.
+   * @param {Window} aWindow window of viewport that changed.
+   */
+  viewportScrolled(aWindow) {
+    const { windowUtils, devicePixelRatio } = aWindow;
+    const resolution = { value: 1 };
+    windowUtils.getResolution(resolution);
+    const scale = devicePixelRatio * resolution.value;
+    return [{
+      eventType: AndroidEvents.VIEW_SCROLLED,
+      scrollX: aWindow.scrollX * scale,
+      scrollY: aWindow.scrollY * scale,
+      maxScrollX: aWindow.scrollMaxX * scale,
+      maxScrollY: aWindow.scrollMaxY * scale,
+    }];
+  }
+
+  /**
+   * The viewport has changed, either a pan, zoom, or landscape/portrait toggle.
+   * @param {Window} aWindow window of viewport that changed.
+   */
+  viewportChanged(aWindow) {
+    const currentContext = this.displayedAccessibles.get(aWindow);
+    if (!currentContext) {
+      return;
+    }
+
+    const currentAcc = currentContext.accessibleForBounds;
+    if (Utils.isAliveAndVisible(currentAcc)) {
+      return [{
+        eventType: AndroidEvents.WINDOW_CONTENT_CHANGED,
+        bounds: Utils.getBounds(currentAcc)
+      }];
+    }
+  }
+
+  /**
+   * Announce something. Typically an app state change.
+   */
+  announce(aAnnouncement) {
+    let localizedAnnouncement = Utils.localize(aAnnouncement).join(" ");
+    return [{
+      eventType: AndroidEvents.ANNOUNCEMENT,
+      text: [localizedAnnouncement],
+      addedCount: localizedAnnouncement.length,
+      removedCount: 0,
+      fromIndex: 0
+    }];
+  }
+
+
+  /**
+   * User tried to move cursor forward or backward with no success.
+   * @param {string} aMoveMethod move method that was used (eg. 'moveNext').
+   */
+  noMove(aMoveMethod) {
+    return [{
+      eventType: AndroidEvents.VIEW_ACCESSIBILITY_FOCUSED,
+      exitView: aMoveMethod,
+      text: [""]
+    }];
+  }
+
+  /**
+   * Announce a live region.
+   * @param  {PivotContext} aContext context object for an accessible.
+   * @param  {boolean} aIsPolite A politeness level for a live region.
+   * @param  {boolean} aIsHide An indicator of hide/remove event.
+   * @param  {string} aModifiedText Optional modified text.
+   */
+  liveRegion(aAccessible, aIsPolite, aIsHide, aModifiedText) {
+    let context = !aModifiedText ?
+      new PivotContext(aAccessible, null, -1, -1, true, !!aIsHide) : null;
+    return this.announce(
+      UtteranceGenerator.genForLiveRegion(context, aIsHide, aModifiedText));
+  }
+
+  _infoFromContext(aContext) {
+    const state = Utils.getState(aContext.accessible);
+    const info = {
+      bounds: aContext.bounds,
+      focusable: state.contains(States.FOCUSABLE),
+      focused: state.contains(States.FOCUSED),
+      clickable: aContext.accessible.actionCount > 0,
+      checkable: state.contains(States.CHECKABLE),
+      checked: state.contains(States.CHECKED),
+      editable: state.contains(States.EDITABLE),
+      selected: state.contains(States.SELECTED)
+    };
+
+    if (EDIT_TEXT_ROLES.has(aContext.accessible.role)) {
+      let textAcc = aContext.accessible.QueryInterface(Ci.nsIAccessibleText);
+      return {
+        ...info,
+        className: "android.widget.EditText",
+        hint: aContext.accessible.name,
+        text: [textAcc.getText(0, -1)]
+      };
+    }
+
+    return {
+      ...info,
+      className: "android.view.View",
+      text: Utils.localize(UtteranceGenerator.genForContext(aContext)),
+    };
+  }
+}
+
+const Presentation = new AndroidPresentor();
--- a/accessible/jsat/moz.build
+++ b/accessible/jsat/moz.build
@@ -4,13 +4,15 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXTRA_JS_MODULES.accessibility += [
     'AccessFu.jsm',
     'Constants.jsm',
     'ContentControl.jsm',
     'EventManager.jsm',
+    'OutputGenerator.jsm',
+    'Presentation.jsm',
     'Traversal.jsm',
     'Utils.jsm'
 ]
 
 JAR_MANIFESTS += ['jar.mn']
--- a/accessible/tests/mochitest/jsat/a11y.ini
+++ b/accessible/tests/mochitest/jsat/a11y.ini
@@ -5,33 +5,24 @@ support-files =
   doc_traversal.html
   doc_content_integration.html
   doc_content_text.html
   !/accessible/tests/mochitest/*.js
   !/accessible/tests/mochitest/moz.png
 
 [test_alive.html]
 [test_content_integration.html]
-skip-if = true
 [test_explicit_names.html]
-skip-if = true
 [test_hints.html]
-skip-if = true
 [test_landmarks.html]
-skip-if = true
 [test_live_regions.html]
-skip-if = true
 [test_output_mathml.html]
-skip-if = true
 [test_output.html]
-skip-if = true
 [test_tables.html]
-skip-if = true
 [test_text_editable_navigation.html]
-skip-if = true
+skip-if = (verify && !debug && (os == 'linux'))
 [test_text_editing.html]
-skip-if = true
+skip-if = (verify && !debug && (os == 'linux'))
 [test_text_navigation_focus.html]
-skip-if = true
+skip-if = (verify && !debug && (os == 'linux'))
 [test_text_navigation.html]
-skip-if = true
 [test_traversal.html]
 [test_traversal_helper.html]
--- a/devtools/client/aboutdebugging-new/src/modules/usb-runtimes.js
+++ b/devtools/client/aboutdebugging-new/src/modules/usb-runtimes.js
@@ -1,43 +1,39 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { ADBScanner } = require("devtools/shared/adb/adb-scanner");
-loader.lazyRequireGetter(this, "adbAddon", "devtools/shared/adb/adb-addon", true);
-loader.lazyRequireGetter(this, "ADB_ADDON_STATES", "devtools/shared/adb/adb-addon", true);
+loader.lazyGetter(this, "adbScanner", () => {
+  const { AddonAwareADBScanner } = require("devtools/shared/adb/addon-aware-adb-scanner");
+  return new AddonAwareADBScanner();
+});
 
 /**
  * This module provides a collection of helper methods to detect USB runtimes whom Firefox
  * is running on.
  */
 function addUSBRuntimesObserver(listener) {
-  ADBScanner.on("runtime-list-updated", listener);
+  adbScanner.on("runtime-list-updated", listener);
 }
 exports.addUSBRuntimesObserver = addUSBRuntimesObserver;
 
 function disableUSBRuntimes() {
-  ADBScanner.disable();
+  adbScanner.disable();
 }
 exports.disableUSBRuntimes = disableUSBRuntimes;
 
 async function enableUSBRuntimes() {
-  if (adbAddon.status !== ADB_ADDON_STATES.INSTALLED) {
-    console.error("ADB extension is not installed");
-    return;
-  }
-
-  ADBScanner.enable();
+  adbScanner.enable();
 }
 exports.enableUSBRuntimes = enableUSBRuntimes;
 
 function getUSBRuntimes() {
-  return ADBScanner.listRuntimes();
+  return adbScanner.listRuntimes();
 }
 exports.getUSBRuntimes = getUSBRuntimes;
 
 function removeUSBRuntimesObserver(listener) {
-  ADBScanner.off("runtime-list-updated", listener);
+  adbScanner.off("runtime-list-updated", listener);
 }
 exports.removeUSBRuntimesObserver = removeUSBRuntimesObserver;
--- a/devtools/client/webide/content/addons.js
+++ b/devtools/client/webide/content/addons.js
@@ -3,21 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const {loader, require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
 
 const Services = require("Services");
 const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
 
 const {gDevTools} = require("devtools/client/framework/devtools");
-const {ADBScanner} = require("devtools/shared/adb/adb-scanner");
-const {RuntimeScanners} = require("devtools/client/webide/modules/runtimes");
 
 loader.lazyRequireGetter(this, "adbAddon", "devtools/shared/adb/adb-addon", true);
-loader.lazyRequireGetter(this, "ADB_ADDON_STATES", "devtools/shared/adb/adb-addon", true);
 
 window.addEventListener("load", function() {
   document.querySelector("#aboutaddons").onclick = function() {
     const browserWin = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
     if (browserWin && browserWin.BrowserOpenAddonsMgr) {
       browserWin.BrowserOpenAddonsMgr("addons://list/extension");
     }
   };
@@ -29,21 +26,16 @@ function CloseUI() {
   window.parent.UI.openProject();
 }
 
 function BuildUI() {
   function onAddonUpdate(arg) {
     progress.removeAttribute("value");
     li.setAttribute("status", adbAddon.status);
     status.textContent = Strings.GetStringFromName("addons_status_" + adbAddon.status);
-    if (adbAddon.status == ADB_ADDON_STATES.INSTALLED) {
-      RuntimeScanners.add(ADBScanner);
-    } else if (adbAddon.status == ADB_ADDON_STATES.UNINSTALLED) {
-      RuntimeScanners.remove(ADBScanner);
-    }
   }
 
   function onAddonFailure(arg) {
     window.parent.UI.reportError("error_operationFail", arg);
   }
 
   function onAddonProgress(arg) {
     if (arg == -1) {
--- a/devtools/client/webide/modules/runtimes.js
+++ b/devtools/client/webide/modules/runtimes.js
@@ -5,16 +5,22 @@
 "use strict";
 
 const Services = require("Services");
 const {DebuggerServer} = require("devtools/server/main");
 const discovery = require("devtools/shared/discovery/discovery");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {RuntimeTypes} = require("devtools/client/webide/modules/runtime-types");
 const promise = require("promise");
+
+loader.lazyGetter(this, "adbScanner", () => {
+  const { AddonAwareADBScanner } = require("devtools/shared/adb/addon-aware-adb-scanner");
+  return new AddonAwareADBScanner();
+});
+
 loader.lazyRequireGetter(this, "AuthenticationResult",
   "devtools/shared/security/auth", true);
 loader.lazyRequireGetter(this, "DevToolsUtils",
   "devtools/shared/DevToolsUtils");
 
 const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
 
 /**
@@ -187,16 +193,20 @@ var RuntimeScanners = {
 };
 
 EventEmitter.decorate(RuntimeScanners);
 
 exports.RuntimeScanners = RuntimeScanners;
 
 /* SCANNERS */
 
+// The adb-scanner will automatically start and stop when the ADB extension is installed
+// and uninstalled, so the scanner itself can always be used.
+RuntimeScanners.add(adbScanner);
+
 var WiFiScanner = {
 
   _runtimes: [],
 
   init() {
     this.updateRegistration();
     Services.prefs.addObserver(this.ALLOWED_PREF, this);
   },
--- a/devtools/shared/adb/adb-scanner.js
+++ b/devtools/shared/adb/adb-scanner.js
@@ -7,57 +7,60 @@
 const EventEmitter = require("devtools/shared/event-emitter");
 const { Devices } = require("devtools/shared/apps/Devices.jsm");
 const { dumpn } = require("devtools/shared/DevToolsUtils");
 const { RuntimeTypes } =
   require("devtools/client/webide/modules/runtime-types");
 const { ADB } = require("devtools/shared/adb/adb");
 loader.lazyRequireGetter(this, "Device", "devtools/shared/adb/adb-device");
 
-const ADBScanner = {
+class ADBScanner extends EventEmitter {
+  constructor() {
+    super();
+    this._runtimes = [];
 
-  _runtimes: [],
+    this._onDeviceConnected = this._onDeviceConnected.bind(this);
+    this._onDeviceDisconnected = this._onDeviceDisconnected.bind(this);
+    this._updateRuntimes = this._updateRuntimes.bind(this);
+  }
 
   enable() {
-    this._onDeviceConnected = this._onDeviceConnected.bind(this);
-    this._onDeviceDisconnected = this._onDeviceDisconnected.bind(this);
     EventEmitter.on(ADB, "device-connected", this._onDeviceConnected);
     EventEmitter.on(ADB, "device-disconnected", this._onDeviceDisconnected);
 
-    this._updateRuntimes = this._updateRuntimes.bind(this);
     Devices.on("register", this._updateRuntimes);
     Devices.on("unregister", this._updateRuntimes);
     Devices.on("addon-status-updated", this._updateRuntimes);
 
     ADB.start().then(() => {
       ADB.trackDevices();
     });
     this._updateRuntimes();
-  },
+  }
 
   disable() {
     EventEmitter.off(ADB, "device-connected", this._onDeviceConnected);
     EventEmitter.off(ADB, "device-disconnected", this._onDeviceDisconnected);
     Devices.off("register", this._updateRuntimes);
     Devices.off("unregister", this._updateRuntimes);
     Devices.off("addon-status-updated", this._updateRuntimes);
-  },
+  }
 
   _emitUpdated() {
     this.emit("runtime-list-updated");
-  },
+  }
 
   _onDeviceConnected(deviceId) {
     const device = new Device(deviceId);
     Devices.register(deviceId, device);
-  },
+  }
 
   _onDeviceDisconnected(deviceId) {
     Devices.unregister(deviceId);
-  },
+  }
 
   _updateRuntimes() {
     if (this._updatingPromise) {
       return this._updatingPromise;
     }
     this._runtimes = [];
     const promises = [];
     for (const id of Devices.available()) {
@@ -67,36 +70,33 @@ const ADBScanner = {
     this._updatingPromise = Promise.all(promises);
     this._updatingPromise.then(() => {
       this._emitUpdated();
       this._updatingPromise = null;
     }, () => {
       this._updatingPromise = null;
     });
     return this._updatingPromise;
-  },
+  }
 
-  _detectRuntimes: async function(device) {
+  async _detectRuntimes(device) {
     const model = await device.getModel();
     const detectedRuntimes =
       await FirefoxOnAndroidRuntime.detect(device, model);
     this._runtimes.push(...detectedRuntimes);
-  },
+  }
 
   scan() {
     return this._updateRuntimes();
-  },
+  }
 
   listRuntimes() {
     return this._runtimes;
   }
-
-};
-
-EventEmitter.decorate(ADBScanner);
+}
 
 function Runtime(device, model, socketPath) {
   this.device = device;
   this._model = model;
   this._socketPath = socketPath;
 }
 
 Runtime.prototype = {
new file mode 100644
--- /dev/null
+++ b/devtools/shared/adb/addon-aware-adb-scanner.js
@@ -0,0 +1,98 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const EventEmitter = require("devtools/shared/event-emitter");
+
+loader.lazyRequireGetter(this, "adbAddon", "devtools/shared/adb/adb-addon", true);
+loader.lazyRequireGetter(this, "ADBScanner", "devtools/shared/adb/adb-scanner", true);
+
+/**
+ * The AddonAwareADBScanner decorates an instance of ADBScanner. It will wait until the
+ * ADB addon is installed to enable() the real scanner, and will automatically disable
+ * it if the addon is uninstalled.
+ *
+ * It implements the following public API of ADBScanner:
+ * - enable
+ * - disable
+ * - scan
+ * - listRuntimes
+ * - event "runtime-list-updated"
+ */
+class AddonAwareADBScanner extends EventEmitter {
+  /**
+   * Parameters are provided only to allow tests to replace actual implementations with
+   * mocks.
+   *
+   * @param {ADBScanner} scanner
+   *        Only provided in tests for mocks
+   * @param {ADBAddon} addon
+   *        Only provided in tests for mocks
+   */
+  constructor(scanner = new ADBScanner(), addon = adbAddon) {
+    super();
+
+    this._onScannerListUpdated = this._onScannerListUpdated.bind(this);
+    this._onAddonUpdate = this._onAddonUpdate.bind(this);
+
+    this._scanner = scanner;
+    this._scanner.on("runtime-list-updated", this._onScannerListUpdated);
+
+    this._addon = addon;
+  }
+
+  /**
+   * Only forward the enable() call if the addon is installed, because ADBScanner::enable
+   * only works if the addon is installed.
+   */
+  enable() {
+    if (this._addon.status === "installed") {
+      this._scanner.enable();
+    }
+
+    // Remove any previous listener, to make sure we only add one listener if enable() is
+    // called several times.
+    this._addon.off("update", this._onAddonUpdate);
+
+    this._addon.on("update", this._onAddonUpdate);
+  }
+
+  disable() {
+    this._scanner.disable();
+
+    this._addon.off("update", this._onAddonUpdate);
+  }
+
+  /**
+   * Scan for USB devices.
+   *
+   * @return {Promise} Promise that will resolve when the scan is completed.
+   */
+  scan() {
+    return this._scanner.scan();
+  }
+
+  /**
+   * Get the list of currently detected runtimes.
+   *
+   * @return {Array} Array of currently detected runtimes.
+   */
+  listRuntimes() {
+    return this._scanner.listRuntimes();
+  }
+
+  _onAddonUpdate() {
+    if (this._addon.status === "installed") {
+      this._scanner.enable();
+    } else {
+      this._scanner.disable();
+    }
+  }
+
+  _onScannerListUpdated() {
+    this.emit("runtime-list-updated");
+  }
+}
+exports.AddonAwareADBScanner = AddonAwareADBScanner;
--- a/devtools/shared/adb/moz.build
+++ b/devtools/shared/adb/moz.build
@@ -6,14 +6,15 @@ DevToolsModules(
     'adb-addon.js',
     'adb-binary.js',
     'adb-client.js',
     'adb-device.js',
     'adb-running-checker.js',
     'adb-scanner.js',
     'adb-socket.js',
     'adb.js',
+    'addon-aware-adb-scanner.js',
 )
 
 with Files('**'):
     BUG_COMPONENT = ('DevTools', 'about:debugging')
 
 XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
--- a/devtools/shared/adb/test/test_adb.js
+++ b/devtools/shared/adb/test/test_adb.js
@@ -1,8 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
 "use strict";
 
 const EventEmitter = require("devtools/shared/event-emitter");
 const { ExtensionTestUtils } = ChromeUtils.import("resource://testing-common/ExtensionXPCShellUtils.jsm", {});
 const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
 const { getFileForBinary } = require("devtools/shared/adb/adb-binary");
 const { check } = require("devtools/shared/adb/adb-running-checker");
 const { ADB } = require("devtools/shared/adb/adb");
new file mode 100644
--- /dev/null
+++ b/devtools/shared/adb/test/test_addon-aware-adb-scanner.js
@@ -0,0 +1,221 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const EventEmitter = require("devtools/shared/event-emitter");
+const { AddonAwareADBScanner } = require("devtools/shared/adb/addon-aware-adb-scanner");
+
+/**
+ * For the scanner mock, we create an object with spies for each of the public methods
+ * used by the AddonAwareADBScanner, and the ability to emit events.
+ */
+function prepareMockScanner() {
+  const mockScanner = {
+    enable: sinon.spy(),
+    disable: sinon.spy(),
+    scan: sinon.spy(),
+    listRuntimes: sinon.spy()
+  };
+  EventEmitter.decorate(mockScanner);
+  return mockScanner;
+}
+
+/**
+ * For the addon mock, we simply need an object that is able to emit events and has a
+ * status.
+ */
+function prepareMockAddon() {
+  const mockAddon = {
+    status: "unknown",
+  };
+  EventEmitter.decorate(mockAddon);
+  return mockAddon;
+}
+
+/**
+ * Prepare all mocks needed for the scanner tests.
+ */
+function prepareMocks() {
+  const mockScanner = prepareMockScanner();
+  const mockAddon = prepareMockAddon();
+  const addonAwareAdbScanner = new AddonAwareADBScanner(mockScanner, mockAddon);
+  return { addonAwareAdbScanner, mockAddon, mockScanner };
+}
+
+/**
+ * This test covers basic usage of enable() on the AddonAwareADBScanner, and checks the
+ * different behaviors based on the addon status.
+ */
+add_task(async function testCallingEnable() {
+  const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
+
+  // Check that enable() is not called if the addon is uninstalled
+  mockAddon.status = "uninstalled";
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.notCalled, "enable() was not called");
+  mockScanner.enable.reset();
+
+  // Check that enable() is called if the addon is installed
+  mockAddon.status = "installed";
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.called, "enable() was called");
+  mockScanner.enable.reset();
+});
+
+/**
+ * This test checks that enable()/disable() methods from the internal ADBScanner are
+ * called when the addon is installed or uninstalled.
+ */
+add_task(async function testUpdatingAddonEnablesDisablesScanner() {
+  const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
+
+  // Enable the addon aware scanner
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.notCalled, "enable() was not called initially");
+
+  // Check that enable() is called automatically when the addon is installed
+  mockAddon.status = "installed";
+  mockAddon.emit("update");
+  ok(mockScanner.enable.called, "enable() was called when installing the addon");
+  ok(mockScanner.disable.notCalled, "disable() was not called when installing the addon");
+  mockScanner.enable.reset();
+  mockScanner.disable.reset();
+
+  // Check that disabled() is called automatically when the addon is uninstalled
+  mockAddon.status = "uninstalled";
+  mockAddon.emit("update");
+  ok(mockScanner.enable.notCalled, "enable() was not called when uninstalling the addon");
+  ok(mockScanner.disable.called, "disable() was called when uninstalling the addon");
+  mockScanner.enable.reset();
+  mockScanner.disable.reset();
+
+  // Check that enable() is called again when the addon is reinstalled
+  mockAddon.status = "installed";
+  mockAddon.emit("update");
+  ok(mockScanner.enable.called, "enable() was called when installing the addon");
+  ok(mockScanner.disable.notCalled, "disable() was not called when installing the addon");
+  mockScanner.enable.reset();
+  mockScanner.disable.reset();
+});
+
+/**
+ * This test checks that disable() is forwarded from the AddonAwareADBScanner to the real
+ * scanner even if the addon is uninstalled. We might miss the addon uninstall
+ * notification, so it is safer to always proceed with disabling.
+ */
+add_task(async function testScannerIsDisabledWhenMissingAddonUpdate() {
+  const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
+
+  // Enable the addon aware scanner
+  mockAddon.status = "installed";
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.called, "enable() was called initially");
+  mockScanner.enable.reset();
+
+  // Uninstall the addon without firing any event
+  mockAddon.status = "uninstalled";
+
+  // Programmatically call disable, check that the scanner's disable is called even though
+  // the addon was uninstalled.
+  addonAwareAdbScanner.disable();
+  ok(mockScanner.disable.called, "disable() was called when uninstalling the addon");
+  mockScanner.disable.reset();
+});
+
+/**
+ * This test checks that when the AddonAwareADBScanner is disabled, then enable/disable
+ * are not called on the inner scanner when the addon status changes.
+ */
+add_task(async function testInnerEnableIsNotCalledIfNotStarted() {
+  const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
+
+  // Check that enable() is not called on the inner scanner when the addon is installed
+  // if the AddonAwareADBScanner was not enabled
+  mockAddon.status = "installed";
+  mockAddon.emit("update");
+  ok(mockScanner.enable.notCalled, "enable() was not called");
+
+  // Same for disable() and "uninstall"
+  mockAddon.status = "uninstalled";
+  mockAddon.emit("update");
+  ok(mockScanner.disable.notCalled, "disable() was not called");
+
+  // Enable the addon aware scanner
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.notCalled, "enable() was not called");
+  ok(mockScanner.disable.notCalled, "disable() was not called");
+});
+
+/**
+ * This test checks that when the AddonAwareADBScanner is disabled, installing the addon
+ * no longer enables the internal ADBScanner.
+ */
+add_task(async function testEnableIsNoLongerCalledAfterDisabling() {
+  const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
+
+  // Start with the addon installed
+  mockAddon.status = "installed";
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.called, "enable() was called since addon was already installed");
+  mockScanner.enable.reset();
+
+  // Here we call enable again to check that we will not add too many events.
+  // A single call to disable() should stop all listeners, even if we called enable()
+  // several times.
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.called, "enable() was called again");
+  mockScanner.enable.reset();
+
+  // Disable the scanner
+  addonAwareAdbScanner.disable();
+  ok(mockScanner.disable.called, "disable() was called");
+  mockScanner.disable.reset();
+
+  // Emit an addon update event
+  mockAddon.emit("update");
+  ok(mockScanner.enable.notCalled,
+    "enable() is not called since the main scanner is disabled");
+});
+
+/**
+ * Basic check that the "runtime-list-updated" event is forwarded.
+ */
+add_task(async function testListUpdatedEventForwarding() {
+  const { mockScanner, addonAwareAdbScanner } = prepareMocks();
+
+  const spy = sinon.spy();
+  addonAwareAdbScanner.on("runtime-list-updated", spy);
+  mockScanner.emit("runtime-list-updated");
+  ok(spy.called, "The runtime-list-updated event was forwarded from ADBScanner");
+  addonAwareAdbScanner.off("runtime-list-updated", spy);
+});
+
+/**
+ * Basic check that calls to scan() are forwarded.
+ */
+add_task(async function testScanCallForwarding() {
+  const { mockScanner, addonAwareAdbScanner } = prepareMocks();
+
+  ok(mockScanner.scan.notCalled, "ADBScanner scan() is not called initially");
+
+  addonAwareAdbScanner.scan();
+  mockScanner.emit("runtime-list-updated");
+  ok(mockScanner.scan.called, "ADBScanner scan() was called");
+  mockScanner.scan.reset();
+});
+
+/**
+ * Basic check that calls to scan() are forwarded.
+ */
+add_task(async function testListRuntimesCallForwarding() {
+  const { mockScanner, addonAwareAdbScanner } = prepareMocks();
+
+  ok(mockScanner.listRuntimes.notCalled,
+    "ADBScanner listRuntimes() is not called initially");
+
+  addonAwareAdbScanner.listRuntimes();
+  mockScanner.emit("runtime-list-updated");
+  ok(mockScanner.listRuntimes.called, "ADBScanner listRuntimes() was called");
+  mockScanner.scan.reset();
+});
--- a/devtools/shared/adb/test/xpcshell-head.js
+++ b/devtools/shared/adb/test/xpcshell-head.js
@@ -1,8 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /* eslint no-unused-vars: [2, {"vars": "local"}] */
 
 const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
+const Services = require("Services");
+
+// ================================================
+// Load mocking/stubbing library, sinon
+// docs: http://sinonjs.org/releases/v2.3.2/
+ChromeUtils.import("resource://gre/modules/Timer.jsm");
+Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js", this);
+/* globals sinon */
+// ================================================
--- a/devtools/shared/adb/test/xpcshell.ini
+++ b/devtools/shared/adb/test/xpcshell.ini
@@ -3,8 +3,9 @@ tags = devtools
 head = xpcshell-head.js
 firefox-appdir = browser
 skip-if = toolkit == 'android'
 support-files =
   adb.py
 
 [test_adb.js]
 run-sequentially = An extension having the same id is installed/uninstalled in different tests
+[test_addon-aware-adb-scanner.js]
--- a/dom/canvas/test/webgl-conf/generated-mochitest.ini
+++ b/dom/canvas/test/webgl-conf/generated-mochitest.ini
@@ -5335,17 +5335,16 @@ subsuite = webgl2-core
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance2__rendering__instanced-arrays.html]
 subsuite = webgl2-core
 fail-if = 1
 [generated/test_2_conformance2__rendering__instanced-rendering-bug.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__instanced-rendering-large-divisor.html]
 subsuite = webgl2-core
-skip-if = (os == 'win')
 [generated/test_2_conformance2__rendering__line-rendering-quality.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__multisampling-fragment-evaluation.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__out-of-bounds-index-buffers-after-copying.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__read-draw-when-missing-image.html]
 subsuite = webgl2-core
--- a/dom/canvas/test/webgl-conf/mochitest-errata.ini
+++ b/dom/canvas/test/webgl-conf/mochitest-errata.ini
@@ -1167,11 +1167,8 @@ skip-if = (os == 'win')
 [generated/test_conformance__misc__webgl-specific-stencil-settings.html]
 skip-if = (os == 'win')
 [generated/test_conformance__textures__misc__tex-video-using-tex-unit-non-zero.html]
 # Fails on QuantumRender configs, but passes on standard configs?
 # Might be intermittant.
 skip-if = (os == 'win')
 [generated/test_2_conformance__textures__misc__tex-video-using-tex-unit-non-zero.html]
 skip-if = (os == 'win')
-[generated/test_2_conformance2__rendering__instanced-rendering-large-divisor.html]
-# Assertion failed: static_cast<unsigned int>(packedAttrib.divisor) == divisor, file z:/build/build/src/gfx/angle/checkout/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp, line 89
-skip-if = (os == 'win')
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2097,31 +2097,16 @@ ContentParent::RecvCreateReplayingProces
   mReplayingChildren[aChannelId] = new GeckoChildProcessHost(GeckoProcessType_Content);
   if (!mReplayingChildren[aChannelId]->LaunchAndWaitForProcessHandle(extraArgs)) {
     return IPC_FAIL_NO_REASON(this);
   }
 
   return IPC_OK();
 }
 
-mozilla::ipc::IPCResult
-ContentParent::RecvTerminateReplayingProcess(const uint32_t& aChannelId)
-{
-  // We should only get this message from the child if it is recording or replaying.
-  if (!this->IsRecordingOrReplaying()) {
-    return IPC_FAIL_NO_REASON(this);
-  }
-
-  if (aChannelId < mReplayingChildren.length() && mReplayingChildren[aChannelId]) {
-    DelayedDeleteSubprocess(mReplayingChildren[aChannelId]);
-    mReplayingChildren[aChannelId] = nullptr;
-  }
-  return IPC_OK();
-}
-
 jsipc::CPOWManager*
 ContentParent::GetCPOWManager()
 {
   if (PJavaScriptParent* p = LoneManagedOrNullAsserts(ManagedPJavaScriptParent())) {
     return CPOWManagerFor(p);
   }
   return nullptr;
 }
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -302,17 +302,16 @@ public:
                                                          bool* aIsForBrowser) override;
 
   virtual mozilla::ipc::IPCResult RecvBridgeToChildProcess(const ContentParentId& aCpId,
                                                            Endpoint<PContentBridgeParent>* aEndpoint) override;
 
   virtual mozilla::ipc::IPCResult RecvOpenRecordReplayChannel(const uint32_t& channelId,
                                                               FileDescriptor* connection) override;
   virtual mozilla::ipc::IPCResult RecvCreateReplayingProcess(const uint32_t& aChannelId) override;
-  virtual mozilla::ipc::IPCResult RecvTerminateReplayingProcess(const uint32_t& aChannelId) override;
 
   virtual mozilla::ipc::IPCResult RecvCreateGMPService() override;
 
   virtual mozilla::ipc::IPCResult RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv,
                                                  uint32_t* aRunID,
                                                  Endpoint<PPluginModuleParent>* aEndpoint) override;
 
   virtual mozilla::ipc::IPCResult RecvMaybeReloadPlugins() override;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -737,17 +737,16 @@ parent:
                             TabId tabId)
         returns (ContentParentId cpId, bool isForBrowser);
     sync BridgeToChildProcess(ContentParentId cpId)
         returns (Endpoint<PContentBridgeParent> endpoint);
 
     sync OpenRecordReplayChannel(uint32_t channelId)
         returns (FileDescriptor connection);
     async CreateReplayingProcess(uint32_t channelId);
-    async TerminateReplayingProcess(uint32_t channelId);
 
     async CreateGMPService();
 
     async InitStreamFilter(uint64_t channelId, nsString addonId)
         returns (Endpoint<PStreamFilterChild> aEndpoint);
 
     /**
      * This call connects the content process to a plugin process. This call
--- a/gfx/angle/checkout/out/gen/angle/id/commit.h
+++ b/gfx/angle/checkout/out/gen/angle/id/commit.h
@@ -1,3 +1,3 @@
-#define ANGLE_COMMIT_HASH "598f2502e3a4"
+#define ANGLE_COMMIT_HASH "790e8e6b4179"
 #define ANGLE_COMMIT_HASH_SIZE 12
-#define ANGLE_COMMIT_DATE "2018-10-09 13:54:05 -0700"
+#define ANGLE_COMMIT_DATE "2018-10-09 17:41:46 -0700"
--- a/gfx/angle/checkout/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp
+++ b/gfx/angle/checkout/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp
@@ -54,17 +54,18 @@ GLenum GetGLSLAttributeType(const std::v
     return GL_NONE;
 }
 
 struct PackedAttribute
 {
     uint8_t attribType;
     uint8_t semanticIndex;
     uint8_t vertexFormatType;
-    uint8_t divisor;
+    uint8_t dummyPadding;
+    uint32_t divisor;
 };
 
 }  // anonymous namespace
 
 PackedAttributeLayout::PackedAttributeLayout() : numAttributes(0), flags(0), attributeData({})
 {
 }
 
@@ -76,27 +77,28 @@ void PackedAttributeLayout::addAttribute
                                              unsigned int divisor)
 {
     gl::AttributeType attribType = gl::GetAttributeType(glType);
 
     PackedAttribute packedAttrib;
     packedAttrib.attribType       = static_cast<uint8_t>(attribType);
     packedAttrib.semanticIndex    = static_cast<uint8_t>(semanticIndex);
     packedAttrib.vertexFormatType = static_cast<uint8_t>(vertexFormatType);
-    packedAttrib.divisor          = static_cast<uint8_t>(divisor);
+    packedAttrib.dummyPadding     = 0u;
+    packedAttrib.divisor          = static_cast<uint32_t>(divisor);
 
     ASSERT(static_cast<gl::AttributeType>(packedAttrib.attribType) == attribType);
     ASSERT(static_cast<UINT>(packedAttrib.semanticIndex) == semanticIndex);
     ASSERT(static_cast<gl::VertexFormatType>(packedAttrib.vertexFormatType) == vertexFormatType);
     ASSERT(static_cast<unsigned int>(packedAttrib.divisor) == divisor);
 
-    static_assert(sizeof(uint32_t) == sizeof(PackedAttribute),
-                  "PackedAttributes must be 32-bits exactly.");
+    static_assert(sizeof(uint64_t) == sizeof(PackedAttribute),
+                  "PackedAttributes must be 64-bits exactly.");
 
-    attributeData[numAttributes++] = gl::bitCast<uint32_t>(packedAttrib);
+    attributeData[numAttributes++] = gl::bitCast<uint64_t>(packedAttrib);
 }
 
 bool PackedAttributeLayout::operator==(const PackedAttributeLayout &other) const
 {
     return (numAttributes == other.numAttributes) && (flags == other.flags) &&
            (attributeData == other.attributeData);
 }
 
--- a/gfx/angle/checkout/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.h
+++ b/gfx/angle/checkout/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.h
@@ -43,17 +43,17 @@ struct PackedAttributeLayout
     {
         FLAG_USES_INSTANCED_SPRITES     = 0x1,
         FLAG_INSTANCED_SPRITES_ACTIVE   = 0x2,
         FLAG_INSTANCED_RENDERING_ACTIVE = 0x4,
     };
 
     uint32_t numAttributes;
     uint32_t flags;
-    gl::AttribArray<uint32_t> attributeData;
+    gl::AttribArray<uint64_t> attributeData;
 };
 }  // namespace rx
 
 namespace std
 {
 template <>
 struct hash<rx::PackedAttributeLayout>
 {
--- a/gfx/angle/cherry_picks.txt
+++ b/gfx/angle/cherry_picks.txt
@@ -1,8 +1,28 @@
+commit 790e8e6b417905eca335d06c16ec54c977188110
+Author: Olli Etuaho <oetuaho@nvidia.com>
+Date:   Thu Sep 20 13:20:50 2018 +0300
+
+    Fix using a large vertex attrib divisor on D3D11
+    
+    A divisor >= 256 used to trigger an assert on the D3D11 backend since
+    it couldn't fit into the input layout cache. Increase the space
+    reserved for the divisor in the input layout cache to make sure that
+    the correct input layout will get used and to fix the assert.
+    
+    BUG=angleproject:2832
+    TEST=angle_end2end_tests
+    
+    Change-Id: I34eead6c4e8c4fea379bbafc8670b4e32a5b633b
+    Reviewed-on: https://chromium-review.googlesource.com/1236293
+    Reviewed-by: Jamie Madill <jmadill@chromium.org>
+    Reviewed-by: Geoff Lang <geofflang@chromium.org>
+    Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>
+
 commit 598f2502e3a41b76d90037e7858c43c18e66399d
 Merge: 8212058a6 e15a25c6f
 Author: Jeff Gilbert <jdashg@gmail.com>
 Date:   Tue Oct 9 13:54:05 2018 -0700
 
     Merge pull request #13 from mattwoodrow/async-load-program
     
     Support async glProgramBInary
--- a/gfx/gl/GLContextProviderEGL.cpp
+++ b/gfx/gl/GLContextProviderEGL.cpp
@@ -529,16 +529,18 @@ GLContextEGL::GetWSIInfo(nsCString* cons
 
 // hold a reference to the given surface
 // for the lifetime of this context.
 void
 GLContextEGL::HoldSurface(gfxASurface* aSurf) {
     mThebesSurface = aSurf;
 }
 
+#define LOCAL_EGL_CONTEXT_PROVOKING_VERTEX_DONT_CARE_MOZ 0x6000
+
 already_AddRefed<GLContextEGL>
 GLContextEGL::CreateGLContext(CreateContextFlags flags,
                 const SurfaceCaps& caps,
                 bool isOffscreen,
                 EGLConfig config,
                 EGLSurface surface,
                 nsACString* const out_failureId)
 {
@@ -562,16 +564,23 @@ GLContextEGL::CreateGLContext(CreateCont
     if (!debugFlags &&
         flags & CreateContextFlags::NO_VALIDATION &&
         egl->IsExtensionSupported(GLLibraryEGL::KHR_create_context_no_error))
     {
         required_attribs.push_back(LOCAL_EGL_CONTEXT_OPENGL_NO_ERROR_KHR);
         required_attribs.push_back(LOCAL_EGL_TRUE);
     }
 
+    if (flags & CreateContextFlags::PROVOKING_VERTEX_DONT_CARE &&
+        egl->IsExtensionSupported(GLLibraryEGL::MOZ_create_context_provoking_vertex_dont_care))
+    {
+        required_attribs.push_back(LOCAL_EGL_CONTEXT_PROVOKING_VERTEX_DONT_CARE_MOZ);
+        required_attribs.push_back(LOCAL_EGL_TRUE);
+    }
+
     std::vector<EGLint> robustness_attribs;
     std::vector<EGLint> rbab_attribs; // RBAB: Robust Buffer Access Behavior
     if (flags & CreateContextFlags::PREFER_ROBUSTNESS) {
         if (egl->IsExtensionSupported(GLLibraryEGL::EXT_create_context_robustness)) {
             robustness_attribs = required_attribs;
             robustness_attribs.push_back(LOCAL_EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT);
             robustness_attribs.push_back(LOCAL_EGL_LOSE_CONTEXT_ON_RESET_EXT);
 
--- a/gfx/gl/GLContextTypes.h
+++ b/gfx/gl/GLContextTypes.h
@@ -54,15 +54,17 @@ enum class CreateContextFlags : uint8_t 
     ALLOW_OFFLINE_RENDERER =  1 << 2,
     // Ask for ES3 if possible
     PREFER_ES3 = 1 << 3,
 
     NO_VALIDATION = 1 << 4,
     PREFER_ROBUSTNESS = 1 << 5,
 
     HIGH_POWER = 1 << 6,
+
+    PROVOKING_VERTEX_DONT_CARE = 1 << 7,
 };
 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CreateContextFlags)
 
 } /* namespace gl */
 } /* namespace mozilla */
 
 #endif /* GLCONTEXT_TYPES_H_ */
--- a/gfx/gl/GLLibraryEGL.cpp
+++ b/gfx/gl/GLLibraryEGL.cpp
@@ -64,17 +64,18 @@ static const char* sEGLExtensionNames[] 
     "EGL_KHR_stream",
     "EGL_KHR_stream_consumer_gltexture",
     "EGL_EXT_device_query",
     "EGL_NV_stream_consumer_gltexture_yuv",
     "EGL_ANGLE_stream_producer_d3d_texture",
     "EGL_ANGLE_device_creation",
     "EGL_ANGLE_device_creation_d3d11",
     "EGL_KHR_surfaceless_context",
-    "EGL_KHR_create_context_no_error"
+    "EGL_KHR_create_context_no_error",
+    "EGL_MOZ_create_context_provoking_vertex_dont_care"
 };
 
 #if defined(ANDROID)
 
 static PRLibrary* LoadApitraceLibrary()
 {
     // Initialization of gfx prefs here is only needed during the unit tests...
     gfxPrefs::GetSingleton();
--- a/gfx/gl/GLLibraryEGL.h
+++ b/gfx/gl/GLLibraryEGL.h
@@ -92,16 +92,17 @@ public:
         KHR_stream_consumer_gltexture,
         EXT_device_query,
         NV_stream_consumer_gltexture_yuv,
         ANGLE_stream_producer_d3d_texture,
         ANGLE_device_creation,
         ANGLE_device_creation_d3d11,
         KHR_surfaceless_context,
         KHR_create_context_no_error,
+        MOZ_create_context_provoking_vertex_dont_care,
         Extensions_Max
     };
 
     bool IsExtensionSupported(EGLExtensions aKnownExtension) const {
         return mAvailableExtensions[aKnownExtension];
     }
 
     void MarkExtensionUnsupported(EGLExtensions aKnownExtension) {
--- a/gfx/layers/wr/AsyncImagePipelineManager.cpp
+++ b/gfx/layers/wr/AsyncImagePipelineManager.cpp
@@ -275,16 +275,18 @@ AsyncImagePipelineManager::UpdateImageKe
     }
     Range<wr::ImageKey> keys(&aKeys[0], aKeys.Length());
     auto externalImageKey =
       aPipeline->mWrTextureWrapper ? aPipeline->mWrTextureWrapper->GetExternalImageKey() : wrTexture->GetExternalImageKey();
     wrTexture->PushResourceUpdates(aMaybeFastTxn, op, keys, externalImageKey);
   }
 
   if (aPipeline->mWrTextureWrapper) {
+    // Force frame rendering, since WebRenderTextureHost update its data outside of WebRender.
+    aMaybeFastTxn.InvalidateRenderedFrame();
     HoldExternalImage(aPipelineId, aEpoch, aPipeline->mWrTextureWrapper);
   }
 
   return Some(op);
 }
 
 Maybe<TextureHost::ResourceUpdateOp>
 AsyncImagePipelineManager::UpdateWithoutExternalImage(TextureHost* aTexture,
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -1388,16 +1388,22 @@ WebRenderBridgeParent::UpdateWebRender(C
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvScheduleComposite()
 {
   if (mDestroyed) {
     return IPC_OK();
   }
+
+  // Force frame rendering during next frame generation.
+  wr::TransactionBuilder fastTxn(/* aUseSceneBuilderThread */ false);
+  fastTxn.InvalidateRenderedFrame();
+  mApi->SendTransaction(fastTxn);
+
   ScheduleGenerateFrame();
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvCapture()
 {
   if (!mDestroyed) {
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -2675,16 +2675,22 @@ gfxPlatform::InitWebRenderConfig()
   //   WR? WR+   => means WR was enabled via gfx.webrender.all.qualified
   //   WR! WR+   => means WR was enabled via gfx.webrender.{all,enabled} or envvar
   // On Beta/Release:
   //   WR? WR+   => means WR was enabled via gfx.webrender.all.qualified on qualified hardware
   //   WR! WR+   => means WR was enabled via envvar, possibly on unqualified hardware.
   // In all cases WR- means WR was not enabled, for one of many possible reasons.
   ScopedGfxFeatureReporter reporter("WR", prefEnabled || envvarEnabled);
   if (!XRE_IsParentProcess()) {
+    // Force-disable WebRender in recording/replaying child processes, which
+    // have their own compositor.
+    if (recordreplay::IsRecordingOrReplaying()) {
+      gfxVars::SetUseWebRender(false);
+    }
+
     // The parent process runs through all the real decision-making code
     // later in this function. For other processes we still want to report
     // the state of the feature for crash reports.
     if (gfxVars::UseWebRender()) {
       reporter.SetSuccessful();
     }
     return;
   }
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -14,17 +14,17 @@ use gpu_types::{BrushFlags, BrushInstanc
 use gpu_types::{ClipMaskInstance, SplitCompositeInstance};
 use gpu_types::{PrimitiveInstanceData, RasterizationSpace, GlyphInstance};
 use gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
 use internal_types::{FastHashMap, SavedTargetIndex, TextureSource};
 use picture::{PictureCompositeMode, PicturePrimitive, PictureSurface};
 use plane_split::{BspSplitter, Clipper, Polygon, Splitter};
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, DeferredResolve};
 use prim_store::{EdgeAaSegmentMask, ImageSource};
-use prim_store::{PrimitiveMetadata, VisibleGradientTile, PrimitiveInstance};
+use prim_store::{VisibleGradientTile, PrimitiveInstance};
 use prim_store::{BorderSource, Primitive, PrimitiveDetails};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
 use renderer::BLOCKS_PER_UV_RECT;
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache, ImageProperties};
 use scene::FilterOpHelpers;
 use std::{f32, i32};
 use tiling::{RenderTargetContext};
@@ -471,24 +471,24 @@ impl AlphaBatchBuilder {
         // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order.
         for poly in splitter.sort(vec3(0.0, 0.0, 1.0)) {
             let prim_instance = &pic.prim_instances[poly.anchor];
             let prim_index = prim_instance.prim_index;
             let pic_metadata = &ctx.prim_store.primitives[prim_index.0].metadata;
             if cfg!(debug_assertions) && ctx.prim_store.chase_id == Some(prim_index) {
                 println!("\t\tsplit polygon {:?}", poly.points);
             }
-            let transform = transforms.get_world_transform(pic_metadata.spatial_node_index).inverse().unwrap();
+            let transform = transforms.get_world_transform(prim_instance.spatial_node_index).inverse().unwrap();
             let transform_id = transforms.get_id(
-                pic_metadata.spatial_node_index,
+                prim_instance.spatial_node_index,
                 ROOT_SPATIAL_NODE_INDEX,
                 ctx.clip_scroll_tree,
             );
 
-            let clip_task_address = pic_metadata
+            let clip_task_address = prim_instance
                 .clip_task_id
                 .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
 
             let prim_header = PrimitiveHeader {
                 local_rect: pic_metadata.local_rect,
                 local_clip_rect: prim_instance.combined_local_clip_rect,
                 task_address,
                 specific_prim_address: GpuCacheAddress::invalid(),
@@ -533,17 +533,17 @@ impl AlphaBatchBuilder {
             let key = BatchKey::new(
                 BatchKind::SplitComposite,
                 BlendMode::PremultipliedAlpha,
                 BatchTextures::no_texture(),
             );
             let batch = self.batch_list
                             .get_suitable_batch(
                                 key,
-                                &pic_metadata.clipped_world_rect.as_ref().expect("bug"),
+                                &prim_instance.clipped_world_rect.as_ref().expect("bug"),
                             );
 
             let gpu_address = gpu_cache.get_address(&gpu_handle);
 
             let instance = SplitCompositeInstance::new(
                 prim_header_index,
                 gpu_address,
                 prim_headers.z_generator.next(),
@@ -570,35 +570,35 @@ impl AlphaBatchBuilder {
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
         root_spatial_node_index: SpatialNodeIndex,
         plane_split_anchor: usize,
     ) {
         let prim = &ctx.prim_store.primitives[prim_instance.prim_index.0];
         let prim_metadata = &prim.metadata;
 
-        if prim_metadata.clipped_world_rect.is_none() {
+        if prim_instance.clipped_world_rect.is_none() {
             return;
         }
 
         #[cfg(debug_assertions)] //TODO: why is this needed?
-        debug_assert_eq!(prim_metadata.prepared_frame_id, render_tasks.frame_id());
+        debug_assert_eq!(prim_instance.prepared_frame_id, render_tasks.frame_id());
 
         let transform_id = transforms
             .get_id(
-                prim_metadata.spatial_node_index,
+                prim_instance.spatial_node_index,
                 root_spatial_node_index,
                 ctx.clip_scroll_tree,
             );
 
         // TODO(gw): Calculating this for every primitive is a bit
         //           wasteful. We should probably cache this in
         //           the scroll node...
         let transform_kind = transform_id.transform_kind();
-        let bounding_rect = prim_metadata.clipped_world_rect
+        let bounding_rect = prim_instance.clipped_world_rect
                                          .as_ref()
                                          .expect("bug");
 
         // If the primitive is internally decomposed into multiple sub-primitives we may not
         // use some of the per-primitive data typically stored in PrimitiveMetadata and get
         // it from each sub-primitive instead.
         let is_multiple_primitives = match prim.details {
             PrimitiveDetails::Brush(ref brush) => {
@@ -610,27 +610,27 @@ impl AlphaBatchBuilder {
                 }
             }
             _ => false,
         };
 
         let prim_cache_address = if is_multiple_primitives {
             GpuCacheAddress::invalid()
         } else {
-            gpu_cache.get_address(&prim_metadata.gpu_location)
+            gpu_cache.get_address(&prim_instance.gpu_location)
         };
 
-        let clip_task_address = prim_metadata
+        let clip_task_address = prim_instance
             .clip_task_id
             .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
 
         let specified_blend_mode = prim.get_blend_mode();
 
-        let non_segmented_blend_mode = if !prim_metadata.opacity.is_opaque ||
-            prim_metadata.clip_task_id.is_some() ||
+        let non_segmented_blend_mode = if !prim_instance.opacity.is_opaque ||
+            prim_instance.clip_task_id.is_some() ||
             transform_kind == TransformedRectKind::Complex {
             specified_blend_mode
         } else {
             BlendMode::None
         };
 
         let prim_header = PrimitiveHeader {
             local_rect: prim_metadata.local_rect,
@@ -651,31 +651,31 @@ impl AlphaBatchBuilder {
                 match brush.kind {
                     BrushKind::Picture(ref picture) => {
                         // If this picture is participating in a 3D rendering context,
                         // then don't add it to any batches here. Instead, create a polygon
                         // for it and add it to the current plane splitter.
                         if picture.is_in_3d_context {
                             // Push into parent plane splitter.
                             debug_assert!(picture.raster_config.is_some());
-                            let transform = transforms.get_world_transform(prim_metadata.spatial_node_index);
+                            let transform = transforms.get_world_transform(prim_instance.spatial_node_index);
 
                             // Apply the local clip rect here, before splitting. This is
                             // because the local clip rect can't be applied in the vertex
                             // shader for split composites, since we are drawing polygons
                             // rather that rectangles. The interpolation still works correctly
                             // since we determine the UVs by doing a bilerp with a factor
                             // from the original local rect.
                             let local_rect = prim_metadata.local_rect
                                                           .intersection(&prim_instance.combined_local_clip_rect);
 
                             if let Some(local_rect) = local_rect {
                                 match transform.transform_kind() {
                                     TransformedRectKind::AxisAligned => {
-                                        let inv_transform = transforms.get_world_inv_transform(prim_metadata.spatial_node_index);
+                                        let inv_transform = transforms.get_world_inv_transform(prim_instance.spatial_node_index);
                                         let polygon = Polygon::from_transformed_rect_with_inverse(
                                             local_rect.cast(),
                                             &transform.cast(),
                                             &inv_transform.cast(),
                                             plane_split_anchor,
                                         ).unwrap();
                                         splitter.add(polygon);
                                     }
@@ -1057,17 +1057,17 @@ impl AlphaBatchBuilder {
                             let prim_header_index = prim_headers.push(&prim_header, user_data);
                             if cfg!(debug_assertions) && ctx.prim_store.chase_id == Some(prim_instance.prim_index) {
                                 println!("\t{:?} {:?}, task relative bounds {:?}",
                                     batch_kind, prim_header_index, bounding_rect);
                             }
 
                             self.add_brush_to_batch(
                                 brush,
-                                prim_metadata,
+                                prim_instance,
                                 batch_kind,
                                 specified_blend_mode,
                                 non_segmented_blend_mode,
                                 textures,
                                 prim_header_index,
                                 clip_task_address,
                                 bounding_rect,
                                 transform_kind,
@@ -1195,17 +1195,17 @@ impl AlphaBatchBuilder {
         };
         let batch = self.batch_list.get_suitable_batch(batch_key, bounding_rect);
         batch.push(PrimitiveInstanceData::from(base_instance));
     }
 
     fn add_brush_to_batch(
         &mut self,
         brush: &BrushPrimitive,
-        prim_metadata: &PrimitiveMetadata,
+        prim_instance: &PrimitiveInstance,
         batch_kind: BrushBatchKind,
         alpha_blend_mode: BlendMode,
         non_segmented_blend_mode: BlendMode,
         textures: BatchTextures,
         prim_header_index: PrimitiveHeaderIndex,
         clip_task_address: RenderTaskAddress,
         bounding_rect: &WorldRect,
         transform_kind: TransformedRectKind,
@@ -1240,17 +1240,17 @@ impl AlphaBatchBuilder {
 
                 let opaque_batch = self.batch_list.opaque_batch_list.get_suitable_batch(
                     opaque_batch_key,
                     bounding_rect,
                 );
 
                 for (i, segment) in segment_desc.segments.iter().enumerate() {
                     let is_inner = segment.edge_flags.is_empty();
-                    let needs_blending = !prim_metadata.opacity.is_opaque ||
+                    let needs_blending = !prim_instance.opacity.is_opaque ||
                                          segment.clip_task_id.needs_blending() ||
                                          (!is_inner && transform_kind == TransformedRectKind::Complex);
 
                     let clip_task_address = match segment.clip_task_id {
                         BrushSegmentTaskId::RenderTaskId(id) =>
                             render_tasks.get_task_address(id),
                         BrushSegmentTaskId::Opaque => OPAQUE_TASK_ADDRESS,
                         BrushSegmentTaskId::Empty => continue,
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderRadius, ClipMode, ComplexClipRegion, DeviceIntRect, DevicePixelScale, ImageMask};
-use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D, LocalClip};
+use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D};
 use api::{BoxShadowClipMode, LayoutToWorldScale, LineOrientation, LineStyle, PicturePixel, WorldPixel};
 use api::{PictureRect, LayoutPixel, WorldPoint, WorldSize, WorldRect, LayoutToWorldTransform};
 use api::{VoidPtrToSizeFn, LayoutRectAu, ImageKey, AuHelpers};
 use app_units::Au;
 use border::{ensure_no_corner_overlap, BorderRadiusAu};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
 use clip_scroll_tree::{ClipScrollTree, CoordinateSystemId, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use ellipse::Ellipse;
@@ -708,34 +708,23 @@ impl<J> ClipRegion<ComplexTranslateIter<
                 offset: *reference_frame_relative_offset,
             },
         }
     }
 }
 
 impl ClipRegion<Option<ComplexClipRegion>> {
     pub fn create_for_clip_node_with_local_clip(
-        local_clip: &LocalClip,
+        local_clip: &LayoutRect,
         reference_frame_relative_offset: &LayoutVector2D
     ) -> Self {
         ClipRegion {
-            main: local_clip
-                .clip_rect()
-                .translate(reference_frame_relative_offset),
+            main: local_clip.translate(reference_frame_relative_offset),
             image_mask: None,
-            complex_clips: match *local_clip {
-                LocalClip::Rect(_) => None,
-                LocalClip::RoundedRect(_, ref region) => {
-                    Some(ComplexClipRegion {
-                        rect: region.rect.translate(reference_frame_relative_offset),
-                        radii: region.radii,
-                        mode: region.mode,
-                    })
-                },
-            }
+            complex_clips: None,
         }
     }
 }
 
 // The ClipItemKey is a hashable representation of the contents
 // of a clip item. It is used during interning to de-duplicate
 // clip nodes between frames and display lists. This allows quick
 // comparison of clip node equality by handle, and also allows
--- a/gfx/webrender/src/device/gl.rs
+++ b/gfx/webrender/src/device/gl.rs
@@ -757,17 +757,16 @@ pub struct Device {
     extensions: Vec<String>,
 }
 
 impl Device {
     pub fn new(
         gl: Rc<gl::Gl>,
         resource_override_path: Option<PathBuf>,
         upload_method: UploadMethod,
-        _file_changed_handler: Box<FileWatcherHandler>,
         cached_programs: Option<Rc<ProgramCache>>,
     ) -> Device {
         let mut max_texture_size = [0];
         unsafe {
             gl.get_integer_v(gl::MAX_TEXTURE_SIZE, &mut max_texture_size);
         }
         let max_texture_size = max_texture_size[0] as u32;
         let renderer_name = gl.get_string(gl::RENDERER);
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -4,41 +4,42 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter, ClipAndScrollInfo};
 use api::{ClipId, ColorF, ComplexClipRegion, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{DevicePixelScale, DeviceUintRect, DisplayItemRef, ExtendMode, ExternalScrollId};
 use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, RasterSpace, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint, ColorDepth};
 use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
-use api::{LineOrientation, LineStyle, LocalClip, NinePatchBorderSource, PipelineId};
+use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId};
 use api::{PropertyBinding, ReferenceFrame, RepeatMode, ScrollFrameDisplayItem, ScrollSensitivity};
 use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
 use api::{ClipMode, TransformStyle, YuvColorSpace, YuvData};
 use clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore};
 use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
 use euclid::vec2;
 use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCacheHandle;
 use gpu_types::BrushFlags;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
 use internal_types::{FastHashMap, FastHashSet};
 use picture::{PictureCompositeMode, PictureIdGenerator, PicturePrimitive};
-use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor};
-use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveOpacity};
+use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor, PrimitiveInstance};
+use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveOpacity, PrimitiveKey};
 use prim_store::{BorderSource, BrushSegment, BrushSegmentVec, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
 use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitive};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::DocumentResources;
 use spatial_node::{SpatialNodeType, StickyFrameInfo};
 use std::{f32, mem};
+use std::collections::vec_deque::VecDeque;
 use tiling::{CompositeOps};
 use util::{MaxRect, RectHelpers};
 
 #[derive(Debug, Copy, Clone)]
 struct ClipNode {
     id: ClipChainId,
     count: usize,
 }
@@ -127,18 +128,18 @@ pub struct DisplayListFlattener<'a> {
 
     /// The data structure that converting between ClipId and the various index
     /// types that the ClipScrollTree uses.
     id_to_index_mapper: ClipIdToIndexMapper,
 
     /// A stack of stacking context properties.
     sc_stack: Vec<FlattenedStackingContext>,
 
-    /// A stack of the currently active shadows
-    shadow_stack: Vec<(Shadow, PrimitiveIndex)>,
+    /// Maintains state for any currently active shadows
+    pending_shadow_items: VecDeque<ShadowItem>,
 
     /// The stack keeping track of the root clip chains associated with pipelines.
     pipeline_clip_chain_stack: Vec<ClipChainId>,
 
     /// The store of primitives.
     pub prim_store: PrimitiveStore,
 
     /// Information about all primitives involved in hit testing.
@@ -152,16 +153,20 @@ pub struct DisplayListFlattener<'a> {
     pub config: FrameBuilderConfig,
 
     /// Reference to the document resources, which contains
     /// shared (interned) data between display lists.
     resources: &'a mut DocumentResources,
 
     /// The estimated count of primtives we expect to encounter during flattening.
     prim_count_estimate: usize,
+
+    /// The root primitive index for this flattener. This is the primitive
+    /// to start the culling phase from.
+    pub root_prim_index: PrimitiveIndex,
 }
 
 impl<'a> DisplayListFlattener<'a> {
     pub fn create_frame_builder(
         scene: &Scene,
         clip_scroll_tree: &mut ClipScrollTree,
         font_instances: FontInstanceMap,
         view: &DocumentView,
@@ -183,24 +188,25 @@ impl<'a> DisplayListFlattener<'a> {
         let mut flattener = DisplayListFlattener {
             scene,
             clip_scroll_tree,
             font_instances,
             config: *frame_builder_config,
             output_pipelines,
             id_to_index_mapper: ClipIdToIndexMapper::default(),
             hit_testing_runs: Vec::new(),
-            shadow_stack: Vec::new(),
+            pending_shadow_items: VecDeque::new(),
             sc_stack: Vec::new(),
             pipeline_clip_chain_stack: vec![ClipChainId::NONE],
             prim_store: PrimitiveStore::new(),
             clip_store: ClipStore::new(),
             picture_id_generator,
             resources,
             prim_count_estimate: 0,
+            root_prim_index: PrimitiveIndex(0),
         };
 
         flattener.push_root(
             root_pipeline_id,
             &root_pipeline.viewport_size,
             &root_pipeline.content_size,
         );
         flattener.setup_viewport_offset(view.inner_rect, view.accumulated_scale_factor());
@@ -469,17 +475,17 @@ impl<'a> DisplayListFlattener<'a> {
             },
         };
 
         //TODO: use or assert on `clip_and_scroll_ids.clip_node_id` ?
         let clip_chain_index = self.add_clip_node(
             info.clip_id,
             clip_and_scroll_ids.scroll_node_id,
             ClipRegion::create_for_clip_node_with_local_clip(
-                &LocalClip::from(*item.clip_rect()),
+                item.clip_rect(),
                 reference_frame_relative_offset
             ),
         );
         self.pipeline_clip_chain_stack.push(clip_chain_index);
 
         let bounds = item.rect();
         let origin = *reference_frame_relative_offset + bounds.origin.to_vector();
         self.push_reference_frame(
@@ -816,27 +822,36 @@ impl<'a> DisplayListFlattener<'a> {
     /// add the primitive to the draw list, so can be used for creating
     /// sub-primitives.
     pub fn create_primitive(
         &mut self,
         info: &LayoutPrimitiveInfo,
         clip_chain_id: ClipChainId,
         spatial_node_index: SpatialNodeIndex,
         container: PrimitiveContainer,
-    ) -> PrimitiveIndex {
+    ) -> PrimitiveInstance {
         let stacking_context = self.sc_stack.last().expect("bug: no stacking context!");
 
-        self.prim_store.add_primitive(
+        let prim_key = PrimitiveKey::new(
+            info.is_backface_visible && stacking_context.is_backface_visible,
+        );
+
+        let prim_data_handle = self.resources.prim_interner.intern(&prim_key);
+
+        let prim_index = self.prim_store.add_primitive(
             &info.rect,
             &info.clip_rect,
-            info.is_backface_visible && stacking_context.is_backface_visible,
+            container,
+        );
+
+        PrimitiveInstance::new(
+            prim_index,
+            prim_data_handle,
             clip_chain_id,
             spatial_node_index,
-            info.tag,
-            container,
         )
     }
 
     pub fn add_primitive_to_hit_testing_list(
         &mut self,
         info: &LayoutPrimitiveInfo,
         clip_and_scroll: ScrollNodeAndClipChain
     ) {
@@ -856,92 +871,66 @@ impl<'a> DisplayListFlattener<'a> {
         }
 
         self.hit_testing_runs.push(HitTestingRun(vec![new_item], clip_and_scroll));
     }
 
     /// Add an already created primitive to the draw lists.
     pub fn add_primitive_to_draw_list(
         &mut self,
-        prim_index: PrimitiveIndex,
+        prim_instance: PrimitiveInstance,
     ) {
-        // Add primitive to the top-most Picture on the stack.
-        let pic_prim_index = self.sc_stack.last().unwrap().leaf_prim_index;
-        if cfg!(debug_assertions) && self.prim_store.chase_id == Some(prim_index) {
-            println!("\tadded to picture primitive {:?}", pic_prim_index);
+        // Add primitive to the top-most stacking context on the stack.
+        if cfg!(debug_assertions) && self.prim_store.chase_id == Some(prim_instance.prim_index) {
+            println!("\tadded to stacking context at {}", self.sc_stack.len());
         }
-        let pic = self.prim_store.get_pic_mut(pic_prim_index);
-        pic.add_primitive(prim_index);
+        let stacking_context = self.sc_stack.last_mut().unwrap();
+        stacking_context.normal_primitives.push(prim_instance);
     }
 
     /// Convenience interface that creates a primitive entry and adds it
     /// to the draw list.
     pub fn add_primitive(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         clip_items: Vec<ClipItemKey>,
         container: PrimitiveContainer,
     ) {
-        if !self.shadow_stack.is_empty() {
-            // TODO(gw): Restructure this so we don't need to move the shadow
-            //           stack out (borrowck due to create_primitive below).
-            let shadow_stack = mem::replace(&mut self.shadow_stack, Vec::new());
-            for &(ref shadow, shadow_pic_prim_index) in &shadow_stack {
-                // Offset the local rect and clip rect by the shadow offset.
-                let mut info = info.clone();
-                info.rect = info.rect.translate(&shadow.offset);
-                info.clip_rect = info.clip_rect.translate(&shadow.offset);
-
-                // Offset any local clip sources by the shadow offset.
-                let clip_items: Vec<ClipItemKey> = clip_items
-                    .iter()
-                    .map(|cs| cs.offset(&shadow.offset))
-                    .collect();
+        // If a shadow context is not active, then add the primitive
+        // directly to the parent picture.
+        if self.pending_shadow_items.is_empty() {
+            if container.is_visible() {
                 let clip_chain_id = self.build_clip_chain(
                     clip_items,
                     clip_and_scroll.spatial_node_index,
                     clip_and_scroll.clip_chain_id,
                 );
-
-                // Construct and add a primitive for the given shadow.
-                let shadow_prim_index = self.create_primitive(
-                    &info,
+                let prim_instance = self.create_primitive(
+                    info,
                     clip_chain_id,
                     clip_and_scroll.spatial_node_index,
-                    container.create_shadow(shadow),
+                    container,
                 );
-
-                // Add the new primitive to the shadow picture.
-                let shadow_pic = self.prim_store.get_pic_mut(shadow_pic_prim_index);
-                shadow_pic.add_primitive(shadow_prim_index);
+                if cfg!(debug_assertions) && ChasePrimitive::LocalRect(info.rect) == self.config.chase_primitive {
+                    println!("Chasing {:?} by local rect", prim_instance.prim_index);
+                    self.prim_store.chase_id = Some(prim_instance.prim_index);
+                }
+                self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
+                self.add_primitive_to_draw_list(prim_instance);
             }
-            self.shadow_stack = shadow_stack;
-        }
-
-        if container.is_visible() {
-            let clip_chain_id = self.build_clip_chain(
+        } else {
+            // There is an active shadow context. Store as a pending primitive
+            // for processing during pop_all_shadows.
+            self.pending_shadow_items.push_back(ShadowItem::Primitive(PendingPrimitive {
+                clip_and_scroll,
+                info: *info,
                 clip_items,
-                clip_and_scroll.spatial_node_index,
-                clip_and_scroll.clip_chain_id,
-            );
-            let prim_index = self.create_primitive(
-                info,
-                clip_chain_id,
-                clip_and_scroll.spatial_node_index,
                 container,
-            );
-            if cfg!(debug_assertions) && ChasePrimitive::LocalRect(info.rect) == self.config.chase_primitive {
-                println!("Chasing {:?} by local rect", prim_index);
-                self.prim_store.chase_id = Some(prim_index);
-            }
-            self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
-            self.add_primitive_to_draw_list(
-                prim_index,
-            );
+            }));
         }
     }
 
     pub fn push_stacking_context(
         &mut self,
         pipeline_id: PipelineId,
         composite_ops: CompositeOps,
         transform_style: TransformStyle,
@@ -952,23 +941,23 @@ impl<'a> DisplayListFlattener<'a> {
         requested_raster_space: RasterSpace,
     ) {
         let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(spatial_node);
         let clip_chain_id = match clipping_node {
             Some(ref clipping_node) => self.id_to_index_mapper.get_clip_chain_id(clipping_node),
             None => ClipChainId::NONE,
         };
 
-        // An arbitrary large clip rect. For now, we don't
-        // specify a clip specific to the stacking context.
-        // However, now that they are represented as Picture
-        // primitives, we can apply any kind of clip mask
-        // to them, as for a normal primitive. This is needed
-        // to correctly handle some CSS cases (see #1957).
-        let max_clip = LayoutRect::max_rect();
+        // Check if this stacking context is the root of a pipeline, and the caller
+        // has requested it as an output frame.
+        let frame_output_pipeline_id = if is_pipeline_root && self.output_pipelines.contains(&pipeline_id) {
+            Some(pipeline_id)
+        } else {
+            None
+        };
 
         // Get the transform-style of the parent stacking context,
         // which determines if we *might* need to draw this on
         // an intermediate surface for plane splitting purposes.
         let parent_transform_style = match self.sc_stack.last() {
             Some(sc) => sc.transform_style,
             None => TransformStyle::Flat,
         };
@@ -985,153 +974,21 @@ impl<'a> DisplayListFlattener<'a> {
 
         // If this is participating in a 3d context *and* the
         // parent was not a 3d context, then this must be the
         // element that establishes a new 3d context.
         let establishes_3d_context =
             participating_in_3d_context &&
             parent_transform_style == TransformStyle::Flat;
 
-        // By default, this picture will be collapsed into
-        // the owning target.
-        let mut composite_mode = None;
-
-        // If this stacking context is the root of a pipeline, and the caller
-        // has requested it as an output frame, create a render task to isolate it.
-        let mut frame_output_pipeline_id = None;
-        if is_pipeline_root && self.output_pipelines.contains(&pipeline_id) {
-            composite_mode = Some(PictureCompositeMode::Blit);
-            frame_output_pipeline_id = Some(pipeline_id);
-        }
-
         // Force an intermediate surface if the stacking context
         // has a clip node. In the future, we may decide during
         // prepare step to skip the intermediate surface if the
         // clip node doesn't affect the stacking context rect.
-        if participating_in_3d_context || clipping_node.is_some() {
-            // TODO(gw): For now, as soon as this picture is in
-            //           a 3D context, we draw it to an intermediate
-            //           surface and apply plane splitting. However,
-            //           there is a large optimization opportunity here.
-            //           During culling, we can check if there is actually
-            //           perspective present, and skip the plane splitting
-            //           completely when that is not the case.
-            composite_mode = Some(PictureCompositeMode::Blit);
-        }
-
-        // Add picture for this actual stacking context contents to render into.
-        let leaf_picture = PicturePrimitive::new_image(
-            self.picture_id_generator.next(),
-            composite_mode,
-            participating_in_3d_context,
-            pipeline_id,
-            frame_output_pipeline_id,
-            true,
-            requested_raster_space,
-        );
-
-        // Create a brush primitive that draws this picture.
-        let leaf_prim = BrushPrimitive::new_picture(leaf_picture);
-
-        // Add the brush to the parent picture.
-        let leaf_prim_index = self.prim_store.add_primitive(
-            &LayoutRect::zero(),
-            &max_clip,
-            true,
-            clip_chain_id,
-            spatial_node_index,
-            None,
-            PrimitiveContainer::Brush(leaf_prim),
-        );
-
-        // Create a chain of pictures based on presence of filters,
-        // mix-blend-mode and/or 3d rendering context containers.
-        let mut current_prim_index = leaf_prim_index;
-
-        // For each filter, create a new image with that composite mode.
-        for filter in &composite_ops.filters {
-            let filter = filter.sanitize();
-
-            let mut filter_picture = PicturePrimitive::new_image(
-                self.picture_id_generator.next(),
-                Some(PictureCompositeMode::Filter(filter)),
-                false,
-                pipeline_id,
-                None,
-                true,
-                requested_raster_space,
-            );
-
-            filter_picture.add_primitive(current_prim_index);
-            let filter_prim = BrushPrimitive::new_picture(filter_picture);
-
-            current_prim_index = self.prim_store.add_primitive(
-                &LayoutRect::zero(),
-                &max_clip,
-                true,
-                clip_chain_id,
-                spatial_node_index,
-                None,
-                PrimitiveContainer::Brush(filter_prim),
-            );
-        }
-
-        // Same for mix-blend-mode.
-        if let Some(mix_blend_mode) = composite_ops.mix_blend_mode {
-            let mut blend_picture = PicturePrimitive::new_image(
-                self.picture_id_generator.next(),
-                Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
-                false,
-                pipeline_id,
-                None,
-                true,
-                requested_raster_space,
-            );
-
-            blend_picture.add_primitive(current_prim_index);
-            let blend_prim = BrushPrimitive::new_picture(blend_picture);
-
-            current_prim_index = self.prim_store.add_primitive(
-                &LayoutRect::zero(),
-                &max_clip,
-                true,
-                clip_chain_id,
-                spatial_node_index,
-                None,
-                PrimitiveContainer::Brush(blend_prim),
-            );
-        }
-
-        if establishes_3d_context {
-            // If establishing a 3d context, we need to add a picture
-            // that will be the container for all the planes and any
-            // un-transformed content.
-            let mut container_picture = PicturePrimitive::new_image(
-                self.picture_id_generator.next(),
-                None,
-                false,
-                pipeline_id,
-                None,
-                true,
-                requested_raster_space,
-            );
-
-            container_picture.add_primitive(current_prim_index);
-            let container_prim = BrushPrimitive::new_picture(container_picture);
-
-            current_prim_index = self.prim_store.add_primitive(
-                &LayoutRect::zero(),
-                &max_clip,
-                true,
-                clip_chain_id,
-                spatial_node_index,
-                None,
-                PrimitiveContainer::Brush(container_prim),
-            );
-        }
+        let should_isolate = clipping_node.is_some();
 
         // preserve-3d's semantics are to hoist all your children to be your siblings
         // when doing backface-visibility checking, so we need to grab the backface-visibility
         // of the lowest ancestor which *doesn't* preserve-3d, and AND it in with ours.
         //
         // No this isn't obvious or clear, it's just what we worked out over a day of testing.
         // There's probably a bug in here, but I couldn't find it with the examples and tests
         // at my disposal!
@@ -1141,79 +998,243 @@ impl<'a> DisplayListFlattener<'a> {
                 .rfind(|sc| sc.transform_style == TransformStyle::Flat)
                 .map(|sc| sc.is_backface_visible)
                 .unwrap_or(is_backface_visible);
 
         let is_backface_visible = is_backface_visible && ancestor_is_backface_visible;
 
         // Push the SC onto the stack, so we know how to handle things in
         // pop_stacking_context.
-        let sc = FlattenedStackingContext {
-            is_backface_visible,
+        self.sc_stack.push(FlattenedStackingContext {
+            preserve3d_primitives: Vec::new(),
+            normal_primitives: Vec::new(),
             pipeline_id,
+            is_backface_visible,
+            requested_raster_space,
+            spatial_node_index,
+            clip_chain_id,
+            frame_output_pipeline_id,
+            composite_ops,
+            should_isolate,
             transform_style,
-            establishes_3d_context,
             participating_in_3d_context,
-            leaf_prim_index,
-            root_prim_index: current_prim_index,
-            has_mix_blend_mode: composite_ops.mix_blend_mode.is_some(),
-        };
-
-        self.sc_stack.push(sc);
+            establishes_3d_context,
+        });
     }
 
     pub fn pop_stacking_context(&mut self) {
-        let sc = self.sc_stack.pop().unwrap();
+        let stacking_context = self.sc_stack.pop().unwrap();
+
+        // An arbitrary large clip rect. For now, we don't
+        // specify a clip specific to the stacking context.
+        // However, now that they are represented as Picture
+        // primitives, we can apply any kind of clip mask
+        // to them, as for a normal primitive. This is needed
+        // to correctly handle some CSS cases (see #1957).
+        let max_clip = LayoutRect::max_rect();
+
+        // By default, this picture will be collapsed into
+        // the owning target.
+        let mut composite_mode = if stacking_context.should_isolate {
+            Some(PictureCompositeMode::Blit)
+        } else {
+            None
+        };
+
+        // Force an intermediate surface if the stacking context
+        // has a clip node. In the future, we may decide during
+        // prepare step to skip the intermediate surface if the
+        // clip node doesn't affect the stacking context rect.
+        if stacking_context.participating_in_3d_context {
+            // TODO(gw): For now, as soon as this picture is in
+            //           a 3D context, we draw it to an intermediate
+            //           surface and apply plane splitting. However,
+            //           there is a large optimization opportunity here.
+            //           During culling, we can check if there is actually
+            //           perspective present, and skip the plane splitting
+            //           completely when that is not the case.
+            composite_mode = Some(PictureCompositeMode::Blit);
+        }
+
+        let prim_key = PrimitiveKey::new(true);
+        let prim_data_handle = self.resources.prim_interner.intern(&prim_key);
+
+        // Add picture for this actual stacking context contents to render into.
+        let leaf_picture = PicturePrimitive::new_image(
+            self.picture_id_generator.next(),
+            composite_mode,
+            stacking_context.participating_in_3d_context,
+            stacking_context.pipeline_id,
+            stacking_context.frame_output_pipeline_id,
+            true,
+            stacking_context.requested_raster_space,
+            stacking_context.normal_primitives,
+        );
+
+        // Create a brush primitive that draws this picture.
+        let leaf_prim = BrushPrimitive::new_picture(leaf_picture);
+
+        // Add the brush to the parent picture.
+        let leaf_prim_index = self.prim_store.add_primitive(
+            &LayoutRect::zero(),
+            &max_clip,
+            PrimitiveContainer::Brush(leaf_prim),
+        );
+
+        // Create a chain of pictures based on presence of filters,
+        // mix-blend-mode and/or 3d rendering context containers.
+        let mut current_prim_index = leaf_prim_index;
+
+        // For each filter, create a new image with that composite mode.
+        for filter in &stacking_context.composite_ops.filters {
+            let filter = filter.sanitize();
 
-        // Run the optimize pass on each picture in the chain,
-        // to see if we can collapse opacity and avoid drawing
-        // to an off-screen surface.
-        for i in sc.leaf_prim_index.0 .. sc.root_prim_index.0 + 1 {
-            let prim_index = PrimitiveIndex(i);
-            self.prim_store.optimize_picture_if_possible(prim_index);
+            let filter_picture = PicturePrimitive::new_image(
+                self.picture_id_generator.next(),
+                Some(PictureCompositeMode::Filter(filter)),
+                false,
+                stacking_context.pipeline_id,
+                None,
+                true,
+                stacking_context.requested_raster_space,
+                vec![
+                    PrimitiveInstance::new(
+                        current_prim_index,
+                        prim_data_handle,
+                        stacking_context.clip_chain_id,
+                        stacking_context.spatial_node_index,
+                    ),
+                ],
+            );
+
+            let filter_prim = BrushPrimitive::new_picture(filter_picture);
+
+            current_prim_index = self.prim_store.add_primitive(
+                &LayoutRect::zero(),
+                &max_clip,
+                PrimitiveContainer::Brush(filter_prim),
+            );
+
+            // Run the optimize pass on this picture, to see if we can
+            // collapse opacity and avoid drawing to an off-screen surface.
+            self.prim_store.optimize_picture_if_possible(current_prim_index);
+        }
+
+        // Same for mix-blend-mode.
+        if let Some(mix_blend_mode) = stacking_context.composite_ops.mix_blend_mode {
+            let blend_picture = PicturePrimitive::new_image(
+                self.picture_id_generator.next(),
+                Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
+                false,
+                stacking_context.pipeline_id,
+                None,
+                true,
+                stacking_context.requested_raster_space,
+                vec![
+                    PrimitiveInstance::new(
+                        current_prim_index,
+                        prim_data_handle,
+                        stacking_context.clip_chain_id,
+                        stacking_context.spatial_node_index,
+                    ),
+                ],
+            );
+
+            let blend_prim = BrushPrimitive::new_picture(blend_picture);
+
+            current_prim_index = self.prim_store.add_primitive(
+                &LayoutRect::zero(),
+                &max_clip,
+                PrimitiveContainer::Brush(blend_prim),
+            );
+        }
+
+        if stacking_context.establishes_3d_context {
+            // If establishing a 3d context, we need to add a picture
+            // that will be the container for all the planes and any
+            // un-transformed content.
+            let mut prims = vec![
+                PrimitiveInstance::new(
+                    current_prim_index,
+                    prim_data_handle,
+                    stacking_context.clip_chain_id,
+                    stacking_context.spatial_node_index,
+                ),
+            ];
+            prims.extend(stacking_context.preserve3d_primitives);
+
+            let container_picture = PicturePrimitive::new_image(
+                self.picture_id_generator.next(),
+                None,
+                false,
+                stacking_context.pipeline_id,
+                None,
+                true,
+                stacking_context.requested_raster_space,
+                prims,
+            );
+
+            let container_prim = BrushPrimitive::new_picture(container_picture);
+
+            current_prim_index = self.prim_store.add_primitive(
+                &LayoutRect::zero(),
+                &max_clip,
+                PrimitiveContainer::Brush(container_prim),
+            );
+        } else {
+            debug_assert!(stacking_context.preserve3d_primitives.is_empty());
         }
 
         if self.sc_stack.is_empty() {
             // This must be the root stacking context
+            self.root_prim_index = current_prim_index;
             return;
         }
 
-        let parent_prim_index = if !sc.establishes_3d_context && sc.participating_in_3d_context {
+        let sc_count = self.sc_stack.len();
+        let prim_instance = PrimitiveInstance::new(
+            current_prim_index,
+            prim_data_handle,
+            stacking_context.clip_chain_id,
+            stacking_context.spatial_node_index,
+        );
+
+        if !stacking_context.establishes_3d_context && stacking_context.participating_in_3d_context {
             // If we're in a 3D context, we will parent the picture
             // to the first stacking context we find that is a
             // 3D rendering context container. This follows the spec
             // by hoisting these items out into the same 3D context
             // for plane splitting.
-            self.sc_stack
+            let parent_index = self.sc_stack
                 .iter()
-                .rev()
-                .find(|sc| sc.establishes_3d_context)
-                .map(|sc| sc.root_prim_index)
-                .unwrap()
+                .rposition(|sc| sc.establishes_3d_context)
+                .unwrap();
+
+            let parent_stacking_context = &mut self.sc_stack[parent_index];
+            parent_stacking_context.preserve3d_primitives.push(prim_instance);
+
         } else {
-            self.sc_stack.last().unwrap().leaf_prim_index
+            let parent_stacking_context = self.sc_stack.last_mut().unwrap();
+
+            // If we have a mix-blend-mode, and we aren't the primary framebuffer,
+            // the stacking context needs to be isolated to blend correctly as per
+            // the CSS spec.
+            // If not already isolated for some other reason,
+            // make this picture as isolated.
+            if stacking_context.composite_ops.mix_blend_mode.is_some() &&
+               sc_count > 2 {
+                parent_stacking_context.should_isolate = true;
+            }
+
+            parent_stacking_context.normal_primitives.push(prim_instance);
         };
 
-        let parent_pic = self.prim_store.get_pic_mut(parent_prim_index);
-        parent_pic.add_primitive(sc.root_prim_index);
-
-        // If we have a mix-blend-mode, and we aren't the primary framebuffer,
-        // the stacking context needs to be isolated to blend correctly as per
-        // the CSS spec.
-        // If not already isolated for some other reason,
-        // make this picture as isolated.
-        if sc.has_mix_blend_mode &&
-           self.sc_stack.len() > 2 &&
-           parent_pic.requested_composite_mode.is_none() {
-            parent_pic.requested_composite_mode = Some(PictureCompositeMode::Blit);
-        }
-
         assert!(
-            self.shadow_stack.is_empty(),
-            "Found unpopped text shadows when popping stacking context!"
+            self.pending_shadow_items.is_empty(),
+            "Found unpopped shadows when popping stacking context!"
         );
     }
 
     pub fn push_reference_frame(
         &mut self,
         reference_frame_id: ClipId,
         parent_id: Option<ClipId>,
         pipeline_id: PipelineId,
@@ -1395,79 +1416,172 @@ impl<'a> DisplayListFlattener<'a> {
         node_index
     }
 
     pub fn push_shadow(
         &mut self,
         shadow: Shadow,
         clip_and_scroll: ScrollNodeAndClipChain,
     ) {
-        let pipeline_id = self.sc_stack.last().unwrap().pipeline_id;
-        let max_clip = LayoutRect::max_rect();
-
-        // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
-        // "the image that would be generated by applying to the shadow a
-        // Gaussian blur with a standard deviation equal to half the blur radius."
-        let std_deviation = shadow.blur_radius * 0.5;
-
-        // If the shadow has no blur, any elements will get directly rendered
-        // into the parent picture surface, instead of allocating and drawing
-        // into an intermediate surface. In this case, we will need to apply
-        // the local clip rect to primitives.
-        let is_passthrough = shadow.blur_radius == 0.0;
-
-        // shadows always rasterize in local space.
-        // TODO(gw): expose API for clients to specify a raster scale
-        let raster_space = if is_passthrough {
-            let parent_pic_prim_index = self.sc_stack.last().unwrap().leaf_prim_index;
-            self.prim_store
-                .get_pic(parent_pic_prim_index)
-                .requested_raster_space
-        } else {
-            RasterSpace::Local(1.0)
-        };
-
-        // Create a picture that the shadow primitives will be added to. If the
-        // blur radius is 0, the code in Picture::prepare_for_render will
-        // detect this and mark the picture to be drawn directly into the
-        // parent picture, which avoids an intermediate surface and blur.
-        let blur_filter = FilterOp::Blur(std_deviation).sanitize();
-        let shadow_pic = PicturePrimitive::new_image(
-            self.picture_id_generator.next(),
-            Some(PictureCompositeMode::Filter(blur_filter)),
-            false,
-            pipeline_id,
-            None,
-            is_passthrough,
-            raster_space,
-        );
-
-        // Create the primitive to draw the shadow picture into the scene.
-        let shadow_prim = BrushPrimitive::new_picture(shadow_pic);
-        let shadow_prim_index = self.prim_store.add_primitive(
-            &LayoutRect::zero(),
-            &max_clip,
-            true,
-            clip_and_scroll.clip_chain_id,
-            clip_and_scroll.spatial_node_index,
-            None,
-            PrimitiveContainer::Brush(shadow_prim),
-        );
-
-        // Add the shadow primitive. This must be done before pushing this
-        // picture on to the shadow stack, to avoid infinite recursion!
-        self.add_primitive_to_draw_list(
-            shadow_prim_index,
-        );
-        self.shadow_stack.push((shadow, shadow_prim_index));
+        // Store this shadow in the pending list, for processing
+        // during pop_all_shadows.
+        self.pending_shadow_items.push_back(ShadowItem::Shadow(PendingShadow {
+            shadow,
+            clip_and_scroll,
+        }));
     }
 
     pub fn pop_all_shadows(&mut self) {
-        assert!(self.shadow_stack.len() > 0, "popped shadows, but none were present");
-        self.shadow_stack.clear();
+        assert!(!self.pending_shadow_items.is_empty(), "popped shadows, but none were present");
+
+        let pipeline_id = self.sc_stack.last().unwrap().pipeline_id;
+        let max_clip = LayoutRect::max_rect();
+        let mut items = mem::replace(&mut self.pending_shadow_items, VecDeque::new());
+
+        //
+        // The pending_shadow_items queue contains a list of shadows and primitives
+        // that were pushed during the active shadow context. To process these, we:
+        //
+        // Iterate the list, popping an item from the front each iteration.
+        //
+        // If the item is a shadow:
+        //      - Create a shadow picture primitive.
+        //      - Add *any* primitives that remain in the item list to this shadow.
+        // If the item is a primitive:
+        //      - Add that primitive as a normal item (if alpha > 0)
+        //
+
+        while let Some(item) = items.pop_front() {
+            match item {
+                ShadowItem::Shadow(pending_shadow) => {
+                    // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
+                    // "the image that would be generated by applying to the shadow a
+                    // Gaussian blur with a standard deviation equal to half the blur radius."
+                    let std_deviation = pending_shadow.shadow.blur_radius * 0.5;
+
+                    // If the shadow has no blur, any elements will get directly rendered
+                    // into the parent picture surface, instead of allocating and drawing
+                    // into an intermediate surface. In this case, we will need to apply
+                    // the local clip rect to primitives.
+                    let is_passthrough = pending_shadow.shadow.blur_radius == 0.0;
+
+                    // shadows always rasterize in local space.
+                    // TODO(gw): expose API for clients to specify a raster scale
+                    let raster_space = if is_passthrough {
+                        self.sc_stack.last().unwrap().requested_raster_space
+                    } else {
+                        RasterSpace::Local(1.0)
+                    };
+
+                    // Add any primitives that come after this shadow in the item
+                    // list to this shadow.
+                    let mut prims = Vec::new();
+
+                    for item in &items {
+                        if let ShadowItem::Primitive(ref pending_primitive) = item {
+                            // Offset the local rect and clip rect by the shadow offset.
+                            let mut info = pending_primitive.info.clone();
+                            info.rect = info.rect.translate(&pending_shadow.shadow.offset);
+                            info.clip_rect = info.clip_rect.translate(&pending_shadow.shadow.offset);
+
+                            // Offset any local clip sources by the shadow offset.
+                            let clip_items: Vec<ClipItemKey> = pending_primitive
+                                .clip_items
+                                .iter()
+                                .map(|cs| cs.offset(&pending_shadow.shadow.offset))
+                                .collect();
+                            let clip_chain_id = self.build_clip_chain(
+                                clip_items,
+                                pending_primitive.clip_and_scroll.spatial_node_index,
+                                pending_primitive.clip_and_scroll.clip_chain_id,
+                            );
+
+                            // Construct and add a primitive for the given shadow.
+                            let shadow_prim_instance = self.create_primitive(
+                                &info,
+                                clip_chain_id,
+                                pending_primitive.clip_and_scroll.spatial_node_index,
+                                pending_primitive.container.create_shadow(&pending_shadow.shadow),
+                            );
+
+                            // Add the new primitive to the shadow picture.
+                            prims.push(shadow_prim_instance);
+                        }
+                    }
+
+                    // No point in adding a shadow here if there were no primitives
+                    // added to the shadow.
+                    if !prims.is_empty() {
+                        // Create a picture that the shadow primitives will be added to. If the
+                        // blur radius is 0, the code in Picture::prepare_for_render will
+                        // detect this and mark the picture to be drawn directly into the
+                        // parent picture, which avoids an intermediate surface and blur.
+                        let blur_filter = FilterOp::Blur(std_deviation).sanitize();
+                        let mut shadow_pic = PicturePrimitive::new_image(
+                            self.picture_id_generator.next(),
+                            Some(PictureCompositeMode::Filter(blur_filter)),
+                            false,
+                            pipeline_id,
+                            None,
+                            is_passthrough,
+                            raster_space,
+                            prims,
+                        );
+
+                        // Create the primitive to draw the shadow picture into the scene.
+                        let shadow_prim = BrushPrimitive::new_picture(shadow_pic);
+                        let shadow_prim_index = self.prim_store.add_primitive(
+                            &LayoutRect::zero(),
+                            &max_clip,
+                            PrimitiveContainer::Brush(shadow_prim),
+                        );
+
+                        let shadow_prim_key = PrimitiveKey::new(true);
+                        let shadow_prim_data_handle = self.resources.prim_interner.intern(&shadow_prim_key);
+
+                        let shadow_prim_instance = PrimitiveInstance::new(
+                            shadow_prim_index,
+                            shadow_prim_data_handle,
+                            pending_shadow.clip_and_scroll.clip_chain_id,
+                            pending_shadow.clip_and_scroll.spatial_node_index,
+                        );
+
+                        // Add the shadow primitive. This must be done before pushing this
+                        // picture on to the shadow stack, to avoid infinite recursion!
+                        self.add_primitive_to_draw_list(shadow_prim_instance);
+                    }
+                }
+                ShadowItem::Primitive(pending_primitive) => {
+                    // For a normal primitive, if it has alpha > 0, then we add this
+                    // as a normal primitive to the parent picture.
+                    if pending_primitive.container.is_visible() {
+                        let clip_chain_id = self.build_clip_chain(
+                            pending_primitive.clip_items,
+                            pending_primitive.clip_and_scroll.spatial_node_index,
+                            pending_primitive.clip_and_scroll.clip_chain_id,
+                        );
+                        let prim_instance = self.create_primitive(
+                            &pending_primitive.info,
+                            clip_chain_id,
+                            pending_primitive.clip_and_scroll.spatial_node_index,
+                            pending_primitive.container,
+                        );
+                        if cfg!(debug_assertions) && ChasePrimitive::LocalRect(pending_primitive.info.rect) == self.config.chase_primitive {
+                            println!("Chasing {:?} by local rect", prim_instance.prim_index);
+                            self.prim_store.chase_id = Some(prim_instance.prim_index);
+                        }
+                        self.add_primitive_to_hit_testing_list(&pending_primitive.info, pending_primitive.clip_and_scroll);
+                        self.add_primitive_to_draw_list(prim_instance);
+                    }
+                }
+            }
+        }
+
+        debug_assert!(items.is_empty());
+        self.pending_shadow_items = items;
     }
 
     pub fn add_solid_rectangle(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         color: ColorF,
         segments: Option<BrushSegmentDescriptor>,
@@ -2043,26 +2157,78 @@ impl<'a> DisplayListFlattener<'a> {
         self.map_clip_and_scroll(&ClipAndScrollInfo::simple(*id))
     }
 }
 
 /// Properties of a stacking context that are maintained
 /// during creation of the scene. These structures are
 /// not persisted after the initial scene build.
 struct FlattenedStackingContext {
+    /// The list of un-transformed content being
+    /// added to this stacking context.
+    normal_primitives: Vec<PrimitiveInstance>,
+
+    /// The list of preserve-3d primitives that
+    /// are being hoisted to this stacking context
+    /// (implies establishes_3d_context).
+    preserve3d_primitives: Vec<PrimitiveInstance>,
+
+    /// Whether or not the caller wants this drawn in
+    /// screen space (quality) or local space (performance)
+    requested_raster_space: RasterSpace,
+
+    /// The positioning node for this stacking context
+    spatial_node_index: SpatialNodeIndex,
+
+    /// The clip chain for this stacking context
+    clip_chain_id: ClipChainId,
+
+    /// If set, this should be provided to caller
+    /// as an output texture.
+    frame_output_pipeline_id: Option<PipelineId>,
+
+    /// The list of filters / mix-blend-mode for this
+    /// stacking context.
+    composite_ops: CompositeOps,
+
+    /// If true, this stacking context should be
+    /// isolated by forcing an off-screen surface.
+    should_isolate: bool,
+
     /// Pipeline this stacking context belongs to.
     pipeline_id: PipelineId,
 
     /// If true, visible when backface is visible.
     is_backface_visible: bool,
 
     /// CSS transform-style property.
     transform_style: TransformStyle,
 
-    root_prim_index: PrimitiveIndex,
-    leaf_prim_index: PrimitiveIndex,
-
     /// If true, this stacking context establishes a new
     /// 3d rendering context.
     establishes_3d_context: bool,
+
+    /// If true, this stacking context is part of a
+    /// surrounding 3d rendering context.
     participating_in_3d_context: bool,
-    has_mix_blend_mode: bool,
+}
+
+/// A primitive that is added while a shadow context is
+/// active is stored as a pending primitive and only
+/// added to pictures during pop_all_shadows.
+struct PendingPrimitive {
+    clip_and_scroll: ScrollNodeAndClipChain,
+    info: LayoutPrimitiveInfo,
+    clip_items: Vec<ClipItemKey>,
+    container: PrimitiveContainer,
 }
+
+/// As shadows are pushed, they are stored as pending
+/// shadows, and handled at once during pop_all_shadows.
+struct PendingShadow {
+    shadow: Shadow,
+    clip_and_scroll: ScrollNodeAndClipChain,
+}
+
+enum ShadowItem {
+    Shadow(PendingShadow),
+    Primitive(PendingPrimitive),
+}
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -53,16 +53,17 @@ pub struct FrameBuilderConfig {
 }
 
 /// A builder structure for `tiling::Frame`
 pub struct FrameBuilder {
     screen_rect: DeviceUintRect,
     background_color: Option<ColorF>,
     window_size: DeviceUintSize,
     scene_id: u64,
+    root_prim_index: PrimitiveIndex,
     pub prim_store: PrimitiveStore,
     pub clip_store: ClipStore,
     pub hit_testing_runs: Vec<HitTestingRun>,
     pub config: FrameBuilderConfig,
 }
 
 pub struct FrameBuildingContext<'a> {
     pub scene_id: u64,
@@ -132,16 +133,17 @@ impl FrameBuilder {
         FrameBuilder {
             hit_testing_runs: Vec::new(),
             prim_store: PrimitiveStore::new(),
             clip_store: ClipStore::new(),
             screen_rect: DeviceUintRect::zero(),
             window_size: DeviceUintSize::zero(),
             background_color: None,
             scene_id: 0,
+            root_prim_index: PrimitiveIndex(0),
             config: FrameBuilderConfig {
                 default_font_render_mode: FontRenderMode::Mono,
                 dual_source_blending_is_enabled: true,
                 dual_source_blending_is_supported: false,
                 chase_primitive: ChasePrimitive::Nothing,
             },
         }
     }
@@ -152,16 +154,17 @@ impl FrameBuilder {
         window_size: DeviceUintSize,
         scene_id: u64,
         flattener: DisplayListFlattener,
     ) -> Self {
         FrameBuilder {
             hit_testing_runs: flattener.hit_testing_runs,
             prim_store: flattener.prim_store,
             clip_store: flattener.clip_store,
+            root_prim_index: flattener.root_prim_index,
             screen_rect,
             background_color,
             window_size,
             scene_id,
             config: flattener.config,
         }
     }
 
@@ -181,20 +184,17 @@ impl FrameBuilder {
         transform_palette: &mut TransformPalette,
         resources: &mut FrameResources,
     ) -> Option<RenderTaskId> {
         profile_scope!("cull");
 
         if self.prim_store.primitives.is_empty() {
             return None
         }
-        self.prim_store.reset_prim_visibility();
 
-        // The root picture is always the first one added.
-        let root_prim_index = PrimitiveIndex(0);
         let root_spatial_node_index = clip_scroll_tree.root_reference_frame_index();
 
         const MAX_CLIP_COORD: f32 = 1.0e9;
 
         let world_rect = (self.screen_rect.to_f32() / device_pixel_scale).round_out();
 
         let frame_context = FrameBuildingContext {
             scene_id: self.scene_id,
@@ -223,17 +223,17 @@ impl FrameBuilder {
 
         let prim_context = PrimitiveContext::new(
             &clip_scroll_tree.spatial_nodes[root_spatial_node_index.0],
             root_spatial_node_index,
         );
 
         let (pic_context, mut pic_state, mut instances) = self
             .prim_store
-            .get_pic_mut(root_prim_index)
+            .get_pic_mut(self.root_prim_index)
             .take_context(
                 &prim_context,
                 root_spatial_node_index,
                 root_spatial_node_index,
                 true,
                 &mut frame_state,
                 &frame_context,
                 false,
@@ -248,31 +248,31 @@ impl FrameBuilder {
             &mut pic_state,
             &frame_context,
             &mut frame_state,
             &mut pic_rect,
         );
 
         let pic = self
             .prim_store
-            .get_pic_mut(root_prim_index);
+            .get_pic_mut(self.root_prim_index);
         pic.restore_context(
             instances,
             pic_context,
             pic_state,
             Some(pic_rect),
             &mut frame_state,
         );
 
         let pic_state = pic.take_state();
 
         let root_render_task = RenderTask::new_picture(
             RenderTaskLocation::Fixed(self.screen_rect.to_i32()),
             self.screen_rect.size.to_f32(),
-            root_prim_index,
+            self.root_prim_index,
             DeviceIntPoint::zero(),
             pic_state.tasks,
             UvRectKind::Rect,
             root_spatial_node_index,
         );
 
         let render_task_id = frame_state.render_tasks.add(root_render_task);
         pic.raster_config = Some(RasterConfig {
--- a/gfx/webrender/src/glyph_cache.rs
+++ b/gfx/webrender/src/glyph_cache.rs
@@ -1,15 +1,15 @@
 /* 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/. */
 
 #[cfg(feature = "pathfinder")]
 use api::DeviceIntPoint;
-use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphKey};
+use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphKey, GlyphRasterizer};
 use internal_types::FastHashMap;
 use render_task::RenderTaskCache;
 #[cfg(feature = "pathfinder")]
 use render_task::RenderTaskCacheKey;
 use resource_cache::ResourceClassCache;
 use std::sync::Arc;
 use texture_cache::{EvictionNotice, TextureCache};
 #[cfg(not(feature = "pathfinder"))]
@@ -138,36 +138,43 @@ impl GlyphCache {
 
             cache.clear();
             false
         })
     }
 
     // Clear out evicted entries from glyph key caches and, if possible,
     // also remove entirely any subsequently empty glyph key caches.
-    fn clear_evicted(&mut self,
-                     texture_cache: &TextureCache,
-                     render_task_cache: &RenderTaskCache) {
-        self.glyph_key_caches.retain(|_, cache| {
+    fn clear_evicted(
+        &mut self,
+        texture_cache: &TextureCache,
+        render_task_cache: &RenderTaskCache,
+        glyph_rasterizer: &mut GlyphRasterizer,
+    ) {
+        self.glyph_key_caches.retain(|key, cache| {
             // Scan for any glyph key caches that have evictions.
             if cache.eviction_notice().check() {
                 // If there are evictions, filter out any glyphs evicted from the
                 // texture cache from the glyph key cache.
                 let mut keep_cache = false;
                 cache.retain(|_, entry| {
                     let keep_glyph = entry.is_allocated(texture_cache, render_task_cache);
                     keep_cache |= keep_glyph;
                     keep_glyph
                 });
+                if !keep_cache {
+                    glyph_rasterizer.delete_font_instance(key);
+                }
                 // Only keep the glyph key cache if it still has valid glyphs.
                 keep_cache
             } else {
                 true
             }
         });
     }
 
     pub fn begin_frame(&mut self,
                        texture_cache: &TextureCache,
-                       render_task_cache: &RenderTaskCache) {
-        self.clear_evicted(texture_cache, render_task_cache);
+                       render_task_cache: &RenderTaskCache,
+                       glyph_rasterizer: &mut GlyphRasterizer) {
+        self.clear_evicted(texture_cache, render_task_cache, glyph_rasterizer);
     }
 }
--- a/gfx/webrender/src/glyph_rasterizer/mod.rs
+++ b/gfx/webrender/src/glyph_rasterizer/mod.rs
@@ -540,16 +540,18 @@ pub struct GlyphRasterizer {
     #[allow(dead_code)]
     glyph_tx: Sender<GlyphRasterJobs>,
 
     // We defer removing fonts to the end of the frame so that:
     // - this work is done outside of the critical path,
     // - we don't have to worry about the ordering of events if a font is used on
     //   a frame where it is used (although it seems unlikely).
     fonts_to_remove: Vec<FontKey>,
+    // Defer removal of font instances, as for fonts.
+    font_instances_to_remove: Vec<FontInstance>,
 
     #[allow(dead_code)]
     next_gpu_glyph_cache_key: GpuGlyphCacheKey,
 }
 
 impl GlyphRasterizer {
     pub fn new(workers: Arc<ThreadPool>) -> Result<Self, ResourceCacheError> {
         let (glyph_tx, glyph_rx) = channel();
@@ -575,16 +577,17 @@ impl GlyphRasterizer {
 
         Ok(GlyphRasterizer {
             font_contexts: Arc::new(font_context),
             pending_glyphs: 0,
             glyph_rx,
             glyph_tx,
             workers,
             fonts_to_remove: Vec::new(),
+            font_instances_to_remove: Vec::new(),
             next_gpu_glyph_cache_key: GpuGlyphCacheKey(0),
         })
     }
 
     pub fn add_font(&mut self, font_key: FontKey, template: FontTemplate) {
         #[cfg(feature = "pathfinder")]
         self.add_font_to_pathfinder(&font_key, &template);
 
@@ -592,16 +595,20 @@ impl GlyphRasterizer {
             context.add_font(&font_key, &template);
         });
     }
 
     pub fn delete_font(&mut self, font_key: FontKey) {
         self.fonts_to_remove.push(font_key);
     }
 
+    pub fn delete_font_instance(&mut self, instance: &FontInstance) {
+        self.font_instances_to_remove.push(instance.clone());
+    }
+
     pub fn prepare_font(&self, font: &mut FontInstance) {
         FontContext::prepare_font(font);
     }
 
     pub fn get_glyph_dimensions(
         &mut self,
         font: &FontInstance,
         glyph_index: GlyphIndex,
@@ -619,33 +626,38 @@ impl GlyphRasterizer {
 
     pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
         self.font_contexts
             .lock_shared_context()
             .get_glyph_index(font_key, ch)
     }
 
     fn remove_dead_fonts(&mut self) {
-        if self.fonts_to_remove.is_empty() {
+        if self.fonts_to_remove.is_empty() && self.font_instances_to_remove.is_empty() {
             return
         }
 
         let fonts_to_remove = mem::replace(&mut self.fonts_to_remove, Vec::new());
+        let font_instances_to_remove = mem::replace(& mut self.font_instances_to_remove, Vec::new());
         self.font_contexts.for_each(move |mut context| {
             for font_key in &fonts_to_remove {
                 context.delete_font(font_key);
             }
+            for instance in &font_instances_to_remove {
+                context.delete_font_instance(instance);
+            }
         });
     }
 
     #[cfg(feature = "replay")]
     pub fn reset(&mut self) {
         //TODO: any signals need to be sent to the workers?
         self.pending_glyphs = 0;
         self.fonts_to_remove.clear();
+        self.font_instances_to_remove.clear();
     }
 }
 
 trait AddFont {
     fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate);
 }
 
 impl AddFont for FontContext {
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -145,16 +145,17 @@ pub enum DebugOutput {
     FetchDocuments(String),
     FetchClipScrollTree(String),
     #[cfg(feature = "capture")]
     SaveCapture(CaptureConfig, Vec<ExternalCaptureImage>),
     #[cfg(feature = "replay")]
     LoadCapture(PathBuf, Vec<PlainExternalImage>),
 }
 
+#[allow(dead_code)]
 pub enum ResultMsg {
     DebugCommand(DebugCommand),
     DebugOutput(DebugOutput),
     RefreshShader(PathBuf),
     UpdateGpuCache(GpuCacheUpdateList),
     UpdateResources {
         updates: TextureUpdateList,
         memory_pressure: bool,
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -177,16 +177,18 @@ extern crate base64;
 #[cfg(all(feature = "capture", feature = "png"))]
 extern crate png;
 
 pub extern crate webrender_api;
 
 #[doc(hidden)]
 pub use device::{build_shader_strings, ReadPixelsFormat, UploadMethod, VertexUsageHint};
 pub use device::{ProgramBinary, ProgramCache, ProgramCacheObserver, ProgramSources};
+pub use device::{Device};
 pub use frame_builder::ChasePrimitive;
 pub use renderer::{AsyncPropertySampler, CpuProfile, DebugFlags, OutputImageHandler, RendererKind};
 pub use renderer::{ExternalImage, ExternalImageHandler, ExternalImageSource, GpuProfile};
 pub use renderer::{GraphicsApi, GraphicsApiInfo, PipelineInfo, Renderer, RendererOptions};
 pub use renderer::{RendererStats, SceneBuilderHooks, ThreadListener, ShaderPrecacheFlags};
 pub use renderer::MAX_VERTEX_TEXTURE_WIDTH;
+pub use shade::{Shaders, WrShaders};
 pub use webrender_api as api;
 pub use resource_cache::intersect_for_tile;
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -9,17 +9,17 @@ use api::{PicturePixel, RasterPixel, Wor
 use box_shadow::{BLUR_SAMPLE_SCALE};
 use clip::ClipNodeCollector;
 use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use euclid::TypedScale;
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState};
 use frame_builder::{PictureContext, PrimitiveContext};
 use gpu_cache::{GpuCacheHandle};
 use gpu_types::UvRectKind;
-use prim_store::{PrimitiveIndex, PrimitiveInstance, SpaceMapper};
+use prim_store::{PrimitiveInstance, SpaceMapper};
 use prim_store::{PrimitiveMetadata, get_raster_rects};
 use render_task::{ClearMode, RenderTask, RenderTaskCacheEntryHandle};
 use render_task::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
 use scene::{FilterOpHelpers, SceneProperties};
 use std::mem;
 use tiling::RenderTargetKind;
 use util::{TransformedRectKind, MatrixHelpers, MaxRect};
 
@@ -217,19 +217,20 @@ impl PicturePrimitive {
     pub fn new_image(
         id: PictureId,
         requested_composite_mode: Option<PictureCompositeMode>,
         is_in_3d_context: bool,
         pipeline_id: PipelineId,
         frame_output_pipeline_id: Option<PipelineId>,
         apply_local_clip_rect: bool,
         requested_raster_space: RasterSpace,
+        prim_instances: Vec<PrimitiveInstance>,
     ) -> Self {
         PicturePrimitive {
-            prim_instances: Vec::new(),
+            prim_instances,
             state: None,
             secondary_render_task_id: None,
             requested_composite_mode,
             raster_config: None,
             is_in_3d_context,
             frame_output_pipeline_id,
             extra_gpu_data_handle: GpuCacheHandle::new(),
             apply_local_clip_rect,
@@ -373,26 +374,16 @@ impl PicturePrimitive {
             raster_space,
         };
 
         let instances = mem::replace(&mut self.prim_instances, Vec::new());
 
         Some((context, state, instances))
     }
 
-    pub fn add_primitive(
-        &mut self,
-        prim_index: PrimitiveIndex,
-    ) {
-        self.prim_instances.push(PrimitiveInstance {
-            prim_index,
-            combined_local_clip_rect: LayoutRect::zero(),
-        });
-    }
-
     pub fn restore_context(
         &mut self,
         prim_instances: Vec<PrimitiveInstance>,
         context: PictureContext,
         state: PictureState,
         local_rect: Option<PictureRect>,
         frame_state: &mut FrameBuildingState,
     ) -> (LayoutRect, Option<ClipNodeCollector>) {
@@ -443,39 +434,39 @@ impl PicturePrimitive {
     }
 
     pub fn take_state(&mut self) -> PictureState {
         self.state.take().expect("bug: no state present!")
     }
 
     pub fn prepare_for_render(
         &mut self,
-        prim_index: PrimitiveIndex,
-        prim_metadata: &mut PrimitiveMetadata,
+        prim_instance: &PrimitiveInstance,
+        prim_metadata: &PrimitiveMetadata,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
     ) -> bool {
         let mut pic_state_for_children = self.take_state();
 
         match self.raster_config {
             Some(ref mut raster_config) => {
                 let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers(
-                    prim_metadata.spatial_node_index,
+                    prim_instance.spatial_node_index,
                     raster_config.raster_spatial_node_index,
                     frame_context,
                 );
 
                 let pic_rect = PictureRect::from_untyped(&prim_metadata.local_rect.to_untyped());
 
                 let (clipped, unclipped, transform) = match get_raster_rects(
                     pic_rect,
                     &map_pic_to_raster,
                     &map_raster_to_world,
-                    prim_metadata.clipped_world_rect.expect("bug1"),
+                    prim_instance.clipped_world_rect.expect("bug1"),
                     frame_context.device_pixel_scale,
                 ) {
                     Some(info) => info,
                     None => return false,
                 };
 
                 // TODO(gw): Almost all of the Picture types below use extra_gpu_cache_data
                 //           to store the same type of data. The exception is the filter
@@ -515,17 +506,17 @@ impl PicturePrimitive {
                         // anyway. In the future we should relax this a bit, so that we can
                         // cache tasks with complex coordinate systems if we detect the
                         // relevant transforms haven't changed from frame to frame.
                         let surface = if pic_state_for_children.has_non_root_coord_system ||
                                          !pic_state_for_children.is_cacheable {
                             let picture_task = RenderTask::new_picture(
                                 RenderTaskLocation::Dynamic(None, device_rect.size),
                                 unclipped.size,
-                                prim_index,
+                                prim_instance.prim_index,
                                 device_rect.origin,
                                 pic_state_for_children.tasks,
                                 uv_rect_kind,
                                 pic_state_for_children.raster_spatial_node_index,
                             );
 
                             let picture_task_id = frame_state.render_tasks.add(picture_task);
 
@@ -573,17 +564,17 @@ impl PicturePrimitive {
                                 None,
                                 false,
                                 |render_tasks| {
                                     let child_tasks = mem::replace(&mut pic_state_for_children.tasks, Vec::new());
 
                                     let picture_task = RenderTask::new_picture(
                                         RenderTaskLocation::Dynamic(None, device_rect.size),
                                         unclipped.size,
-                                        prim_index,
+                                        prim_instance.prim_index,
                                         device_rect.origin,
                                         child_tasks,
                                         uv_rect_kind,
                                         pic_state_for_children.raster_spatial_node_index,
                                     );
 
                                     let picture_task_id = render_tasks.add(picture_task);
 
@@ -630,17 +621,17 @@ impl PicturePrimitive {
                             &transform,
                             &device_rect,
                             frame_context.device_pixel_scale,
                         );
 
                         let mut picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, device_rect.size),
                             unclipped.size,
-                            prim_index,
+                            prim_instance.prim_index,
                             device_rect.origin,
                             pic_state_for_children.tasks,
                             uv_rect_kind,
                             pic_state_for_children.raster_spatial_node_index,
                         );
                         picture_task.mark_for_saving();
 
                         let picture_task_id = frame_state.render_tasks.add(picture_task);
@@ -701,17 +692,17 @@ impl PicturePrimitive {
                             &transform,
                             &clipped,
                             frame_context.device_pixel_scale,
                         );
 
                         let picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, clipped.size),
                             unclipped.size,
-                            prim_index,
+                            prim_instance.prim_index,
                             clipped.origin,
                             pic_state_for_children.tasks,
                             uv_rect_kind,
                             pic_state_for_children.raster_spatial_node_index,
                         );
 
                         let readback_task_id = frame_state.render_tasks.add(
                             RenderTask::new_readback(clipped)
@@ -738,17 +729,17 @@ impl PicturePrimitive {
                             &transform,
                             &clipped,
                             frame_context.device_pixel_scale,
                         );
 
                         let picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, clipped.size),
                             unclipped.size,
-                            prim_index,
+                            prim_instance.prim_index,
                             clipped.origin,
                             pic_state_for_children.tasks,
                             uv_rect_kind,
                             pic_state_for_children.raster_spatial_node_index,
                         );
 
                         let render_task_id = frame_state.render_tasks.add(picture_task);
                         pic_state.tasks.push(render_task_id);
@@ -760,17 +751,17 @@ impl PicturePrimitive {
                             &transform,
                             &clipped,
                             frame_context.device_pixel_scale,
                         );
 
                         let picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, clipped.size),
                             unclipped.size,
-                            prim_index,
+                            prim_instance.prim_index,
                             clipped.origin,
                             pic_state_for_children.tasks,
                             uv_rect_kind,
                             pic_state_for_children.raster_spatial_node_index,
                         );
 
                         let render_task_id = frame_state.render_tasks.add(picture_task);
                         pic_state.tasks.push(render_task_id);
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -320,16 +320,21 @@ impl FontContext {
     }
 
     pub fn delete_font(&mut self, font_key: &FontKey) {
         if let Some(_) = self.cg_fonts.remove(font_key) {
             self.ct_fonts.retain(|k, _| k.0 != *font_key);
         }
     }
 
+    pub fn delete_font_instance(&mut self, instance: &FontInstance) {
+        // Remove the CoreText font corresponding to this instance.
+        self.ct_fonts.remove(&(instance.font_key, instance.size, instance.variations.clone()));
+    }
+
     fn get_ct_font(
         &mut self,
         font_key: FontKey,
         size: Au,
         variations: &[FontVariation],
     ) -> Option<CTFont> {
         match self.ct_fonts.entry((font_key, size, variations.to_vec())) {
             Entry::Occupied(entry) => Some((*entry.get()).clone()),
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.rs
@@ -235,16 +235,20 @@ impl FontContext {
 
     pub fn delete_font(&mut self, font_key: &FontKey) {
         if let Some(face) = self.faces.remove(font_key) {
             let result = unsafe { FT_Done_Face(face.face) };
             assert!(succeeded(result));
         }
     }
 
+    pub fn delete_font_instance(&mut self, _instance: &FontInstance) {
+        // This backend does not yet support variations, so there is nothing to do here.
+    }
+
     fn load_glyph(&self, font: &FontInstance, glyph: &GlyphKey) -> Option<(FT_GlyphSlot, f32)> {
         debug_assert!(self.faces.contains_key(&font.font_key));
         let face = self.faces.get(&font.font_key).unwrap();
 
         let mut load_flags = FT_LOAD_DEFAULT;
         let FontInstancePlatformOptions { mut hinting, .. } = font.platform_options.unwrap_or_default();
         // Disable hinting if there is a non-axis-aligned transform.
         if font.synthetic_italics.is_enabled() ||
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -140,16 +140,28 @@ impl FontContext {
     }
 
     pub fn delete_font(&mut self, font_key: &FontKey) {
         if let Some(_) = self.fonts.remove(font_key) {
             self.variations.retain(|k, _| k.0 != *font_key);
         }
     }
 
+    pub fn delete_font_instance(&mut self, instance: &FontInstance) {
+        // Ensure we don't keep around excessive amounts of stale variations.
+        if !instance.variations.is_empty() {
+            let sims = if instance.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) {
+                dwrote::DWRITE_FONT_SIMULATIONS_BOLD
+            } else {
+                dwrote::DWRITE_FONT_SIMULATIONS_NONE
+            };
+            self.variations.remove(&(instance.font_key, sims, instance.variations.clone()));
+        }
+    }
+
     // Assumes RGB format from dwrite, which is 3 bytes per pixel as dwrite
     // doesn't output an alpha value via GlyphRunAnalysis::CreateAlphaTexture
     #[allow(dead_code)]
     fn print_glyph_data(&self, data: &[u8], width: usize, height: usize) {
         // Rust doesn't have step_by support on stable :(
         for i in 0 .. height {
             let current_height = i * width * 3;
 
@@ -180,17 +192,18 @@ impl FontContext {
             Entry::Occupied(entry) => entry.into_mut(),
             Entry::Vacant(entry) => {
                 let normal_face = self.fonts.get(&font.font_key).unwrap();
                 if !font.variations.is_empty() {
                     if let Some(var_face) = normal_face.create_font_face_with_variations(
                         sims,
                         &font.variations.iter().map(|var| {
                             dwrote::DWRITE_FONT_AXIS_VALUE {
-                                axisTag: var.tag,
+                                // OpenType tags are big-endian, but DWrite wants little-endian.
+                                axisTag: var.tag.swap_bytes(),
                                 value: var.value,
                             }
                         }).collect::<Vec<_>>(),
                     ) {
                         return entry.insert(var_face);
                     }
                 }
                 entry.insert(normal_face.create_font_face_with_simulations(sims))
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,31 +1,32 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, BorderRadius, BuiltDisplayList, ClipMode, ColorF, PictureRect};
 use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, ExtendMode, DeviceRect, PictureToRasterTransform};
-use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, TileOffset};
+use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, TileOffset};
 use api::{RasterSpace, LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize, LayoutToWorldTransform};
 use api::{LayoutVector2D, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat};
 use api::{DeviceIntSideOffsets, WorldPixel, BoxShadowClipMode, LayoutToWorldScale, NormalBorder, WorldRect};
 use api::{PicturePixel, RasterPixel, ColorDepth};
 use app_units::Au;
 use border::{BorderCacheKey, BorderRenderTaskInfo};
 use clip_scroll_tree::{ClipScrollTree, CoordinateSystemId, SpatialNodeIndex};
 use clip::{ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem, ClipNodeCollector};
 use euclid::{TypedTransform3D, TypedRect};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::PrimitiveContext;
 use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey, FONT_SIZE_LIMIT};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
 use gpu_types::BrushFlags;
 use image::{for_each_tile, for_each_repetition};
+use intern;
 use picture::{PictureCompositeMode, PicturePrimitive};
 #[cfg(debug_assertions)]
 use render_backend::FrameId;
 use render_task::{BlitSource, RenderTask, RenderTaskCacheKey};
 use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskCacheEntryHandle};
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{ImageProperties, ImageRequest, ResourceCache};
 use scene::SceneProperties;
@@ -233,42 +234,63 @@ impl GpuCacheAddress {
     pub fn as_int(&self) -> i32 {
         // TODO(gw): Temporarily encode GPU Cache addresses as a single int.
         //           In the future, we can change the PrimitiveInstanceData struct
         //           to use 2x u16 for the vertex attribute instead of an i32.
         self.v as i32 * MAX_VERTEX_TEXTURE_WIDTH as i32 + self.u as i32
     }
 }
 
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct PrimitiveKey {
+    pub is_backface_visible: bool,
+}
+
+impl PrimitiveKey {
+    pub fn new(
+        is_backface_visible: bool,
+    ) -> Self {
+        PrimitiveKey {
+            is_backface_visible,
+        }
+    }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct PrimitiveTemplate {
+    pub is_backface_visible: bool,
+}
+
+impl From<PrimitiveKey> for PrimitiveTemplate {
+    fn from(item: PrimitiveKey) -> Self {
+        PrimitiveTemplate {
+            is_backface_visible: item.is_backface_visible,
+        }
+    }
+}
+
+// Type definitions for interning primitives.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Clone, Copy, Debug)]
+pub struct PrimitiveDataMarker;
+
+pub type PrimitiveDataStore = intern::DataStore<PrimitiveKey, PrimitiveTemplate, PrimitiveDataMarker>;
+pub type PrimitiveDataHandle = intern::Handle<PrimitiveDataMarker>;
+pub type PrimitiveDataUpdateList = intern::UpdateList<PrimitiveKey>;
+pub type PrimitiveDataInterner = intern::Interner<PrimitiveKey, PrimitiveDataMarker>;
+
 // TODO(gw): Pack the fields here better!
 #[derive(Debug)]
 pub struct PrimitiveMetadata {
-    pub opacity: PrimitiveOpacity,
-    pub clip_chain_id: ClipChainId,
-    pub spatial_node_index: SpatialNodeIndex,
-    pub gpu_location: GpuCacheHandle,
-    pub clip_task_id: Option<RenderTaskId>,
-
-    // TODO(gw): In the future, we should just pull these
-    //           directly from the DL item, instead of
-    //           storing them here.
     pub local_rect: LayoutRect,
     pub local_clip_rect: LayoutRect,
-
-    pub is_backface_visible: bool,
-    pub clipped_world_rect: Option<WorldRect>,
-
-    /// A tag used to identify this primitive outside of WebRender. This is
-    /// used for returning useful data during hit testing.
-    pub tag: Option<ItemTag>,
-
-    /// The last frame ID (of the `RenderTaskTree`) this primitive
-    /// was prepared for rendering in.
-    #[cfg(debug_assertions)]
-    pub prepared_frame_id: FrameId,
 }
 
 // Maintains a list of opacity bindings that have been collapsed into
 // the color of a single primitive. This is an important optimization
 // that avoids allocating an intermediate surface for most common
 // uses of opacity filters.
 #[derive(Debug)]
 pub struct OpacityBinding {
@@ -1411,21 +1433,80 @@ impl Primitive {
                 panic!("bug: not a picture!");
             }
         }
     }
 }
 
 #[derive(Debug)]
 pub struct PrimitiveInstance {
+    /// Index into the prim store containing information about
+    /// the specific primitive. This will be removed once all
+    /// primitive data is interned.
     pub prim_index: PrimitiveIndex,
 
-    // The current combined local clip for this primitive, from
-    // the primitive local clip above and the current clip chain.
+    /// Handle to the common interned data for this primitive.
+    pub prim_data_handle: PrimitiveDataHandle,
+
+    /// The current combined local clip for this primitive, from
+    /// the primitive local clip above and the current clip chain.
     pub combined_local_clip_rect: LayoutRect,
+
+    /// The last frame ID (of the `RenderTaskTree`) this primitive
+    /// was prepared for rendering in.
+    #[cfg(debug_assertions)]
+    pub prepared_frame_id: FrameId,
+
+    /// The current world rect of this primitive, clipped to the
+    /// world rect of the screen. None means the primitive is
+    /// completely off-screen.
+    pub clipped_world_rect: Option<WorldRect>,
+
+    /// If this primitive has a global clip mask, this identifies
+    /// the render task for it.
+    pub clip_task_id: Option<RenderTaskId>,
+
+    /// The main GPU cache handle that this primitive uses to
+    /// store data accessible to shaders. This should be moved
+    /// into the interned data in order to retain this between
+    /// display list changes, but needs to be split into shared
+    /// and per-instance data.
+    pub gpu_location: GpuCacheHandle,
+
+    /// The current opacity of the primitive contents.
+    pub opacity: PrimitiveOpacity,
+
+    /// ID of the clip chain that this primitive is clipped by.
+    pub clip_chain_id: ClipChainId,
+
+    /// ID of the spatial node that this primitive is positioned by.
+    pub spatial_node_index: SpatialNodeIndex,
+}
+
+impl PrimitiveInstance {
+    pub fn new(
+        prim_index: PrimitiveIndex,
+        prim_data_handle: PrimitiveDataHandle,
+        clip_chain_id: ClipChainId,
+        spatial_node_index: SpatialNodeIndex,
+    ) -> Self {
+        PrimitiveInstance {
+            prim_index,
+            prim_data_handle,
+            combined_local_clip_rect: LayoutRect::zero(),
+            clipped_world_rect: None,
+            #[cfg(debug_assertions)]
+            prepared_frame_id: FrameId(0),
+            clip_task_id: None,
+            gpu_location: GpuCacheHandle::new(),
+            opacity: PrimitiveOpacity::translucent(),
+            clip_chain_id,
+            spatial_node_index,
+        }
+    }
 }
 
 pub struct PrimitiveStore {
     pub primitives: Vec<Primitive>,
 
     /// A primitive index to chase through debugging.
     pub chase_id: Option<PrimitiveIndex>,
 }
@@ -1445,78 +1526,38 @@ impl PrimitiveStore {
     pub fn get_pic_mut(&mut self, index: PrimitiveIndex) -> &mut PicturePrimitive {
         self.primitives[index.0].as_pic_mut()
     }
 
     pub fn add_primitive(
         &mut self,
         local_rect: &LayoutRect,
         local_clip_rect: &LayoutRect,
-        is_backface_visible: bool,
-        clip_chain_id: ClipChainId,
-        spatial_node_index: SpatialNodeIndex,
-        tag: Option<ItemTag>,
         container: PrimitiveContainer,
     ) -> PrimitiveIndex {
         let prim_index = self.primitives.len();
 
         let base_metadata = PrimitiveMetadata {
-            clip_chain_id,
-            gpu_location: GpuCacheHandle::new(),
-            clip_task_id: None,
-            spatial_node_index,
             local_rect: *local_rect,
             local_clip_rect: *local_clip_rect,
-            is_backface_visible,
-            clipped_world_rect: None,
-            tag,
-            opacity: PrimitiveOpacity::translucent(),
-            #[cfg(debug_assertions)]
-            prepared_frame_id: FrameId(0),
         };
 
         let prim = match container {
             PrimitiveContainer::Brush(brush) => {
-                let opacity = match brush.kind {
-                    BrushKind::Clear => PrimitiveOpacity::translucent(),
-                    BrushKind::Solid { ref color, .. } => PrimitiveOpacity::from_alpha(color.a),
-                    BrushKind::Image { .. } => PrimitiveOpacity::translucent(),
-                    BrushKind::YuvImage { .. } => PrimitiveOpacity::opaque(),
-                    BrushKind::RadialGradient { .. } => PrimitiveOpacity::translucent(),
-                    BrushKind::LinearGradient { stretch_size, tile_spacing, stops_opacity, .. } => {
-                        // If the coverage of the gradient extends to or beyond
-                        // the primitive rect, then the opacity can be determined
-                        // by the colors of the stops. If we have tiling / spacing
-                        // then we just assume the gradient is translucent for now.
-                        // (In the future we could consider segmenting in some cases).
-                        let stride = stretch_size + tile_spacing;
-                        if stride.width >= local_rect.size.width &&
-                           stride.height >= local_rect.size.height {
-                            stops_opacity
-                        } else {
-                            PrimitiveOpacity::translucent()
-                        }
-                    }
-                    BrushKind::Picture { .. } => PrimitiveOpacity::translucent(),
-                    BrushKind::Border { .. } => PrimitiveOpacity::translucent(),
-                };
-
                 let metadata = PrimitiveMetadata {
-                    opacity,
                     ..base_metadata
                 };
 
                 Primitive {
                     metadata,
                     details: PrimitiveDetails::Brush(brush),
                 }
             }
             PrimitiveContainer::TextRun(text_cpu) => {
                 let metadata = PrimitiveMetadata {
-                    opacity: PrimitiveOpacity::translucent(),
                     ..base_metadata
                 };
 
                 Primitive {
                     metadata,
                     details: PrimitiveDetails::TextRun(text_cpu),
                 }
             }
@@ -1718,17 +1759,17 @@ impl PrimitiveStore {
                         pic_context_for_children,
                         pic_state_for_children,
                         pic_rect,
                         frame_state,
                     );
 
                 if new_local_rect != prim.metadata.local_rect {
                     prim.metadata.local_rect = new_local_rect;
-                    frame_state.gpu_cache.invalidate(&mut prim.metadata.gpu_location);
+                    frame_state.gpu_cache.invalidate(&mut prim_instance.gpu_location);
                     pic_state.local_rect_changed = true;
                 }
 
                 (is_passthrough, clip_node_collector)
             }
             None => {
                 (false, None)
             }
@@ -1736,17 +1777,17 @@ impl PrimitiveStore {
 
         let prim = &mut self.primitives[prim_instance.prim_index.0];
 
         if !prim.is_cacheable(frame_state.resource_cache) {
             pic_state.is_cacheable = false;
         }
 
         if is_passthrough {
-            prim.metadata.clipped_world_rect = Some(pic_state.map_pic_to_world.bounds);
+            prim_instance.clipped_world_rect = Some(pic_state.map_pic_to_world.bounds);
         } else {
             if prim.metadata.local_rect.size.width <= 0.0 ||
                prim.metadata.local_rect.size.height <= 0.0 {
                 if cfg!(debug_assertions) && is_chased {
                     println!("\tculled for zero local rectangle");
                 }
                 return false;
             }
@@ -1769,17 +1810,17 @@ impl PrimitiveStore {
                     }
                     return false;
                 }
             };
 
             let clip_chain = frame_state
                 .clip_store
                 .build_clip_chain_instance(
-                    prim.metadata.clip_chain_id,
+                    prim_instance.clip_chain_id,
                     local_rect,
                     prim.metadata.local_clip_rect,
                     prim_context.spatial_node_index,
                     &pic_state.map_local_to_pic,
                     &pic_state.map_pic_to_world,
                     &frame_context.clip_scroll_tree,
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
@@ -1787,17 +1828,17 @@ impl PrimitiveStore {
                     &frame_context.world_rect,
                     &clip_node_collector,
                     &mut frame_state.resources.clip_data_store,
                 );
 
             let clip_chain = match clip_chain {
                 Some(clip_chain) => clip_chain,
                 None => {
-                    prim.metadata.clipped_world_rect = None;
+                    prim_instance.clipped_world_rect = None;
                     return false;
                 }
             };
 
             if cfg!(debug_assertions) && is_chased {
                 println!("\teffective clip chain from {:?} {}",
                     clip_chain.clips_range,
                     if pic_context.apply_local_clip_rect { "(applied)" } else { "" },
@@ -1830,25 +1871,27 @@ impl PrimitiveStore {
 
             let clipped_world_rect = match world_rect.intersection(&frame_context.world_rect) {
                 Some(rect) => rect,
                 None => {
                     return false;
                 }
             };
 
-            prim.metadata.clipped_world_rect = Some(clipped_world_rect);
+            prim_instance.clipped_world_rect = Some(clipped_world_rect);
 
             prim.build_prim_segments_if_needed(
+                prim_instance,
                 pic_state,
                 frame_state,
                 frame_context,
             );
 
             prim.update_clip_task(
+                prim_instance,
                 prim_context,
                 clipped_world_rect,
                 pic_state.raster_spatial_node_index,
                 &clip_chain,
                 pic_state,
                 frame_context,
                 frame_state,
                 is_chased,
@@ -1871,66 +1914,57 @@ impl PrimitiveStore {
             frame_state,
             display_list,
             is_chased,
         );
 
         true
     }
 
-    // TODO(gw): Make this simpler / more efficient by tidying
-    //           up the logic that early outs from prepare_prim_for_render.
-    pub fn reset_prim_visibility(&mut self) {
-        for prim in &mut self.primitives {
-            prim.metadata.clipped_world_rect = None;
-        }
-    }
-
     pub fn prepare_primitives(
         &mut self,
         prim_instances: &mut Vec<PrimitiveInstance>,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         current_pic_rect: &mut PictureRect,
     ) {
         let display_list = &frame_context
             .pipelines
             .get(&pic_context.pipeline_id)
             .expect("No display list?")
             .display_list;
 
         for prim_instance in prim_instances {
+            prim_instance.clipped_world_rect = None;
+
             let prim_index = prim_instance.prim_index;
             let is_chased = Some(prim_index) == self.chase_id;
 
             if is_chased {
                 println!("\tpreparing prim {:?} in pipeline {:?}",
                     prim_instance.prim_index, pic_context.pipeline_id);
             }
 
-            // TODO(gw): These workarounds for borrowck are unfortunate. We
-            //           should see if we can re-structure these to avoid so
-            //           many special borrow blocks.
-            let (spatial_node_index, is_backface_visible) = {
-                let prim = &self.primitives[prim_instance.prim_index.0];
-                (prim.metadata.spatial_node_index, prim.metadata.is_backface_visible)
-            };
+            let is_backface_visible = frame_state
+                .resources
+                .prim_data_store[prim_instance.prim_data_handle]
+                .is_backface_visible;
 
             let spatial_node = &frame_context
                 .clip_scroll_tree
-                .spatial_nodes[spatial_node_index.0];
+                .spatial_nodes[prim_instance.spatial_node_index.0];
 
             // TODO(gw): Although constructing these is cheap, they are often
             //           the same for many consecutive primitives, so it may
             //           be worth caching the most recent context.
             let prim_context = PrimitiveContext::new(
                 spatial_node,
-                spatial_node_index,
+                prim_instance.spatial_node_index,
             );
 
             // Do some basic checks first, that can early out
             // without even knowing the local rect.
             if !is_backface_visible && spatial_node.world_content_transform.is_backface_visible() {
                 if cfg!(debug_assertions) && is_chased {
                     println!("\tculled for not having visible back faces");
                 }
@@ -1945,17 +1979,17 @@ impl PrimitiveStore {
             }
 
             // Mark whether this picture contains any complex coordinate
             // systems, due to either the scroll node or the clip-chain.
             pic_state.has_non_root_coord_system |=
                 spatial_node.coordinate_system_id != CoordinateSystemId::root();
 
             pic_state.map_local_to_pic.set_target_spatial_node(
-                spatial_node_index,
+                prim_instance.spatial_node_index,
                 frame_context.clip_scroll_tree,
             );
 
             if self.prepare_prim_for_render(
                 prim_instance,
                 &prim_context,
                 pic_context,
                 pic_state,
@@ -1987,34 +2021,34 @@ fn build_gradient_stops_request(
             reverse_stops,
             &mut request,
         );
     }
 }
 
 fn decompose_repeated_primitive(
     visible_tiles: &mut Vec<VisibleGradientTile>,
-    instance: &PrimitiveInstance,
+    instance: &mut PrimitiveInstance,
     metadata: &mut PrimitiveMetadata,
     stretch_size: &LayoutSize,
     tile_spacing: &LayoutSize,
     prim_context: &PrimitiveContext,
     frame_state: &mut FrameBuildingState,
     callback: &mut FnMut(&LayoutRect, GpuDataRequest),
 ) {
     visible_tiles.clear();
 
     // Tighten the clip rect because decomposing the repeated image can
     // produce primitives that are partially covering the original image
     // rect and we want to clip these extra parts out.
     let tight_clip_rect = instance
         .combined_local_clip_rect
         .intersection(&metadata.local_rect).unwrap();
 
-    let clipped_world_rect = &metadata
+    let clipped_world_rect = &instance
         .clipped_world_rect
         .unwrap();
 
     let visible_rect = compute_conservative_visible_rect(
         prim_context,
         clipped_world_rect,
         &tight_clip_rect
     );
@@ -2044,17 +2078,17 @@ fn decompose_repeated_primitive(
     );
 
     if visible_tiles.is_empty() {
         // At this point if we don't have tiles to show it means we could probably
         // have done a better a job at culling during an earlier stage.
         // Clearing the screen rect has the effect of "culling out" the primitive
         // from the point of view of the batch builder, and ensures we don't hit
         // assertions later on because we didn't request any image.
-        metadata.clipped_world_rect = None;
+        instance.clipped_world_rect = None;
     }
 }
 
 fn compute_conservative_visible_rect(
     prim_context: &PrimitiveContext,
     clipped_world_rect: &WorldRect,
     local_clip_rect: &LayoutRect,
 ) -> LayoutRect {
@@ -2257,16 +2291,17 @@ fn write_brush_segment_description(
             }
         }
     }
 }
 
 impl Primitive {
     fn update_clip_task_for_brush(
         &mut self,
+        prim_instance: &PrimitiveInstance,
         root_spatial_node_index: SpatialNodeIndex,
         prim_bounding_rect: WorldRect,
         prim_context: &PrimitiveContext,
         prim_clip_chain: &ClipChainInstance,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         clip_node_collector: &Option<ClipNodeCollector>,
@@ -2304,17 +2339,17 @@ impl Primitive {
         } else {
             for segment in &mut segment_desc.segments {
                 // Build a clip chain for the smaller segment rect. This will
                 // often manage to eliminate most/all clips, and sometimes
                 // clip the segment completely.
                 let segment_clip_chain = frame_state
                     .clip_store
                     .build_clip_chain_instance(
-                        self.metadata.clip_chain_id,
+                        prim_instance.clip_chain_id,
                         segment.local_rect,
                         self.metadata.local_clip_rect,
                         prim_context.spatial_node_index,
                         &pic_state.map_local_to_pic,
                         &pic_state.map_pic_to_world,
                         &frame_context.clip_scroll_tree,
                         frame_state.gpu_cache,
                         frame_state.resource_cache,
@@ -2336,18 +2371,21 @@ impl Primitive {
         }
 
         true
     }
 
     // Returns true if the primitive *might* need a clip mask. If
     // false, there is no need to even check for clip masks for
     // this primitive.
-    fn reset_clip_task(&mut self) -> bool {
-        self.metadata.clip_task_id = None;
+    fn reset_clip_task(
+        &mut self,
+        prim_instance: &mut PrimitiveInstance,
+    ) -> bool {
+        prim_instance.clip_task_id = None;
         match self.details {
             PrimitiveDetails::Brush(ref mut brush) => {
                 if let Some(ref mut desc) = brush.segment_desc {
                     for segment in &mut desc.segments {
                         segment.clip_task_id = BrushSegmentTaskId::Opaque;
                     }
                 }
                 brush.may_need_clip_mask()
@@ -2355,30 +2393,30 @@ impl Primitive {
             PrimitiveDetails::TextRun(..) => {
                 true
             }
         }
     }
 
     fn prepare_prim_for_render_inner(
         &mut self,
-        prim_instance: &PrimitiveInstance,
+        prim_instance: &mut PrimitiveInstance,
         prim_context: &PrimitiveContext,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         display_list: &BuiltDisplayList,
         is_chased: bool,
     ) {
         let mut is_tiled = false;
         let metadata = &mut self.metadata;
         #[cfg(debug_assertions)]
         {
-            metadata.prepared_frame_id = frame_state.render_tasks.frame_id();
+            prim_instance.prepared_frame_id = frame_state.render_tasks.frame_id();
         }
 
         match self.details {
             PrimitiveDetails::TextRun(ref mut text) => {
                 // The transform only makes sense for screen space rasterization
                 let transform = prim_context.spatial_node.world_content_transform.to_transform();
                 text.prepare_for_render(
                     frame_context.device_pixel_scale,
@@ -2409,22 +2447,22 @@ impl Primitive {
 
                         // Set if we need to request the source image from the cache this frame.
                         if let Some(image_properties) = image_properties {
                             is_tiled = image_properties.tiling.is_some();
 
                             // If the opacity changed, invalidate the GPU cache so that
                             // the new color for this primitive gets uploaded.
                             if opacity_binding.update(frame_context.scene_properties) {
-                                frame_state.gpu_cache.invalidate(&mut metadata.gpu_location);
+                                frame_state.gpu_cache.invalidate(&mut prim_instance.gpu_location);
                             }
 
                             // Update opacity for this primitive to ensure the correct
                             // batching parameters are used.
-                            metadata.opacity.is_opaque =
+                            prim_instance.opacity.is_opaque =
                                 image_properties.descriptor.is_opaque &&
                                 opacity_binding.current == 1.0 &&
                                 color.a == 1.0;
 
                             if *tile_spacing != LayoutSize::zero() && !is_tiled {
                                 *source = ImageSource::Cache {
                                     // Size in device-pixels we need to allocate in render task cache.
                                     size: image_properties.descriptor.size.to_i32(),
@@ -2459,17 +2497,17 @@ impl Primitive {
                                         0,
                                     );
 
                                     let inner_size = *size;
                                     size.width += padding.horizontal();
                                     size.height += padding.vertical();
 
                                     if padding != DeviceIntSideOffsets::zero() {
-                                        metadata.opacity.is_opaque = false;
+                                        prim_instance.opacity.is_opaque = false;
                                     }
 
                                     let image_cache_key = ImageCacheKey {
                                         request,
                                         texel_rect: sub_rect,
                                     };
 
                                     // Request a pre-rendered image task.
@@ -2530,17 +2568,17 @@ impl Primitive {
                                 // produce primitives that are partially covering the original image
                                 // rect and we want to clip these extra parts out.
                                 let tight_clip_rect = prim_instance
                                     .combined_local_clip_rect
                                     .intersection(&metadata.local_rect).unwrap();
 
                                 let visible_rect = compute_conservative_visible_rect(
                                     prim_context,
-                                    &metadata.clipped_world_rect.unwrap(),
+                                    &prim_instance.clipped_world_rect.unwrap(),
                                     &tight_clip_rect
                                 );
 
                                 let base_edge_flags = edge_flags_for_tile_spacing(tile_spacing);
 
                                 let stride = stretch_size + *tile_spacing;
 
                                 visible_tiles.clear();
@@ -2590,27 +2628,29 @@ impl Primitive {
                                 );
 
                                 if visible_tiles.is_empty() {
                                     // At this point if we don't have tiles to show it means we could probably
                                     // have done a better a job at culling during an earlier stage.
                                     // Clearing the screen rect has the effect of "culling out" the primitive
                                     // from the point of view of the batch builder, and ensures we don't hit
                                     // assertions later on because we didn't request any image.
-                                    metadata.clipped_world_rect = None;
+                                    prim_instance.clipped_world_rect = None;
                                 }
                             } else if request_source_image {
                                 frame_state.resource_cache.request_image(
                                     request,
                                     frame_state.gpu_cache,
                                 );
                             }
                         }
                     }
                     BrushKind::YuvImage { format, yuv_key, image_rendering, .. } => {
+                        prim_instance.opacity = PrimitiveOpacity::opaque();
+
                         let channel_num = format.get_plane_num();
                         debug_assert!(channel_num <= 3);
                         for channel in 0 .. channel_num {
                             frame_state.resource_cache.request_image(
                                 ImageRequest {
                                     key: yuv_key[channel],
                                     rendering: image_rendering,
                                     tile: None,
@@ -2624,17 +2664,17 @@ impl Primitive {
                             BorderSource::Image(request) => {
                                 let image_properties = frame_state
                                     .resource_cache
                                     .get_image_properties(request.key);
 
                                 if let Some(image_properties) = image_properties {
                                     // Update opacity for this primitive to ensure the correct
                                     // batching parameters are used.
-                                    metadata.opacity.is_opaque =
+                                    prim_instance.opacity.is_opaque =
                                         image_properties.descriptor.is_opaque;
 
                                     frame_state.resource_cache.request_image(
                                         request,
                                         frame_state.gpu_cache,
                                     );
                                 }
                             }
@@ -2697,20 +2737,33 @@ impl Primitive {
                     BrushKind::LinearGradient {
                         stops_range,
                         reverse_stops,
                         start_point,
                         end_point,
                         extend_mode,
                         stretch_size,
                         tile_spacing,
+                        stops_opacity,
                         ref mut stops_handle,
                         ref mut visible_tiles,
                         ..
                     } => {
+                        // If the coverage of the gradient extends to or beyond
+                        // the primitive rect, then the opacity can be determined
+                        // by the colors of the stops. If we have tiling / spacing
+                        // then we just assume the gradient is translucent for now.
+                        // (In the future we could consider segmenting in some cases).
+                        let stride = stretch_size + tile_spacing;
+                        prim_instance.opacity = if stride.width >= metadata.local_rect.size.width &&
+                           stride.height >= metadata.local_rect.size.height {
+                            stops_opacity
+                        } else {
+                            PrimitiveOpacity::translucent()
+                        };
 
                         build_gradient_stops_request(
                             stops_handle,
                             stops_range,
                             reverse_stops,
                             frame_state,
                             display_list,
                         );
@@ -2741,47 +2794,47 @@ impl Primitive {
                                     ]);
                                     request.write_segment(*rect, [0.0; 4]);
                                 }
                             );
                         }
                     }
                     BrushKind::Picture(ref mut pic) => {
                         if !pic.prepare_for_render(
-                            prim_instance.prim_index,
+                            prim_instance,
                             metadata,
                             pic_state,
                             frame_context,
                             frame_state,
                         ) {
-                            metadata.clipped_world_rect = None;
+                            prim_instance.clipped_world_rect = None;
                         }
                     }
                     BrushKind::Solid { ref color, ref mut opacity_binding, .. } => {
                         // If the opacity changed, invalidate the GPU cache so that
                         // the new color for this primitive gets uploaded. Also update
                         // the opacity field that controls which batches this primitive
                         // will be added to.
                         if opacity_binding.update(frame_context.scene_properties) {
-                            metadata.opacity = PrimitiveOpacity::from_alpha(opacity_binding.current * color.a);
-                            frame_state.gpu_cache.invalidate(&mut metadata.gpu_location);
+                            frame_state.gpu_cache.invalidate(&mut prim_instance.gpu_location);
                         }
+                        prim_instance.opacity = PrimitiveOpacity::from_alpha(opacity_binding.current * color.a);
                     }
                     BrushKind::Clear => {}
                 }
             }
         }
 
         if is_tiled {
             // we already requested each tile's gpu data.
             return;
         }
 
         // Mark this GPU resource as required for this frame.
-        if let Some(mut request) = frame_state.gpu_cache.request(&mut metadata.gpu_location) {
+        if let Some(mut request) = frame_state.gpu_cache.request(&mut prim_instance.gpu_location) {
             match self.details {
                 PrimitiveDetails::TextRun(ref mut text) => {
                     text.write_gpu_blocks(&mut request);
                 }
                 PrimitiveDetails::Brush(ref mut brush) => {
                     brush.write_gpu_blocks(&mut request, metadata.local_rect);
 
                     match brush.segment_desc {
@@ -2806,37 +2859,39 @@ impl Primitive {
                     }
                 }
             }
         }
     }
 
     fn update_clip_task(
         &mut self,
+        prim_instance: &mut PrimitiveInstance,
         prim_context: &PrimitiveContext,
         prim_bounding_rect: WorldRect,
         root_spatial_node_index: SpatialNodeIndex,
         clip_chain: &ClipChainInstance,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         is_chased: bool,
         clip_node_collector: &Option<ClipNodeCollector>,
     ) {
         if cfg!(debug_assertions) && is_chased {
             println!("\tupdating clip task with pic rect {:?}", clip_chain.pic_clip_rect);
         }
         // Reset clips from previous frames since we may clip differently each frame.
         // If this primitive never needs clip masks, just return straight away.
-        if !self.reset_clip_task() {
+        if !self.reset_clip_task(prim_instance) {
             return;
         }
 
         // First try to  render this primitive's mask using optimized brush rendering.
         if self.update_clip_task_for_brush(
+            prim_instance,
             root_spatial_node_index,
             prim_bounding_rect,
             prim_context,
             &clip_chain,
             pic_state,
             frame_context,
             frame_state,
             clip_node_collector,
@@ -2863,27 +2918,28 @@ impl Primitive {
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
                     frame_state.render_tasks,
                     &mut frame_state.resources.clip_data_store,
                 );
 
                 let clip_task_id = frame_state.render_tasks.add(clip_task);
                 if cfg!(debug_assertions) && is_chased {
-                    println!("\tcreated task {:?} with world rect {:?}",
-                        clip_task_id, self.metadata.clipped_world_rect);
+                    println!("\tcreated task {:?} with device rect {:?}",
+                        clip_task_id, device_rect);
                 }
-                self.metadata.clip_task_id = Some(clip_task_id);
+                prim_instance.clip_task_id = Some(clip_task_id);
                 pic_state.tasks.push(clip_task_id);
             }
         }
     }
 
     fn build_prim_segments_if_needed(
         &mut self,
+        prim_instance: &mut PrimitiveInstance,
         pic_state: &mut PictureState,
         frame_state: &mut FrameBuildingState,
         frame_context: &FrameBuildingContext,
     ) {
         let brush = match self.details {
             PrimitiveDetails::Brush(ref mut brush) => brush,
             PrimitiveDetails::TextRun(..) => return,
         };
@@ -2955,17 +3011,17 @@ impl Primitive {
 
             if needs_update {
                 brush.segment_desc = Some(BrushSegmentDescriptor {
                     segments: new_segments,
                 });
 
                 // The segments have changed, so force the GPU cache to
                 // re-upload the primitive information.
-                frame_state.gpu_cache.invalidate(&mut self.metadata.gpu_location);
+                frame_state.gpu_cache.invalidate(&mut prim_instance.gpu_location);
             }
         }
     }
 }
 
 pub fn get_raster_rects(
     pic_rect: PictureRect,
     map_to_raster: &SpaceMapper<PicturePixel, RasterPixel>,
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -25,16 +25,17 @@ use api::CapturedDocument;
 use clip::ClipDataStore;
 use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree};
 #[cfg(feature = "debugger")]
 use debug_server;
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use gpu_cache::GpuCache;
 use hit_test::{HitTest, HitTester};
 use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
+use prim_store::PrimitiveDataStore;
 use profiler::{BackendProfileCounters, IpcProfileCounters, ResourceProfileCounters};
 use record::ApiRecordingReceiver;
 use renderer::{AsyncPropertySampler, PipelineInfo};
 use resource_cache::ResourceCache;
 #[cfg(feature = "replay")]
 use resource_cache::PlainCacheOwn;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use resource_cache::PlainResources;
@@ -84,25 +85,30 @@ impl DocumentView {
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FrameId(pub u32);
 
 // A collection of resources that are shared by clips, primitives
 // between display lists.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FrameResources {
-    // The store of currently active / available clip nodes. This is kept
-    // in sync with the clip interner in the scene builder for each document.
+    /// The store of currently active / available clip nodes. This is kept
+    /// in sync with the clip interner in the scene builder for each document.
     pub clip_data_store: ClipDataStore,
+
+    /// Currently active / available primitives. Kept in sync with the
+    /// primitive interner in the scene builder, per document.
+    pub prim_data_store: PrimitiveDataStore,
 }
 
 impl FrameResources {
     fn new() -> Self {
         FrameResources {
             clip_data_store: ClipDataStore::new(),
+            prim_data_store: PrimitiveDataStore::new(),
         }
     }
 }
 
 struct Document {
     // The latest built scene, usable to build frames.
     // received from the scene builder thread.
     scene: Scene,
@@ -134,16 +140,17 @@ struct Document {
     /// Properties that are resolved during frame building and can be changed at any time
     /// without requiring the scene to be re-built.
     dynamic_properties: SceneProperties,
 
     /// Track whether the last built frame is up to date or if it will need to be re-built
     /// before rendering again.
     frame_is_valid: bool,
     hit_tester_is_valid: bool,
+    rendered_frame_is_valid: bool,
 
     resources: FrameResources,
 }
 
 impl Document {
     pub fn new(
         window_size: DeviceUintSize,
         layer: DocumentLayer,
@@ -164,16 +171,17 @@ impl Document {
             clip_scroll_tree: ClipScrollTree::new(),
             frame_id: FrameId(0),
             frame_builder: None,
             output_pipelines: FastHashSet::default(),
             hit_tester: None,
             dynamic_properties: SceneProperties::new(),
             frame_is_valid: false,
             hit_tester_is_valid: false,
+            rendered_frame_is_valid: false,
             resources: FrameResources::new(),
         }
     }
 
     fn can_render(&self) -> bool {
         self.frame_builder.is_some() && self.scene.has_root_pipeline()
     }
 
@@ -651,16 +659,17 @@ impl RenderBackend {
 
                         self.update_document(
                             txn.document_id,
                             replace(&mut txn.resource_updates, Vec::new()),
                             txn.doc_resource_updates.take(),
                             replace(&mut txn.frame_ops, Vec::new()),
                             replace(&mut txn.notifications, Vec::new()),
                             txn.render_frame,
+                            txn.invalidate_rendered_frame,
                             &mut frame_counter,
                             &mut profile_counters,
                             has_built_scene,
                         );
                     },
                     SceneBuilderResult::FlushComplete(tx) => {
                         tx.send(()).ok();
                     }
@@ -931,16 +940,17 @@ impl RenderBackend {
             blob_rasterizer: None,
             blob_requests: Vec::new(),
             resource_updates: transaction_msg.resource_updates,
             frame_ops: transaction_msg.frame_ops,
             rasterized_blobs: Vec::new(),
             notifications: transaction_msg.notifications,
             set_root_pipeline: None,
             render_frame: transaction_msg.generate_frame,
+            invalidate_rendered_frame: transaction_msg.invalidate_rendered_frame,
         });
 
         self.resource_cache.pre_scene_building_update(
             &mut txn.resource_updates,
             &mut profile_counters.resources,
         );
 
         for scene_msg in transaction_msg.scene_ops.drain(..) {
@@ -966,16 +976,17 @@ impl RenderBackend {
         if !transaction_msg.use_scene_builder_thread && txn.can_skip_scene_builder() {
             self.update_document(
                 txn.document_id,
                 replace(&mut txn.resource_updates, Vec::new()),
                 None,
                 replace(&mut txn.frame_ops, Vec::new()),
                 replace(&mut txn.notifications, Vec::new()),
                 txn.render_frame,
+                txn.invalidate_rendered_frame,
                 frame_counter,
                 profile_counters,
                 false
             );
 
             return;
         }
 
@@ -1003,16 +1014,17 @@ impl RenderBackend {
     fn update_document(
         &mut self,
         document_id: DocumentId,
         resource_updates: Vec<ResourceUpdate>,
         doc_resource_updates: Option<DocumentResourceUpdates>,
         mut frame_ops: Vec<FrameMsg>,
         mut notifications: Vec<NotificationRequest>,
         mut render_frame: bool,
+        invalidate_rendered_frame: bool,
         frame_counter: &mut u32,
         profile_counters: &mut BackendProfileCounters,
         has_built_scene: bool,
     ) {
         let requested_frame = render_frame;
 
         // If we have a sampler, get more frame ops from it and add them
         // to the transaction. This is a hook to allow the WR user code to
@@ -1026,16 +1038,17 @@ impl RenderBackend {
         }
 
         let doc = self.documents.get_mut(&document_id).unwrap();
 
         // If there are any additions or removals of clip modes
         // during the scene build, apply them to the data store now.
         if let Some(updates) = doc_resource_updates {
             doc.resources.clip_data_store.apply_updates(updates.clip_updates);
+            doc.resources.prim_data_store.apply_updates(updates.prim_updates);
         }
 
         // TODO: this scroll variable doesn't necessarily mean we scrolled. It is only used
         // for something wrench specific and we should remove it.
         let mut scroll = false;
         for frame_msg in frame_ops {
             let _timer = profile_counters.total_time.timer();
             let op = doc.process_frame_msg(frame_msg);
@@ -1063,21 +1076,29 @@ impl RenderBackend {
             // scroll at the same time. we should keep track of the fact that we skipped
             // composition here and do it as soon as we receive the scene.
             render_frame = false;
         }
 
         // Avoid re-building the frame if the current built frame is still valid.
         let build_frame = render_frame && !doc.frame_is_valid;
 
+        // Request composite is true when we want to composite frame even when
+        // there is no frame update. This happens when video frame is updated under
+        // external image with NativeTexture or when platform requested to composite frame.
+        if invalidate_rendered_frame {
+            doc.rendered_frame_is_valid = false;
+        }
+
         let mut frame_build_time = None;
         if build_frame && doc.has_pixels() {
             profile_scope!("generate frame");
 
             *frame_counter += 1;
+            doc.rendered_frame_is_valid = false;
 
             // borrow ck hack for profile_counters
             let (pending_update, rendered_document) = {
                 let _timer = profile_counters.total_time.timer();
                 let frame_build_start_time = precise_time_ns();
 
                 let rendered_document = doc.build_frame(
                     &mut self.resource_cache,
@@ -1128,16 +1149,22 @@ impl RenderBackend {
         if !notifications.is_empty() {
             self.result_tx.send(ResultMsg::AppendNotificationRequests(notifications)).unwrap();
         }
 
         // Always forward the transaction to the renderer if a frame was requested,
         // otherwise gecko can get into a state where it waits (forever) for the
         // transaction to complete before sending new work.
         if requested_frame {
+            // If rendered frame is already valid, there is no need to render frame.
+            if doc.rendered_frame_is_valid {
+                render_frame = false;
+            } else if render_frame {
+                doc.rendered_frame_is_valid = true;
+            }
             self.notifier.new_frame_ready(document_id, scroll, render_frame, frame_build_time);
         }
 
         if !doc.hit_tester_is_valid {
             doc.rebuild_hit_tester();
         }
     }
 
@@ -1449,16 +1476,17 @@ impl RenderBackend {
                 clip_scroll_tree: ClipScrollTree::new(),
                 frame_id: FrameId(0),
                 frame_builder: Some(FrameBuilder::empty()),
                 output_pipelines: FastHashSet::default(),
                 dynamic_properties: SceneProperties::new(),
                 hit_tester: None,
                 frame_is_valid: false,
                 hit_tester_is_valid: false,
+                rendered_frame_is_valid: false,
                 resources: frame_resources,
             };
 
             let frame_name = format!("frame-{}-{}", (id.0).0, id.1);
             let frame = CaptureConfig::deserialize::<Frame, _>(root, frame_name);
             let build_frame = match frame {
                 Some(frame) => {
                     info!("\tloaded a built frame with {} passes", frame.passes.len());
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -32,17 +32,17 @@ use api::{channel};
 use api::DebugCommand;
 use api::channel::PayloadReceiverHelperMethods;
 use batch::{BatchKind, BatchTextures, BrushBatchKind};
 #[cfg(any(feature = "capture", feature = "replay"))]
 use capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
 use debug_colors;
 use device::{DepthFunction, Device, FrameId, Program, UploadMethod, Texture, PBO};
 use device::{ExternalTexture, FBOId, TextureSlot};
-use device::{FileWatcherHandler, ShaderError, TextureFilter,
+use device::{ShaderError, TextureFilter,
              VertexUsageHint, VAO, VBO, CustomVAO};
 use device::{ProgramCache, ReadPixelsFormat};
 #[cfg(feature = "debug_renderer")]
 use euclid::rect;
 use euclid::Transform3D;
 use frame_builder::{ChasePrimitive, FrameBuilderConfig};
 use gleam::gl;
 use glyph_rasterizer::{GlyphFormat, GlyphRasterizer};
@@ -59,34 +59,35 @@ use internal_types::{RenderTargetInfo, S
 use prim_store::DeferredResolve;
 use profiler::{BackendProfileCounters, FrameProfileCounters,
                GpuProfileTag, RendererProfileCounters, RendererProfileTimers};
 use device::query::GpuProfiler;
 use rayon::{ThreadPool, ThreadPoolBuilder};
 use record::ApiRecordingReceiver;
 use render_backend::RenderBackend;
 use scene_builder::{SceneBuilder, LowPrioritySceneBuilder};
-use shade::Shaders;
+use shade::{Shaders, WrShaders};
 use smallvec::SmallVec;
 use render_task::{RenderTask, RenderTaskKind, RenderTaskTree};
 use resource_cache::ResourceCache;
 use util::drain_filter;
 
 use std;
 use std::cmp;
 use std::collections::VecDeque;
 use std::collections::hash_map::Entry;
 use std::f32;
 use std::mem;
 use std::os::raw::c_void;
 use std::path::PathBuf;
 use std::rc::Rc;
 use std::sync::Arc;
-use std::sync::mpsc::{channel, Receiver, Sender};
+use std::sync::mpsc::{channel, Receiver};
 use std::thread;
+use std::cell::RefCell;
 use texture_cache::TextureCache;
 use thread_profiler::{register_thread_with_profiler, write_profile};
 use tiling::{AlphaRenderTarget, ColorRenderTarget};
 use tiling::{BlitJob, BlitJobSource, RenderPass, RenderPassKind, RenderTargetList};
 use tiling::{Frame, RenderTarget, RenderTargetKind, TextureCacheRenderTarget};
 #[cfg(not(feature = "pathfinder"))]
 use tiling::GlyphJob;
 use time::precise_time_ns;
@@ -1336,28 +1337,16 @@ impl VertexDataTexture {
     }
 
     fn deinit(self, device: &mut Device) {
         device.delete_pbo(self.pbo);
         device.delete_texture(self.texture);
     }
 }
 
-struct FileWatcher {
-    notifier: Box<RenderNotifier>,
-    result_tx: Sender<ResultMsg>,
-}
-
-impl FileWatcherHandler for FileWatcher {
-    fn file_changed(&self, path: PathBuf) {
-        self.result_tx.send(ResultMsg::RefreshShader(path)).ok();
-        self.notifier.wake_up();
-    }
-}
-
 struct FrameOutput {
     last_access: FrameId,
     fbo_id: FBOId,
 }
 
 #[derive(PartialEq)]
 struct TargetSelector {
     size: DeviceUintSize,
@@ -1428,17 +1417,17 @@ pub struct Renderer {
     result_rx: Receiver<ResultMsg>,
     debug_server: DebugServer,
     pub device: Device,
     pending_texture_updates: Vec<TextureUpdateList>,
     pending_gpu_cache_updates: Vec<GpuCacheUpdateList>,
     pending_shader_updates: Vec<PathBuf>,
     active_documents: Vec<(DocumentId, RenderedDocument)>,
 
-    shaders: Shaders,
+    shaders: Rc<RefCell<Shaders>>,
 
     pub gpu_glyph_renderer: GpuGlyphRenderer,
 
     max_texture_size: u32,
     max_recorded_profiles: usize,
 
     clear_color: Option<ColorF>,
     enable_clear_scissor: bool,
@@ -1554,34 +1543,29 @@ impl Renderer {
     /// };
     /// let (renderer, sender) = Renderer::new(opts);
     /// ```
     /// [rendereroptions]: struct.RendererOptions.html
     pub fn new(
         gl: Rc<gl::Gl>,
         notifier: Box<RenderNotifier>,
         mut options: RendererOptions,
+        shaders: Option<&mut WrShaders>
     ) -> Result<(Self, RenderApiSender), RendererError> {
         let (api_tx, api_rx) = channel::msg_channel()?;
         let (payload_tx, payload_rx) = channel::payload_channel()?;
         let (result_tx, result_rx) = channel();
         let gl_type = gl.get_type();
 
         let debug_server = DebugServer::new(api_tx.clone());
 
-        let file_watch_handler = FileWatcher {
-            result_tx: result_tx.clone(),
-            notifier: notifier.clone(),
-        };
-
         let mut device = Device::new(
             gl,
             options.resource_override_path.clone(),
             options.upload_method.clone(),
-            Box::new(file_watch_handler),
             options.cached_programs.take(),
         );
 
         let ext_dual_source_blending = !options.disable_dual_source_blending &&
             device.supports_extension("GL_ARB_blend_func_extended") &&
             device.supports_extension("GL_ARB_explicit_attrib_location");
 
         let device_max_size = device.max_texture_size();
@@ -1603,17 +1587,20 @@ impl Renderer {
             ),
             min_texture_size,
         );
 
         register_thread_with_profiler("Compositor".to_owned());
 
         device.begin_frame();
 
-        let shaders = Shaders::new(&mut device, gl_type, &options)?;
+        let shaders = match shaders {
+            Some(shaders) => Rc::clone(&shaders.shaders),
+            None => Rc::new(RefCell::new(Shaders::new(&mut device, gl_type, &options)?)),
+        };
 
         let backend_profile_counters = BackendProfileCounters::new();
 
         let dither_matrix_texture = if options.enable_dithering {
             let dither_matrix: [u8; 64] = [
                 00,
                 48,
                 12,
@@ -2960,24 +2947,24 @@ impl Renderer {
         stats: &mut RendererStats,
     ) {
         if scalings.is_empty() {
             return
         }
 
         match source {
             TextureSource::PrevPassColor => {
-                self.shaders.cs_scale_rgba8.bind(&mut self.device,
-                                                 &projection,
-                                                 &mut self.renderer_errors);
+                self.shaders.borrow_mut().cs_scale_rgba8.bind(&mut self.device,
+                                                              &projection,
+                                                              &mut self.renderer_errors);
             }
             TextureSource::PrevPassAlpha => {
-                self.shaders.cs_scale_a8.bind(&mut self.device,
-                                              &projection,
-                                              &mut self.renderer_errors);
+                self.shaders.borrow_mut().cs_scale_a8.bind(&mut self.device,
+                                                           &projection,
+                                                           &mut self.renderer_errors);
             }
             _ => unreachable!(),
         }
 
         self.draw_instanced_batch(
             &scalings,
             VertexArrayKind::Scale,
             &BatchTextures::no_texture(),
@@ -3064,17 +3051,17 @@ impl Renderer {
         // separable implementation.
         // TODO(gw): In the future, consider having
         //           fast path blur shaders for common
         //           blur radii with fixed weights.
         if !target.vertical_blurs.is_empty() || !target.horizontal_blurs.is_empty() {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR);
 
             self.set_blend(false, framebuffer_kind);
-            self.shaders.cs_blur_rgba8
+            self.shaders.borrow_mut().cs_blur_rgba8
                 .bind(&mut self.device, projection, &mut self.renderer_errors);
 
             if !target.vertical_blurs.is_empty() {
                 self.draw_instanced_batch(
                     &target.vertical_blurs,
                     VertexArrayKind::Blur,
                     &BatchTextures::no_texture(),
                     stats,
@@ -3122,17 +3109,17 @@ impl Renderer {
 
                 // Draw opaque batches front-to-back for maximum
                 // z-buffer efficiency!
                 for batch in alpha_batch_container
                     .opaque_batches
                     .iter()
                     .rev()
                 {
-                    self.shaders
+                    self.shaders.borrow_mut()
                         .get(&batch.key, self.debug_flags)
                         .bind(
                             &mut self.device, projection,
                             &mut self.renderer_errors,
                         );
 
                     let _timer = self.gpu_profile.start_timer(batch.key.kind.sampler_tag());
                     self.draw_instanced_batch(
@@ -3169,17 +3156,17 @@ impl Renderer {
                 } else {
                     target_rect
                 };
                 self.device.enable_scissor();
                 self.device.set_scissor_rect(rect);
             }
 
             for batch in &alpha_batch_container.alpha_batches {
-                self.shaders
+                self.shaders.borrow_mut()
                     .get(&batch.key, self.debug_flags)
                     .bind(
                         &mut self.device, projection,
                         &mut self.renderer_errors,
                     );
 
                 if batch.key.blend_mode != prev_blend_mode {
                     match batch.key.blend_mode {
@@ -3356,17 +3343,17 @@ impl Renderer {
         // separable implementation.
         // TODO(gw): In the future, consider having
         //           fast path blur shaders for common
         //           blur radii with fixed weights.
         if !target.vertical_blurs.is_empty() || !target.horizontal_blurs.is_empty() {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR);
 
             self.set_blend(false, FramebufferKind::Other);
-            self.shaders.cs_blur_a8
+            self.shaders.borrow_mut().cs_blur_a8
                 .bind(&mut self.device, projection, &mut self.renderer_errors);
 
             if !target.vertical_blurs.is_empty() {
                 self.draw_instanced_batch(
                     &target.vertical_blurs,
                     VertexArrayKind::Blur,
                     &BatchTextures::no_texture(),
                     stats,
@@ -3391,17 +3378,17 @@ impl Renderer {
 
             // switch to multiplicative blending
             self.set_blend(true, FramebufferKind::Other);
             self.set_blend_mode_multiply(FramebufferKind::Other);
 
             // draw rounded cornered rectangles
             if !target.clip_batcher.rectangles.is_empty() {
                 let _gm2 = self.gpu_profile.start_marker("clip rectangles");
-                self.shaders.cs_clip_rectangle.bind(
+                self.shaders.borrow_mut().cs_clip_rectangle.bind(
                     &mut self.device,
                     projection,
                     &mut self.renderer_errors,
                 );
                 self.draw_instanced_batch(
                     &target.clip_batcher.rectangles,
                     VertexArrayKind::Clip,
                     &BatchTextures::no_texture(),
@@ -3413,30 +3400,30 @@ impl Renderer {
                 let _gm2 = self.gpu_profile.start_marker("box-shadows");
                 let textures = BatchTextures {
                     colors: [
                         mask_texture_id.clone(),
                         TextureSource::Invalid,
                         TextureSource::Invalid,
                     ],
                 };
-                self.shaders.cs_clip_box_shadow
+                self.shaders.borrow_mut().cs_clip_box_shadow
                     .bind(&mut self.device, projection, &mut self.renderer_errors);
                 self.draw_instanced_batch(
                     items,
                     VertexArrayKind::Clip,
                     &textures,
                     stats,
                 );
             }
 
             // draw line decoration clips
             if !target.clip_batcher.line_decorations.is_empty() {
                 let _gm2 = self.gpu_profile.start_marker("clip lines");
-                self.shaders.cs_clip_line.bind(
+                self.shaders.borrow_mut().cs_clip_line.bind(
                     &mut self.device,
                     projection,
                     &mut self.renderer_errors,
                 );
                 self.draw_instanced_batch(
                     &target.clip_batcher.line_decorations,
                     VertexArrayKind::Clip,
                     &BatchTextures::no_texture(),
@@ -3449,17 +3436,17 @@ impl Renderer {
                 let _gm2 = self.gpu_profile.start_marker("clip images");
                 let textures = BatchTextures {
                     colors: [
                         mask_texture_id.clone(),
                         TextureSource::Invalid,
                         TextureSource::Invalid,
                     ],
                 };
-                self.shaders.cs_clip_image
+                self.shaders.borrow_mut().cs_clip_image
                     .bind(&mut self.device, projection, &mut self.renderer_errors);
                 self.draw_instanced_batch(
                     items,
                     VertexArrayKind::Clip,
                     &textures,
                     stats,
                 );
             }
@@ -3525,32 +3512,32 @@ impl Renderer {
            !target.border_segments_complex.is_empty()
         {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_BORDER);
 
             self.set_blend(true, FramebufferKind::Other);
             self.set_blend_mode_premultiplied_alpha(FramebufferKind::Other);
 
             if !target.border_segments_solid.is_empty() {
-                self.shaders.cs_border_solid.bind(
+                self.shaders.borrow_mut().cs_border_solid.bind(
                     &mut self.device,
                     &projection,
                     &mut self.renderer_errors,
                 );
 
                 self.draw_instanced_batch(
                     &target.border_segments_solid,
                     VertexArrayKind::Border,
                     &BatchTextures::no_texture(),
                     stats,
                 );
             }
 
             if !target.border_segments_complex.is_empty() {
-                self.shaders.cs_border_segment.bind(
+                self.shaders.borrow_mut().cs_border_segment.bind(
                     &mut self.device,
                     &projection,
                     &mut self.renderer_errors,
                 );
 
                 self.draw_instanced_batch(
                     &target.border_segments_complex,
                     VertexArrayKind::Border,
@@ -3561,20 +3548,23 @@ impl Renderer {
 
             self.set_blend(false, FramebufferKind::Other);
         }
 
         // Draw any blurs for this target.
         if !target.horizontal_blurs.is_empty() {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR);
 
-            match target.target_kind {
-                RenderTargetKind::Alpha => &mut self.shaders.cs_blur_a8,
-                RenderTargetKind::Color => &mut self.shaders.cs_blur_rgba8,
-            }.bind(&mut self.device, &projection, &mut self.renderer_errors);
+            {
+                let mut shaders = self.shaders.borrow_mut();
+                match target.target_kind {
+                    RenderTargetKind::Alpha => &mut shaders.cs_blur_a8,
+                    RenderTargetKind::Color => &mut shaders.cs_blur_rgba8,
+                }.bind(&mut self.device, &projection, &mut self.renderer_errors);
+            }
 
             self.draw_instanced_batch(
                 &target.horizontal_blurs,
                 VertexArrayKind::Blur,
                 &BatchTextures::no_texture(),
                 stats,
             );
         }
@@ -4238,17 +4228,19 @@ impl Renderer {
         #[cfg(feature = "debug_renderer")]
         {
             self.debug.deinit(&mut self.device);
         }
 
         for (_, target) in self.output_targets {
             self.device.delete_fbo(target.fbo_id);
         }
-        self.shaders.deinit(&mut self.device);
+        if let Ok(shaders) = Rc::try_unwrap(self.shaders) {
+            shaders.into_inner().deinit(&mut self.device);
+        }
         #[cfg(feature = "capture")]
         self.device.delete_fbo(self.read_fbo);
         #[cfg(feature = "replay")]
         for (_, ext) in self.owned_external_images {
             self.device.delete_external_texture(ext);
         }
         self.device.end_frame();
     }
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -1390,17 +1390,17 @@ impl ResourceCache {
             }
         })
     }
 
     pub fn begin_frame(&mut self, frame_id: FrameId) {
         debug_assert_eq!(self.state, State::Idle);
         self.state = State::AddResources;
         self.texture_cache.begin_frame(frame_id);
-        self.cached_glyphs.begin_frame(&self.texture_cache, &self.cached_render_tasks);
+        self.cached_glyphs.begin_frame(&self.texture_cache, &self.cached_render_tasks, &mut self.glyph_rasterizer);
         self.cached_render_tasks.begin_frame(&mut self.texture_cache);
         self.current_frame_id = frame_id;
     }
 
     pub fn block_until_all_resources_added(
         &mut self,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
--- a/gfx/webrender/src/scene_builder.rs
+++ b/gfx/webrender/src/scene_builder.rs
@@ -9,29 +9,31 @@ use api::channel::MsgSender;
 #[cfg(feature = "capture")]
 use capture::CaptureConfig;
 use frame_builder::{FrameBuilderConfig, FrameBuilder};
 use clip::{ClipDataInterner, ClipDataUpdateList};
 use clip_scroll_tree::ClipScrollTree;
 use display_list_flattener::DisplayListFlattener;
 use internal_types::{FastHashMap, FastHashSet};
 use picture::PictureIdGenerator;
+use prim_store::{PrimitiveDataInterner, PrimitiveDataUpdateList};
 use resource_cache::FontInstanceMap;
 use render_backend::DocumentView;
 use renderer::{PipelineInfo, SceneBuilderHooks};
 use scene::Scene;
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::mem::replace;
 use time::precise_time_ns;
 use util::drain_filter;
 use std::thread;
 use std::time::Duration;
 
 pub struct DocumentResourceUpdates {
     pub clip_updates: ClipDataUpdateList,
+    pub prim_updates: PrimitiveDataUpdateList,
 }
 
 /// Represents the work associated to a transaction before scene building.
 pub struct Transaction {
     pub document_id: DocumentId,
     pub display_list_updates: Vec<DisplayListUpdate>,
     pub removed_pipelines: Vec<PipelineId>,
     pub epoch_updates: Vec<(PipelineId, Epoch)>,
@@ -39,16 +41,17 @@ pub struct Transaction {
     pub blob_requests: Vec<BlobImageParams>,
     pub blob_rasterizer: Option<Box<AsyncBlobImageRasterizer>>,
     pub rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>,
     pub resource_updates: Vec<ResourceUpdate>,
     pub frame_ops: Vec<FrameMsg>,
     pub notifications: Vec<NotificationRequest>,
     pub set_root_pipeline: Option<PipelineId>,
     pub render_frame: bool,
+    pub invalidate_rendered_frame: bool,
 }
 
 impl Transaction {
     pub fn can_skip_scene_builder(&self) -> bool {
         self.request_scene_build.is_none() &&
             self.display_list_updates.is_empty() &&
             self.epoch_updates.is_empty() &&
             self.removed_pipelines.is_empty() &&
@@ -72,16 +75,17 @@ pub struct BuiltTransaction {
     pub blob_rasterizer: Option<Box<AsyncBlobImageRasterizer>>,
     pub frame_ops: Vec<FrameMsg>,
     pub removed_pipelines: Vec<PipelineId>,
     pub notifications: Vec<NotificationRequest>,
     pub doc_resource_updates: Option<DocumentResourceUpdates>,
     pub scene_build_start_time: u64,
     pub scene_build_end_time: u64,
     pub render_frame: bool,
+    pub invalidate_rendered_frame: bool,
 }
 
 pub struct DisplayListUpdate {
     pub pipeline_id: PipelineId,
     pub epoch: Epoch,
     pub built_display_list: BuiltDisplayList,
     pub background: Option<ColorF>,
     pub viewport_size: LayoutSize,
@@ -155,22 +159,24 @@ pub enum SceneSwapResult {
 // primitives and other things between display lists so that:
 // - GPU cache handles remain valid, reducing GPU cache updates.
 // - Comparison of primitives and pictures between two
 //   display lists is (a) fast (b) done during scene building.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct DocumentResources {
     pub clip_interner: ClipDataInterner,
+    pub prim_interner: PrimitiveDataInterner,
 }
 
 impl DocumentResources {
     fn new() -> Self {
         DocumentResources {
             clip_interner: ClipDataInterner::new(),
+            prim_interner: PrimitiveDataInterner::new(),
         }
     }
 }
 
 // A document in the scene builder contains the current scene,
 // as well as a persistent clip interner. This allows clips
 // to be de-duplicated, and persisted in the GPU cache between
 // display lists.
@@ -329,19 +335,25 @@ impl SceneBuilder {
                     &mut item.doc_resources,
                 );
 
                 let clip_updates = item
                     .doc_resources
                     .clip_interner
                     .end_frame_and_get_pending_updates();
 
+                let prim_updates = item
+                    .doc_resources
+                    .prim_interner
+                    .end_frame_and_get_pending_updates();
+
                 doc_resource_updates = Some(
                     DocumentResourceUpdates {
                         clip_updates,
+                        prim_updates,
                     }
                 );
 
                 built_scene = Some(BuiltScene {
                     scene: new_scene,
                     frame_builder,
                     clip_scroll_tree,
                 });
@@ -353,16 +365,17 @@ impl SceneBuilder {
                     scene: item.scene,
                     resources: item.doc_resources,
                 },
             );
 
             let txn = Box::new(BuiltTransaction {
                 document_id: item.document_id,
                 render_frame: item.build_frame,
+                invalidate_rendered_frame: false,
                 built_scene,
                 resource_updates: Vec::new(),
                 rasterized_blobs: Vec::new(),
                 blob_rasterizer: None,
                 frame_ops: Vec::new(),
                 removed_pipelines: Vec::new(),
                 notifications: Vec::new(),
                 scene_build_start_time,
@@ -428,19 +441,25 @@ impl SceneBuilder {
                 );
 
                 // Retrieve the list of updates from the clip interner.
                 let clip_updates = doc
                     .resources
                     .clip_interner
                     .end_frame_and_get_pending_updates();
 
+                let prim_updates = doc
+                    .resources
+                    .prim_interner
+                    .end_frame_and_get_pending_updates();
+
                 doc_resource_updates = Some(
                     DocumentResourceUpdates {
                         clip_updates,
+                        prim_updates,
                     }
                 );
 
                 built_scene = Some(BuiltScene {
                     scene: new_scene,
                     frame_builder,
                     clip_scroll_tree,
                 });
@@ -463,16 +482,17 @@ impl SceneBuilder {
 
         if self.simulate_slow_ms > 0 {
             thread::sleep(Duration::from_millis(self.simulate_slow_ms as u64));
         }
 
         Box::new(BuiltTransaction {
             document_id: txn.document_id,
             render_frame: txn.render_frame,
+            invalidate_rendered_frame: txn.invalidate_rendered_frame,
             built_scene,
             rasterized_blobs,
             resource_updates: replace(&mut txn.resource_updates, Vec::new()),
             blob_rasterizer: replace(&mut txn.blob_rasterizer, None),
             frame_ops: replace(&mut txn.frame_ops, Vec::new()),
             removed_pipelines: replace(&mut txn.removed_pipelines, Vec::new()),
             notifications: replace(&mut txn.notifications, Vec::new()),
             doc_resource_updates,
--- a/gfx/webrender/src/shade.rs
+++ b/gfx/webrender/src/shade.rs
@@ -15,16 +15,18 @@ use renderer::{
     MAX_VERTEX_TEXTURE_WIDTH,
     BlendMode, DebugFlags, ImageBufferKind, RendererError, RendererOptions,
     TextureSampler, VertexArrayKind, ShaderPrecacheFlags,
 };
 
 use gleam::gl::GlType;
 use time::precise_time_ns;
 
+use std::cell::RefCell;
+use std::rc::Rc;
 
 impl ImageBufferKind {
     pub(crate) fn get_feature_string(&self) -> &'static str {
         match *self {
             ImageBufferKind::Texture2D => "TEXTURE_2D",
             ImageBufferKind::Texture2DArray => "",
             ImageBufferKind::TextureRect => "TEXTURE_RECT",
             ImageBufferKind::TextureExternal => "TEXTURE_EXTERNAL",
@@ -812,8 +814,16 @@ impl Shaders {
                 shader.deinit(device);
             }
         }
         self.cs_border_solid.deinit(device);
         self.cs_border_segment.deinit(device);
         self.ps_split_composite.deinit(device);
     }
 }
+
+// A wrapper around a strong reference to a Shaders
+// object. We have this so that external (ffi)
+// consumers can own a reference to a shared Shaders
+// instance without understanding rust's refcounting.
+pub struct WrShaders {
+    pub shaders: Rc<RefCell<Shaders>>,
+}
--- a/gfx/webrender/src/spatial_node.rs
+++ b/gfx/webrender/src/spatial_node.rs
@@ -246,20 +246,25 @@ impl SpatialNode {
         state: &mut TransformUpdateState,
         coord_systems: &mut Vec<CoordinateSystem>,
         scene_properties: &SceneProperties,
     ) {
         match self.node_type {
             SpatialNodeType::ReferenceFrame(ref mut info) => {
                 // Resolve the transform against any property bindings.
                 let source_transform = scene_properties.resolve_layout_transform(&info.source_transform);
+                // Do a change-basis operation on the perspective matrix using
+                // the scroll offset.
+                let scrolled_perspective = info.source_perspective
+                    .pre_translate(&state.parent_accumulated_scroll_offset)
+                    .post_translate(-state.parent_accumulated_scroll_offset);
                 info.resolved_transform =
                     LayoutFastTransform::with_vector(info.origin_in_parent_reference_frame)
                     .pre_mul(&source_transform.into())
-                    .pre_mul(&info.source_perspective);
+                    .pre_mul(&scrolled_perspective);
 
                 // The transformation for this viewport in world coordinates is the transformation for
                 // our parent reference frame, plus any accumulated scrolling offsets from nodes
                 // between our reference frame and this node. Finally, we also include
                 // whatever local transformation this reference frame provides.
                 let relative_transform = info.resolved_transform
                     .post_translate(state.parent_accumulated_scroll_offset)
                     .to_transform()
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -55,29 +55,32 @@ pub struct Transaction {
     pub resource_updates: Vec<ResourceUpdate>,
 
     // If true the transaction is piped through the scene building thread, if false
     // it will be applied directly on the render backend.
     use_scene_builder_thread: bool,
 
     generate_frame: bool,
 
+    invalidate_rendered_frame: bool,
+
     low_priority: bool,
 }
 
 impl Transaction {
     pub fn new() -> Self {
         Transaction {
             scene_ops: Vec::new(),
             frame_ops: Vec::new(),
             resource_updates: Vec::new(),
             payloads: Vec::new(),
             notifications: Vec::new(),
             use_scene_builder_thread: true,
             generate_frame: false,
+            invalidate_rendered_frame: false,
             low_priority: false,
         }
     }
 
     // TODO: better name?
     pub fn skip_scene_builder(&mut self) {
         self.use_scene_builder_thread = false;
     }
@@ -85,16 +88,17 @@ impl Transaction {
     // TODO: this is temporary, using the scene builder thread is the default for
     // most transactions, and opt-in for specific cases like scrolling and async video.
     pub fn use_scene_builder_thread(&mut self) {
         self.use_scene_builder_thread = true;
     }
 
     pub fn is_empty(&self) -> bool {
         !self.generate_frame &&
+            !self.invalidate_rendered_frame &&
             self.scene_ops.is_empty() &&
             self.frame_ops.is_empty() &&
             self.resource_updates.is_empty() &&
             self.notifications.is_empty()
     }
 
     pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) {
         // We track epochs before and after scene building.
@@ -241,16 +245,26 @@ impl Transaction {
     /// no-op; the arguments passed to `new_frame_ready` will provide information
     /// as to when happened.
     ///
     /// [notifier]: trait.RenderNotifier.html#tymethod.new_frame_ready
     pub fn generate_frame(&mut self) {
         self.generate_frame = true;
     }
 
+    /// Invalidate rendered frame. It ensure that frame will be rendered during
+    /// next frame generation. WebRender could skip frame rendering if there
+    /// is no update.
+    /// But there are cases that needs to force rendering.
+    ///  - Content of image is updated by reusing same ExternalImageId.
+    ///  - Platform requests it if pixels become stale (like wakeup from standby).
+    pub fn invalidate_rendered_frame(&mut self) {
+        self.invalidate_rendered_frame = true;
+    }
+
     /// Supply a list of animated property bindings that should be used to resolve
     /// bindings in the current display list.
     pub fn update_dynamic_properties(&mut self, properties: DynamicProperties) {
         self.frame_ops.push(FrameMsg::UpdateDynamicProperties(properties));
     }
 
     /// Add to the list of animated property bindings that should be used to
     /// resolve bindings in the current display list. This is a convenience method
@@ -275,16 +289,17 @@ impl Transaction {
         (
             TransactionMsg {
                 scene_ops: self.scene_ops,
                 frame_ops: self.frame_ops,
                 resource_updates: self.resource_updates,
                 notifications: self.notifications,
                 use_scene_builder_thread: self.use_scene_builder_thread,
                 generate_frame: self.generate_frame,
+                invalidate_rendered_frame: self.invalidate_rendered_frame,
                 low_priority: self.low_priority,
             },
             self.payloads,
         )
     }
 
     pub fn add_image(
         &mut self,
@@ -385,52 +400,56 @@ impl Transaction {
 
 /// Represents a transaction in the format sent through the channel.
 #[derive(Clone, Deserialize, Serialize)]
 pub struct TransactionMsg {
     pub scene_ops: Vec<SceneMsg>,
     pub frame_ops: Vec<FrameMsg>,
     pub resource_updates: Vec<ResourceUpdate>,
     pub generate_frame: bool,
+    pub invalidate_rendered_frame: bool,
     pub use_scene_builder_thread: bool,
     pub low_priority: bool,
 
     #[serde(skip)]
     pub notifications: Vec<NotificationRequest>,
 }
 
 impl TransactionMsg {
     pub fn is_empty(&self) -> bool {
         !self.generate_frame &&
+            !self.invalidate_rendered_frame &&
             self.scene_ops.is_empty() &&
             self.frame_ops.is_empty() &&
             self.resource_updates.is_empty() &&
             self.notifications.is_empty()
     }
 
     // TODO: We only need this for a few RenderApi methods which we should remove.
     pub fn frame_message(msg: FrameMsg) -> Self {
         TransactionMsg {
             scene_ops: Vec::new(),
             frame_ops: vec![msg],
             resource_updates: Vec::new(),
             notifications: Vec::new(),
             generate_frame: false,
+            invalidate_rendered_frame: false,
             use_scene_builder_thread: false,
             low_priority: false,
         }
     }
 
     pub fn scene_message(msg: SceneMsg) -> Self {
         TransactionMsg {
             scene_ops: vec![msg],
             frame_ops: Vec::new(),
             resource_updates: Vec::new(),
             notifications: Vec::new(),
             generate_frame: false,
+            invalidate_rendered_frame: false,
             use_scene_builder_thread: false,
             low_priority: false,
         }
     }
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub struct AddImage {
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -738,67 +738,16 @@ impl YuvFormat {
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ImageMask {
     pub image: ImageKey,
     pub rect: LayoutRect,
     pub repeat: bool,
 }
 
-#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
-pub enum LocalClip {
-    Rect(LayoutRect),
-    RoundedRect(LayoutRect, ComplexClipRegion),
-}
-
-impl From<LayoutRect> for LocalClip {
-    fn from(rect: LayoutRect) -> Self {
-        LocalClip::Rect(rect)
-    }
-}
-
-impl LocalClip {
-    pub fn clip_rect(&self) -> &LayoutRect {
-        match *self {
-            LocalClip::Rect(ref rect) => rect,
-            LocalClip::RoundedRect(ref rect, _) => rect,
-        }
-    }
-
-    pub fn create_with_offset(&self, offset: &LayoutVector2D) -> LocalClip {
-        match *self {
-            LocalClip::Rect(rect) => LocalClip::from(rect.translate(offset)),
-            LocalClip::RoundedRect(rect, complex) => LocalClip::RoundedRect(
-                rect.translate(offset),
-                ComplexClipRegion {
-                    rect: complex.rect.translate(offset),
-                    radii: complex.radii,
-                    mode: complex.mode,
-                },
-            ),
-        }
-    }
-
-    pub fn clip_by(&self, rect: &LayoutRect) -> LocalClip {
-        match *self {
-            LocalClip::Rect(clip_rect) => {
-                LocalClip::Rect(
-                    clip_rect.intersection(rect).unwrap_or_else(LayoutRect::zero)
-                )
-            }
-            LocalClip::RoundedRect(clip_rect, complex) => {
-                LocalClip::RoundedRect(
-                    clip_rect.intersection(rect).unwrap_or_else(LayoutRect::zero),
-                    complex,
-                )
-            }
-        }
-    }
-}
-
 #[repr(C)]
 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
 pub enum ClipMode {
     Clip,    // Pixels inside the region are visible.
     ClipOut, // Pixels outside the region are visible.
 }
 
 impl Not for ClipMode {
--- a/gfx/webrender_bindings/RenderThread.cpp
+++ b/gfx/webrender_bindings/RenderThread.cpp
@@ -78,28 +78,18 @@ RenderThread::Start()
   sRenderThread = new RenderThread(thread);
 #ifdef XP_WIN
   widget::WinCompositorWindowThread::Start();
 #endif
   layers::SharedSurfacesParent::Initialize();
 
   RefPtr<Runnable> runnable = WrapRunnable(
     RefPtr<RenderThread>(sRenderThread.get()),
-    &RenderThread::InitSharedGLContext);
+    &RenderThread::InitDeviceTask);
   sRenderThread->Loop()->PostTask(runnable.forget());
-
-  if (XRE_IsGPUProcess() &&
-      gfx::gfxVars::UseWebRenderProgramBinary()) {
-    MOZ_ASSERT(gfx::gfxVars::UseWebRender());
-    // Initialize program cache if necessary
-    RefPtr<Runnable> runnable = WrapRunnable(
-      RefPtr<RenderThread>(sRenderThread.get()),
-      &RenderThread::ProgramCacheTask);
-    sRenderThread->Loop()->PostTask(runnable.forget());
-  }
 }
 
 // static
 void
 RenderThread::ShutDown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(sRenderThread);
@@ -661,19 +651,29 @@ RenderThread::GetRenderTexture(wr::WrExt
   MOZ_ASSERT(it != mRenderTextures.end());
   if (it == mRenderTextures.end()) {
     return nullptr;
   }
   return it->second;
 }
 
 void
-RenderThread::ProgramCacheTask()
+RenderThread::InitDeviceTask()
 {
-  ProgramCache();
+  MOZ_ASSERT(IsInRenderThread());
+  MOZ_ASSERT(!mSharedGL);
+
+  mSharedGL = CreateGLContext();
+  if (XRE_IsGPUProcess() &&
+      gfx::gfxVars::UseWebRenderProgramBinary()) {
+    ProgramCache();
+  }
+  // Query the shared GL context to force the
+  // lazy initialization to happen now.
+  SharedGL();
 }
 
 void
 RenderThread::HandleDeviceReset(const char* aWhere, bool aNotify)
 {
   MOZ_ASSERT(IsInRenderThread());
 
   if (mHandlingDeviceReset) {
@@ -727,42 +727,49 @@ RenderThread::ProgramCache()
   MOZ_ASSERT(IsInRenderThread());
 
   if (!mProgramCache) {
     mProgramCache = MakeUnique<WebRenderProgramCache>(ThreadPool().Raw());
   }
   return mProgramCache.get();
 }
 
-void
-RenderThread::InitSharedGLContext()
-{
-  MOZ_ASSERT(IsInRenderThread());
-
-  if (!mSharedGL) {
-    mSharedGL = CreateGLContext();
-  }
-}
-
 gl::GLContext*
 RenderThread::SharedGL()
 {
   MOZ_ASSERT(IsInRenderThread());
-  InitSharedGLContext();
+  if (!mSharedGL) {
+    mSharedGL = CreateGLContext();
+    mShaders = nullptr;
+  }
+  if (mSharedGL && !mShaders) {
+    mShaders = MakeUnique<WebRenderShaders>(mSharedGL, mProgramCache.get());
+  }
 
   return mSharedGL.get();
 }
 
 void
 RenderThread::ClearSharedGL()
 {
   MOZ_ASSERT(IsInRenderThread());
+  mShaders = nullptr;
   mSharedGL = nullptr;
 }
 
+WebRenderShaders::WebRenderShaders(gl::GLContext* gl,
+                                   WebRenderProgramCache* programCache)
+{
+  mGL = gl;
+  mShaders = wr_shaders_new(gl, programCache ? programCache->Raw() : nullptr);
+}
+
+WebRenderShaders::~WebRenderShaders() {
+  wr_shaders_delete(mShaders, mGL.get());
+}
 
 WebRenderThreadPool::WebRenderThreadPool()
 {
   mThreadPool = wr_thread_pool_new();
 }
 
 WebRenderThreadPool::~WebRenderThreadPool()
 {
@@ -797,16 +804,22 @@ CreateGLContextANGLE()
   if (!gl::GLLibraryEGL::EnsureInitialized(/* forceAccel */ true, &discardFailureId)) {
     gfxCriticalNote << "Failed to load EGL library: " << discardFailureId.get();
     return nullptr;
   }
 
   auto* egl = gl::GLLibraryEGL::Get();
   auto flags = gl::CreateContextFlags::PREFER_ES3;
 
+  if (egl->IsExtensionSupported(
+     gl::GLLibraryEGL::MOZ_create_context_provoking_vertex_dont_care))
+  {
+     flags |= gl::CreateContextFlags::PROVOKING_VERTEX_DONT_CARE;
+  }
+
   // Create GLContext with dummy EGLSurface, the EGLSurface is not used.
   // Instread we override it with EGLSurface of SwapChain's back buffer.
   RefPtr<gl::GLContext> gl = gl::GLContextProviderEGL::CreateHeadless(flags, &discardFailureId);
   if (!gl || !gl->IsANGLE()) {
     gfxCriticalNote << "Failed ANGLE GL context creation for WebRender: " << gfx::hexa(gl.get());
     return nullptr;
   }
 
--- a/gfx/webrender_bindings/RenderThread.h
+++ b/gfx/webrender_bindings/RenderThread.h
@@ -54,16 +54,28 @@ public:
   ~WebRenderProgramCache();
 
   wr::WrProgramCache* Raw() { return mProgramCache; }
 
 protected:
   wr::WrProgramCache* mProgramCache;
 };
 
+class WebRenderShaders {
+public:
+  WebRenderShaders(gl::GLContext* gl, WebRenderProgramCache* programCache);
+  ~WebRenderShaders();
+
+  wr::WrShaders* RawShaders() { return mShaders; }
+
+protected:
+  RefPtr<gl::GLContext> mGL;
+  wr::WrShaders* mShaders;
+};
+
 /// Base class for an event that can be scheduled to run on the render thread.
 ///
 /// The event can be passed through the same channels as regular WebRender messages
 /// to preserve ordering.
 class RendererEvent
 {
 public:
   virtual ~RendererEvent() {}
@@ -176,17 +188,18 @@ public:
 
   /// Can be called from any thread.
   WebRenderThreadPool& ThreadPool() { return mThreadPool; }
 
   /// Can only be called from the render thread.
   WebRenderProgramCache* ProgramCache();
 
   /// Can only be called from the render thread.
-  void InitSharedGLContext();
+  WebRenderShaders* Shaders() { return mShaders.get(); }
+
   /// Can only be called from the render thread.
   gl::GLContext* SharedGL();
 
   void ClearSharedGL();
 
   /// Can only be called from the render thread.
   void HandleDeviceReset(const char* aWhere, bool aNotify);
   /// Can only be called from the render thread.
@@ -196,26 +209,28 @@ public:
 
   size_t RendererCount();
 
 private:
   explicit RenderThread(base::Thread* aThread);
 
   void DeferredRenderTextureHostDestroy();
   void ShutDownTask(layers::SynchronousTask* aTask);
-  void ProgramCacheTask();
+  void InitDeviceTask();
 
   void DoAccumulateMemoryReport(MemoryReport, const RefPtr<MemoryReportPromise::Private>&);
 
   ~RenderThread();
 
   base::Thread* const mThread;
 
   WebRenderThreadPool mThreadPool;
+
   UniquePtr<WebRenderProgramCache> mProgramCache;
+  UniquePtr<WebRenderShaders> mShaders;
 
   // An optional shared GLContext to be used for all
   // windows.
   RefPtr<gl::GLContext> mSharedGL;
 
   std::map<wr::WindowId, UniquePtr<RendererOGL>> mRenderers;
 
   struct WindowInfo {
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -68,16 +68,18 @@ public:
 
     *mUseANGLE = compositor->UseANGLE();
     *mUseDComp = compositor->UseDComp();
 
     bool supportLowPriorityTransactions = true; // TODO only for main windows.
     wr::Renderer* wrRenderer = nullptr;
     if (!wr_window_new(aWindowId, mSize.width, mSize.height, supportLowPriorityTransactions,
                        compositor->gl(),
+                       aRenderThread.ProgramCache() ? aRenderThread.ProgramCache()->Raw() : nullptr,
+                       aRenderThread.Shaders() ? aRenderThread.Shaders()->RawShaders() : nullptr,
                        aRenderThread.ThreadPool().Raw(),
                        &WebRenderMallocSizeOf,
                        mDocHandle, &wrRenderer,
                        mMaxTextureSize)) {
       // wr_window_new puts a message into gfxCriticalNote if it returns false
       return;
     }
     MOZ_ASSERT(wrRenderer);
@@ -86,19 +88,16 @@ public:
     auto renderer = MakeUnique<RendererOGL>(std::move(thread),
                                             std::move(compositor),
                                             aWindowId,
                                             wrRenderer,
                                             mBridge);
     if (wrRenderer && renderer) {
       wr::WrExternalImageHandler handler = renderer->GetExternalImageHandler();
       wr_renderer_set_external_image_handler(wrRenderer, &handler);
-      if (gfx::gfxVars::UseWebRenderProgramBinary()) {
-        wr_renderer_update_program_cache(wrRenderer, aRenderThread.ProgramCache()->Raw());
-      }
     }
 
     if (renderer) {
       layers::SyncObjectHost* syncObj = renderer->GetSyncObject();
       if (syncObj) {
         *mSyncHandle = syncObj->GetSyncHandle();
       }
     }
@@ -205,16 +204,22 @@ TransactionBuilder::ClearDisplayList(Epo
 
 void
 TransactionBuilder::GenerateFrame()
 {
   wr_transaction_generate_frame(mTxn);
 }
 
 void
+TransactionBuilder::InvalidateRenderedFrame()
+{
+  wr_transaction_invalidate_rendered_frame(mTxn);
+}
+
+void
 TransactionBuilder::UpdateDynamicProperties(const nsTArray<wr::WrOpacityProperty>& aOpacityArray,
                                      const nsTArray<wr::WrTransformProperty>& aTransformArray)
 {
   wr_transaction_update_dynamic_properties(
       mTxn,
       aOpacityArray.IsEmpty() ?  nullptr : aOpacityArray.Elements(),
       aOpacityArray.Length(),
       aTransformArray.IsEmpty() ?  nullptr : aTransformArray.Elements(),
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -84,16 +84,18 @@ public:
                       const wr::LayoutSize& content_size,
                       wr::BuiltDisplayListDescriptor dl_descriptor,
                       wr::Vec<uint8_t>& dl_data);
 
   void ClearDisplayList(Epoch aEpoch, wr::WrPipelineId aPipeline);
 
   void GenerateFrame();
 
+  void InvalidateRenderedFrame();
+
   void UpdateDynamicProperties(const nsTArray<wr::WrOpacityProperty>& aOpacityArray,
                                const nsTArray<wr::WrTransformProperty>& aTransformArray);
 
   void SetWindowParameters(const LayoutDeviceIntSize& aWindowSize,
                            const LayoutDeviceIntRect& aDocRect);
 
   void UpdateScrollPosition(const wr::WrPipelineId& aPipelineId,
                             const layers::FrameMetrics::ViewID& aScrollId,
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-3c3f9a4e919b81639f078d7bd101012de61b9396
+69fddc3faf1a379a560106f12687d08cbbe304dd
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1,25 +1,26 @@
 use std::ffi::{CStr, CString};
 use std::{mem, slice, ptr, env};
 use std::path::PathBuf;
 use std::rc::Rc;
+use std::cell::RefCell;
 use std::sync::Arc;
 use std::sync::atomic::{AtomicUsize, Ordering};
 use std::os::raw::{c_void, c_char, c_float};
 use gleam::gl;
 
 use webrender::api::*;
 use webrender::{ReadPixelsFormat, Renderer, RendererOptions, ThreadListener};
 use webrender::{ExternalImage, ExternalImageHandler, ExternalImageSource};
 use webrender::DebugFlags;
 use webrender::{ApiRecordingReceiver, BinaryRecorder};
 use webrender::{AsyncPropertySampler, PipelineInfo, SceneBuilderHooks};
 use webrender::{UploadMethod, VertexUsageHint};
-use webrender::ShaderPrecacheFlags;
+use webrender::{Device, Shaders, WrShaders, ShaderPrecacheFlags};
 use thread_profiler::register_thread_with_profiler;
 use moz2d_renderer::Moz2dBlobImageHandler;
 use program_cache::{WrProgramCache, remove_disk_cache};
 use app_units::Au;
 use rayon;
 use euclid::SideOffsets2D;
 use nsstring::nsAString;
 
@@ -920,22 +921,67 @@ pub extern "C" fn wr_renderer_update_pro
 }
 
 // This matches IsEnvSet in gfxEnv.h
 fn env_var_to_bool(key: &'static str) -> bool {
     env::var(key).ok().map_or(false, |v| !v.is_empty())
 }
 
 // Call MakeCurrent before this.
+fn wr_device_new(gl_context: *mut c_void, pc: Option<&mut WrProgramCache>)
+    -> Device
+{
+    assert!(unsafe { is_in_render_thread() });
+
+    let gl;
+    if unsafe { is_glcontext_egl(gl_context) } {
+        gl = unsafe { gl::GlesFns::load_with(|symbol| get_proc_address(gl_context, symbol)) };
+    } else {
+        gl = unsafe { gl::GlFns::load_with(|symbol| get_proc_address(gl_context, symbol)) };
+    }
+
+    let version = gl.get_string(gl::VERSION);
+
+    info!("WebRender - OpenGL version new {}", version);
+
+    let upload_method = if unsafe { is_glcontext_angle(gl_context) } {
+        UploadMethod::Immediate
+    } else {
+        UploadMethod::PixelBuffer(VertexUsageHint::Dynamic)
+    };
+
+    let resource_override_path = unsafe {
+        let override_charptr = gfx_wr_resource_path_override();
+        if override_charptr.is_null() {
+            None
+        } else {
+            match CStr::from_ptr(override_charptr).to_str() {
+                Ok(override_str) => Some(PathBuf::from(override_str)),
+                _ => None
+            }
+        }
+    };
+
+    let cached_programs = match pc {
+      Some(cached_programs) => Some(Rc::clone(cached_programs.rc_get())),
+      None => None,
+    };
+
+    Device::new(gl, resource_override_path, upload_method, cached_programs)
+}
+
+// Call MakeCurrent before this.
 #[no_mangle]
 pub extern "C" fn wr_window_new(window_id: WrWindowId,
                                 window_width: u32,
                                 window_height: u32,
                                 support_low_priority_transactions: bool,
                                 gl_context: *mut c_void,
+                                program_cache: Option<&mut WrProgramCache>,
+                                shaders: Option<&mut WrShaders>,
                                 thread_pool: *mut WrThreadPool,
                                 size_of_op: VoidPtrToSizeFn,
                                 out_handle: &mut *mut DocumentHandle,
                                 out_renderer: &mut *mut Renderer,
                                 out_max_texture_size: *mut u32)
                                 -> bool {
     assert!(unsafe { is_in_render_thread() });
 
@@ -969,25 +1015,31 @@ pub extern "C" fn wr_window_new(window_i
     };
 
     let precache_flags = if env_var_to_bool("MOZ_WR_PRECACHE_SHADERS") {
         ShaderPrecacheFlags::FULL_COMPILE
     } else {
         ShaderPrecacheFlags::empty()
     };
 
+    let cached_programs = match program_cache {
+        Some(program_cache) => Some(Rc::clone(&program_cache.rc_get())),
+        None => None,
+    };
+
     let opts = RendererOptions {
         enable_aa: true,
         enable_subpixel_aa: true,
         support_low_priority_transactions,
         recorder: recorder,
         blob_image_handler: Some(Box::new(Moz2dBlobImageHandler::new(workers.clone()))),
         workers: Some(workers.clone()),
         thread_listener: Some(Box::new(GeckoProfilerThreadListener::new())),
         size_of_op: Some(size_of_op),
+        cached_programs,
         resource_override_path: unsafe {
             let override_charptr = gfx_wr_resource_path_override();
             if override_charptr.is_null() {
                 None
             } else {
                 match CStr::from_ptr(override_charptr).to_str() {
                     Ok(override_str) => Some(PathBuf::from(override_str)),
                     _ => None
@@ -1003,17 +1055,17 @@ pub extern "C" fn wr_window_new(window_i
         precache_flags,
         namespace_alloc_by_client: true,
         ..Default::default()
     };
 
     let notifier = Box::new(CppNotifier {
         window_id: window_id,
     });
-    let (renderer, sender) = match Renderer::new(gl, notifier, opts) {
+    let (renderer, sender) = match Renderer::new(gl, notifier, opts, shaders) {
         Ok((renderer, sender)) => (renderer, sender),
         Err(e) => {
             warn!(" Failed to create a Renderer: {:?}", e);
             let msg = CString::new(format!("wr_window_new: {:?}", e)).unwrap();
             unsafe {
                 gfx_critical_note(msg.as_ptr());
             }
             return false;
@@ -1218,16 +1270,21 @@ pub extern "C" fn wr_transaction_set_win
 }
 
 #[no_mangle]
 pub extern "C" fn wr_transaction_generate_frame(txn: &mut Transaction) {
     txn.generate_frame();
 }
 
 #[no_mangle]
+pub extern "C" fn wr_transaction_invalidate_rendered_frame(txn: &mut Transaction) {
+    txn.invalidate_rendered_frame();
+}
+
+#[no_mangle]
 pub extern "C" fn wr_transaction_update_dynamic_properties(
     txn: &mut Transaction,
     opacity_array: *const WrOpacityProperty,
     opacity_count: usize,
     transform_array: *const WrTransformProperty,
     transform_count: usize,
 ) {
     let mut properties = DynamicProperties {
@@ -2590,8 +2647,63 @@ fn unpack_clip_id(id: usize, pipeline_id
     let id = id >> 1;
 
     match type_value {
         0 => ClipId::Spatial(id, pipeline_id),
         1 => ClipId::Clip(id, pipeline_id),
         _ => unreachable!("Got a bizarre value for the clip type"),
     }
 }
+
+/// cbindgen:postfix=WR_DESTRUCTOR_SAFE_FUNC
+#[no_mangle]
+pub unsafe extern "C" fn wr_device_delete(device: *mut Device) {
+    Box::from_raw(device);
+}
+
+// Call MakeCurrent before this.
+#[no_mangle]
+pub extern "C" fn wr_shaders_new(gl_context: *mut c_void,
+                                 program_cache: Option<&mut WrProgramCache>) -> *mut WrShaders {
+    let mut device = wr_device_new(gl_context, program_cache);
+
+    let precache_flags = if env_var_to_bool("MOZ_WR_PRECACHE_SHADERS") {
+        ShaderPrecacheFlags::FULL_COMPILE
+    } else {
+        ShaderPrecacheFlags::ASYNC_COMPILE
+    };
+
+    let opts = RendererOptions {
+        precache_flags,
+        ..Default::default()
+    };
+
+    let gl_type = device.gl().get_type();
+    device.begin_frame();
+
+    let shaders = Rc::new(RefCell::new(match Shaders::new(&mut device, gl_type, &opts) {
+        Ok(shaders) => shaders,
+        Err(e) => {
+            warn!(" Failed to create a Shaders: {:?}", e);
+            let msg = CString::new(format!("wr_shaders_new: {:?}", e)).unwrap();
+            unsafe {
+                gfx_critical_note(msg.as_ptr());
+            }
+            return ptr::null_mut();
+        }
+    }));
+
+    let shaders = WrShaders { shaders };
+
+    device.end_frame();
+    Box::into_raw(Box::new(shaders))
+}
+
+/// cbindgen:postfix=WR_DESTRUCTOR_SAFE_FUNC
+#[no_mangle]
+pub unsafe extern "C" fn wr_shaders_delete(shaders: *mut WrShaders, gl_context: *mut c_void) {
+    let mut device = wr_device_new(gl_context, None);
+    let shaders = Box::from_raw(shaders);
+    if let Ok(shaders) = Rc::try_unwrap(shaders.shaders) {
+      shaders.into_inner().deinit(&mut device);
+    }
+    // let shaders go out of scope and get dropped
+}
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -265,16 +265,18 @@ enum class YuvColorSpace : uint32_t {
   Rec709 = 1,
 
   Sentinel /* this must be last for serialization purposes. */
 };
 
 template<typename T>
 struct Arc;
 
+struct Device;
+
 // Geometry in the coordinate system of the render target (screen or intermediate
 // surface) in physical pixels.
 struct DevicePixel;
 
 struct DocumentHandle;
 
 // Geometry in a stacking context's local coordinate space (logical pixels).
 struct LayoutPixel;
@@ -298,16 +300,18 @@ struct UnknownUnit;
 template<typename T>
 struct Vec;
 
 // Geometry in the document's coordinate space (logical pixels).
 struct WorldPixel;
 
 struct WrProgramCache;
 
+struct WrShaders;
+
 struct WrState;
 
 struct WrThreadPool;
 
 struct IdNamespace {
   uint32_t mHandle;
 
   bool operator==(const IdNamespace& aOther) const {
@@ -1237,16 +1241,20 @@ WR_INLINE
 void wr_clear_item_tag(WrState *aState)
 WR_FUNC;
 
 WR_INLINE
 void wr_dec_ref_arc(const VecU8 *aArc)
 WR_DESTRUCTOR_SAFE_FUNC;
 
 WR_INLINE
+void wr_device_delete(Device *aDevice)
+WR_DESTRUCTOR_SAFE_FUNC;
+
+WR_INLINE
 void wr_dp_clear_save(WrState *aState)
 WR_FUNC;
 
 WR_INLINE
 uintptr_t wr_dp_define_clip(WrState *aState,
                             const uintptr_t *aParentId,
                             LayoutRect aClipRect,
                             const ComplexClipRegion *aComplex,
@@ -1760,16 +1768,26 @@ extern void wr_schedule_render(WrWindowI
 
 WR_INLINE
 void wr_set_item_tag(WrState *aState,
                      uint64_t aScrollId,
                      uint16_t aHitInfo)
 WR_FUNC;
 
 WR_INLINE
+void wr_shaders_delete(WrShaders *aShaders,
+                       void *aGlContext)
+WR_DESTRUCTOR_SAFE_FUNC;
+
+WR_INLINE
+WrShaders *wr_shaders_new(void *aGlContext,
+                          WrProgramCache *aProgramCache)
+WR_FUNC;
+
+WR_INLINE
 void wr_state_delete(WrState *aState)
 WR_DESTRUCTOR_SAFE_FUNC;
 
 WR_INLINE
 WrState *wr_state_new(WrPipelineId aPipelineId,
                       LayoutSize aContentSize,
                       uintptr_t aCapacity)
 WR_FUNC;
@@ -1798,16 +1816,20 @@ WR_INLINE
 void wr_transaction_delete(Transaction *aTxn)
 WR_DESTRUCTOR_SAFE_FUNC;
 
 WR_INLINE
 void wr_transaction_generate_frame(Transaction *aTxn)
 WR_FUNC;
 
 WR_INLINE
+void wr_transaction_invalidate_rendered_frame(Transaction *aTxn)
+WR_FUNC;
+
+WR_INLINE
 bool wr_transaction_is_empty(const Transaction *aTxn)
 WR_FUNC;
 
 WR_INLINE
 Transaction *wr_transaction_new(bool aDoAsync)
 WR_FUNC;
 
 extern void wr_transaction_notification_notified(uintptr_t aHandler,
@@ -1887,16 +1909,18 @@ void wr_vec_u8_push_bytes(WrVecU8 *aV,
 WR_FUNC;
 
 WR_INLINE
 bool wr_window_new(WrWindowId aWindowId,
                    uint32_t aWindowWidth,
                    uint32_t aWindowHeight,
                    bool aSupportLowPriorityTransactions,
                    void *aGlContext,
+                   WrProgramCache *aProgramCache,
+                   WrShaders *aShaders,
                    WrThreadPool *aThreadPool,
                    VoidPtrToSizeFn aSizeOfOp,
                    DocumentHandle **aOutHandle,
                    Renderer **aOutRenderer,
                    uint32_t *aOutMaxTextureSize)
 WR_FUNC;
 
 } // extern "C"
--- a/gfx/wrench/src/wrench.rs
+++ b/gfx/wrench/src/wrench.rs
@@ -233,17 +233,17 @@ impl Wrench {
         }
 
         let (timing_sender, timing_receiver) = chase_lev::deque();
         let notifier = notifier.unwrap_or_else(|| {
             let data = Arc::new(Mutex::new(NotifierData::new(proxy, timing_receiver, verbose)));
             Box::new(Notifier(data))
         });
 
-        let (renderer, sender) = webrender::Renderer::new(window.clone_gl(), notifier, opts).unwrap();
+        let (renderer, sender) = webrender::Renderer::new(window.clone_gl(), notifier, opts, None).unwrap();
         let api = sender.create_api();
         let document_id = api.add_document(size, 0);
 
         let graphics_api = renderer.get_graphics_api_info();
         let zoom_factor = ZoomFactor::new(zoom_factor);
 
         let mut wrench = Wrench {
             window_size: size,
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -20,24 +20,29 @@
 #include <string.h>
 
 #include "jsnum.h"
 #include "jstypes.h"
 #include "jsutil.h"
 
 #include "ds/Nestable.h"
 #include "frontend/BytecodeControlStructures.h"
+#include "frontend/CallOrNewEmitter.h"
 #include "frontend/CForEmitter.h"
 #include "frontend/DoWhileEmitter.h"
+#include "frontend/ElemOpEmitter.h"
 #include "frontend/EmitterScope.h"
+#include "frontend/ExpressionStatementEmitter.h"
 #include "frontend/ForInEmitter.h"
 #include "frontend/ForOfEmitter.h"
 #include "frontend/ForOfLoopControl.h"
 #include "frontend/IfEmitter.h"
+#include "frontend/NameOpEmitter.h"
 #include "frontend/Parser.h"
+#include "frontend/PropOpEmitter.h"
 #include "frontend/SwitchEmitter.h"
 #include "frontend/TDZCheckCache.h"
 #include "frontend/TryEmitter.h"
 #include "frontend/WhileEmitter.h"
 #include "js/CompileOptions.h"
 #include "vm/BytecodeUtil.h"
 #include "vm/Debugger.h"
 #include "vm/GeneratorObject.h"
@@ -874,17 +879,16 @@ BytecodeEmitter::emitIndexOp(JSOp op, ui
     updateDepth(offset);
     return true;
 }
 
 bool
 BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op)
 {
     MOZ_ASSERT(atom);
-    MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
 
     // .generator lookups should be emitted as JSOP_GETALIASEDVAR instead of
     // JSOP_GETNAME etc, to bypass |with| objects on the scope chain.
     // It's safe to emit .this lookups though because |with| objects skip
     // those.
     MOZ_ASSERT_IF(op == JSOP_GETNAME || op == JSOP_GETGNAME,
                   atom != cx->names().dotGenerator);
 
@@ -893,24 +897,25 @@ BytecodeEmitter::emitAtomOp(JSAtom* atom
         op = JSOP_LENGTH;
     }
 
     uint32_t index;
     if (!makeAtomIndex(atom, &index)) {
         return false;
     }
 
-    return emitIndexOp(op, index);
-}
-
-bool
-BytecodeEmitter::emitAtomOp(NameNode* nameNode, JSOp op)
-{
-    MOZ_ASSERT(nameNode->atom() != nullptr);
-    return emitAtomOp(nameNode->atom(), op);
+    return emitAtomOp(index, op);
+}
+
+bool
+BytecodeEmitter::emitAtomOp(uint32_t atomIndex, JSOp op)
+{
+    MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
+
+    return emitIndexOp(op, atomIndex);
 }
 
 bool
 BytecodeEmitter::emitInternedScopeOp(uint32_t index, JSOp op)
 {
     MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPE);
     MOZ_ASSERT(index < scopeList.length());
     return emitIndex32(op, index);
@@ -989,29 +994,16 @@ BytecodeEmitter::emitEnvCoordOp(JSOp op,
     SET_ENVCOORD_HOPS(pc, ec.hops());
     pc += ENVCOORD_HOPS_LEN;
     SET_ENVCOORD_SLOT(pc, ec.slot());
     pc += ENVCOORD_SLOT_LEN;
     checkTypeSet(op);
     return true;
 }
 
-static JSOp
-GetIncDecInfo(ParseNodeKind kind, bool* post)
-{
-    MOZ_ASSERT(kind == ParseNodeKind::PostIncrement ||
-               kind == ParseNodeKind::PreIncrement ||
-               kind == ParseNodeKind::PostDecrement ||
-               kind == ParseNodeKind::PreDecrement);
-    *post = kind == ParseNodeKind::PostIncrement || kind == ParseNodeKind::PostDecrement;
-    return (kind == ParseNodeKind::PostIncrement || kind == ParseNodeKind::PreIncrement)
-           ? JSOP_ADD
-           : JSOP_SUB;
-}
-
 JSOp
 BytecodeEmitter::strictifySetNameOp(JSOp op)
 {
     switch (op) {
       case JSOP_SETNAME:
         if (sc->strict()) {
             op = JSOP_STRICTSETNAME;
         }
@@ -1708,298 +1700,30 @@ BytecodeEmitter::emitFinishIteratorResul
     }
     if (!emitIndex32(JSOP_INITPROP, done_id)) {
         return false;
     }
     return true;
 }
 
 bool
-BytecodeEmitter::emitGetNameAtLocation(JSAtom* name, const NameLocation& loc, bool callContext)
-{
-    switch (loc.kind()) {
-      case NameLocation::Kind::Dynamic:
-        if (!emitAtomOp(name, JSOP_GETNAME)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::Global:
-        if (!emitAtomOp(name, JSOP_GETGNAME)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::Intrinsic:
-        if (!emitAtomOp(name, JSOP_GETINTRINSIC)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::NamedLambdaCallee:
-        if (!emit1(JSOP_CALLEE)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::Import:
-        if (!emitAtomOp(name, JSOP_GETIMPORT)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::ArgumentSlot:
-        if (!emitArgOp(JSOP_GETARG, loc.argumentSlot())) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::FrameSlot:
-        if (loc.isLexical()) {
-            if (!emitTDZCheckIfNeeded(name, loc)) {
-                return false;
-            }
-        }
-        if (!emitLocalOp(JSOP_GETLOCAL, loc.frameSlot())) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::EnvironmentCoordinate:
-        if (loc.isLexical()) {
-            if (!emitTDZCheckIfNeeded(name, loc)) {
-                return false;
-            }
-        }
-        if (!emitEnvCoordOp(JSOP_GETALIASEDVAR, loc.environmentCoordinate())) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::DynamicAnnexBVar:
-        MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
-    }
-
-    // Need to provide |this| value for call.
-    if (callContext) {
-        switch (loc.kind()) {
-          case NameLocation::Kind::Dynamic: {
-            JSOp thisOp = needsImplicitThis() ? JSOP_IMPLICITTHIS : JSOP_GIMPLICITTHIS;
-            if (!emitAtomOp(name, thisOp)) {
-                return false;
-            }
-            break;
-          }
-
-          case NameLocation::Kind::Global:
-            if (!emitAtomOp(name, JSOP_GIMPLICITTHIS)) {
-                return false;
-            }
-            break;
-
-          case NameLocation::Kind::Intrinsic:
-          case NameLocation::Kind::NamedLambdaCallee:
-          case NameLocation::Kind::Import:
-          case NameLocation::Kind::ArgumentSlot:
-          case NameLocation::Kind::FrameSlot:
-          case NameLocation::Kind::EnvironmentCoordinate:
-            if (!emit1(JSOP_UNDEFINED)) {
-                return false;
-            }
-            break;
-
-          case NameLocation::Kind::DynamicAnnexBVar:
-            MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
-        }
-    }
-
-    return true;
-}
-
-bool
-BytecodeEmitter::emitGetName(ParseNode* pn, bool callContext)
-{
-    return emitGetName(pn->name(), callContext);
-}
-
-template <typename RHSEmitter>
-bool
-BytecodeEmitter::emitSetOrInitializeNameAtLocation(HandleAtom name, const NameLocation& loc,
-                                                   RHSEmitter emitRhs, bool initialize)
-{
-    bool emittedBindOp = false;
-
-    switch (loc.kind()) {
-      case NameLocation::Kind::Dynamic:
-      case NameLocation::Kind::Import:
-      case NameLocation::Kind::DynamicAnnexBVar: {
-        uint32_t atomIndex;
-        if (!makeAtomIndex(name, &atomIndex)) {
-            return false;
-        }
-        if (loc.kind() == NameLocation::Kind::DynamicAnnexBVar) {
-            // Annex B vars always go on the nearest variable environment,
-            // even if lexical environments in between contain same-named
-            // bindings.
-            if (!emit1(JSOP_BINDVAR)) {
-                return false;
-            }
-        } else {
-            if (!emitIndexOp(JSOP_BINDNAME, atomIndex)) {
-                return false;
-            }
-        }
-        emittedBindOp = true;
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        if (!emitIndexOp(strictifySetNameOp(JSOP_SETNAME), atomIndex)) {
-            return false;
-        }
-        break;
-      }
-
-      case NameLocation::Kind::Global: {
-        JSOp op;
-        uint32_t atomIndex;
-        if (!makeAtomIndex(name, &atomIndex)) {
-            return false;
-        }
-        if (loc.isLexical() && initialize) {
-            // INITGLEXICAL always gets the global lexical scope. It doesn't
-            // need a BINDGNAME.
-            MOZ_ASSERT(innermostScope()->is<GlobalScope>());
-            op = JSOP_INITGLEXICAL;
-        } else {
-            if (!emitIndexOp(JSOP_BINDGNAME, atomIndex)) {
-                return false;
-            }
-            emittedBindOp = true;
-            op = strictifySetNameOp(JSOP_SETGNAME);
-        }
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        if (!emitIndexOp(op, atomIndex)) {
-            return false;
-        }
-        break;
-      }
-
-      case NameLocation::Kind::Intrinsic:
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        if (!emitAtomOp(name, JSOP_SETINTRINSIC)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::NamedLambdaCallee:
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        // Assigning to the named lambda is a no-op in sloppy mode but
-        // throws in strict mode.
-        if (sc->strict() && !emit1(JSOP_THROWSETCALLEE)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::ArgumentSlot: {
-        // If we assign to a positional formal parameter and the arguments
-        // object is unmapped (strict mode or function with
-        // default/rest/destructing args), parameters do not alias
-        // arguments[i], and to make the arguments object reflect initial
-        // parameter values prior to any mutation we create it eagerly
-        // whenever parameters are (or might, in the case of calls to eval)
-        // assigned.
-        FunctionBox* funbox = sc->asFunctionBox();
-        if (funbox->argumentsHasLocalBinding() && !funbox->hasMappedArgsObj()) {
-            funbox->setDefinitelyNeedsArgsObj();
-        }
-
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        if (!emitArgOp(JSOP_SETARG, loc.argumentSlot())) {
-            return false;
-        }
-        break;
-      }
-
-      case NameLocation::Kind::FrameSlot: {
-        JSOp op = JSOP_SETLOCAL;
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        if (loc.isLexical()) {
-            if (initialize) {
-                op = JSOP_INITLEXICAL;
-            } else {
-                if (loc.isConst()) {
-                    op = JSOP_THROWSETCONST;
-                }
-
-                if (!emitTDZCheckIfNeeded(name, loc)) {
-                    return false;
-                }
-            }
-        }
-        if (!emitLocalOp(op, loc.frameSlot())) {
-            return false;
-        }
-        if (op == JSOP_INITLEXICAL) {
-            if (!innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ)) {
-                return false;
-            }
-        }
-        break;
-      }
-
-      case NameLocation::Kind::EnvironmentCoordinate: {
-        JSOp op = JSOP_SETALIASEDVAR;
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        if (loc.isLexical()) {
-            if (initialize) {
-                op = JSOP_INITALIASEDLEXICAL;
-            } else {
-                if (loc.isConst()) {
-                    op = JSOP_THROWSETALIASEDCONST;
-                }
-
-                if (!emitTDZCheckIfNeeded(name, loc)) {
-                    return false;
-                }
-            }
-        }
-        if (loc.bindingKind() == BindingKind::NamedLambdaCallee) {
-            // Assigning to the named lambda is a no-op in sloppy mode and throws
-            // in strict mode.
-            op = JSOP_THROWSETALIASEDCONST;
-            if (sc->strict() && !emitEnvCoordOp(op, loc.environmentCoordinate())) {
-                return false;
-            }
-        } else {
-            if (!emitEnvCoordOp(op, loc.environmentCoordinate())) {
-                return false;
-            }
-        }
-        if (op == JSOP_INITALIASEDLEXICAL) {
-            if (!innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ)) {
-                return false;
-            }
-        }
-        break;
-      }
-    }
-
-    return true;
+BytecodeEmitter::emitGetNameAtLocation(JSAtom* name, const NameLocation& loc)
+{
+    NameOpEmitter noe(this, name, loc, NameOpEmitter::Kind::Get);
+    if (!noe.emitGet()) {
+        return false;
+    }
+
+    return true;
+}
+
+bool
+BytecodeEmitter::emitGetName(ParseNode* pn)
+{
+    return emitGetName(pn->name());
 }
 
 bool
 BytecodeEmitter::emitTDZCheckIfNeeded(JSAtom* name, const NameLocation& loc)
 {
     // Dynamic accesses have TDZ checks built into their VM code and should
     // never emit explicit TDZ checks.
     MOZ_ASSERT(loc.hasKnownSlot());
@@ -2059,294 +1783,80 @@ BytecodeEmitter::emitPropLHS(PropertyAcc
 
     // pndown is a primary expression, not a dotted property reference.
     if (!emitTree(pndown)) {
         return false;
     }
 
     while (true) {
         // Walk back up the list, emitting annotated name ops.
-        if (!emitAtomOp(&pndot->key(), JSOP_GETPROP)) {
+        if (!emitAtomOp(pndot->key().atom(), JSOP_GETPROP)) {
             return false;
         }
 
         // Reverse the pndot->expression() link again.
         pnup = pndot->maybeExpression();
         pndot->setExpression(pndown);
         pndown = pndot;
         if (!pnup) {
             break;
         }
         pndot = &pnup->as<PropertyAccess>();
     }
     return true;
 }
 
 bool
-BytecodeEmitter::emitSuperPropLHS(UnaryNode* superBase, bool isCall)
-{
-    if (!emitGetThisForSuperBase(superBase)) {
-        return false;
-    }
-    if (isCall && !emit1(JSOP_DUP)) {
-        return false;
-    }
-    if (!emit1(JSOP_SUPERBASE)) {
-        return false;
-    }
-    return true;
-}
-
-bool
-BytecodeEmitter::emitPropOp(PropertyAccess* prop, JSOp op)
-{
-    if (!emitPropLHS(prop)) {
-        return false;
-    }
-
-    if (op == JSOP_CALLPROP && !emit1(JSOP_DUP)) {
-        return false;
-    }
-
-    if (!emitAtomOp(&prop->key(), op)) {
-        return false;
-    }
-
-    if (op == JSOP_CALLPROP && !emit1(JSOP_SWAP)) {
-        return false;
-    }
-
-    return true;
-}
-
-bool
-BytecodeEmitter::emitSuperGetProp(PropertyAccess* prop, bool isCall)
-{
-    UnaryNode* base = &prop->expression().as<UnaryNode>();
-    if (!emitSuperPropLHS(base, isCall)) {
-        return false;
-    }
-
-    if (!emitAtomOp(&prop->key(), JSOP_GETPROP_SUPER)) {
-        return false;
-    }
-
-    if (isCall && !emit1(JSOP_SWAP)) {
-        return false;
-    }
-
-    return true;
-}