Merge m-c to b2g-inbound a=merge
authorWes Kocher <wkocher@mozilla.com>
Wed, 31 Dec 2014 13:12:51 -0800
changeset 247593 0991331d99f4fb7cc3c84020fe458827d15f646f
parent 247592 74dc2a4ee7ef133746193ee603b55a1a85a7b23d (current diff)
parent 247584 b3e6101aa94b36c7afe97b7f23e769680c10c0a1 (diff)
child 247594 1309c33cc06bf07f285cb66828f981bb7c096ecb
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone37.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to b2g-inbound a=merge
browser/base/content/test/general/browser_sanitize-download-history.js
browser/base/content/test/general/browser_sanitizeDialog_treeView.js
mobile/android/base/resources/drawable/handle_end_level.xml
mobile/android/base/resources/drawable/handle_start_level.xml
--- a/accessible/generic/Accessible-inl.h
+++ b/accessible/generic/Accessible-inl.h
@@ -62,20 +62,12 @@ Accessible::HasNumericValue() const
 
 inline void
 Accessible::ScrollTo(uint32_t aHow) const
 {
   if (mContent)
     nsCoreUtils::ScrollTo(mDoc->PresShell(), mContent, aHow);
 }
 
-inline bool
-Accessible::UpdateChildren()
-{
-  AutoTreeMutation mut(this);
-  InvalidateChildren();
-  return EnsureChildren();
-}
-
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -2007,18 +2007,17 @@ Accessible::Language(nsAString& aLanguag
   }
 }
 
 void
 Accessible::InvalidateChildren()
 {
   int32_t childCount = mChildren.Length();
   for (int32_t childIdx = 0; childIdx < childCount; childIdx++) {
-    Accessible* child = mChildren.ElementAt(childIdx);
-    child->UnbindFromParent();
+    mChildren.ElementAt(childIdx)->UnbindFromParent();
   }
 
   mEmbeddedObjCollector = nullptr;
   mChildren.Clear();
   SetChildrenFlag(eChildrenUninitialized);
 }
 
 bool
@@ -2441,33 +2440,27 @@ Accessible::TestChildCache(Accessible* a
       break;
   }
 
   NS_ASSERTION(child == aCachedChild,
                "[TestChildCache] cached accessible wasn't found. Wrong accessible tree!");
 #endif
 }
 
-// Accessible public
-bool
+void
 Accessible::EnsureChildren()
 {
-  if (IsDefunct()) {
-    SetChildrenFlag(eChildrenUninitialized);
-    return true;
-  }
+  NS_ASSERTION(!IsDefunct(), "Caching children for defunct accessible!");
 
   if (!IsChildrenFlag(eChildrenUninitialized))
-    return false;
+    return;
 
   // State is embedded children until text leaf accessible is appended.
   SetChildrenFlag(eEmbeddedChildren); // Prevent reentry
-
   CacheChildren();
-  return false;
 }
 
 Accessible*
 Accessible::GetSiblingAtOffset(int32_t aOffset, nsresult* aError) const
 {
   if (!mParent || mIndexInParent == -1) {
     if (aError)
       *aError = NS_ERROR_UNEXPECTED;
--- a/accessible/generic/Accessible.h
+++ b/accessible/generic/Accessible.h
@@ -361,24 +361,19 @@ public:
 
   /**
    * Set the ARIA role map entry for a new accessible.
    */
   void SetRoleMapEntry(nsRoleMapEntry* aRoleMapEntry)
     { mRoleMapEntry = aRoleMapEntry; }
 
   /**
-   * Update the children cache.
+   * Cache children if necessary.
    */
-  bool UpdateChildren();
-
-  /**
-   * Cache children if necessary. Return true if the accessible is defunct.
-   */
-  bool EnsureChildren();
+  void EnsureChildren();
 
   /**
    * Set the child count to -1 (unknown) and null out cached child pointers.
    * Should be called when accessible tree is changed because document has
    * transformed. Note, if accessible cares about its parent relation chain
    * itself should override this method to do nothing.
    */
   virtual void InvalidateChildren();
@@ -582,16 +577,17 @@ public:
 
   bool IsDoc() const { return HasGenericType(eDocument); }
   DocAccessible* AsDoc();
 
   bool IsHyperText() const { return HasGenericType(eHyperText); }
   HyperTextAccessible* AsHyperText();
 
   bool IsHTMLBr() const { return mType == eHTMLBRType; }
+  bool IsHTMLCombobox() const { return mType == eHTMLComboboxType; }
   bool IsHTMLFileInput() const { return mType == eHTMLFileInputType; }
 
   bool IsHTMLListItem() const { return mType == eHTMLLiType; }
   HTMLLIAccessible* AsHTMLListItem();
 
   bool IsHTMLOptGroup() const { return mType == eHTMLOptGroupType; }
 
   bool IsHTMLTable() const { return mType == eHTMLTableType; }
@@ -866,16 +862,29 @@ public:
    * DOM UI event, if otherwise then false. For example, HTMLCheckboxAccessible
    * process nsIDocumentObserver::ContentStateChanged instead
    * 'CheckboxStateChange' event.
    */
   bool NeedsDOMUIEvent() const
     { return !(mStateFlags & eIgnoreDOMUIEvent); }
 
   /**
+   * Get/set survivingInUpdate bit on child indicating that parent recollects
+   * its children.
+   */
+  bool IsSurvivingInUpdate() const { return mStateFlags & eSurvivingInUpdate; }
+  void SetSurvivingInUpdate(bool aIsSurviving)
+  {
+    if (aIsSurviving)
+      mStateFlags |= eSurvivingInUpdate;
+    else
+      mStateFlags &= ~eSurvivingInUpdate;
+  }
+
+  /**
    * Return true if this accessible has a parent whose name depends on this
    * accessible.
    */
   bool HasNameDependentParent() const
     { return mContextFlags & eHasNameDependentParent; }
 
 protected:
 
@@ -948,18 +957,19 @@ protected:
     eIsDefunct = 1 << 0, // accessible is defunct
     eIsNotInDocument = 1 << 1, // accessible is not in document
     eSharedNode = 1 << 2, // accessible shares DOM node from another accessible
     eNotNodeMapEntry = 1 << 3, // accessible shouldn't be in document node map
     eHasNumericValue = 1 << 4, // accessible has a numeric value
     eGroupInfoDirty = 1 << 5, // accessible needs to update group info
     eSubtreeMutating = 1 << 6, // subtree is being mutated
     eIgnoreDOMUIEvent = 1 << 7, // don't process DOM UI events for a11y events
+    eSurvivingInUpdate = 1 << 8, // parent drops children to recollect them
 
-    eLastStateFlag = eIgnoreDOMUIEvent
+    eLastStateFlag = eSurvivingInUpdate
   };
 
   /**
    * Flags used for contextual information about the accessible.
    */
   enum ContextFlags {
     eHasNameDependentParent = 1 << 0, // Parent's name depends on this accessible.
 
@@ -1063,17 +1073,17 @@ protected:
   nsCOMPtr<nsIContent> mContent;
   DocAccessible* mDoc;
 
   nsRefPtr<Accessible> mParent;
   nsTArray<nsRefPtr<Accessible> > mChildren;
   int32_t mIndexInParent;
 
   static const uint8_t kChildrenFlagsBits = 2;
-  static const uint8_t kStateFlagsBits = 8;
+  static const uint8_t kStateFlagsBits = 9;
   static const uint8_t kContextFlagsBits = 1;
   static const uint8_t kTypeBits = 6;
   static const uint8_t kGenericTypesBits = 13;
 
   /**
    * Keep in sync with ChildrenFlags, StateFlags, ContextFlags, and AccTypes.
    */
   uint32_t mChildrenFlags : kChildrenFlagsBits;
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -1303,29 +1303,20 @@ DocAccessible::RecreateAccessible(nsICon
 void
 DocAccessible::ProcessInvalidationList()
 {
   // Invalidate children of container accessible for each element in
   // invalidation list. Allow invalidation list insertions while container
   // children are recached.
   for (uint32_t idx = 0; idx < mInvalidationList.Length(); idx++) {
     nsIContent* content = mInvalidationList[idx];
-    Accessible* accessible = GetAccessible(content);
-    if (!accessible) {
+    if (!HasAccessible(content)) {
       Accessible* container = GetContainerAccessible(content);
-      if (container) {
-        container->UpdateChildren();
-        accessible = GetAccessible(content);
-      }
-    }
-
-    // Make sure the subtree is created.
-    if (accessible) {
-      AutoTreeMutation mut(accessible);
-      CacheChildrenInSubtree(accessible);
+      if (container)
+        UpdateTreeOnInsertion(container);
     }
   }
 
   mInvalidationList.Clear();
 }
 
 Accessible*
 DocAccessible::GetAccessibleEvenIfNotInMap(nsINode* aNode) const
@@ -1628,138 +1619,101 @@ DocAccessible::UpdateAccessibleOnAttrCha
 void
 DocAccessible::ProcessContentInserted(Accessible* aContainer,
                                       const nsTArray<nsCOMPtr<nsIContent> >* aInsertedContent)
 {
   // Process insertions if the container accessible is still in tree.
   if (!HasAccessible(aContainer->GetNode()))
     return;
 
-  bool containerNotUpdated = true;
-
   for (uint32_t idx = 0; idx < aInsertedContent->Length(); idx++) {
     // The container might be changed, for example, because of the subsequent
     // overlapping content insertion (i.e. other content was inserted between
     // this inserted content and its container or the content was reinserted
     // into different container of unrelated part of tree). To avoid a double
     // processing of the content insertion ignore this insertion notification.
     // Note, the inserted content might be not in tree at all at this point what
     // means there's no container. Ignore the insertion too.
 
-    Accessible* presentContainer =
+    Accessible* container =
       GetContainerAccessible(aInsertedContent->ElementAt(idx));
-    if (presentContainer != aContainer)
+    if (container != aContainer)
       continue;
 
-    if (containerNotUpdated) {
-      containerNotUpdated = false;
-
-      if (aContainer == this) {
-        // If new root content has been inserted then update it.
-        nsIContent* rootContent = nsCoreUtils::GetRoleContent(mDocumentNode);
-        if (rootContent != mContent) {
-          mContent = rootContent;
-          SetRoleMapEntry(aria::GetRoleMap(mContent));
-        }
-
-        // Continue to update the tree even if we don't have root content.
-        // For example, elements may be inserted under the document element while
-        // there is no HTML body element.
+    if (container == this) {
+      // If new root content has been inserted then update it.
+      nsIContent* rootContent = nsCoreUtils::GetRoleContent(mDocumentNode);
+      if (rootContent != mContent) {
+        mContent = rootContent;
+        SetRoleMapEntry(aria::GetRoleMap(mContent));
       }
 
-      // XXX: Invalidate parent-child relations for container accessible and its
-      // children because there's no good way to find insertion point of new child
-      // accessibles into accessible tree. We need to invalidate children even
-      // there's no inserted accessibles in the end because accessible children
-      // are created while parent recaches child accessibles.
-      // XXX Group invalidation here may be redundant with invalidation in
-      // UpdateTree.
-      AutoTreeMutation mut(aContainer);
-      aContainer->InvalidateChildren();
-      CacheChildrenInSubtree(aContainer);
+      // Continue to update the tree even if we don't have root content.
+      // For example, elements may be inserted under the document element while
+      // there is no HTML body element.
     }
 
-    UpdateTree(aContainer, aInsertedContent->ElementAt(idx), true);
+    // HTML comboboxes have no-content list accessible as an intermidiate
+    // containing all options.
+    if (container->IsHTMLCombobox())
+      container = container->FirstChild();
+
+    // We have a DOM/layout change under the container accessible, and its tree
+    // might need an update. Since DOM/layout change of the element may affect
+    // on the accessibleness of adjacent elements (for example, insertion of
+    // extra HTML:body make the old body accessible) then we have to recache
+    // children of the container, and then fire show/hide events for a change.
+    UpdateTreeOnInsertion(container);
+    break;
   }
 }
 
 void
-DocAccessible::UpdateTree(Accessible* aContainer, nsIContent* aChildNode,
-                          bool aIsInsert)
+DocAccessible::UpdateTreeOnInsertion(Accessible* aContainer)
 {
-  uint32_t updateFlags = eNoAccessible;
+  for (uint32_t idx = 0; idx < aContainer->ContentChildCount(); idx++) {
+    Accessible* child = aContainer->ContentChildAt(idx);
+    child->SetSurvivingInUpdate(true);
+   }
 
-  // If child node is not accessible then look for its accessible children.
-  Accessible* child = GetAccessible(aChildNode);
-#ifdef A11Y_LOG
-  if (logging::IsEnabled(logging::eTree)) {
-    logging::MsgBegin("TREE", "process content %s",
-                      (aIsInsert ? "insertion" : "removal"));
-    logging::Node("container", aContainer->GetNode());
-    logging::Node("child", aChildNode);
-    if (child)
-      logging::Address("child", child);
-    else
-      logging::MsgEntry("child accessible: null");
-
-    logging::MsgEnd();
-  }
-#endif
+  AutoTreeMutation mut(aContainer);
+  aContainer->InvalidateChildren();
+  aContainer->EnsureChildren();
 
   nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(aContainer);
-  AutoTreeMutation mut(aContainer);
 
-  if (child) {
-    updateFlags |= UpdateTreeInternal(child, aIsInsert, reorderEvent);
-  } else {
-    if (aIsInsert) {
-      TreeWalker walker(aContainer, aChildNode, TreeWalker::eWalkCache);
+  uint32_t updateFlags = eNoAccessible;
+  for (uint32_t idx = 0; idx < aContainer->ContentChildCount(); idx++) {
+    Accessible* child = aContainer->ContentChildAt(idx);
+    if (child->IsSurvivingInUpdate()) {
+      child->SetSurvivingInUpdate(false);
+      continue;
+    }
 
-      while ((child = walker.NextChild()))
-        updateFlags |= UpdateTreeInternal(child, aIsInsert, reorderEvent);
-    } else {
-      // aChildNode may not coorespond to a particular accessible, to handle
-      // this we go through all the children of aContainer.  Then if a child
-      // has aChildNode as an ancestor, or does not have the node for
-      // aContainer as an ancestor remove that child of aContainer.  Note that
-      // when we are called aChildNode may already have been removed
-      // from the DOM so we can't expect it to have a parent or what was it's
-      // parent to have it as a child.
-      nsINode* containerNode = aContainer->GetNode();
-      for (uint32_t idx = 0; idx < aContainer->ContentChildCount();) {
-        Accessible* child = aContainer->ContentChildAt(idx);
+    // A new child has been created, update its tree.
+#ifdef A11Y_LOG
+    if (logging::IsEnabled(logging::eTree)) {
+      logging::MsgBegin("TREE", "process content insertion");
+      logging::Node("container", aContainer->GetNode());
+      logging::Node("child", child->GetContent());
+      logging::Address("child", child);
+      logging::MsgEnd();
+    }
+#endif
 
-        // If accessible doesn't have its own content then we assume parent
-        // will handle its update.  If child is DocAccessible then we don't
-        // handle updating it here either.
-        if (!child->HasOwnContent() || child->IsDoc()) {
-          idx++;
-          continue;
-        }
-
-        nsINode* childNode = child->GetContent();
-        while (childNode != aChildNode && childNode != containerNode &&
-               (childNode = childNode->GetParentNode()));
-
-        if (childNode != containerNode) {
-          updateFlags |= UpdateTreeInternal(child, false, reorderEvent);
-        } else {
-          idx++;
-        }
-      }
-    }
+    updateFlags |= UpdateTreeInternal(child, true, reorderEvent);
   }
 
   // Content insertion/removal is not cause of accessible tree change.
   if (updateFlags == eNoAccessible)
     return;
 
   // Check to see if change occurred inside an alert, and fire an EVENT_ALERT
   // if it did.
-  if (aIsInsert && !(updateFlags & eAlertAccessible)) {
+  if (!(updateFlags & eAlertAccessible)) {
     // XXX: tree traversal is perf issue, accessible should know if they are
     // children of alert accessible to avoid this.
     Accessible* ancestor = aContainer;
     while (ancestor) {
       if (ancestor->ARIARole() == roles::ALERT) {
         FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, ancestor);
         break;
       }
@@ -1768,19 +1722,81 @@ DocAccessible::UpdateTree(Accessible* aC
       if (ancestor == this)
         break;
 
       ancestor = ancestor->Parent();
     }
   }
 
   MaybeNotifyOfValueChange(aContainer);
+  FireDelayedEvent(reorderEvent);
+}
 
-  // Fire reorder event so the MSAA clients know the children have changed. Also
-  // the event is used internally by MSAA layer.
+void
+DocAccessible::UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNode)
+{
+  // If child node is not accessible then look for its accessible children.
+  Accessible* child = GetAccessible(aChildNode);
+#ifdef A11Y_LOG
+  if (logging::IsEnabled(logging::eTree)) {
+    logging::MsgBegin("TREE", "process content removal");
+    logging::Node("container", aContainer->GetNode());
+    logging::Node("child", aChildNode);
+    if (child)
+      logging::Address("child", child);
+    else
+      logging::MsgEntry("child accessible: null");
+
+    logging::MsgEnd();
+  }
+#endif
+
+  uint32_t updateFlags = eNoAccessible;
+  nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(aContainer);
+  AutoTreeMutation mut(aContainer);
+
+  if (child) {
+    updateFlags |= UpdateTreeInternal(child, false, reorderEvent);
+  } else {
+    // aChildNode may not coorespond to a particular accessible, to handle
+    // this we go through all the children of aContainer.  Then if a child
+    // has aChildNode as an ancestor, or does not have the node for
+    // aContainer as an ancestor remove that child of aContainer.  Note that
+    // when we are called aChildNode may already have been removed from the DOM
+    // so we can't expect it to have a parent or what was it's parent to have
+    // it as a child.
+    nsINode* containerNode = aContainer->GetNode();
+    for (uint32_t idx = 0; idx < aContainer->ContentChildCount();) {
+      Accessible* child = aContainer->ContentChildAt(idx);
+
+      // If accessible doesn't have its own content then we assume parent
+      // will handle its update.  If child is DocAccessible then we don't
+      // handle updating it here either.
+      if (!child->HasOwnContent() || child->IsDoc()) {
+        idx++;
+        continue;
+      }
+
+      nsINode* childNode = child->GetContent();
+      while (childNode != aChildNode && childNode != containerNode &&
+             (childNode = childNode->GetParentNode()));
+
+      if (childNode != containerNode) {
+        updateFlags |= UpdateTreeInternal(child, false, reorderEvent);
+      } else {
+        idx++;
+      }
+    }
+  }
+
+  // Content insertion/removal is not cause of accessible tree change.
+  if (updateFlags == eNoAccessible)
+    return;
+
+  MaybeNotifyOfValueChange(aContainer);
   FireDelayedEvent(reorderEvent);
 }
 
 uint32_t
 DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert,
                                   AccReorderEvent* aReorderEvent)
 {
   uint32_t updateFlags = eAccessible;
--- a/accessible/generic/DocAccessible.h
+++ b/accessible/generic/DocAccessible.h
@@ -316,17 +316,17 @@ public:
 
   /**
    * Notify the document accessible that content was removed.
    */
   void ContentRemoved(Accessible* aContainer, nsIContent* aChildNode)
   {
     // Update the whole tree of this document accessible when the container is
     // null (document element is removed).
-    UpdateTree((aContainer ? aContainer : this), aChildNode, false);
+    UpdateTreeOnRemoval((aContainer ? aContainer : this), aChildNode);
   }
   void ContentRemoved(nsIContent* aContainerNode, nsIContent* aChildNode)
   {
     ContentRemoved(GetAccessibleOrContainer(aContainerNode), aChildNode);
   }
 
   /**
    * Updates accessible tree when rendered text is changed.
@@ -460,23 +460,27 @@ protected:
    *
    * While children are cached we may encounter the case there's no accessible
    * for referred content by related accessible. Store these related nodes to
    * invalidate their containers later.
    */
   void ProcessInvalidationList();
 
   /**
-   * Update the accessible tree for content insertion or removal.
+   * Update the tree on content insertion.
    */
-  void UpdateTree(Accessible* aContainer, nsIContent* aChildNode,
-                  bool aIsInsert);
+  void UpdateTreeOnInsertion(Accessible* aContainer);
 
   /**
-   * Helper for UpdateTree() method. Go down to DOM subtree and updates
+   * Update the accessible tree for content removal.
+   */
+  void UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNode);
+
+  /**
+   * Helper for UpdateTreeOn methods. Go down to DOM subtree and updates
    * accessible tree. Return one of these flags.
    */
   enum EUpdateTreeFlags {
     eNoAccessible = 0,
     eAccessible = 1,
     eAlertAccessible = 2
   };
 
--- a/accessible/html/HTMLSelectAccessible.cpp
+++ b/accessible/html/HTMLSelectAccessible.cpp
@@ -354,16 +354,17 @@ HTMLSelectOptGroupAccessible::DoAction(u
 ////////////////////////////////////////////////////////////////////////////////
 // HTMLComboboxAccessible
 ////////////////////////////////////////////////////////////////////////////////
 
 HTMLComboboxAccessible::
   HTMLComboboxAccessible(nsIContent* aContent, DocAccessible* aDoc) :
   AccessibleWrap(aContent, aDoc)
 {
+  mType = eHTMLComboboxType;
   mGenericTypes |= eCombobox;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // HTMLComboboxAccessible: Accessible
 
 role
 HTMLComboboxAccessible::NativeRole()
--- a/accessible/tests/mochitest/events/test_mutation.html
+++ b/accessible/tests/mochitest/events/test_mutation.html
@@ -331,23 +331,49 @@
       }
 
       this.getID = function test3_getID()
       {
         return "fuzzy test #3: content insertion (flush layout) and removal";
       }
     }
 
+    function insertReferredElm(aContainerID)
+    {
+      this.containerNode = getNode(aContainerID);
+
+      this.eventSeq = [
+        new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.lastChild; }, this.containerNode),
+        new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.firstChild; }, this.containerNode),
+        new invokerChecker(EVENT_REORDER, this.containerNode)
+      ];
+
+      this.invoke = function insertReferredElm_invoke()
+      {
+        this.containerNode.innerHTML =
+          "<span id='insertReferredElms_span'></span><input aria-labelledby='insertReferredElms_span'>";
+      }
+
+      this.getID = function insertReferredElm_getID()
+      {
+        return "insert inaccessible element and then insert referring element to make it accessible";
+      }
+    }
+
     /**
      * Target getters.
      */
     function getFirstChild(aNode)
     {
       return [aNode.firstChild];
     }
+    function getLastChild(aNode)
+    {
+      return [aNode.lastChild];
+    }
 
     function getNEnsureFirstChild(aNode)
     {
       var node = aNode.firstChild;
       getAccessible(node);
       return [node];
     }
 
@@ -452,16 +478,17 @@
       gQueue.push(new changeClass("container3", "link8", "", kShowEvents));
       gQueue.push(new changeClass("container3", "link8", "visibilityHidden",
                                   kHideEvents));
 
       gQueue.push(new test1("testContainer"));
       gQueue.push(new test2("testContainer", "testContainer2"));
       gQueue.push(new test2("testContainer", "testNestedContainer"));
       gQueue.push(new test3("testContainer"));
+      gQueue.push(new insertReferredElm("testContainer3"));
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 </head>
@@ -511,10 +538,11 @@
 
     <a id="link6" href="http://www.google.com">Link #6</a>
 
     <div id="container2" class="displayNone"><a id="link7">Link #7</a></div>
     <div id="container3" class="visibilityHidden"><a id="link8">Link #8</a></div>
     <div id="testNestedContainer"></div>
   </div>
   <div id="testContainer2"></div>
+  <div id="testContainer3"></div>
 </body>
 </html>
--- a/accessible/tests/mochitest/tree/test_select.html
+++ b/accessible/tests/mochitest/tree/test_select.html
@@ -81,17 +81,17 @@
                     role: ROLE_COMBOBOX_OPTION,
                     children: [
                       {
                         role: ROLE_TEXT_LEAF
                       }
                     ]
                   },
                 ]
-},
+              },
               {
                 role: ROLE_COMBOBOX_OPTION,
                 children: [
                   {
                     role: ROLE_TEXT_LEAF
                   }
                 ]
               }
--- a/accessible/tests/mochitest/treeupdate/test_optgroup.html
+++ b/accessible/tests/mochitest/treeupdate/test_optgroup.html
@@ -16,16 +16,17 @@
           src="../events.js"></script>
 
   <script type="application/javascript">
 
     function addOptGroup(aID)
     {
       this.selectNode = getNode(aID);
       this.select = getAccessible(this.selectNode);
+      this.selectList = this.select.firstChild;
 
       this.invoke = function addOptGroup_invoke()
       {
         var optGroup = document.createElement("optgroup");
         for (i = 0; i < 2; i++) {
           var opt = document.createElement("option");
           opt.value = i;
           opt.text = "Option: Value " + i;
@@ -34,17 +35,17 @@
         }
 
         this.selectNode.add(optGroup, null);
         var option = document.createElement("option");
         this.selectNode.add(option, null);
       }
 
       this.eventSeq = [
-        new invokerChecker(EVENT_REORDER, this.select)
+        new invokerChecker(EVENT_REORDER, this.selectList)
       ];
 
       this.finalCheck = function addOptGroup_finalCheck()
       {
         var tree =
           { COMBOBOX: [
             { COMBOBOX_LIST: [
               { GROUPING: [
--- a/accessible/tests/mochitest/treeupdate/test_select.html
+++ b/accessible/tests/mochitest/treeupdate/test_select.html
@@ -16,30 +16,31 @@
           src="../events.js"></script>
 
   <script type="application/javascript">
 
     function addOptions(aID)
     {
       this.selectNode = getNode(aID);
       this.select = getAccessible(this.selectNode);
+      this.selectList = this.select.firstChild;
 
       this.invoke = function addOptions_invoke()
       {
         for (i = 0; i < 2; i++) {
           var opt = document.createElement("option");
           opt.value = i;
           opt.text = "Option: Value " + i;
 
           this.selectNode.add(opt, null);
         }
       }
 
       this.eventSeq = [
-        new invokerChecker(EVENT_REORDER, this.select)
+        new invokerChecker(EVENT_REORDER, this.selectList)
       ];
 
       this.finalCheck = function addOptions_finalCheck()
       {
         var tree =
           { COMBOBOX: [
             { COMBOBOX_LIST: [
               { COMBOBOX_OPTION: [
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -371,28 +371,22 @@ skip-if = buildapp == 'mulet'
 [browser_relatedTabs.js]
 [browser_remoteTroubleshoot.js]
 support-files =
   test_remoteTroubleshoot.html
 [browser_removeTabsToTheEnd.js]
 [browser_removeUnsafeProtocolsFromURLBarPaste.js]
 skip-if = e10s
 [browser_restore_isAppTab.js]
-[browser_sanitize-download-history.js]
-skip-if = true # bug 432425
 [browser_sanitize-passwordDisabledHosts.js]
 [browser_sanitize-sitepermissions.js]
 [browser_sanitize-timespans.js]
 skip-if = buildapp == 'mulet'
 [browser_sanitizeDialog.js]
 skip-if = buildapp == 'mulet'
-[browser_sanitizeDialog_treeView.js]
-skip-if = true  # disabled until the tree view is added
-                # back to the clear recent history dialog (sanitize.xul), if
-                # it ever is (bug 480169)
 [browser_save_link-perwindowpb.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
 [browser_save_private_link_perwindowpb.js]
 skip-if = buildapp == 'mulet' || e10s # e10s: Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
 [browser_save_video.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1100698 - test uses synthesizeMouse and then does a load of other stuff that breaks in e10s
 [browser_save_video_frame.js]
 [browser_scope.js]
deleted file mode 100644
--- a/browser/base/content/test/general/browser_sanitize-download-history.js
+++ /dev/null
@@ -1,142 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-
-function test()
-{
-  //////////////////////////////////////////////////////////////////////////////
-  //// Tests (defined locally for scope's sake)
-
-  function test_checkedAndDisabledAtStart(aWin)
-  {
-    let doc = aWin.document;
-    let downloads = doc.getElementById("downloads-checkbox");
-    let history = doc.getElementById("history-checkbox");
-
-    ok(history.checked, "history checkbox is checked");
-    ok(downloads.disabled, "downloads checkbox is disabled");
-    ok(downloads.checked, "downloads checkbox is checked");
-  }
-
-  function test_checkedAndDisabledOnHistoryToggle(aWin)
-  {
-    let doc = aWin.document;
-    let downloads = doc.getElementById("downloads-checkbox");
-    let history = doc.getElementById("history-checkbox");
-
-    EventUtils.synthesizeMouse(history, 0, 0, {}, aWin);
-    ok(!history.checked, "history checkbox is not checked");
-    ok(downloads.disabled, "downloads checkbox is disabled");
-    ok(downloads.checked, "downloads checkbox is checked");
-  }
-
-  function test_checkedAfterAddingDownload(aWin)
-  {
-    let doc = aWin.document;
-    let downloads = doc.getElementById("downloads-checkbox");
-    let history = doc.getElementById("history-checkbox");
-
-    // Add download to DB
-    let file = Cc["@mozilla.org/file/directory_service;1"].
-               getService(Ci.nsIProperties).get("TmpD", Ci.nsIFile);
-    file.append("sanitize-dm-test.file");
-    file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0666);
-    let testPath = Services.io.newFileURI(file).spec;
-    let data = {
-      name: "381603.patch",
-      source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520",
-      target: testPath,
-      startTime: 1180493839859230,
-      endTime: 1180493839859239,
-      state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
-      currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
-      guid: "a1bcD23eF4g5"
-    };
-    let db = Cc["@mozilla.org/download-manager;1"].
-             getService(Ci.nsIDownloadManager).DBConnection;
-    let stmt = db.createStatement(
-      "INSERT INTO moz_downloads (name, source, target, startTime, endTime, " +
-        "state, currBytes, maxBytes, preferredAction, autoResume, guid) " +
-      "VALUES (:name, :source, :target, :startTime, :endTime, :state, " +
-        ":currBytes, :maxBytes, :preferredAction, :autoResume, :guid)");
-    try {
-      for (let prop in data)
-        stmt.params[prop] = data[prop];
-      stmt.execute();
-    }
-    finally {
-      stmt.finalize();
-    }
-
-    // Toggle history to get everything to update
-    EventUtils.synthesizeMouse(history, 0, 0, {}, aWin);
-    EventUtils.synthesizeMouse(history, 0, 0, {}, aWin);
-
-    ok(!history.checked, "history checkbox is not checked");
-    ok(!downloads.disabled, "downloads checkbox is not disabled");
-    ok(downloads.checked, "downloads checkbox is checked");
-  }
-
-  function test_checkedAndDisabledWithHistoryChecked(aWin)
-  {
-    let doc = aWin.document;
-    let downloads = doc.getElementById("downloads-checkbox");
-    let history = doc.getElementById("history-checkbox");
-
-    EventUtils.synthesizeMouse(history, 0, 0, {}, aWin);
-    ok(history.checked, "history checkbox is checked");
-    ok(downloads.disabled, "downloads checkbox is disabled");
-    ok(downloads.checked, "downloads checkbox is checked");
-  }
-
-  let tests = [
-    test_checkedAndDisabledAtStart,
-    test_checkedAndDisabledOnHistoryToggle,
-    test_checkedAfterAddingDownload,
-    test_checkedAndDisabledWithHistoryChecked,
-  ];
-
-  //////////////////////////////////////////////////////////////////////////////
-  //// Run the tests
-
-  let dm = Cc["@mozilla.org/download-manager;1"].
-           getService(Ci.nsIDownloadManager);
-  let db = dm.DBConnection;
-
-  // Empty any old downloads
-  db.executeSimpleSQL("DELETE FROM moz_downloads");
-
-  // Close the UI if necessary
-  let win = Services.ww.getWindowByName("Sanitize", null);
-  if (win && (win instanceof Ci.nsIDOMWindow))
-    win.close();
-
-  // Start the test when the sanitize window loads
-  Services.ww.registerNotification(function (aSubject, aTopic, aData) {
-    Services.ww.unregisterNotification(arguments.callee);
-    aSubject.QueryInterface(Ci.nsIDOMEventTarget)
-            .addEventListener("DOMContentLoaded", doTest, false);
-  });
-
-  // Let the methods that run onload finish before we test
-  let doTest = function() setTimeout(function() {
-    let win = Services.ww.getWindowByName("Sanitize", null)
-                .QueryInterface(Ci.nsIDOMWindow);
-
-    for (let i = 0; i < tests.length; i++)
-      tests[i](win);
-
-    win.close();
-    finish();
-  }, 0);
- 
-  // Show the UI
-  Services.ww.openWindow(window,
-                         "chrome://browser/content/sanitize.xul",
-                         "Sanitize",
-                         "chrome,titlebar,centerscreen",
-                         null);
-
-  waitForExplicitFinish();
-}
deleted file mode 100644
--- a/browser/base/content/test/general/browser_sanitizeDialog_treeView.js
+++ /dev/null
@@ -1,632 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/**
- * Tests the sanitize dialog (a.k.a. the clear recent history dialog).
- * See bug 480169.
- *
- * The purpose of this test is not to fully flex the sanitize timespan code;
- * browser/base/content/test/general/browser_sanitize-timespans.js does that.  This
- * test checks the UI of the dialog and makes sure it's correctly connected to
- * the sanitize timespan code.
- *
- * Some of this code, especially the history creation parts, was taken from
- * browser/base/content/test/general/browser_sanitize-timespans.js.
- */
-
-Cc["@mozilla.org/moz/jssubscript-loader;1"].
-  getService(Ci.mozIJSSubScriptLoader).
-  loadSubScript("chrome://browser/content/sanitize.js");
-
-const dm = Cc["@mozilla.org/download-manager;1"].
-           getService(Ci.nsIDownloadManager);
-const formhist = Cc["@mozilla.org/satchel/form-history;1"].
-                 getService(Ci.nsIFormHistory2);
-
-// Add tests here.  Each is a function that's called by doNextTest().
-var gAllTests = [
-
-  /**
-   * Moves the grippy around, makes sure it works OK.
-   */
-  function () {
-    // Add history (within the past hour) to get some rows in the tree.
-    let uris = [];
-    let places = [];
-    let pURI;
-    for (let i = 0; i < 30; i++) {
-      pURI = makeURI("http://" + i + "-minutes-ago.com/");
-      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
-      uris.push(pURI);
-    }
-
-    addVisits(places, function() {
-      // Open the dialog and do our tests.
-      openWindow(function (aWin) {
-        let wh = new WindowHelper(aWin);
-        wh.selectDuration(Sanitizer.TIMESPAN_HOUR);
-        wh.checkGrippy("Grippy should be at last row after selecting HOUR " +
-                       "duration",
-                       wh.getRowCount() - 1);
-
-        // Move the grippy around.
-        let row = wh.getGrippyRow();
-        while (row !== 0) {
-          row--;
-          wh.moveGrippyBy(-1);
-          wh.checkGrippy("Grippy should be moved up one row", row);
-        }
-        wh.moveGrippyBy(-1);
-        wh.checkGrippy("Grippy should remain at first row after trying to move " +
-                       "it up",
-                       0);
-        while (row !== wh.getRowCount() - 1) {
-          row++;
-          wh.moveGrippyBy(1);
-          wh.checkGrippy("Grippy should be moved down one row", row);
-        }
-        wh.moveGrippyBy(1);
-        wh.checkGrippy("Grippy should remain at last row after trying to move " +
-                       "it down",
-                       wh.getRowCount() - 1);
-
-        // Cancel the dialog, make sure history visits are not cleared.
-        wh.checkPrefCheckbox("history", false);
-
-        wh.cancelDialog();
-        yield promiseHistoryClearedState(uris, false);
-
-        // OK, done, cleanup after ourselves.
-        blankSlate();
-        yield promiseHistoryClearedState(uris, true);
-      });
-    });
-  },
-
-  /**
-   * Ensures that the combined history-downloads checkbox clears both history
-   * visits and downloads when checked; the dialog respects simple timespan.
-   */
-  function () {
-    // Add history (within the past hour).
-    let uris = [];
-    let places = [];
-    let pURI;
-    for (let i = 0; i < 30; i++) {
-      pURI = makeURI("http://" + i + "-minutes-ago.com/");
-      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
-      uris.push(pURI);
-    }
-    // Add history (over an hour ago).
-    let olderURIs = [];
-    for (let i = 0; i < 5; i++) {
-      pURI = makeURI("http://" + (60 + i) + "-minutes-ago.com/");
-      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(60 + i)});
-      olderURIs.push(pURI);
-    }
-
-    addVisits(places, function() {
-      // Add downloads (within the past hour).
-      let downloadIDs = [];
-      for (let i = 0; i < 5; i++) {
-        downloadIDs.push(addDownloadWithMinutesAgo(i));
-      }
-      // Add downloads (over an hour ago).
-      let olderDownloadIDs = [];
-      for (let i = 0; i < 5; i++) {
-        olderDownloadIDs.push(addDownloadWithMinutesAgo(61 + i));
-      }
-      let totalHistoryVisits = uris.length + olderURIs.length;
-
-      // Open the dialog and do our tests.
-      openWindow(function (aWin) {
-        let wh = new WindowHelper(aWin);
-        wh.selectDuration(Sanitizer.TIMESPAN_HOUR);
-        wh.checkGrippy("Grippy should be at proper row after selecting HOUR " +
-                       "duration",
-                       uris.length);
-
-        // Accept the dialog, make sure history visits and downloads within one
-        // hour are cleared.
-        wh.checkPrefCheckbox("history", true);
-        wh.acceptDialog();
-        yield promiseHistoryClearedState(uris, true);
-        ensureDownloadsClearedState(downloadIDs, true);
-
-        // Make sure visits and downloads > 1 hour still exist.
-        yield promiseHistoryClearedState(olderURIs, false);
-        ensureDownloadsClearedState(olderDownloadIDs, false);
-
-        // OK, done, cleanup after ourselves.
-        blankSlate();
-        yield promiseHistoryClearedState(olderURIs, true);
-        ensureDownloadsClearedState(olderDownloadIDs, true);
-      });
-    });
-  },
-
-  /**
-   * Ensures that the combined history-downloads checkbox removes neither
-   * history visits nor downloads when not checked.
-   */
-  function () {
-    // Add history, downloads, form entries (within the past hour).
-    let uris = [];
-    let places = [];
-    let pURI;
-    for (let i = 0; i < 5; i++) {
-      pURI = makeURI("http://" + i + "-minutes-ago.com/");
-      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
-      uris.push(pURI);
-    }
-
-    addVisits(places, function() {
-      let downloadIDs = [];
-      for (let i = 0; i < 5; i++) {
-        downloadIDs.push(addDownloadWithMinutesAgo(i));
-      }
-      let formEntries = [];
-      for (let i = 0; i < 5; i++) {
-        formEntries.push(addFormEntryWithMinutesAgo(i));
-      }
-
-      // Open the dialog and do our tests.
-      openWindow(function (aWin) {
-        let wh = new WindowHelper(aWin);
-        wh.selectDuration(Sanitizer.TIMESPAN_HOUR);
-        wh.checkGrippy("Grippy should be at last row after selecting HOUR " +
-                       "duration",
-                       wh.getRowCount() - 1);
-
-        // Remove only form entries, leave history (including downloads).
-        wh.checkPrefCheckbox("history", false);
-        wh.checkPrefCheckbox("formdata", true);
-        wh.acceptDialog();
-
-        // Of the three only form entries should be cleared.
-        yield promiseHistoryClearedState(uris, false);
-        ensureDownloadsClearedState(downloadIDs, false);
-        ensureFormEntriesClearedState(formEntries, true);
-
-        // OK, done, cleanup after ourselves.
-        blankSlate();
-        yield promiseHistoryClearedState(uris, true);
-        ensureDownloadsClearedState(downloadIDs, true);
-      });
-    });
-  },
-
-  /**
-   * Ensures that the "Everything" duration option works.
-   */
-  function () {
-    // Add history.
-    let uris = [];
-    let places = [];
-    let pURI;
-    // within past hour, within past two hours, within past four hours and 
-    // outside past four hours
-    [10, 70, 130, 250].forEach(function(aValue) {
-      pURI = makeURI("http://" + aValue + "-minutes-ago.com/");
-      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)});
-      uris.push(pURI);
-    });
-    addVisits(places, function() {
-
-      // Open the dialog and do our tests.
-      openWindow(function (aWin) {
-        let wh = new WindowHelper(aWin);
-        wh.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
-        wh.checkPrefCheckbox("history", true);
-        wh.acceptDialog();
-        yield promiseHistoryClearedState(uris, true);
-      });
-    });
-  }
-];
-
-// Used as the download database ID for a new download.  Incremented for each
-// new download.  See addDownloadWithMinutesAgo().
-var gDownloadId = 5555551;
-
-// Index in gAllTests of the test currently being run.  Incremented for each
-// test run.  See doNextTest().
-var gCurrTest = 0;
-
-var now_uSec = Date.now() * 1000;
-
-///////////////////////////////////////////////////////////////////////////////
-
-/**
- * This wraps the dialog and provides some convenience methods for interacting
- * with it.
- *
- * A warning:  Before you call any function that uses the tree (or any function
- * that calls a function that uses the tree), you must set a non-everything
- * duration by calling selectDuration().  The dialog does not initialize the
- * tree if it does not yet need to be shown.
- *
- * @param aWin
- *        The dialog's nsIDOMWindow
- */
-function WindowHelper(aWin) {
-  this.win = aWin;
-}
-
-WindowHelper.prototype = {
-  /**
-   * "Presses" the dialog's OK button.
-   */
-  acceptDialog: function () {
-    is(this.win.document.documentElement.getButton("accept").disabled, false,
-       "Dialog's OK button should not be disabled");
-    this.win.document.documentElement.acceptDialog();
-  },
-
-  /**
-   * "Presses" the dialog's Cancel button.
-   */
-  cancelDialog: function () {
-    this.win.document.documentElement.cancelDialog();
-  },
-
-  /**
-   * Ensures that the grippy row is in the right place, tree selection is OK,
-   * and that the grippy's visible.
-   *
-   * @param aMsg
-   *        Passed to is() when checking grippy location
-   * @param aExpectedRow
-   *        The row that the grippy should be at
-   */
-  checkGrippy: function (aMsg, aExpectedRow) {
-    is(this.getGrippyRow(), aExpectedRow, aMsg);
-    this.checkTreeSelection();
-    this.ensureGrippyIsVisible();
-  },
-
-  /**
-   * (Un)checks a history scope checkbox (browser & download history,
-   * form history, etc.).
-   *
-   * @param aPrefName
-   *        The final portion of the checkbox's privacy.cpd.* preference name
-   * @param aCheckState
-   *        True if the checkbox should be checked, false otherwise
-   */
-  checkPrefCheckbox: function (aPrefName, aCheckState) {
-    var pref = "privacy.cpd." + aPrefName;
-    var cb = this.win.document.querySelectorAll(
-               "#itemList > [preference='" + pref + "']");
-    is(cb.length, 1, "found checkbox for " + pref + " preference");
-    if (cb[0].checked != aCheckState)
-      cb[0].click();
-  },
-
-  /**
-   * Ensures that the tree selection is appropriate to the grippy row.  (A
-   * single, contiguous selection should exist from the first row all the way
-   * to the grippy.)
-   */
-  checkTreeSelection: function () {
-    let grippyRow = this.getGrippyRow();
-    let sel = this.getTree().view.selection;
-    if (grippyRow === 0) {
-      is(sel.getRangeCount(), 0,
-         "Grippy row is 0, so no tree selection should exist");
-    }
-    else {
-      is(sel.getRangeCount(), 1,
-         "Grippy row > 0, so only one tree selection range should exist");
-      let min = {};
-      let max = {};
-      sel.getRangeAt(0, min, max);
-      is(min.value, 0, "Tree selection should start at first row");
-      is(max.value, grippyRow - 1,
-         "Tree selection should end at row before grippy");
-    }
-  },
-
-  /**
-   * The grippy should always be visible when it's moved directly.  This method
-   * ensures that.
-   */
-  ensureGrippyIsVisible: function () {
-    let tbo = this.getTree().treeBoxObject;
-    let firstVis = tbo.getFirstVisibleRow();
-    let lastVis = tbo.getLastVisibleRow();
-    let grippyRow = this.getGrippyRow();
-    ok(firstVis <= grippyRow && grippyRow <= lastVis,
-       "Grippy row should be visible; this inequality should be true: " +
-       firstVis + " <= " + grippyRow + " <= " + lastVis);
-  },
-
-  /**
-   * @return The dialog's duration dropdown
-   */
-  getDurationDropdown: function () {
-    return this.win.document.getElementById("sanitizeDurationChoice");
-  },
-
-  /**
-   * @return The grippy row index
-   */
-  getGrippyRow: function () {
-    return this.win.gContiguousSelectionTreeHelper.getGrippyRow();
-  },
-
-  /**
-   * @return The tree's row count (includes the grippy row)
-   */
-  getRowCount: function () {
-    return this.getTree().view.rowCount;
-  },
-
-  /**
-   * @return The tree
-   */
-  getTree: function () {
-    return this.win.gContiguousSelectionTreeHelper.tree;
-  },
-
-  /**
-   * @return True if the "Everything" warning panel is visible (as opposed to
-   *         the tree)
-   */
-  isWarningPanelVisible: function () {
-    return this.win.document.getElementById("durationDeck").selectedIndex == 1;
-  },
-
-  /**
-   * @return True if the tree is visible (as opposed to the warning panel)
-   */
-  isTreeVisible: function () {
-    return this.win.document.getElementById("durationDeck").selectedIndex == 0;
-  },
-
-  /**
-   * Moves the grippy one row at a time in the direction and magnitude specified.
-   * If aDelta < 0, moves the grippy up; if aDelta > 0, moves it down.
-   *
-   * @param aDelta
-   *        The amount and direction to move
-   */
-  moveGrippyBy: function (aDelta) {
-    if (aDelta === 0)
-      return;
-    let key = aDelta < 0 ? "UP" : "DOWN";
-    let abs = Math.abs(aDelta);
-    let treechildren = this.getTree().treeBoxObject.treeBody;
-    treechildren.focus();
-    for (let i = 0; i < abs; i++) {
-      EventUtils.sendKey(key);
-    }
-  },
-
-  /**
-   * Selects a duration in the duration dropdown.
-   *
-   * @param aDurVal
-   *        One of the Sanitizer.TIMESPAN_* values
-   */
-  selectDuration: function (aDurVal) {
-    this.getDurationDropdown().value = aDurVal;
-    if (aDurVal === Sanitizer.TIMESPAN_EVERYTHING) {
-      is(this.isTreeVisible(), false,
-         "Tree should not be visible for TIMESPAN_EVERYTHING");
-      is(this.isWarningPanelVisible(), true,
-         "Warning panel should be visible for TIMESPAN_EVERYTHING");
-    }
-    else {
-      is(this.isTreeVisible(), true,
-         "Tree should be visible for non-TIMESPAN_EVERYTHING");
-      is(this.isWarningPanelVisible(), false,
-         "Warning panel should not be visible for non-TIMESPAN_EVERYTHING");
-    }
-  }
-};
-
-/**
- * Adds a download to history.
- *
- * @param aMinutesAgo
- *        The download will be downloaded this many minutes ago
- */
-function addDownloadWithMinutesAgo(aMinutesAgo) {
-  let name = "fakefile-" + aMinutesAgo + "-minutes-ago";
-  let data = {
-    id:        gDownloadId,
-    name:      name,
-    source:   "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
-    target:    name,
-    startTime: now_uSec - (aMinutesAgo * 60 * 1000000),
-    endTime:   now_uSec - ((aMinutesAgo + 1) *60 * 1000000),
-    state:     Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
-    currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
-    guid: "a1bcD23eF4g5"
-  };
-
-  let db = dm.DBConnection;
-  let stmt = db.createStatement(
-    "INSERT INTO moz_downloads (id, name, source, target, startTime, endTime, " +
-      "state, currBytes, maxBytes, preferredAction, autoResume, guid) " +
-    "VALUES (:id, :name, :source, :target, :startTime, :endTime, :state, " +
-      ":currBytes, :maxBytes, :preferredAction, :autoResume, :guid)");
-  try {
-    for (let prop in data) {
-      stmt.params[prop] = data[prop];
-    }
-    stmt.execute();
-  }
-  finally {
-    stmt.reset();
-  }
-
-  is(downloadExists(gDownloadId), true,
-     "Sanity check: download " + gDownloadId +
-     " should exist after creating it");
-
-  return gDownloadId++;
-}
-
-/**
- * Adds a form entry to history.
- *
- * @param aMinutesAgo
- *        The entry will be added this many minutes ago
- */
-function addFormEntryWithMinutesAgo(aMinutesAgo) {
-  let name = aMinutesAgo + "-minutes-ago";
-  formhist.addEntry(name, "dummy");
-
-  // Artifically age the entry to the proper vintage.
-  let db = formhist.DBConnection;
-  let timestamp = now_uSec - (aMinutesAgo * 60 * 1000000);
-  db.executeSimpleSQL("UPDATE moz_formhistory SET firstUsed = " +
-                      timestamp +  " WHERE fieldname = '" + name + "'");
-
-  is(formhist.nameExists(name), true,
-     "Sanity check: form entry " + name + " should exist after creating it");
-  return name;
-}
-
-/**
- * Removes all history visits, downloads, and form entries.
- */
-function blankSlate() {
-  PlacesUtils.bhistory.removeAllPages();
-  dm.cleanUp();
-  formhist.removeAllEntries();
-}
-
-/**
- * Checks to see if the download with the specified ID exists.
- *
- * @param  aID
- *         The ID of the download to check
- * @return True if the download exists, false otherwise
- */
-function downloadExists(aID)
-{
-  let db = dm.DBConnection;
-  let stmt = db.createStatement(
-    "SELECT * " +
-    "FROM moz_downloads " +
-    "WHERE id = :id"
-  );
-  stmt.params.id = aID;
-  let rows = stmt.executeStep();
-  stmt.finalize();
-  return !!rows;
-}
-
-/**
- * Runs the next test in the gAllTests array.  If all tests have been run,
- * finishes the entire suite.
- */
-function doNextTest() {
-  if (gAllTests.length <= gCurrTest) {
-    blankSlate();
-    waitForAsyncUpdates(finish);
-  }
-  else {
-    let ct = gCurrTest;
-    gCurrTest++;
-    gAllTests[ct]();
-  }
-}
-
-/**
- * Ensures that the specified downloads are either cleared or not.
- *
- * @param aDownloadIDs
- *        Array of download database IDs
- * @param aShouldBeCleared
- *        True if each download should be cleared, false otherwise
- */
-function ensureDownloadsClearedState(aDownloadIDs, aShouldBeCleared) {
-  let niceStr = aShouldBeCleared ? "no longer" : "still";
-  aDownloadIDs.forEach(function (id) {
-    is(downloadExists(id), !aShouldBeCleared,
-       "download " + id + " should " + niceStr + " exist");
-  });
-}
-
-/**
- * Ensures that the specified form entries are either cleared or not.
- *
- * @param aFormEntries
- *        Array of form entry names
- * @param aShouldBeCleared
- *        True if each form entry should be cleared, false otherwise
- */
-function ensureFormEntriesClearedState(aFormEntries, aShouldBeCleared) {
-  let niceStr = aShouldBeCleared ? "no longer" : "still";
-  aFormEntries.forEach(function (entry) {
-    is(formhist.nameExists(entry), !aShouldBeCleared,
-       "form entry " + entry + " should " + niceStr + " exist");
-  });
-}
-
-/**
- * Opens the sanitize dialog and runs a callback once it's finished loading.
- * 
- * @param aOnloadCallback
- *        A function that will be called once the dialog has loaded
- */
-function openWindow(aOnloadCallback) {
-  function windowObserver(aSubject, aTopic, aData) {
-    if (aTopic != "domwindowopened")
-      return;
-
-    Services.ww.unregisterNotification(windowObserver);
-    let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
-    win.addEventListener("load", function onload(event) {
-      win.removeEventListener("load", onload, false);
-      executeSoon(function () {
-        // Some exceptions that reach here don't reach the test harness, but
-        // ok()/is() do...
-        try {
-          Task.spawn(function() {
-            aOnloadCallback(win);
-          }).then(function() {
-            waitForAsyncUpdates(doNextTest);
-          });
-        }
-        catch (exc) {
-          win.close();
-          ok(false, "Unexpected exception: " + exc + "\n" + exc.stack);
-          finish();
-        }
-      });
-    }, false);
-  }
-  Services.ww.registerNotification(windowObserver);
-  Services.ww.openWindow(null,
-                         "chrome://browser/content/sanitize.xul",
-                         "Sanitize",
-                         "chrome,titlebar,dialog,centerscreen,modal",
-                         null);
-}
-
-/**
- * Creates a visit time.
- *
- * @param aMinutesAgo
- *        The visit will be visited this many minutes ago
- */
-function visitTimeForMinutesAgo(aMinutesAgo) {
-  return now_uSec - (aMinutesAgo * 60 * 1000000);
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-function test() {
-  blankSlate();
-  waitForExplicitFinish();
-  // Kick off all the tests in the gAllTests array.
-  waitForAsyncUpdates(doNextTest);
-}
--- a/browser/components/preferences/main.js
+++ b/browser/components/preferences/main.js
@@ -64,17 +64,17 @@ var gMainPane = {
     let separateProfileModeCheckbox = document.getElementById("separateProfileMode");
     let listener = gMainPane.separateProfileModeChange.bind(gMainPane);
     separateProfileModeCheckbox.addEventListener("command", listener);
 
     let getStartedLink = document.getElementById("getStarted");
     let syncListener = gMainPane.onGetStarted.bind(gMainPane);
     getStartedLink.addEventListener("click", syncListener);
 
-    Cu.import("resource://gre/modules/osfile.jsm");
+    Components.utils.import("resource://gre/modules/osfile.jsm");
     let uAppData = OS.Constants.Path.userApplicationDataDir;
     let ignoreSeparateProfile = OS.Path.join(uAppData, "ignore-dev-edition-profile");
 
     OS.File.stat(ignoreSeparateProfile).then(() => separateProfileModeCheckbox.checked = false,
                                              () => separateProfileModeCheckbox.checked = true);
 #endif
 
     // Notify observers that the UI is now ready
@@ -82,42 +82,43 @@ var gMainPane = {
               .getService(Components.interfaces.nsIObserverService)
               .notifyObservers(window, "main-pane-loaded", null);
   },
 
 #ifdef MOZ_DEV_EDITION
   separateProfileModeChange: function ()
   {
     function quitApp() {
-      Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit |  Ci.nsIAppStartup.eRestartNotSameProfile);
+      Services.startup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit |
+                            Components.interfaces.nsIAppStartup.eRestartNotSameProfile);
     }
     function revertCheckbox(error) {
       separateProfileModeCheckbox.checked = !separateProfileModeCheckbox.checked;
       if (error) {
-        Cu.reportError("Failed to toggle separate profile mode: " + error);
+        Components.utils.reportError("Failed to toggle separate profile mode: " + error);
       }
     }
 
     let separateProfileModeCheckbox = document.getElementById("separateProfileMode");
     let brandName = document.getElementById("bundleBrand").getString("brandShortName");
     let bundle = document.getElementById("bundlePreferences");
     let msg = bundle.getFormattedString(separateProfileModeCheckbox.checked ?
                                         "featureEnableRequiresRestart" : "featureDisableRequiresRestart",
                                         [brandName]);
     let title = bundle.getFormattedString("shouldRestartTitle", [brandName]);
     let shouldProceed = Services.prompt.confirm(window, title, msg)
     if (shouldProceed) {
       let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
-                         .createInstance(Ci.nsISupportsPRBool);
+                         .createInstance(Components.interfaces.nsISupportsPRBool);
       Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
                                    "restart");
       shouldProceed = !cancelQuit.data;
 
       if (shouldProceed) {
-        Cu.import("resource://gre/modules/osfile.jsm");
+        Components.utils.import("resource://gre/modules/osfile.jsm");
         let uAppData = OS.Constants.Path.userApplicationDataDir;
         let ignoreSeparateProfile = OS.Path.join(uAppData, "ignore-dev-edition-profile");
 
         if (separateProfileModeCheckbox.checked) {
           OS.File.remove(ignoreSeparateProfile).then(quitApp, revertCheckbox);
         } else {
           OS.File.writeAtomic(ignoreSeparateProfile, new Uint8Array()).then(quitApp, revertCheckbox);
         }
@@ -126,17 +127,17 @@ var gMainPane = {
 
     // Revert the checkbox in case we didn't quit
     revertCheckbox();
   },
 
   onGetStarted: function (aEvent) {
     const Cc = Components.classes, Ci = Components.interfaces;
     let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
-               .getService(Ci.nsIWindowMediator);
+               .getService(Components.interfaces.nsIWindowMediator);
     let win = wm.getMostRecentWindow("navigator:browser");
 
     if (win) {
       let accountsTab = win.gBrowser.addTab("about:accounts");
       win.gBrowser.selectedTab = accountsTab;
     }
   },
 #endif
@@ -249,17 +250,17 @@ var gMainPane = {
   _getTabsForHomePage: function ()
   {
     var win;
     var tabs = [];
     if (document.documentElement.instantApply) {
       const Cc = Components.classes, Ci = Components.interfaces;
       // If we're in instant-apply mode, use the most recent browser window
       var wm = Cc["@mozilla.org/appshell/window-mediator;1"]
-                 .getService(Ci.nsIWindowMediator);
+                 .getService(Components.interfaces.nsIWindowMediator);
       win = wm.getMostRecentWindow("navigator:browser");
     }
     else {
       win = window.opener;
     }
 
     if (win && win.document.documentElement
                   .getAttribute("windowtype") == "navigator:browser") {
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -84,18 +84,16 @@ public:
   void Activate(already_AddRefed<SourceMediaStream> aStream,
     MediaEngineSource* aAudioSource,
     MediaEngineSource* aVideoSource)
   {
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
     mStream = aStream;
     mAudioSource = aAudioSource;
     mVideoSource = aVideoSource;
-    mLastEndTimeAudio = 0;
-    mLastEndTimeVideo = 0;
 
     mStream->AddListener(this);
   }
 
   MediaStream *Stream() // Can be used to test if Activate was called
   {
     return mStream;
   }
@@ -182,20 +180,20 @@ public:
 
   // Proxy NotifyPull() to sources
   virtual void
   NotifyPull(MediaStreamGraph* aGraph, StreamTime aDesiredTime) MOZ_OVERRIDE
   {
     // Currently audio sources ignore NotifyPull, but they could
     // watch it especially for fake audio.
     if (mAudioSource) {
-      mAudioSource->NotifyPull(aGraph, mStream, kAudioTrack, aDesiredTime, mLastEndTimeAudio);
+      mAudioSource->NotifyPull(aGraph, mStream, kAudioTrack, aDesiredTime);
     }
     if (mVideoSource) {
-      mVideoSource->NotifyPull(aGraph, mStream, kVideoTrack, aDesiredTime, mLastEndTimeVideo);
+      mVideoSource->NotifyPull(aGraph, mStream, kVideoTrack, aDesiredTime);
     }
   }
 
   virtual void
   NotifyEvent(MediaStreamGraph* aGraph,
               MediaStreamListener::MediaStreamGraphEvent aEvent) MOZ_OVERRIDE
   {
     switch (aEvent) {
@@ -234,18 +232,16 @@ private:
 
   // Set at Activate on MainThread
 
   // Accessed from MediaStreamGraph thread, MediaManager thread, and MainThread
   // No locking needed as they're only addrefed except on the MediaManager thread
   nsRefPtr<MediaEngineSource> mAudioSource; // threadsafe refcnt
   nsRefPtr<MediaEngineSource> mVideoSource; // threadsafe refcnt
   nsRefPtr<SourceMediaStream> mStream; // threadsafe refcnt
-  StreamTime mLastEndTimeAudio;
-  StreamTime mLastEndTimeVideo;
   bool mFinished;
 
   // Accessed from MainThread and MSG thread
   Mutex mLock; // protects mRemoved access from MainThread
   bool mRemoved;
 };
 
 class GetUserMediaNotificationEvent: public nsRunnable
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -201,27 +201,29 @@ MediaStreamGraphImpl::ExtractPendingInpu
                                     offset, data->mCommands, *data->mData);
       }
       if (data->mCommands & SourceMediaStream::TRACK_CREATE) {
         MediaSegment* segment = data->mData.forget();
         STREAM_LOG(PR_LOG_DEBUG, ("SourceMediaStream %p creating track %d, start %lld, initial end %lld",
                                   aStream, data->mID, int64_t(data->mStart),
                                   int64_t(segment->GetDuration())));
 
+        data->mEndOfFlushedData += segment->GetDuration();
         aStream->mBuffer.AddTrack(data->mID, data->mStart, segment);
         // The track has taken ownership of data->mData, so let's replace
         // data->mData with an empty clone.
         data->mData = segment->CreateEmptyClone();
         data->mCommands &= ~SourceMediaStream::TRACK_CREATE;
       } else if (data->mData->GetDuration() > 0) {
         MediaSegment* dest = aStream->mBuffer.FindTrack(data->mID)->GetSegment();
         STREAM_LOG(PR_LOG_DEBUG+1, ("SourceMediaStream %p track %d, advancing end from %lld to %lld",
                                     aStream, data->mID,
                                     int64_t(dest->GetDuration()),
                                     int64_t(dest->GetDuration() + data->mData->GetDuration())));
+        data->mEndOfFlushedData += data->mData->GetDuration();
         dest->AppendFrom(data->mData);
       }
       if (data->mCommands & SourceMediaStream::TRACK_END) {
         aStream->mBuffer.FindTrack(data->mID)->SetEnded();
         aStream->mUpdateTracks.RemoveElementAt(i);
       }
     }
     if (!aStream->mFinished) {
@@ -2274,16 +2276,17 @@ void
 SourceMediaStream::AddTrackInternal(TrackID aID, TrackRate aRate, StreamTime aStart,
                                     MediaSegment* aSegment)
 {
   MutexAutoLock lock(mMutex);
   TrackData* data = mUpdateTracks.AppendElement();
   data->mID = aID;
   data->mInputRate = aRate;
   data->mStart = aStart;
+  data->mEndOfFlushedData = aStart;
   data->mCommands = TRACK_CREATE;
   data->mData = aSegment;
   data->mHaveEnough = false;
   if (GraphImpl()) {
     GraphImpl()->EnsureNextIteration();
   }
 }
 
@@ -2432,16 +2435,28 @@ SourceMediaStream::HaveEnoughBuffered(Tr
   MutexAutoLock lock(mMutex);
   TrackData *track = FindDataForTrack(aID);
   if (track) {
     return track->mHaveEnough;
   }
   return false;
 }
 
+StreamTime
+SourceMediaStream::GetEndOfAppendedData(TrackID aID)
+{
+  MutexAutoLock lock(mMutex);
+  TrackData *track = FindDataForTrack(aID);
+  if (track) {
+    return track->mEndOfFlushedData + track->mData->GetDuration();
+  }
+  NS_ERROR("Track not found");
+  return 0;
+}
+
 void
 SourceMediaStream::DispatchWhenNotEnoughBuffered(TrackID aID,
     nsIEventTarget* aSignalThread, nsIRunnable* aSignalRunnable)
 {
   MutexAutoLock lock(mMutex);
   TrackData* data = FindDataForTrack(aID);
   if (!data) {
     aSignalThread->Dispatch(aSignalRunnable, 0);
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -746,16 +746,23 @@ public:
    */
   bool AppendToTrack(TrackID aID, MediaSegment* aSegment, MediaSegment *aRawSegment = nullptr);
   /**
    * Returns true if the buffer currently has enough data.
    * Returns false if there isn't enough data or if no such track exists.
    */
   bool HaveEnoughBuffered(TrackID aID);
   /**
+   * Get the stream time of the end of the data that has been appended so far.
+   * Can be called from any thread but won't be useful if it can race with
+   * an AppendToTrack call, so should probably just be called from the thread
+   * that also calls AppendToTrack.
+   */
+  StreamTime GetEndOfAppendedData(TrackID aID);
+  /**
    * Ensures that aSignalRunnable will be dispatched to aSignalThread
    * when we don't have enough buffered data in the track (which could be
    * immediately). Will dispatch the runnable immediately if the track
    * does not exist. No op if a runnable is already present for this track.
    */
   void DispatchWhenNotEnoughBuffered(TrackID aID,
       nsIEventTarget* aSignalThread, nsIRunnable* aSignalRunnable);
   /**
@@ -843,23 +850,25 @@ protected:
     TrackRate mInputRate;
     // Resampler if the rate of the input track does not match the
     // MediaStreamGraph's.
     nsAutoRef<SpeexResamplerState> mResampler;
 #ifdef DEBUG
     int mResamplerChannelCount;
 #endif
     StreamTime mStart;
-    // Each time the track updates are flushed to the media graph thread,
-    // this is cleared.
-    uint32_t mCommands;
+    // End-time of data already flushed to the track (excluding mData)
+    StreamTime mEndOfFlushedData;
     // Each time the track updates are flushed to the media graph thread,
     // the segment buffer is emptied.
     nsAutoPtr<MediaSegment> mData;
     nsTArray<ThreadAndRunnable> mDispatchWhenNotEnough;
+    // Each time the track updates are flushed to the media graph thread,
+    // this is cleared.
+    uint32_t mCommands;
     bool mHaveEnough;
   };
 
   bool NeedsMixing();
 
   void ResampleAudioToGraphSampleRate(TrackData* aTrackData, MediaSegment* aSegment);
 
   void AddTrackInternal(TrackID aID, TrackRate aRate,
--- a/dom/media/fmp4/android/AndroidDecoderModule.cpp
+++ b/dom/media/fmp4/android/AndroidDecoderModule.cpp
@@ -74,17 +74,17 @@ public:
     if (!EnsureGLContext()) {
       return nullptr;
     }
 
     nsRefPtr<layers::Image> img = mImageContainer->CreateImage(ImageFormat::SURFACE_TEXTURE);
     layers::SurfaceTextureImage::Data data;
     data.mSurfTex = mSurfaceTexture.get();
     data.mSize = gfx::IntSize(mConfig.display_width, mConfig.display_height);
-    data.mInverted = true;
+    data.mOriginPos = gl::OriginPos::BottomLeft;
 
     layers::SurfaceTextureImage* stImg = static_cast<layers::SurfaceTextureImage*>(img.get());
     stImg->SetData(data);
 
     mGLContext->MakeCurrent();
 
     GLuint tex = CreateTextureForOffscreen(mGLContext, mGLContext->GetGLFormats(), data.mSize);
 
@@ -133,17 +133,17 @@ public:
     }
 
     nsRefPtr<layers::Image> img = mImageContainer->CreateImage(ImageFormat::EGLIMAGE);
     layers::EGLImageImage::Data data;
     data.mImage = eglImage;
     data.mSync = eglSync;
     data.mOwns = true;
     data.mSize = gfx::IntSize(mConfig.display_width, mConfig.display_height);
-    data.mInverted = false;
+    data.mOriginPos = gl::OriginPos::TopLeft;
 
     layers::EGLImageImage* typedImg = static_cast<layers::EGLImageImage*>(img.get());
     typedImg->SetData(data);
 
     bool isSync = !!(MediaCodec::getBUFFER_FLAG_SYNC_FRAME() & aInfo->getFlags());
 
     nsRefPtr<VideoData> v = VideoData::CreateFromImage(videoInfo, mImageContainer, aInfo->getOffset(),
                                                        aInfo->getPresentationTimeUs(),
--- a/dom/media/gmp/GMPAudioDecoderParent.cpp
+++ b/dom/media/gmp/GMPAudioDecoderParent.cpp
@@ -144,17 +144,17 @@ GMPAudioDecoderParent::Close()
 
   // Consumer is done with us; we can shut down.  No more callbacks should
   // be made to mCallback.  Note: do this before Shutdown()!
   mCallback = nullptr;
   // Let Shutdown mark us as dead so it knows if we had been alive
 
   // In case this is the last reference
   nsRefPtr<GMPAudioDecoderParent> kungfudeathgrip(this);
-  NS_RELEASE(kungfudeathgrip);
+  Release();
   Shutdown();
 
   return NS_OK;
 }
 
 // Note: Consider keeping ActorDestroy sync'd up when making changes here.
 nsresult
 GMPAudioDecoderParent::Shutdown()
--- a/dom/media/gmp/GMPVideoDecoderParent.cpp
+++ b/dom/media/gmp/GMPVideoDecoderParent.cpp
@@ -71,17 +71,17 @@ GMPVideoDecoderParent::Close()
   MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
   // Consumer is done with us; we can shut down.  No more callbacks should
   // be made to mCallback.  Note: do this before Shutdown()!
   mCallback = nullptr;
   // Let Shutdown mark us as dead so it knows if we had been alive
 
   // In case this is the last reference
   nsRefPtr<GMPVideoDecoderParent> kungfudeathgrip(this);
-  NS_RELEASE(kungfudeathgrip);
+  Release();
   Shutdown();
 }
 
 nsresult
 GMPVideoDecoderParent::InitDecode(const GMPVideoCodec& aCodecSettings,
                                   const nsTArray<uint8_t>& aCodecSpecific,
                                   GMPVideoDecoderCallbackProxy* aCallback,
                                   int32_t aCoreCount)
--- a/dom/media/gmp/GMPVideoEncoderParent.cpp
+++ b/dom/media/gmp/GMPVideoEncoderParent.cpp
@@ -86,17 +86,17 @@ GMPVideoEncoderParent::Close()
   MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
   // Consumer is done with us; we can shut down.  No more callbacks should
   // be made to mCallback.  Note: do this before Shutdown()!
   mCallback = nullptr;
   // Let Shutdown mark us as dead so it knows if we had been alive
 
   // In case this is the last reference
   nsRefPtr<GMPVideoEncoderParent> kungfudeathgrip(this);
-  NS_RELEASE(kungfudeathgrip);
+  Release();
   Shutdown();
 }
 
 GMPErr
 GMPVideoEncoderParent::InitEncode(const GMPVideoCodec& aCodecSettings,
                                   const nsTArray<uint8_t>& aCodecSpecific,
                                   GMPVideoEncoderCallbackProxy* aCallback,
                                   int32_t aNumberOfCores,
--- a/dom/media/mediasource/SourceBuffer.cpp
+++ b/dom/media/mediasource/SourceBuffer.cpp
@@ -44,16 +44,20 @@ void
 SourceBuffer::SetMode(SourceBufferAppendMode aMode, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_API("SourceBuffer(%p)::SetMode(aMode=%d)", this, aMode);
   if (!IsAttached() || mUpdating) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
+  if (aMode == SourceBufferAppendMode::Sequence) {
+    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    return;
+  }
   MOZ_ASSERT(mMediaSource->ReadyState() != MediaSourceReadyState::Closed);
   if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
     mMediaSource->SetReadyState(MediaSourceReadyState::Open);
   }
   // TODO: Test append state.
   // TODO: If aMode is "sequence", set sequence start time.
   mAppendMode = aMode;
 }
--- a/dom/media/mediasource/test/mochitest.ini
+++ b/dom/media/mediasource/test/mochitest.ini
@@ -14,12 +14,13 @@ skip-if = true # bug 1093133
 skip-if = (toolkit == 'android' || buildapp == 'mulet') #timeout android/mulet only bug 1101187
 [test_FrameSelection.html]
 [test_HaveMetadataUnbufferedSeek.html]
 [test_LoadedMetadataFired.html]
 [test_SeekableAfterEndOfStream.html]
 [test_SeekableAfterEndOfStreamSplit.html]
 [test_SeekableBeforeEndOfStream.html]
 [test_SeekableBeforeEndOfStreamSplit.html]
+[test_SetModeThrows.html]
 [test_SplitAppendDelay.html]
 [test_SplitAppend.html]
 [test_WaitingOnMissingData.html]
  skip-if = android_version == '10' # bug 1115148 - frequent failures on Android 2.3
new file mode 100644
--- /dev/null
+++ b/dom/media/mediasource/test/test_SetModeThrows.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>MSE: append initialization only</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="mediasource.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// This test should be removed when we implement sequence mode in bug 1116353 
+runWithMSE(function (ms, v) {
+  ms.addEventListener("sourceopen", function () {
+    var sb = ms.addSourceBuffer("video/webm");
+
+    sb.mode = "segments";
+    ok("true", "Setting to segments does not throw");
+    try {
+      sb.mode = "sequence";
+      ok(false, "Should have thrown");
+    } catch (e) { ok(/supported/.test(e), "Correctly threw not supported: " + e); }
+
+    SimpleTest.finish();
+  });
+});
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/webrtc/MediaEngine.h
+++ b/dom/media/webrtc/MediaEngine.h
@@ -105,18 +105,17 @@ public:
 
   /* tell the source if there are any direct listeners attached */
   virtual void SetDirectListeners(bool) = 0;
 
   /* Called when the stream wants more data */
   virtual void NotifyPull(MediaStreamGraph* aGraph,
                           SourceMediaStream *aSource,
                           TrackID aId,
-                          StreamTime aDesiredTime,
-                          StreamTime &aLastEndTime) = 0;
+                          StreamTime aDesiredTime) = 0;
 
   /* Stop the device and release the corresponding MediaStream */
   virtual nsresult Stop(SourceMediaStream *aSource, TrackID aID) = 0;
 
   /* Change device configuration.  */
   virtual nsresult Config(bool aEchoOn, uint32_t aEcho,
                           bool aAgcOn, uint32_t aAGC,
                           bool aNoiseOn, uint32_t aNoise,
--- a/dom/media/webrtc/MediaEngineDefault.cpp
+++ b/dom/media/webrtc/MediaEngineDefault.cpp
@@ -239,39 +239,36 @@ MediaEngineDefaultVideoSource::Notify(ns
 
   return NS_OK;
 }
 
 void
 MediaEngineDefaultVideoSource::NotifyPull(MediaStreamGraph* aGraph,
                                           SourceMediaStream *aSource,
                                           TrackID aID,
-                                          StreamTime aDesiredTime,
-                                          StreamTime &aLastEndTime)
+                                          StreamTime aDesiredTime)
 {
   // AddTrack takes ownership of segment
   VideoSegment segment;
   MonitorAutoLock lock(mMonitor);
   if (mState != kStarted) {
     return;
   }
 
   // Note: we're not giving up mImage here
   nsRefPtr<layers::Image> image = mImage;
-  StreamTime delta = aDesiredTime - aLastEndTime;
+  StreamTime delta = aDesiredTime - aSource->GetEndOfAppendedData(aID);
 
   if (delta > 0) {
     // nullptr images are allowed
     IntSize size(image ? mOpts.mWidth : 0, image ? mOpts.mHeight : 0);
     segment.AppendFrame(image.forget(), delta, size);
     // This can fail if either a) we haven't added the track yet, or b)
     // we've removed or finished the track.
-    if (aSource->AppendToTrack(aID, &segment)) {
-      aLastEndTime = aDesiredTime;
-    }
+    aSource->AppendToTrack(aID, &segment);
     // Generate null data for fake tracks.
     if (mHasFakeTracks) {
       for (int i = 0; i < kFakeVideoTrackCount; ++i) {
         VideoSegment nullSegment;
         nullSegment.AppendNullData(delta);
         aSource->AppendToTrack(kTrackCount + i, &nullSegment);
       }
     }
--- a/dom/media/webrtc/MediaEngineDefault.h
+++ b/dom/media/webrtc/MediaEngineDefault.h
@@ -48,18 +48,17 @@ public:
   virtual void SetDirectListeners(bool aHasDirectListeners) {};
   virtual nsresult Config(bool aEchoOn, uint32_t aEcho,
                           bool aAgcOn, uint32_t aAGC,
                           bool aNoiseOn, uint32_t aNoise,
                           int32_t aPlayoutDelay) { return NS_OK; };
   virtual void NotifyPull(MediaStreamGraph* aGraph,
                           SourceMediaStream *aSource,
                           TrackID aId,
-                          StreamTime aDesiredTime,
-                          StreamTime &aLastEndTime);
+                          StreamTime aDesiredTime) MOZ_OVERRIDE;
   virtual bool SatisfiesConstraintSets(
       const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets)
   {
     return true;
   }
 
   virtual bool IsFake() {
     return true;
@@ -117,18 +116,17 @@ public:
   virtual void SetDirectListeners(bool aHasDirectListeners) {};
   virtual nsresult Config(bool aEchoOn, uint32_t aEcho,
                           bool aAgcOn, uint32_t aAGC,
                           bool aNoiseOn, uint32_t aNoise,
                           int32_t aPlayoutDelay) { return NS_OK; };
   virtual void NotifyPull(MediaStreamGraph* aGraph,
                           SourceMediaStream *aSource,
                           TrackID aId,
-                          StreamTime aDesiredTime,
-                          StreamTime &aLastEndTime) {}
+                          StreamTime aDesiredTime) MOZ_OVERRIDE {}
 
   virtual bool IsFake() {
     return true;
   }
 
   virtual const MediaSourceType GetMediaSource() {
     return MediaSourceType::Microphone;
   }
--- a/dom/media/webrtc/MediaEngineGonkVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineGonkVideoSource.cpp
@@ -65,29 +65,28 @@ NS_IMPL_RELEASE_INHERITED(MediaEngineGon
 
 // Called if the graph thinks it's running out of buffered video; repeat
 // the last frame for whatever minimum period it think it needs. Note that
 // this means that no *real* frame can be inserted during this period.
 void
 MediaEngineGonkVideoSource::NotifyPull(MediaStreamGraph* aGraph,
                                        SourceMediaStream* aSource,
                                        TrackID aID,
-                                       StreamTime aDesiredTime,
-                                       StreamTime& aLastEndTime)
+                                       StreamTime aDesiredTime)
 {
   VideoSegment segment;
 
   MonitorAutoLock lock(mMonitor);
   // B2G does AddTrack, but holds kStarted until the hardware changes state.
   // So mState could be kReleased here. We really don't care about the state,
   // though.
 
   // Note: we're not giving up mImage here
   nsRefPtr<layers::Image> image = mImage;
-  StreamTime delta = aDesiredTime - aLastEndTime;
+  StreamTime delta = aDesiredTime - aSource->GetEndOfAppendedData(aID);
   LOGFRAME(("NotifyPull, desired = %ld, delta = %ld %s", (int64_t) aDesiredTime,
             (int64_t) delta, image ? "" : "<null>"));
 
   // Bug 846188 We may want to limit incoming frames to the requested frame rate
   // mFps - if you want 30FPS, and the camera gives you 60FPS, this could
   // cause issues.
   // We may want to signal if the actual frame rate is below mMinFPS -
   // cameras often don't return the requested frame rate especially in low
@@ -97,19 +96,17 @@ MediaEngineGonkVideoSource::NotifyPull(M
   // Don't append if we've already provided a frame that supposedly goes past the current aDesiredTime
   // Doing so means a negative delta and thus messes up handling of the graph
   if (delta > 0) {
     // nullptr images are allowed
     IntSize size(image ? mWidth : 0, image ? mHeight : 0);
     segment.AppendFrame(image.forget(), delta, size);
     // This can fail if either a) we haven't added the track yet, or b)
     // we've removed or finished the track.
-    if (aSource->AppendToTrack(aID, &(segment))) {
-      aLastEndTime = aDesiredTime;
-    }
+    aSource->AppendToTrack(aID, &(segment));
   }
 }
 
 void
 MediaEngineGonkVideoSource::ChooseCapability(const VideoTrackConstraintsN& aConstraints,
                                              const MediaEnginePrefs& aPrefs)
 {
   return GuessCapability(aConstraints, aPrefs);
--- a/dom/media/webrtc/MediaEngineGonkVideoSource.h
+++ b/dom/media/webrtc/MediaEngineGonkVideoSource.h
@@ -63,18 +63,17 @@ public:
   virtual nsresult Allocate(const VideoTrackConstraintsN &aConstraints,
                             const MediaEnginePrefs &aPrefs) MOZ_OVERRIDE;
   virtual nsresult Deallocate() MOZ_OVERRIDE;
   virtual nsresult Start(SourceMediaStream* aStream, TrackID aID) MOZ_OVERRIDE;
   virtual nsresult Stop(SourceMediaStream* aSource, TrackID aID) MOZ_OVERRIDE;
   virtual void NotifyPull(MediaStreamGraph* aGraph,
                           SourceMediaStream* aSource,
                           TrackID aId,
-                          StreamTime aDesiredTime,
-                          StreamTime &aLastEndTime) MOZ_OVERRIDE;
+                          StreamTime aDesiredTime) MOZ_OVERRIDE;
   virtual bool SatisfiesConstraintSets(
       const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets)
   {
     return true;
   }
 
   void OnHardwareStateChange(HardwareState aState, nsresult aReason) MOZ_OVERRIDE;
   void GetRotation();
--- a/dom/media/webrtc/MediaEngineTabVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.cpp
@@ -187,34 +187,31 @@ MediaEngineTabVideoSource::Start(SourceM
   aStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
 
   return NS_OK;
 }
 
 void
 MediaEngineTabVideoSource::NotifyPull(MediaStreamGraph*,
                                       SourceMediaStream* aSource,
-                                      TrackID aID, StreamTime aDesiredTime,
-                                      StreamTime& aLastEndTime)
+                                      TrackID aID, StreamTime aDesiredTime)
 {
   VideoSegment segment;
   MonitorAutoLock mon(mMonitor);
 
   // Note: we're not giving up mImage here
   nsRefPtr<layers::CairoImage> image = mImage;
-  StreamTime delta = aDesiredTime - aLastEndTime;
+  StreamTime delta = aDesiredTime - aSource->GetEndOfAppendedData(aID);
   if (delta > 0) {
     // nullptr images are allowed
     gfx::IntSize size = image ? image->GetSize() : IntSize(0, 0);
     segment.AppendFrame(image.forget().downcast<layers::Image>(), delta, size);
     // This can fail if either a) we haven't added the track yet, or b)
     // we've removed or finished the track.
-    if (aSource->AppendToTrack(aID, &(segment))) {
-      aLastEndTime = aDesiredTime;
-    }
+    aSource->AppendToTrack(aID, &(segment));
   }
 }
 
 void
 MediaEngineTabVideoSource::Draw() {
 
   IntSize size(mBufW, mBufH);
 
--- a/dom/media/webrtc/MediaEngineTabVideoSource.h
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.h
@@ -20,17 +20,17 @@ class MediaEngineTabVideoSource : public
 
     virtual void GetName(nsAString_internal&);
     virtual void GetUUID(nsAString_internal&);
     virtual nsresult Allocate(const VideoTrackConstraintsN &,
                               const mozilla::MediaEnginePrefs&);
     virtual nsresult Deallocate();
     virtual nsresult Start(mozilla::SourceMediaStream*, mozilla::TrackID);
     virtual void SetDirectListeners(bool aHasDirectListeners) {};
-    virtual void NotifyPull(mozilla::MediaStreamGraph*, mozilla::SourceMediaStream*, mozilla::TrackID, mozilla::StreamTime, mozilla::StreamTime&);
+    virtual void NotifyPull(mozilla::MediaStreamGraph*, mozilla::SourceMediaStream*, mozilla::TrackID, mozilla::StreamTime) MOZ_OVERRIDE;
     virtual nsresult Stop(mozilla::SourceMediaStream*, mozilla::TrackID);
     virtual nsresult Config(bool, uint32_t, bool, uint32_t, bool, uint32_t, int32_t);
     virtual bool IsFake();
     virtual const MediaSourceType GetMediaSource() {
       return MediaSourceType::Browser;
     }
     virtual bool SatisfiesConstraintSets(
       const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets)
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -92,18 +92,17 @@ public:
   virtual nsresult Allocate(const VideoTrackConstraintsN& aConstraints,
                             const MediaEnginePrefs& aPrefs);
   virtual nsresult Deallocate();
   virtual nsresult Start(SourceMediaStream*, TrackID);
   virtual nsresult Stop(SourceMediaStream*, TrackID);
   virtual void NotifyPull(MediaStreamGraph* aGraph,
                           SourceMediaStream* aSource,
                           TrackID aId,
-                          StreamTime aDesiredTime,
-                          StreamTime &aLastEndTime);
+                          StreamTime aDesiredTime) MOZ_OVERRIDE;
 
   virtual const MediaSourceType GetMediaSource() {
     return mMediaSource;
   }
   virtual nsresult TakePhoto(PhotoCallback* aCallback)
   {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
@@ -175,18 +174,17 @@ public:
   virtual nsresult Config(bool aEchoOn, uint32_t aEcho,
                           bool aAgcOn, uint32_t aAGC,
                           bool aNoiseOn, uint32_t aNoise,
                           int32_t aPlayoutDelay);
 
   virtual void NotifyPull(MediaStreamGraph* aGraph,
                           SourceMediaStream* aSource,
                           TrackID aId,
-                          StreamTime aDesiredTime,
-                          StreamTime &aLastEndTime);
+                          StreamTime aDesiredTime) MOZ_OVERRIDE;
 
   virtual bool IsFake() {
     return false;
   }
 
   virtual const MediaSourceType GetMediaSource() {
     return MediaSourceType::Microphone;
   }
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -393,26 +393,20 @@ MediaEngineWebRTCAudioSource::Stop(Sourc
   }
   return NS_OK;
 }
 
 void
 MediaEngineWebRTCAudioSource::NotifyPull(MediaStreamGraph* aGraph,
                                          SourceMediaStream *aSource,
                                          TrackID aID,
-                                         StreamTime aDesiredTime,
-                                         StreamTime &aLastEndTime)
+                                         StreamTime aDesiredTime)
 {
   // Ignore - we push audio data
-#ifdef DEBUG
-  StreamTime delta = aDesiredTime - aLastEndTime;
-  LOG(("Audio: NotifyPull: aDesiredTime %ld, delta %ld",(int64_t) aDesiredTime,
-       (int64_t) delta));
-  aLastEndTime = aDesiredTime;
-#endif
+  LOG_FRAMES(("NotifyPull, desired = %ld", (int64_t) aDesiredTime));
 }
 
 void
 MediaEngineWebRTCAudioSource::Init()
 {
   mVoEBase = webrtc::VoEBase::GetInterface(mVoiceEngine);
 
   mVoEBase->Init();
--- a/dom/media/webrtc/MediaEngineWebRTCVideo.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCVideo.cpp
@@ -119,45 +119,42 @@ MediaEngineWebRTCVideoSource::DeliverFra
 
 // Called if the graph thinks it's running out of buffered video; repeat
 // the last frame for whatever minimum period it think it needs.  Note that
 // this means that no *real* frame can be inserted during this period.
 void
 MediaEngineWebRTCVideoSource::NotifyPull(MediaStreamGraph* aGraph,
                                          SourceMediaStream* aSource,
                                          TrackID aID,
-                                         StreamTime aDesiredTime,
-                                         StreamTime &aLastEndTime)
+                                         StreamTime aDesiredTime)
 {
   VideoSegment segment;
 
   MonitorAutoLock lock(mMonitor);
   // B2G does AddTrack, but holds kStarted until the hardware changes state.
   // So mState could be kReleased here.  We really don't care about the state,
   // though.
 
-  StreamTime delta = aDesiredTime - aLastEndTime;
+  StreamTime delta = aDesiredTime - aSource->GetEndOfAppendedData(aID);
   LOGFRAME(("NotifyPull, desired = %ld, delta = %ld %s", (int64_t) aDesiredTime,
             (int64_t) delta, mImage.get() ? "" : "<null>"));
 
   // Bug 846188 We may want to limit incoming frames to the requested frame rate
   // mFps - if you want 30FPS, and the camera gives you 60FPS, this could
   // cause issues.
   // We may want to signal if the actual frame rate is below mMinFPS -
   // cameras often don't return the requested frame rate especially in low
   // light; we should consider surfacing this so that we can switch to a
   // lower resolution (which may up the frame rate)
 
   // Don't append if we've already provided a frame that supposedly goes past the current aDesiredTime
   // Doing so means a negative delta and thus messes up handling of the graph
   if (delta > 0) {
     // nullptr images are allowed
-    if (AppendToTrack(aSource, mImage, aID, delta)) {
-      aLastEndTime = aDesiredTime;
-    }
+    AppendToTrack(aSource, mImage, aID, delta);
   }
 }
 
 /*static*/
 bool
 MediaEngineWebRTCVideoSource::SatisfiesConstraintSet(const MediaTrackConstraintSet &aConstraints,
                                                      const webrtc::CaptureCapability& aCandidate) {
   if (!MediaEngineCameraVideoSource::IsWithin(aCandidate.width, aConstraints.mWidth) ||
--- a/dom/plugins/base/android/ANPNativeWindow.cpp
+++ b/dom/plugins/base/android/ANPNativeWindow.cpp
@@ -8,29 +8,33 @@
 #include "AndroidBridge.h"
 #include "ANPBase.h"
 #include "nsIPluginInstanceOwner.h"
 #include "nsPluginInstanceOwner.h"
 #include "nsNPAPIPluginInstance.h"
 #include "gfxRect.h"
 
 using namespace mozilla;
-using namespace mozilla;
 
 #define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args)
 #define ASSIGN(obj, name)   (obj)->name = anp_native_window_##name
 
 static ANPNativeWindow anp_native_window_acquireNativeWindow(NPP instance) {
   nsNPAPIPluginInstance* pinst = static_cast<nsNPAPIPluginInstance*>(instance->ndata);
   return pinst->AcquireContentWindow();
 }
 
 static void anp_native_window_invertPluginContent(NPP instance, bool isContentInverted) {
+    // NativeWindow is TopLeft if uninverted.
+  gl::OriginPos newOriginPos = gl::OriginPos::TopLeft;
+  if (isContentInverted)
+    newOriginPos = gl::OriginPos::BottomLeft;
+
   nsNPAPIPluginInstance* pinst = static_cast<nsNPAPIPluginInstance*>(instance->ndata);
-  pinst->SetInverted(isContentInverted);
+  pinst->SetOriginPos(newOriginPos);
   pinst->RedrawPlugin();
 }
 
 
 void InitNativeWindowInterface(ANPNativeWindowInterfaceV0* i) {
     ASSIGN(i, acquireNativeWindow);
     ASSIGN(i, invertPluginContent);
 }
--- a/dom/plugins/base/android/ANPOpenGL.cpp
+++ b/dom/plugins/base/android/ANPOpenGL.cpp
@@ -55,20 +55,24 @@ static void anp_opengl_releaseTexture(NP
     nsNPAPIPluginInstance* pinst = static_cast<nsNPAPIPluginInstance*>(instance->ndata);
 
     TextureInfo pluginInfo(info->textureId, info->width, info->height, info->internalFormat);
     pinst->ReleaseContentTexture(pluginInfo);
     pinst->RedrawPlugin();
 }
 
 static void anp_opengl_invertPluginContent(NPP instance, bool isContentInverted) {
+    // OpenGL is BottomLeft if uninverted.
+    gl::OriginPos newOriginPos = gl::OriginPos::BottomLeft;
+    if (isContentInverted)
+        newOriginPos = gl::OriginPos::TopLeft;
+
     nsNPAPIPluginInstance* pinst = static_cast<nsNPAPIPluginInstance*>(instance->ndata);
 
-    // Our definition of inverted is the opposite of the plugin's
-    pinst->SetInverted(!isContentInverted);
+    pinst->SetOriginPos(newOriginPos);
     pinst->RedrawPlugin();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
 void InitOpenGLInterface(ANPOpenGLInterfaceV0* i) {
     ASSIGN(i, acquireContext);
     ASSIGN(i, lockTexture);
--- a/dom/plugins/base/nsNPAPIPluginInstance.cpp
+++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp
@@ -174,17 +174,17 @@ NS_IMPL_ISUPPORTS0(nsNPAPIPluginInstance
 
 nsNPAPIPluginInstance::nsNPAPIPluginInstance()
   : mDrawingModel(kDefaultDrawingModel)
 #ifdef MOZ_WIDGET_ANDROID
   , mANPDrawingModel(0)
   , mFullScreenOrientation(dom::eScreenOrientation_LandscapePrimary)
   , mWakeLocked(false)
   , mFullScreen(false)
-  , mInverted(false)
+  , mOriginPos(gl::OriginPos::TopLeft)
 #endif
   , mRunning(NOT_STARTED)
   , mWindowless(false)
   , mTransparent(false)
   , mCached(false)
   , mUsesDOMForCursor(false)
   , mInPluginInitCall(false)
   , mPlugin(nullptr)
@@ -1051,24 +1051,16 @@ void nsNPAPIPluginInstance::SetVideoDime
 
 void nsNPAPIPluginInstance::GetVideos(nsTArray<VideoInfo*>& aVideos)
 {
   std::map<void*, VideoInfo*>::iterator it;
   for (it = mVideos.begin(); it != mVideos.end(); it++)
     aVideos.AppendElement(it->second);
 }
 
-void nsNPAPIPluginInstance::SetInverted(bool aInverted)
-{
-  if (aInverted == mInverted)
-    return;
-
-  mInverted = aInverted;
-}
-
 nsNPAPIPluginInstance* nsNPAPIPluginInstance::GetFromNPP(NPP npp)
 {
   std::map<NPP, nsNPAPIPluginInstance*>::iterator it;
 
   it = sPluginNPPMap.find(npp);
   if (it == sPluginNPPMap.end())
     return nullptr;
 
--- a/dom/plugins/base/nsNPAPIPluginInstance.h
+++ b/dom/plugins/base/nsNPAPIPluginInstance.h
@@ -215,18 +215,20 @@ public:
   };
 
   void* AcquireVideoWindow();
   void ReleaseVideoWindow(void* aWindow);
   void SetVideoDimensions(void* aWindow, gfxRect aDimensions);
 
   void GetVideos(nsTArray<VideoInfo*>& aVideos);
 
-  void SetInverted(bool aInverted);
-  bool Inverted() { return mInverted; }
+  void SetOriginPos(mozilla::gl::OriginPos aOriginPos) {
+    mOriginPos = aOriginPos;
+  }
+  mozilla::gl::OriginPos OriginPos() const { return mOriginPos; }
 
   static nsNPAPIPluginInstance* GetFromNPP(NPP npp);
 #endif
 
   nsresult NewStreamListener(const char* aURL, void* notifyData,
                              nsNPAPIPluginStreamListener** listener);
 
   nsNPAPIPluginInstance();
@@ -323,17 +325,17 @@ protected:
 
   nsTArray<nsRefPtr<PluginEventRunnable>> mPostedEvents;
   void PopPostedEvent(PluginEventRunnable* r);
   void OnSurfaceTextureFrameAvailable();
 
   uint32_t mFullScreenOrientation;
   bool mWakeLocked;
   bool mFullScreen;
-  bool mInverted;
+  mozilla::gl::OriginPos mOriginPos;
 
   mozilla::RefPtr<SharedPluginTexture> mContentTexture;
   mozilla::RefPtr<mozilla::gl::AndroidSurfaceTexture> mContentSurface;
 #endif
 
   enum {
     NOT_STARTED,
     RUNNING,
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -171,17 +171,17 @@ AttachToContainerAsEGLImage(ImageContain
     return;
   }
 
   nsRefPtr<Image> img = container->CreateImage(ImageFormat::EGLIMAGE);
 
   EGLImageImage::Data data;
   data.mImage = image;
   data.mSize = gfx::IntSize(rect.width, rect.height);
-  data.mInverted = instance->Inverted();
+  data.mOriginPos = instance->OriginPos();
 
   EGLImageImage* typedImg = static_cast<EGLImageImage*>(img.get());
   typedImg->SetData(data);
 
   *out_image = img;
 }
 
 static void
@@ -198,17 +198,17 @@ AttachToContainerAsSurfaceTexture(ImageC
     return;
   }
 
   nsRefPtr<Image> img = container->CreateImage(ImageFormat::SURFACE_TEXTURE);
 
   SurfaceTextureImage::Data data;
   data.mSurfTex = surfTex;
   data.mSize = gfx::IntSize(rect.width, rect.height);
-  data.mInverted = instance->Inverted();
+  data.mOriginPos = instance->OriginPos();
 
   SurfaceTextureImage* typedImg = static_cast<SurfaceTextureImage*>(img.get());
   typedImg->SetData(data);
 
   *out_image = img;
 }
 #endif
 
@@ -1381,17 +1381,18 @@ nsPluginInstanceOwner::GetImageContainer
   nsRefPtr<Image> img = container->CreateImage(ImageFormat::SURFACE_TEXTURE);
 
   SurfaceTextureImage::Data data;
 
   data.mSurfTex = aVideoInfo->mSurfaceTexture;
 
   // The logic below for Honeycomb is just a guess, but seems to work. We don't have a separate
   // inverted flag for video.
-  data.mInverted = AndroidBridge::Bridge()->IsHoneycomb() ? true : mInstance->Inverted();
+  data.mOriginPos = AndroidBridge::Bridge()->IsHoneycomb() ? gl::OriginPos::BottomLeft
+                                                           : mInstance->OriginPos();
   data.mSize = gfx::IntSize(aVideoInfo->mDimensions.width, aVideoInfo->mDimensions.height);
 
   SurfaceTextureImage* typedImg = static_cast<SurfaceTextureImage*>(img.get());
   typedImg->SetData(data);
 
   container->SetCurrentImageInTransaction(img);
 
   return container.forget();
--- a/gfx/gl/GLBlitHelper.cpp
+++ b/gfx/gl/GLBlitHelper.cpp
@@ -685,17 +685,17 @@ GLBlitHelper::BindAndUploadEGLImage(EGLI
         mGL->fBindTexture(target, mSrcTexEGL);
     }
     mGL->fEGLImageTargetTexture2D(target, image);
 }
 
 #ifdef MOZ_WIDGET_GONK
 
 bool
-GLBlitHelper::BlitGrallocImage(layers::GrallocImage* grallocImage, bool yFlip)
+GLBlitHelper::BlitGrallocImage(layers::GrallocImage* grallocImage, bool yflip)
 {
     ScopedBindTextureUnit boundTU(mGL, LOCAL_GL_TEXTURE0);
     mGL->fClear(LOCAL_GL_COLOR_BUFFER_BIT);
 
     EGLint attrs[] = {
         LOCAL_EGL_IMAGE_PRESERVED, LOCAL_EGL_TRUE,
         LOCAL_EGL_NONE, LOCAL_EGL_NONE
     };
@@ -706,98 +706,90 @@ GLBlitHelper::BlitGrallocImage(layers::G
     if (image == EGL_NO_IMAGE)
         return false;
 
     int oldBinding = 0;
     mGL->fGetIntegerv(LOCAL_GL_TEXTURE_BINDING_EXTERNAL_OES, &oldBinding);
 
     BindAndUploadEGLImage(image, LOCAL_GL_TEXTURE_EXTERNAL_OES);
 
-    mGL->fUniform1f(mYFlipLoc, yFlip ? (float)1.0f : (float)0.0f);
+    mGL->fUniform1f(mYFlipLoc, yflip ? (float)1.0f : (float)0.0f);
 
     mGL->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
 
     sEGLLibrary.fDestroyImage(sEGLLibrary.Display(), image);
     mGL->fBindTexture(LOCAL_GL_TEXTURE_EXTERNAL_OES, oldBinding);
     return true;
 }
 #endif
 
 #ifdef MOZ_WIDGET_ANDROID
 
 bool
-GLBlitHelper::BlitSurfaceTextureImage(layers::SurfaceTextureImage* stImage, bool yFlip)
+GLBlitHelper::BlitSurfaceTextureImage(layers::SurfaceTextureImage* stImage, bool yflip)
 {
     AndroidSurfaceTexture* surfaceTexture = stImage->GetData()->mSurfTex;
-    if (stImage->GetData()->mInverted) {
-        yFlip = !yFlip;
-    }
 
     ScopedBindTextureUnit boundTU(mGL, LOCAL_GL_TEXTURE0);
 
-    if (NS_FAILED(surfaceTexture->Attach(mGL))) {
+    if (NS_FAILED(surfaceTexture->Attach(mGL)))
         return false;
-    }
 
     // UpdateTexImage() changes the EXTERNAL binding, so save it here
     // so we can restore it after.
     int oldBinding = 0;
     mGL->fGetIntegerv(LOCAL_GL_TEXTURE_BINDING_EXTERNAL, &oldBinding);
 
     surfaceTexture->UpdateTexImage();
 
     gfx::Matrix4x4 transform;
     surfaceTexture->GetTransformMatrix(transform);
 
     mGL->fUniformMatrix4fv(mTextureTransformLoc, 1, false, &transform._11);
-    mGL->fUniform1f(mYFlipLoc, yFlip ? 1.0f : 0.0f);
+    mGL->fUniform1f(mYFlipLoc, yflip ? 1.0f : 0.0f);
     mGL->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
 
     surfaceTexture->Detach();
 
     mGL->fBindTexture(LOCAL_GL_TEXTURE_EXTERNAL, oldBinding);
     return true;
 }
 
 bool
-GLBlitHelper::BlitEGLImageImage(layers::EGLImageImage* image, bool yFlip)
+GLBlitHelper::BlitEGLImageImage(layers::EGLImageImage* image, bool yflip)
 {
     EGLImage eglImage = image->GetData()->mImage;
     EGLSync eglSync = image->GetData()->mSync;
 
-    if (image->GetData()->mInverted) {
-        yFlip = !yFlip;
-    }
-
     if (eglSync) {
         EGLint status = sEGLLibrary.fClientWaitSync(EGL_DISPLAY(), eglSync, 0, LOCAL_EGL_FOREVER);
         if (status != LOCAL_EGL_CONDITION_SATISFIED) {
             return false;
         }
     }
 
     ScopedBindTextureUnit boundTU(mGL, LOCAL_GL_TEXTURE0);
 
     int oldBinding = 0;
     mGL->fGetIntegerv(LOCAL_GL_TEXTURE_BINDING_2D, &oldBinding);
 
     BindAndUploadEGLImage(eglImage, LOCAL_GL_TEXTURE_2D);
 
-    mGL->fUniform1f(mYFlipLoc, yFlip ? 1.0f : 0.0f);
+    mGL->fUniform1f(mYFlipLoc, yflip ? 1.0f : 0.0f);
 
     mGL->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
 
     mGL->fBindTexture(LOCAL_GL_TEXTURE_EXTERNAL_OES, oldBinding);
     return true;
 }
 
 #endif
 
 bool
-GLBlitHelper::BlitPlanarYCbCrImage(layers::PlanarYCbCrImage* yuvImage, bool yFlip)
+GLBlitHelper::BlitPlanarYCbCrImage(layers::PlanarYCbCrImage* yuvImage, bool yflip)
 {
     ScopedBindTextureUnit boundTU(mGL, LOCAL_GL_TEXTURE0);
     const PlanarYCbCrData* yuvData = yuvImage->GetData();
 
     bool needsAllocation = false;
     if (mTexWidth != yuvData->mYStride || mTexHeight != yuvData->mYSize.height) {
         mTexWidth = yuvData->mYStride;
         mTexHeight = yuvData->mYSize.height;
@@ -808,17 +800,17 @@ GLBlitHelper::BlitPlanarYCbCrImage(layer
     for (int i = 0; i < 3; i++) {
         mGL->fActiveTexture(LOCAL_GL_TEXTURE0 + i);
         mGL->fGetIntegerv(LOCAL_GL_TEXTURE_BINDING_2D, &oldTex[i]);
     }
     BindAndUploadYUVTexture(Channel_Y, yuvData->mYStride, yuvData->mYSize.height, yuvData->mYChannel, needsAllocation);
     BindAndUploadYUVTexture(Channel_Cb, yuvData->mCbCrStride, yuvData->mCbCrSize.height, yuvData->mCbChannel, needsAllocation);
     BindAndUploadYUVTexture(Channel_Cr, yuvData->mCbCrStride, yuvData->mCbCrSize.height, yuvData->mCrChannel, needsAllocation);
 
-    mGL->fUniform1f(mYFlipLoc, yFlip ? (float)1.0 : (float)0.0);
+    mGL->fUniform1f(mYFlipLoc, yflip ? (float)1.0 : (float)0.0);
 
     if (needsAllocation) {
         mGL->fUniform2f(mYTexScaleLoc, (float)yuvData->mYSize.width/yuvData->mYStride, 1.0f);
         mGL->fUniform2f(mCbCrTexScaleLoc, (float)yuvData->mCbCrSize.width/yuvData->mCbCrStride, 1.0f);
     }
 
     mGL->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
     for (int i = 0; i < 3; i++) {
@@ -827,17 +819,17 @@ GLBlitHelper::BlitPlanarYCbCrImage(layer
     }
     return true;
 }
 
 bool
 GLBlitHelper::BlitImageToFramebuffer(layers::Image* srcImage,
                                      const gfx::IntSize& destSize,
                                      GLuint destFB,
-                                     bool yFlip,
+                                     bool yflip,
                                      GLuint xoffset,
                                      GLuint yoffset,
                                      GLuint cropWidth,
                                      GLuint cropHeight)
 {
     ScopedGLDrawState autoStates(mGL);
 
     BlitType type;
@@ -873,58 +865,58 @@ GLBlitHelper::BlitImageToFramebuffer(lay
     if (xoffset != 0 && yoffset != 0 && cropWidth != 0 && cropHeight != 0) {
         mGL->fEnable(LOCAL_GL_SCISSOR_TEST);
         mGL->fScissor(xoffset, yoffset, (GLsizei)cropWidth, (GLsizei)cropHeight);
     }
 
 #ifdef MOZ_WIDGET_GONK
     if (type == ConvertGralloc) {
         layers::GrallocImage* grallocImage = static_cast<layers::GrallocImage*>(srcImage);
-        return BlitGrallocImage(grallocImage, yFlip);
+        return BlitGrallocImage(grallocImage, yflip);
     }
 #endif
     if (type == ConvertPlanarYCbCr) {
         mGL->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
         PlanarYCbCrImage* yuvImage = static_cast<PlanarYCbCrImage*>(srcImage);
-        return BlitPlanarYCbCrImage(yuvImage, yFlip);
+        return BlitPlanarYCbCrImage(yuvImage, yflip);
     }
 #ifdef MOZ_WIDGET_ANDROID
     if (type == ConvertSurfaceTexture) {
         layers::SurfaceTextureImage* stImage = static_cast<layers::SurfaceTextureImage*>(srcImage);
-        return BlitSurfaceTextureImage(stImage, yFlip);
+        return BlitSurfaceTextureImage(stImage, yflip);
     }
     if (type == ConvertEGLImage) {
         layers::EGLImageImage* eglImage = static_cast<layers::EGLImageImage*>(srcImage);
-        return BlitEGLImageImage(eglImage, yFlip);
+        return BlitEGLImageImage(eglImage, yflip);
     }
 #endif
 
     return false;
 }
 
 bool
 GLBlitHelper::BlitImageToTexture(layers::Image* srcImage,
                                  const gfx::IntSize& destSize,
                                  GLuint destTex,
                                  GLenum destTarget,
-                                 bool yFlip,
+                                 bool yflip,
                                  GLuint xoffset,
                                  GLuint yoffset,
                                  GLuint cropWidth,
                                  GLuint cropHeight)
 {
     ScopedGLDrawState autoStates(mGL);
 
-    if (!mFBO) {
+    if (!mFBO)
         mGL->fGenFramebuffers(1, &mFBO);
-    }
 
     ScopedBindFramebuffer boundFB(mGL, mFBO);
-    mGL->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, destTarget, destTex, 0);
-    return BlitImageToFramebuffer(srcImage, destSize, mFBO, yFlip, xoffset, yoffset,
+    mGL->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
+                               destTarget, destTex, 0);
+    return BlitImageToFramebuffer(srcImage, destSize, mFBO, yflip, xoffset, yoffset,
                                   cropWidth, cropHeight);
 }
 
 void
 GLBlitHelper::BlitTextureToFramebuffer(GLuint srcTex, GLuint destFB,
                                        const gfx::IntSize& srcSize,
                                        const gfx::IntSize& destSize,
                                        GLenum srcTarget,
@@ -1022,10 +1014,10 @@ GLBlitHelper::BlitTextureToTexture(GLuin
 
     // Generally, just use the CopyTexSubImage path
     ScopedFramebufferForTexture srcWrapper(mGL, srcTex, srcTarget);
 
     BlitFramebufferToTexture(srcWrapper.FB(), destTex,
                              srcSize, destSize, destTarget);
 }
 
-}
-}
+} // namespace gl
+} // namespace mozilla
--- a/gfx/gl/GLBlitHelper.h
+++ b/gfx/gl/GLBlitHelper.h
@@ -140,22 +140,23 @@ class GLBlitHelper MOZ_FINAL
 
     bool UseTexQuadProgram(BlitType target, const gfx::IntSize& srcSize);
     bool InitTexQuadProgram(BlitType target = BlitTex2D);
     void DeleteTexBlitProgram();
     void BindAndUploadYUVTexture(Channel which, uint32_t width, uint32_t height, void* data, bool allocation);
     void BindAndUploadEGLImage(EGLImage image, GLuint target);
 
 #ifdef MOZ_WIDGET_GONK
-    bool BlitGrallocImage(layers::GrallocImage* grallocImage, bool yFlip = false);
+    bool BlitGrallocImage(layers::GrallocImage* grallocImage, bool yflip);
 #endif
-    bool BlitPlanarYCbCrImage(layers::PlanarYCbCrImage* yuvImage, bool yFlip = false);
+    bool BlitPlanarYCbCrImage(layers::PlanarYCbCrImage* yuvImage, bool yflip);
 #ifdef MOZ_WIDGET_ANDROID
-    bool BlitSurfaceTextureImage(layers::SurfaceTextureImage* stImage, bool yFlip = false);
-    bool BlitEGLImageImage(layers::EGLImageImage* eglImage, bool yFlip = false);
+    // Blit onto the current FB.
+    bool BlitSurfaceTextureImage(layers::SurfaceTextureImage* stImage, bool yflip);
+    bool BlitEGLImageImage(layers::EGLImageImage* eglImage, bool yflip);
 #endif
 
 public:
 
     explicit GLBlitHelper(GLContext* gl);
     ~GLBlitHelper();
 
     // If you don't have |srcFormats| for the 2nd definition,
@@ -181,19 +182,20 @@ public:
                                   GLenum destTarget = LOCAL_GL_TEXTURE_2D,
                                   bool internalFBs = false);
     void BlitTextureToTexture(GLuint srcTex, GLuint destTex,
                               const gfx::IntSize& srcSize,
                               const gfx::IntSize& destSize,
                               GLenum srcTarget = LOCAL_GL_TEXTURE_2D,
                               GLenum destTarget = LOCAL_GL_TEXTURE_2D);
     bool BlitImageToFramebuffer(layers::Image* srcImage, const gfx::IntSize& destSize,
-                                GLuint destFB, bool yFlip = false, GLuint xoffset = 0,
+                                GLuint destFB, bool yflip = false, GLuint xoffset = 0,
                                 GLuint yoffset = 0, GLuint width = 0, GLuint height = 0);
     bool BlitImageToTexture(layers::Image* srcImage, const gfx::IntSize& destSize,
-                            GLuint destTex, GLenum destTarget, bool yFlip = false, GLuint xoffset = 0,
-                            GLuint yoffset = 0, GLuint width = 0, GLuint height = 0);
+                            GLuint destTex, GLenum destTarget, bool yflip = false,
+                            GLuint xoffset = 0, GLuint yoffset = 0, GLuint width = 0,
+                            GLuint height = 0);
 };
 
-}
-}
+} // namespace gl
+} // namespace mozilla
 
 #endif // GLBLITHELPER_H_
--- a/gfx/gl/GLContextTypes.h
+++ b/gfx/gl/GLContextTypes.h
@@ -17,16 +17,21 @@ class GLContext;
 MOZ_BEGIN_ENUM_CLASS(GLContextType)
     Unknown,
     WGL,
     CGL,
     GLX,
     EGL
 MOZ_END_ENUM_CLASS(GLContextType)
 
+MOZ_BEGIN_ENUM_CLASS(OriginPos, uint8_t)
+  TopLeft,
+  BottomLeft
+MOZ_END_ENUM_CLASS(OriginPos)
+
 struct GLFormats
 {
     // Constructs a zeroed object:
     GLFormats();
 
     GLenum color_texInternalFormat;
     GLenum color_texFormat;
     GLenum color_texType;
@@ -34,27 +39,25 @@ struct GLFormats
 
     GLenum depthStencil;
     GLenum depth;
     GLenum stencil;
 
     GLsizei samples;
 };
 
-
 struct PixelBufferFormat
 {
     // Constructs a zeroed object:
     PixelBufferFormat();
 
     int red, green, blue;
     int alpha;
     int depth, stencil;
     int samples;
 
     int ColorBits() const { return red + green + blue; }
 };
 
-
 } /* namespace gl */
 } /* namespace mozilla */
 
 #endif /* GLCONTEXT_TYPES_H_ */
--- a/gfx/gl/GLScreenBuffer.cpp
+++ b/gfx/gl/GLScreenBuffer.cpp
@@ -44,17 +44,17 @@ GLScreenBuffer::Create(GLContext* gl,
 #ifdef MOZ_WIDGET_GONK
     /* On B2G, we want a Gralloc factory, and we want one right at the start */
     layers::ISurfaceAllocator* allocator = caps.surfaceAllocator;
     if (!factory &&
         allocator &&
         XRE_GetProcessType() != GeckoProcessType_Default)
     {
         layers::TextureFlags flags = layers::TextureFlags::DEALLOCATE_CLIENT |
-                                     layers::TextureFlags::NEEDS_Y_FLIP;
+                                     layers::TextureFlags::ORIGIN_BOTTOM_LEFT;
         if (!caps.premultAlpha) {
             flags |= layers::TextureFlags::NON_PREMULTIPLIED;
         }
 
         factory = MakeUnique<SurfaceFactory_Gralloc>(gl, caps, flags,
                                                      allocator);
     }
 #endif
--- a/gfx/gl/GLTextureImage.cpp
+++ b/gfx/gl/GLTextureImage.cpp
@@ -592,19 +592,19 @@ gfx::IntRect TiledTextureImage::GetTileR
     unsigned int yPos = (mCurrentImage / mColumns) * mTileSize;
     rect.MoveBy(xPos, yPos);
     return rect;
 }
 
 gfx::IntRect TiledTextureImage::GetSrcTileRect()
 {
     gfx::IntRect rect = GetTileRect();
-    unsigned int srcY = mFlags & NeedsYFlip
-                        ? mSize.height - rect.height - rect.y
-                        : rect.y;
+    const bool needsYFlip = mFlags & OriginBottomLeft;
+    unsigned int srcY = needsYFlip ? mSize.height - rect.height - rect.y
+                                   : rect.y;
     return gfx::IntRect(rect.x, srcY, rect.width, rect.height);
 }
 
 void
 TiledTextureImage::BindTexture(GLenum aTextureUnit)
 {
     if (!GetTileCount()) {
         return;
--- a/gfx/gl/GLTextureImage.h
+++ b/gfx/gl/GLTextureImage.h
@@ -53,17 +53,17 @@ public:
       Created, // Texture created, but has not had glTexImage called to initialize it.
       Allocated,  // Texture memory exists, but contents are invalid.
       Valid  // Texture fully ready to use.
     };
 
     enum Flags {
         NoFlags          = 0x0,
         UseNearestFilter = 0x1,
-        NeedsYFlip       = 0x2,
+        OriginBottomLeft = 0x2,
         DisallowBigImage = 0x4
     };
 
     typedef gfxContentType ContentType;
     typedef gfxImageFormat ImageFormat;
 
     static already_AddRefed<TextureImage> Create(
                        GLContext* gl,
@@ -376,17 +376,18 @@ CreateBasicTextureImage(GLContext* aGL,
 /**
   * Return a valid, allocated TextureImage of |aSize| with
   * |aContentType|.  If |aContentType| is COLOR, |aImageFormat| can be used
   * to hint at the preferred RGB format, however it is not necessarily
   * respected.  The TextureImage's texture is configured to use
   * |aWrapMode| (usually GL_CLAMP_TO_EDGE or GL_REPEAT) and by
   * default, GL_LINEAR filtering.  Specify
   * |aFlags=UseNearestFilter| for GL_NEAREST filtering. Specify
-  * |aFlags=NeedsYFlip| if the image is flipped. Return
+  * |aFlags=OriginBottomLeft| if the image is origin-bottom-left, instead of the
+  * default origin-top-left. Return
   * nullptr if creating the TextureImage fails.
   *
   * The returned TextureImage may only be used with this GLContext.
   * Attempting to use the returned TextureImage after this
   * GLContext is destroyed will result in undefined (and likely
   * crashy) behavior.
   */
 already_AddRefed<TextureImage>
--- a/gfx/layers/CompositorTypes.h
+++ b/gfx/layers/CompositorTypes.h
@@ -24,18 +24,18 @@ namespace layers {
  * side to host side when textures and compositables are created. Usually set
  * by the compositableCient, they may be modified by either the compositable or
  * texture clients.
  */
 MOZ_BEGIN_ENUM_CLASS(TextureFlags, uint32_t)
   NO_FLAGS           = 0,
   // Use nearest-neighbour texture filtering (as opposed to linear filtering).
   USE_NEAREST_FILTER = 1 << 0,
-  // The texture should be flipped along the y-axis when composited.
-  NEEDS_Y_FLIP       = 1 << 1,
+  // The compositor assumes everything is origin-top-left by default.
+  ORIGIN_BOTTOM_LEFT = 1 << 1,
   // Force the texture to be represented using a single tile (note that this means
   // tiled textures, not tiled layers).
   DISALLOW_BIGIMAGE  = 1 << 2,
   // The buffer will be treated as if the RB bytes are swapped.
   // This is useful for rendering using Cairo/Thebes, because there is no
   // BGRX Android pixel format, and so we have to do byte swapping.
   //
   // For example, if the GraphicBuffer has an Android pixel format of
--- a/gfx/layers/CopyableCanvasLayer.cpp
+++ b/gfx/layers/CopyableCanvasLayer.cpp
@@ -27,16 +27,17 @@ namespace layers {
 
 using namespace mozilla::gfx;
 using namespace mozilla::gl;
 
 CopyableCanvasLayer::CopyableCanvasLayer(LayerManager* aLayerManager, void *aImplData) :
   CanvasLayer(aLayerManager, aImplData)
   , mGLFrontbuffer(nullptr)
   , mIsAlphaPremultiplied(true)
+  , mOriginPos(gl::OriginPos::TopLeft)
 {
   MOZ_COUNT_CTOR(CopyableCanvasLayer);
 }
 
 CopyableCanvasLayer::~CopyableCanvasLayer()
 {
   MOZ_COUNT_DTOR(CopyableCanvasLayer);
 }
@@ -44,33 +45,33 @@ CopyableCanvasLayer::~CopyableCanvasLaye
 void
 CopyableCanvasLayer::Initialize(const Data& aData)
 {
   NS_ASSERTION(mSurface == nullptr, "BasicCanvasLayer::Initialize called twice!");
 
   if (aData.mGLContext) {
     mGLContext = aData.mGLContext;
     mIsAlphaPremultiplied = aData.mIsGLAlphaPremult;
-    mNeedsYFlip = true;
+    mOriginPos = gl::OriginPos::BottomLeft;
+
     MOZ_ASSERT(mGLContext->IsOffscreen(), "canvas gl context isn't offscreen");
 
     if (aData.mFrontbufferGLTex) {
       gfx::IntSize size(aData.mSize.width, aData.mSize.height);
       mGLFrontbuffer = SharedSurface_GLTexture::Create(aData.mGLContext,
                                                        nullptr,
                                                        aData.mGLContext->GetGLFormats(),
                                                        size, aData.mHasAlpha,
                                                        aData.mFrontbufferGLTex);
     }
   } else if (aData.mDrawTarget) {
     mDrawTarget = aData.mDrawTarget;
     mSurface = mDrawTarget->Snapshot();
-    mNeedsYFlip = false;
   } else {
-    NS_ERROR("CanvasLayer created without mSurface, mDrawTarget or mGLContext?");
+    MOZ_CRASH("CanvasLayer created without mSurface, mDrawTarget or mGLContext?");
   }
 
   mBounds.SetRect(0, 0, aData.mSize.width, aData.mSize.height);
 }
 
 bool
 CopyableCanvasLayer::IsDataValid(const Data& aData)
 {
--- a/gfx/layers/CopyableCanvasLayer.h
+++ b/gfx/layers/CopyableCanvasLayer.h
@@ -50,17 +50,17 @@ protected:
   RefPtr<gfx::SourceSurface> mSurface;
   nsRefPtr<gl::GLContext> mGLContext;
   GLuint mCanvasFrontbufferTexID;
   mozilla::RefPtr<mozilla::gfx::DrawTarget> mDrawTarget;
 
   UniquePtr<gl::SharedSurface> mGLFrontbuffer;
 
   bool mIsAlphaPremultiplied;
-  bool mNeedsYFlip;
+  gl::OriginPos mOriginPos;
 
   RefPtr<gfx::DataSourceSurface> mCachedTempSurface;
 
   gfx::DataSourceSurface* GetTempSurface(const gfx::IntSize& aSize,
                                          const gfx::SurfaceFormat aFormat);
 
   void DiscardTempSurface();
 };
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -35,19 +35,19 @@ public:
   typedef uint64_t ViewID;
   static const ViewID NULL_SCROLL_ID;   // This container layer does not scroll.
   static const ViewID START_SCROLL_ID = 2;  // This is the ID that scrolling subframes
                                         // will begin at.
   static const FrameMetrics sNullMetrics;   // We often need an empty metrics
 
   FrameMetrics()
     : mCompositionBounds(0, 0, 0, 0)
-    , mCriticalDisplayPort(0, 0, 0, 0)
     , mPresShellResolution(1)
     , mDisplayPort(0, 0, 0, 0)
+    , mCriticalDisplayPort(0, 0, 0, 0)
     , mScrollableRect(0, 0, 0, 0)
     , mCumulativeResolution(1)
     , mDevPixelsPerCSSPixel(1)
     , mMayHaveTouchListeners(false)
     , mMayHaveTouchCaret(false)
     , mIsRoot(false)
     , mHasScrollgrab(false)
     , mScrollId(NULL_SCROLL_ID)
@@ -247,29 +247,16 @@ public:
   // instead.
   //
   // This value is valid for nested scrollable layers as well, and is still
   // relative to the layer tree origin. This value is provided by Gecko at
   // layout/paint time.
   ParentLayerRect mCompositionBounds;
 
   // ---------------------------------------------------------------------------
-  // The following metrics are all in CSS pixels. They are not in any uniform
-  // space, so each is explained separately.
-  //
-
-  // If non-empty, the area of a frame's contents that is considered critical
-  // to paint. Area outside of this area (i.e. area inside mDisplayPort, but
-  // outside of mCriticalDisplayPort) is considered low-priority, and may be
-  // painted with lower precision, or not painted at all.
-  //
-  // The same restrictions for mDisplayPort apply here.
-  CSSRect mCriticalDisplayPort;
-
-  // ---------------------------------------------------------------------------
   // The following metrics are dimensionless.
   //
 
   // The pres-shell resolution that has been induced on the document containing
   // this scroll frame as a result of zooming this scroll frame (whether via
   // user action, or choosing an initial zoom level on page load). This can
   // only be different from 1.0 for frames that are zoomable, which currently
   // is just the root content document's root scroll frame (mIsRoot = true).
@@ -283,16 +270,26 @@ public:
     mDisplayPort = aDisplayPort;
   }
 
   CSSRect GetDisplayPort() const
   {
     return mDisplayPort;
   }
 
+  void SetCriticalDisplayPort(const CSSRect& aCriticalDisplayPort)
+  {
+    mCriticalDisplayPort = aCriticalDisplayPort;
+  }
+
+  CSSRect GetCriticalDisplayPort() const
+  {
+    return mCriticalDisplayPort;
+  }
+
   void SetCumulativeResolution(const LayoutDeviceToLayerScale& aCumulativeResolution)
   {
     mCumulativeResolution = aCumulativeResolution;
   }
 
   LayoutDeviceToLayerScale GetCumulativeResolution() const
   {
     return mCumulativeResolution;
@@ -534,16 +531,24 @@ private:
   //
   // May be larger or smaller than |mScrollableRect|.
   //
   // To pre-render a margin of 100 CSS pixels around the window,
   // { x = -100, y = - 100,
   //   width = window.innerWidth + 200, height = window.innerHeight + 200 }
   CSSRect mDisplayPort;
 
+  // If non-empty, the area of a frame's contents that is considered critical
+  // to paint. Area outside of this area (i.e. area inside mDisplayPort, but
+  // outside of mCriticalDisplayPort) is considered low-priority, and may be
+  // painted with lower precision, or not painted at all.
+  //
+  // The same restrictions for mDisplayPort apply here.
+  CSSRect mCriticalDisplayPort;
+
   // The scrollable bounds of a frame. This is determined by reflow.
   // Ordinarily the x and y will be 0 and the width and height will be the
   // size of the element being scrolled. However for RTL pages or elements
   // the x value may be negative.
   //
   // This is relative to the document. It is in the same coordinate space as
   // |mScrollOffset|, but a different coordinate space than |mViewport| and
   // |mDisplayPort|. Note also that this coordinate system is understood by
--- a/gfx/layers/GLImages.cpp
+++ b/gfx/layers/GLImages.cpp
@@ -35,17 +35,16 @@ EGLImageImage::~EGLImageImage()
 
 TemporaryRef<gfx::SourceSurface>
 GLImage::GetAsSourceSurface()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Should be on the main thread");
 
   if (!sSnapshotContext) {
     sSnapshotContext = GLContextProvider::CreateHeadless();
-
     if (!sSnapshotContext) {
       NS_WARNING("Failed to create snapshot GLContext");
       return nullptr;
     }
   }
 
   sSnapshotContext->MakeCurrent();
   ScopedTexture scopedTex(sSnapshotContext);
@@ -58,16 +57,17 @@ GLImage::GetAsSourceSurface()
                                 LOCAL_GL_UNSIGNED_BYTE,
                                 nullptr);
 
   ScopedFramebufferForTexture fb(sSnapshotContext, scopedTex.Texture());
 
   GLBlitHelper helper(sSnapshotContext);
 
   helper.BlitImageToFramebuffer(this, size, fb.FB(), false);
+
   ScopedBindFramebuffer bind(sSnapshotContext, fb.FB());
 
   RefPtr<gfx::DataSourceSurface> source =
         gfx::Factory::CreateDataSourceSurface(size, gfx::SurfaceFormat::B8G8R8A8);
   if (NS_WARN_IF(!source)) {
     return nullptr;
   }
 
--- a/gfx/layers/GLImages.h
+++ b/gfx/layers/GLImages.h
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GFX_GLIMAGES_H
 #define GFX_GLIMAGES_H
 
+#include "GLContextTypes.h"
 #include "GLTypes.h"
 #include "ImageContainer.h"             // for Image
 #include "ImageTypes.h"                 // for ImageFormat::SHARED_GLTEXTURE
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "mozilla/gfx/Point.h"          // for IntSize
 
 namespace mozilla {
 namespace gl {
@@ -26,20 +27,21 @@ public:
 };
 
 class EGLImageImage : public GLImage {
 public:
   struct Data {
     EGLImage mImage;
     EGLSync mSync;
     gfx::IntSize mSize;
-    bool mInverted;
+    gl::OriginPos mOriginPos;
     bool mOwns;
 
-    Data() : mImage(nullptr), mSync(nullptr), mSize(0, 0), mInverted(false), mOwns(false)
+    Data() : mImage(nullptr), mSync(nullptr), mSize(0, 0),
+             mOriginPos(gl::OriginPos::TopLeft), mOwns(false)
     {
     }
   };
 
   void SetData(const Data& aData) { mData = aData; }
   const Data* GetData() { return &mData; }
 
   gfx::IntSize GetSize() { return mData.mSize; }
@@ -55,17 +57,17 @@ private:
 
 #ifdef MOZ_WIDGET_ANDROID
 
 class SurfaceTextureImage : public GLImage {
 public:
   struct Data {
     mozilla::gl::AndroidSurfaceTexture* mSurfTex;
     gfx::IntSize mSize;
-    bool mInverted;
+    gl::OriginPos mOriginPos;
   };
 
   void SetData(const Data& aData) { mData = aData; }
   const Data* GetData() { return &mData; }
 
   gfx::IntSize GetSize() { return mData.mSize; }
 
   SurfaceTextureImage() : GLImage(ImageFormat::SURFACE_TEXTURE) {}
--- a/gfx/layers/LayersLogging.cpp
+++ b/gfx/layers/LayersLogging.cpp
@@ -171,17 +171,17 @@ AppendToString(std::stringstream& aStrea
   aStream << pfx;
   AppendToString(aStream, m.mCompositionBounds, "{ [cb=");
   AppendToString(aStream, m.GetScrollableRect(), "] [sr=");
   AppendToString(aStream, m.GetScrollOffset(), "] [s=");
   if (m.GetDoSmoothScroll()) {
     AppendToString(aStream, m.GetSmoothScrollOffset(), "] [ss=");
   }
   AppendToString(aStream, m.GetDisplayPort(), "] [dp=");
-  AppendToString(aStream, m.mCriticalDisplayPort, "] [cdp=");
+  AppendToString(aStream, m.GetCriticalDisplayPort(), "] [cdp=");
   AppendToString(aStream, m.GetBackgroundColor(), "] [color=");
   if (!detailed) {
     AppendToString(aStream, m.GetScrollId(), "] [scrollId=");
     if (m.GetScrollParentId() != FrameMetrics::NULL_SCROLL_ID) {
       AppendToString(aStream, m.GetScrollParentId(), "] [scrollParent=");
     }
     aStream << nsPrintfCString("] [z=%.3f] }", m.GetZoom().scale).get();
   } else {
@@ -284,17 +284,17 @@ AppendToString(std::stringstream& aStrea
       aStream << "|"; \
     } \
     aStream << #test; \
     previous = true; \
   } \
 }
     bool previous = false;
     AppendFlag(TextureFlags::USE_NEAREST_FILTER);
-    AppendFlag(TextureFlags::NEEDS_Y_FLIP);
+    AppendFlag(TextureFlags::ORIGIN_BOTTOM_LEFT);
     AppendFlag(TextureFlags::DISALLOW_BIGIMAGE);
 
 #undef AppendFlag
   }
   aStream << sfx;
 }
 
 void
--- a/gfx/layers/LayersTypes.h
+++ b/gfx/layers/LayersTypes.h
@@ -74,17 +74,17 @@ MOZ_BEGIN_ENUM_CLASS(SurfaceMode, int8_t
   SURFACE_COMPONENT_ALPHA
 MOZ_END_ENUM_CLASS(SurfaceMode)
 
 // LayerRenderState for Composer2D
 // We currently only support Composer2D using gralloc. If we want to be backed
 // by other surfaces we will need a more generic LayerRenderState.
 MOZ_BEGIN_ENUM_CLASS(LayerRenderStateFlags, int8_t)
   LAYER_RENDER_STATE_DEFAULT = 0,
-  Y_FLIPPED = 1 << 0,
+  ORIGIN_BOTTOM_LEFT = 1 << 0,
   BUFFER_ROTATION = 1 << 1,
   // Notify Composer2D to swap the RB pixels of gralloc buffer
   FORMAT_RB_SWAP = 1 << 2,
   // We record opaqueness here alongside the actual surface we're going to
   // render. This avoids confusion when a layer might return different kinds
   // of surfaces over time (e.g. video frames).
   OPAQUE = 1 << 3
 MOZ_END_ENUM_CLASS(LayerRenderStateFlags)
@@ -111,18 +111,18 @@ struct LayerRenderState {
     : mFlags(aFlags)
     , mHasOwnOffset(false)
     , mSurface(aSurface)
     , mOverlayId(INVALID_OVERLAY)
     , mSize(aSize)
     , mTexture(aTexture)
   {}
 
-  bool YFlipped() const
-  { return bool(mFlags & LayerRenderStateFlags::Y_FLIPPED); }
+  bool OriginBottomLeft() const
+  { return bool(mFlags & LayerRenderStateFlags::ORIGIN_BOTTOM_LEFT); }
 
   bool BufferRotated() const
   { return bool(mFlags & LayerRenderStateFlags::BUFFER_ROTATION); }
 
   bool FormatRBSwapped() const
   { return bool(mFlags & LayerRenderStateFlags::FORMAT_RB_SWAP); }
 
   void SetOverlayId(const int32_t& aId)
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -2702,19 +2702,19 @@ void AsyncPanZoomController::NotifyLayer
   mFrameMetrics.SetMayHaveTouchListeners(aLayerMetrics.GetMayHaveTouchListeners());
   mFrameMetrics.SetMayHaveTouchCaret(aLayerMetrics.GetMayHaveTouchCaret());
   mFrameMetrics.SetScrollParentId(aLayerMetrics.GetScrollParentId());
   APZC_LOG_FM(aLayerMetrics, "%p got a NotifyLayersUpdated with aIsFirstPaint=%d", this, aIsFirstPaint);
 
   LogRendertraceRect(GetGuid(), "page", "brown", aLayerMetrics.GetScrollableRect());
   LogRendertraceRect(GetGuid(), "painted displayport", "lightgreen",
     aLayerMetrics.GetDisplayPort() + aLayerMetrics.GetScrollOffset());
-  if (!aLayerMetrics.mCriticalDisplayPort.IsEmpty()) {
+  if (!aLayerMetrics.GetCriticalDisplayPort().IsEmpty()) {
     LogRendertraceRect(GetGuid(), "painted critical displayport", "darkgreen",
-      aLayerMetrics.mCriticalDisplayPort + aLayerMetrics.GetScrollOffset());
+      aLayerMetrics.GetCriticalDisplayPort() + aLayerMetrics.GetScrollOffset());
   }
 
   mPaintThrottler.TaskComplete(GetFrameTime());
   bool needContentRepaint = false;
   if (FuzzyEqualsAdditive(aLayerMetrics.mCompositionBounds.width, mFrameMetrics.mCompositionBounds.width) &&
       FuzzyEqualsAdditive(aLayerMetrics.mCompositionBounds.height, mFrameMetrics.mCompositionBounds.height)) {
     // Remote content has sync'd up to the composition geometry
     // change, so we can accept the viewport it's calculated.
--- a/gfx/layers/basic/BasicCanvasLayer.cpp
+++ b/gfx/layers/basic/BasicCanvasLayer.cpp
@@ -35,31 +35,33 @@ BasicCanvasLayer::Paint(DrawTarget* aDT,
     UpdateTarget();
     FireDidTransactionCallback();
   }
 
   if (!mSurface) {
     return;
   }
 
+  const bool needsYFlip = (mOriginPos == gl::OriginPos::BottomLeft);
+
   Matrix oldTM;
-  if (mNeedsYFlip) {
+  if (needsYFlip) {
     oldTM = aDT->GetTransform();
     aDT->SetTransform(Matrix(oldTM).
                         PreTranslate(0.0f, mBounds.height).
                         PreScale(1.0f, -1.0f));
   }
 
   FillRectWithMask(aDT, aDeviceOffset,
                    Rect(0, 0, mBounds.width, mBounds.height),
                    mSurface, ToFilter(mFilter),
                    DrawOptions(GetEffectiveOpacity(), GetEffectiveOperator(this)),
                    aMaskLayer);
 
-  if (mNeedsYFlip) {
+  if (needsYFlip) {
     aDT->SetTransform(oldTM);
   }
 }
 
 already_AddRefed<CanvasLayer>
 BasicLayerManager::CreateCanvasLayer()
 {
   NS_ASSERTION(InConstruction(), "Only allowed in construction phase");
--- a/gfx/layers/client/CanvasClient.cpp
+++ b/gfx/layers/client/CanvasClient.cpp
@@ -67,18 +67,18 @@ CanvasClient2D::Update(gfx::IntSize aSiz
   if (!mBuffer) {
     bool isOpaque = (aLayer->GetContentFlags() & Layer::CONTENT_OPAQUE);
     gfxContentType contentType = isOpaque
                                                 ? gfxContentType::COLOR
                                                 : gfxContentType::COLOR_ALPHA;
     gfxImageFormat format
       = gfxPlatform::GetPlatform()->OptimalFormatForContent(contentType);
     TextureFlags flags = TextureFlags::DEFAULT;
-    if (mTextureFlags & TextureFlags::NEEDS_Y_FLIP) {
-      flags |= TextureFlags::NEEDS_Y_FLIP;
+    if (mTextureFlags & TextureFlags::ORIGIN_BOTTOM_LEFT) {
+      flags |= TextureFlags::ORIGIN_BOTTOM_LEFT;
     }
 
     gfx::SurfaceFormat surfaceFormat = gfx::ImageFormatToSurfaceFormat(format);
     mBuffer = CreateTextureClientForCanvas(surfaceFormat, aSize, flags, aLayer);
     if (!mBuffer) {
       NS_WARNING("Failed to allocate the TextureClient");
       return;
     }
--- a/gfx/layers/client/ClientCanvasLayer.cpp
+++ b/gfx/layers/client/ClientCanvasLayer.cpp
@@ -73,17 +73,17 @@ ClientCanvasLayer::Initialize(const Data
   UniquePtr<SurfaceFactory> factory;
 
   if (!gfxPrefs::WebGLForceLayersReadback()) {
     switch (ClientManager()->AsShadowForwarder()->GetCompositorBackendType()) {
       case mozilla::layers::LayersBackend::LAYERS_OPENGL: {
         if (mGLContext->GetContextType() == GLContextType::EGL) {
 #ifdef MOZ_WIDGET_GONK
           TextureFlags flags = TextureFlags::DEALLOCATE_CLIENT |
-                               TextureFlags::NEEDS_Y_FLIP;
+                               TextureFlags::ORIGIN_BOTTOM_LEFT;
           if (!aData.mIsGLAlphaPremult) {
             flags |= TextureFlags::NON_PREMULTIPLIED;
           }
           factory = MakeUnique<SurfaceFactory_Gralloc>(mGLContext,
                                                        caps,
                                                        flags,
                                                        ClientManager()->AsShadowForwarder());
 #else
@@ -148,18 +148,18 @@ ClientCanvasLayer::RenderLayer()
 
   if (!IsDirty()) {
     return;
   }
   Painted();
 
   if (!mCanvasClient) {
     TextureFlags flags = TextureFlags::IMMEDIATE_UPLOAD;
-    if (mNeedsYFlip) {
-      flags |= TextureFlags::NEEDS_Y_FLIP;
+    if (mOriginPos == gl::OriginPos::BottomLeft) {
+      flags |= TextureFlags::ORIGIN_BOTTOM_LEFT;
     }
 
     if (!mGLContext) {
       // We don't support locking for buffer surfaces currently
       flags |= TextureFlags::IMMEDIATE_UPLOAD;
     }
 
     if (!mIsAlphaPremultiplied) {
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -747,18 +747,18 @@ ClientLayerManager::ProgressiveUpdateCal
                                               bool aDrawingCritical)
 {
 #ifdef MOZ_WIDGET_ANDROID
   MOZ_ASSERT(aMetrics.IsScrollable());
   // This is derived from the code in
   // gfx/layers/ipc/CompositorParent.cpp::TransformShadowTree.
   CSSToLayerScale paintScale = aMetrics.LayersPixelsPerCSSPixel();
   const CSSRect& metricsDisplayPort =
-    (aDrawingCritical && !aMetrics.mCriticalDisplayPort.IsEmpty()) ?
-      aMetrics.mCriticalDisplayPort : aMetrics.GetDisplayPort();
+    (aDrawingCritical && !aMetrics.GetCriticalDisplayPort().IsEmpty()) ?
+      aMetrics.GetCriticalDisplayPort() : aMetrics.GetDisplayPort();
   LayerRect displayPort = (metricsDisplayPort + aMetrics.GetScrollOffset()) * paintScale;
 
   ParentLayerPoint scrollOffset;
   CSSToParentLayerScale zoom;
   bool ret = AndroidBridge::Bridge()->ProgressiveUpdateCallback(
     aHasPendingNewThebesContent, displayPort, paintScale.scale, aDrawingCritical,
     scrollOffset, zoom);
   aMetrics.SetScrollOffset(scrollOffset / zoom);
--- a/gfx/layers/client/ClientTiledPaintedLayer.cpp
+++ b/gfx/layers/client/ClientTiledPaintedLayer.cpp
@@ -147,17 +147,17 @@ ClientTiledPaintedLayer::BeginPaint()
   // display port ancestor to the Layer space of this layer.
   gfx::Matrix4x4 transformDisplayPortToLayer =
     GetTransformToAncestorsParentLayer(this, displayPortAncestor);
   transformDisplayPortToLayer.Invert();
 
   // Compute the critical display port that applies to this layer in the
   // LayoutDevice space of this layer.
   ParentLayerRect criticalDisplayPort =
-    (displayportMetrics.mCriticalDisplayPort * displayportMetrics.GetZoom())
+    (displayportMetrics.GetCriticalDisplayPort() * displayportMetrics.GetZoom())
     + displayportMetrics.mCompositionBounds.TopLeft();
   mPaintData.mCriticalDisplayPort = RoundedOut(
     ApplyParentLayerToLayerTransform(transformDisplayPortToLayer, criticalDisplayPort));
   TILING_LOG("TILING %p: Critical displayport %s\n", this, Stringify(mPaintData.mCriticalDisplayPort).c_str());
 
   // Store the resolution from the displayport ancestor layer. Because this is Gecko-side,
   // before any async transforms have occurred, we can use the zoom for this.
   mPaintData.mResolution = displayportMetrics.GetZoom();
@@ -184,17 +184,17 @@ ClientTiledPaintedLayer::UseFastPath()
   GetAncestorLayers(&scrollAncestor, nullptr);
   if (!scrollAncestor) {
     return true;
   }
   const FrameMetrics& parentMetrics = scrollAncestor.Metrics();
 
   bool multipleTransactionsNeeded = gfxPlatform::GetPlatform()->UseProgressivePaint()
                                  || gfxPrefs::UseLowPrecisionBuffer()
-                                 || !parentMetrics.mCriticalDisplayPort.IsEmpty();
+                                 || !parentMetrics.GetCriticalDisplayPort().IsEmpty();
   bool isFixed = GetIsFixedPosition() || GetParent()->GetIsFixedPosition();
   return !multipleTransactionsNeeded || isFixed || parentMetrics.GetDisplayPort().IsEmpty();
 }
 
 bool
 ClientTiledPaintedLayer::RenderHighPrecision(nsIntRegion& aInvalidRegion,
                                             const nsIntRegion& aVisibleRegion,
                                             LayerManager::DrawPaintedLayerCallback aCallback,
--- a/gfx/layers/client/ImageClient.cpp
+++ b/gfx/layers/client/ImageClient.cpp
@@ -197,17 +197,17 @@ ImageClientSingle::UpdateImage(ImageCont
                                             typedImage,
                                             size);
 #ifdef MOZ_WIDGET_ANDROID
       } else if (image->GetFormat() == ImageFormat::SURFACE_TEXTURE) {
         SurfaceTextureImage* typedImage = static_cast<SurfaceTextureImage*>(image);
         const SurfaceTextureImage::Data* data = typedImage->GetData();
         texture = new SurfaceTextureClient(GetForwarder(), mTextureFlags,
                                            data->mSurfTex, size,
-                                           data->mInverted);
+                                           data->mOriginPos);
 #endif
       } else {
         MOZ_ASSERT(false, "Bad ImageFormat.");
       }
     } else {
       RefPtr<gfx::SourceSurface> surface = image->GetAsSourceSurface();
       MOZ_ASSERT(surface);
       texture = CreateTextureClientForDrawing(surface->GetFormat(), image->GetSize(),
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -252,19 +252,19 @@ SharedFrameMetricsHelper::UpdateFromComp
 bool
 SharedFrameMetricsHelper::AboutToCheckerboard(const FrameMetrics& aContentMetrics,
                                               const FrameMetrics& aCompositorMetrics)
 {
   // The size of the painted area is originally computed in layer pixels in layout, but then
   // converted to app units and then back to CSS pixels before being put in the FrameMetrics.
   // This process can introduce some rounding error, so we inflate the rect by one app unit
   // to account for that.
-  CSSRect painted = (aContentMetrics.mCriticalDisplayPort.IsEmpty()
+  CSSRect painted = (aContentMetrics.GetCriticalDisplayPort().IsEmpty()
                       ? aContentMetrics.GetDisplayPort()
-                      : aContentMetrics.mCriticalDisplayPort)
+                      : aContentMetrics.GetCriticalDisplayPort())
                     + aContentMetrics.GetScrollOffset();
   painted.Inflate(CSSMargin::FromAppUnits(nsMargin(1, 1, 1, 1)));
 
   // Inflate the rect by the danger zone. See the description of the danger zone prefs
   // in AsyncPanZoomController.cpp for an explanation of this.
   CSSRect showing = CSSRect(aCompositorMetrics.GetScrollOffset(),
                             aCompositorMetrics.CalculateBoundedCompositedSizeInCssPixels());
   showing.Inflate(LayerSize(gfxPrefs::APZDangerZoneX(), gfxPrefs::APZDangerZoneY())
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -599,18 +599,18 @@ AsyncCompositionManager::ApplyAsyncConte
     Matrix4x4 overscrollTransform = controller->GetOverscrollTransform();
 
     if (!aLayer->IsScrollInfoLayer()) {
       controller->MarkAsyncTransformAppliedToContent();
     }
 
     const FrameMetrics& metrics = aLayer->GetFrameMetrics(i);
     CSSToLayerScale paintScale = metrics.LayersPixelsPerCSSPixel();
-    CSSRect displayPort(metrics.mCriticalDisplayPort.IsEmpty() ?
-                        metrics.GetDisplayPort() : metrics.mCriticalDisplayPort);
+    CSSRect displayPort(metrics.GetCriticalDisplayPort().IsEmpty() ?
+                        metrics.GetDisplayPort() : metrics.GetCriticalDisplayPort());
     ScreenPoint offset(0, 0);
     // XXX this call to SyncFrameMetrics is not currently being used. It will be cleaned
     // up as part of bug 776030 or one of its dependencies.
     SyncFrameMetrics(scrollOffset, asyncTransformWithoutOverscroll.mScale.scale,
                      metrics.GetScrollableRect(), mLayersUpdated, displayPort,
                      paintScale, mIsFirstPaint, fixedLayerMargins, offset);
 
     mIsFirstPaint = false;
@@ -854,19 +854,19 @@ AsyncCompositionManager::TransformScroll
     mContentRect = metrics.GetScrollableRect();
     SetPageRect(mContentRect);
   }
 
   // We synchronise the viewport information with Java after sending the above
   // notifications, so that Java can take these into account in its response.
   // Calculate the absolute display port to send to Java
   LayerIntRect displayPort = RoundedToInt(
-    (metrics.mCriticalDisplayPort.IsEmpty()
+    (metrics.GetCriticalDisplayPort().IsEmpty()
       ? metrics.GetDisplayPort()
-      : metrics.mCriticalDisplayPort
+      : metrics.GetCriticalDisplayPort()
     ) * geckoZoom);
   displayPort += scrollOffsetLayerPixels;
 
   LayerMargin fixedLayerMargins(0, 0, 0, 0);
   ScreenPoint offset(0, 0);
 
   // Ideally we would initialize userZoom to AsyncPanZoomController::CalculateResolution(metrics)
   // but this causes a reftest-ipc test to fail (see bug 883646 comment 27). The reason for this
--- a/gfx/layers/composite/ImageHost.cpp
+++ b/gfx/layers/composite/ImageHost.cpp
@@ -146,17 +146,17 @@ ImageHost::Composite(EffectChain& aEffec
         rect = rect.Intersect(pictureRect);
         effect->mTextureCoords = Rect(Float(rect.x - tileRect.x)/ tileRect.width,
                                       Float(rect.y - tileRect.y) / tileRect.height,
                                       Float(rect.width) / tileRect.width,
                                       Float(rect.height) / tileRect.height);
       } else {
         effect->mTextureCoords = Rect(0, 0, 1, 1);
       }
-      if (mFrontBuffer->GetFlags() & TextureFlags::NEEDS_Y_FLIP) {
+      if (mFrontBuffer->GetFlags() & TextureFlags::ORIGIN_BOTTOM_LEFT) {
         effect->mTextureCoords.y = effect->mTextureCoords.YMost();
         effect->mTextureCoords.height = -effect->mTextureCoords.height;
       }
       GetCompositor()->DrawQuad(rect, aClipRect, aEffectChain,
                                 aOpacity, aTransform);
       GetCompositor()->DrawDiagnostics(DiagnosticFlags::IMAGE | DiagnosticFlags::BIGIMAGE,
                                        rect, aClipRect, aTransform, mFlashCounter);
     } while (it->NextTile());
@@ -174,17 +174,17 @@ ImageHost::Composite(EffectChain& aEffec
                                     Float(mPictureRect.width) / textureSize.width,
                                     Float(mPictureRect.height) / textureSize.height);
       rect = pictureRect;
     } else {
       effect->mTextureCoords = Rect(0, 0, 1, 1);
       rect = gfx::Rect(0, 0, textureSize.width, textureSize.height);
     }
 
-    if (mFrontBuffer->GetFlags() & TextureFlags::NEEDS_Y_FLIP) {
+    if (mFrontBuffer->GetFlags() & TextureFlags::ORIGIN_BOTTOM_LEFT) {
       effect->mTextureCoords.y = effect->mTextureCoords.YMost();
       effect->mTextureCoords.height = -effect->mTextureCoords.height;
     }
 
     GetCompositor()->DrawQuad(rect, aClipRect, aEffectChain,
                               aOpacity, aTransform);
     GetCompositor()->DrawDiagnostics(DiagnosticFlags::IMAGE,
                                      rect, aClipRect,
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -923,20 +923,20 @@ LayerManagerComposite::ComputeRenderInte
     // If the screen rect is empty, the user has scrolled entirely into
     // over-scroll and so we can be considered to have full integrity.
     if (screenRect.IsEmpty()) {
       return 1.0f;
     }
 
     // Work out how much of the critical display-port covers the screen
     bool hasLowPrecision = false;
-    if (!metrics.mCriticalDisplayPort.IsEmpty()) {
+    if (!metrics.GetCriticalDisplayPort().IsEmpty()) {
       hasLowPrecision = true;
       highPrecisionMultiplier =
-        GetDisplayportCoverage(metrics.mCriticalDisplayPort, transform, screenRect);
+        GetDisplayportCoverage(metrics.GetCriticalDisplayPort(), transform, screenRect);
     }
 
     // Work out how much of the display-port covers the screen
     if (!metrics.GetDisplayPort().IsEmpty()) {
       if (hasLowPrecision) {
         lowPrecisionMultiplier =
           GetDisplayportCoverage(metrics.GetDisplayPort(), transform, screenRect);
       } else {
--- a/gfx/layers/d3d10/CanvasLayerD3D10.cpp
+++ b/gfx/layers/d3d10/CanvasLayerD3D10.cpp
@@ -18,18 +18,18 @@ namespace mozilla {
 namespace layers {
 
 using namespace mozilla::gl;
 using namespace mozilla::gfx;
 
 CanvasLayerD3D10::CanvasLayerD3D10(LayerManagerD3D10 *aManager)
   : CanvasLayer(aManager, nullptr)
   , LayerD3D10(aManager)
-  , mDataIsPremultiplied(false)
-  , mNeedsYFlip(false)
+  , mDataIsPremultiplied(true)
+  , mOriginPos(gl::OriginPos::TopLeft)
   , mHasAlpha(true)
 {
     mImplData = static_cast<LayerD3D10*>(this);
 }
 
 CanvasLayerD3D10::~CanvasLayerD3D10()
 {
 }
@@ -38,35 +38,33 @@ void
 CanvasLayerD3D10::Initialize(const Data& aData)
 {
   NS_ASSERTION(mSurface == nullptr, "BasicCanvasLayer::Initialize called twice!");
 
   if (aData.mGLContext) {
     mGLContext = aData.mGLContext;
     NS_ASSERTION(mGLContext->IsOffscreen(), "Canvas GLContext must be offscreen.");
     mDataIsPremultiplied = aData.mIsGLAlphaPremult;
-    mNeedsYFlip = true;
+    mOriginPos = gl::OriginPos::TopLeft;
 
     GLScreenBuffer* screen = mGLContext->Screen();
 
     UniquePtr<SurfaceFactory> factory = nullptr;
     if (!gfxPrefs::WebGLForceLayersReadback()) {
       if (mGLContext->IsANGLE()) {
         factory = SurfaceFactory_ANGLEShareHandle::Create(mGLContext,
                                                           screen->mCaps);
       }
     }
 
     if (factory) {
       screen->Morph(Move(factory));
     }
   } else if (aData.mDrawTarget) {
     mDrawTarget = aData.mDrawTarget;
-    mNeedsYFlip = false;
-    mDataIsPremultiplied = true;
     void *texture = mDrawTarget->GetNativeSurface(NativeSurfaceType::D3D10_TEXTURE);
 
     if (texture) {
       mTexture = static_cast<ID3D10Texture2D*>(texture);
 
       NS_ASSERTION(!aData.mGLContext,
                    "CanvasLayer can't have both DrawTarget and WebGLContext/Surface");
 
@@ -74,17 +72,17 @@ CanvasLayerD3D10::Initialize(const Data&
       device()->CreateShaderResourceView(mTexture, nullptr, getter_AddRefs(mSRView));
       return;
     }
 
     // XXX we should store mDrawTarget and use it directly in UpdateSurface,
     // bypassing Thebes
     mSurface = mDrawTarget->Snapshot();
   } else {
-    NS_ERROR("CanvasLayer created without mSurface, mDrawTarget or mGLContext?");
+    MOZ_CRASH("CanvasLayer created without mSurface, mDrawTarget or mGLContext?");
   }
 
   mBounds.SetRect(0, 0, aData.mSize.width, aData.mSize.height);
   mIsD2DTexture = false;
 
   // Create a texture in case we need to readback.
   CD3D10_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM, mBounds.width, mBounds.height, 1, 1);
   desc.Usage = D3D10_USAGE_DYNAMIC;
@@ -208,29 +206,31 @@ CanvasLayerD3D10::RenderLayer()
   effect()->GetVariableByName("vLayerQuad")->AsVector()->SetFloatVector(
     ShaderConstantRectD3D10(
       (float)mBounds.x,
       (float)mBounds.y,
       (float)mBounds.width,
       (float)mBounds.height)
     );
 
-  if (mNeedsYFlip) {
+  const bool needsYFlip = (mOriginPos == gl::OriginPos::BottomLeft);
+
+  if (needsYFlip) {
     effect()->GetVariableByName("vTextureCoords")->AsVector()->SetFloatVector(
       ShaderConstantRectD3D10(
         0,
         1.0f,
         1.0f,
         -1.0f)
       );
   }
 
   technique->GetPassByIndex(0)->Apply(0);
   device()->Draw(4, 0);
 
-  if (mNeedsYFlip) {
+  if (needsYFlip) {
     effect()->GetVariableByName("vTextureCoords")->AsVector()->
       SetFloatVector(ShaderConstantRectD3D10(0, 0, 1.0f, 1.0f));
   }
 }
 
 } /* namespace layers */
 } /* namespace mozilla */
--- a/gfx/layers/d3d10/CanvasLayerD3D10.h
+++ b/gfx/layers/d3d10/CanvasLayerD3D10.h
@@ -1,18 +1,18 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GFX_CANVASLAYERD3D10_H
 #define GFX_CANVASLAYERD3D10_H
 
+#include "GLContextTypes.h"
 #include "LayerManagerD3D10.h"
-
 #include "mozilla/Preferences.h"
 
 namespace mozilla {
 
 namespace gl {
 class GLContext;
 }
 
@@ -40,17 +40,17 @@ private:
   RefPtr<gfx::SourceSurface> mSurface;
   mozilla::RefPtr<mozilla::gfx::DrawTarget> mDrawTarget;
   nsRefPtr<GLContext> mGLContext;
   nsRefPtr<ID3D10Texture2D> mTexture;
   nsRefPtr<ID3D10ShaderResourceView> mUploadSRView;
   nsRefPtr<ID3D10ShaderResourceView> mSRView;
 
   bool mDataIsPremultiplied;
-  bool mNeedsYFlip;
+  gl::OriginPos mOriginPos;
   bool mIsD2DTexture;
   bool mHasAlpha;
 
   nsAutoArrayPtr<uint8_t> mCachedTempBlob;
   uint32_t mCachedTempBlob_Size;
 
   uint8_t* GetTempBlob(const uint32_t aSize)
   {
--- a/gfx/layers/d3d9/CanvasLayerD3D9.cpp
+++ b/gfx/layers/d3d9/CanvasLayerD3D9.cpp
@@ -17,18 +17,18 @@ using namespace mozilla::gfx;
 using namespace mozilla::gl;
 
 namespace mozilla {
 namespace layers {
 
 CanvasLayerD3D9::CanvasLayerD3D9(LayerManagerD3D9 *aManager)
   : CanvasLayer(aManager, nullptr)
   , LayerD3D9(aManager)
-  , mDataIsPremultiplied(false)
-  , mNeedsYFlip(false)
+  , mDataIsPremultiplied(true)
+  , mOriginPos(gl::OriginPos::TopLeft)
   , mHasAlpha(true)
 {
     mImplData = static_cast<LayerD3D9*>(this);
     aManager->deviceManager()->mLayersWithResources.AppendElement(this);
 }
 
 CanvasLayerD3D9::~CanvasLayerD3D9()
 {
@@ -39,23 +39,21 @@ CanvasLayerD3D9::~CanvasLayerD3D9()
 
 void
 CanvasLayerD3D9::Initialize(const Data& aData)
 {
   NS_ASSERTION(mDrawTarget == nullptr, "BasicCanvasLayer::Initialize called twice!");
 
   if (aData.mDrawTarget) {
     mDrawTarget = aData.mDrawTarget;
-    mNeedsYFlip = false;
-    mDataIsPremultiplied = true;
   } else if (aData.mGLContext) {
     mGLContext = aData.mGLContext;
     NS_ASSERTION(mGLContext->IsOffscreen(), "Canvas GLContext must be offscreen.");
     mDataIsPremultiplied = aData.mIsGLAlphaPremult;
-    mNeedsYFlip = true;
+    mOriginPos = gl::OriginPos::BottomLeft;
   } else {
     NS_ERROR("CanvasLayer created without mGLContext or mDrawTarget?");
   }
 
   mBounds.SetRect(0, 0, aData.mSize.width, aData.mSize.height);
 
   CreateTexture();
 }
@@ -132,19 +130,20 @@ CanvasLayerD3D9::RenderLayer()
 
   if (!mTexture)
     return;
 
   /*
    * We flip the Y axis here, note we can only do this because we are in
    * CULL_NONE mode!
    */
+  ShaderConstantRect quad(0, 0, mBounds.width, mBounds.height);
 
-  ShaderConstantRect quad(0, 0, mBounds.width, mBounds.height);
-  if (mNeedsYFlip) {
+  const bool needsYFlip = (mOriginPos == gl::OriginPos::BottomLeft);
+  if (needsYFlip) {
     quad.mHeight = (float)-mBounds.height;
     quad.mY = (float)mBounds.height;
   }
 
   device()->SetVertexShaderConstantF(CBvLayerQuad, quad, 1);
 
   SetShaderTransformAndOpacity();
 
--- a/gfx/layers/d3d9/CanvasLayerD3D9.h
+++ b/gfx/layers/d3d9/CanvasLayerD3D9.h
@@ -1,18 +1,18 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GFX_CANVASLAYERD3D9_H
 #define GFX_CANVASLAYERD3D9_H
 
+#include "GLContextTypes.h"
 #include "LayerManagerD3D9.h"
-#include "GLContextTypes.h"
 
 namespace mozilla {
 namespace layers {
 
 
 class CanvasLayerD3D9 :
   public CanvasLayer,
   public LayerD3D9
@@ -37,17 +37,17 @@ protected:
 
   void UpdateSurface();
 
   nsRefPtr<GLContext> mGLContext;
   nsRefPtr<IDirect3DTexture9> mTexture;
   RefPtr<gfx::DrawTarget> mDrawTarget;
 
   bool mDataIsPremultiplied;
-  bool mNeedsYFlip;
+  gl::OriginPos mOriginPos;
   bool mHasAlpha;
 
   nsAutoArrayPtr<uint8_t> mCachedTempBlob;
   uint32_t mCachedTempBlob_Size;
 
   uint8_t* GetTempBlob(const uint32_t aSize)
   {
       if (!mCachedTempBlob || aSize != mCachedTempBlob_Size) {
--- a/gfx/layers/opengl/GrallocTextureClient.cpp
+++ b/gfx/layers/opengl/GrallocTextureClient.cpp
@@ -343,17 +343,17 @@ GrallocTextureClientOGL::GetBufferSize()
 /*static*/ TemporaryRef<TextureClient>
 GrallocTextureClientOGL::FromSharedSurface(gl::SharedSurface* abstractSurf,
                                            TextureFlags flags)
 {
   auto surf = gl::SharedSurface_Gralloc::Cast(abstractSurf);
 
   RefPtr<TextureClient> ret = surf->GetTextureClient();
 
-  TextureFlags mask = TextureFlags::NEEDS_Y_FLIP |
+  TextureFlags mask = TextureFlags::ORIGIN_BOTTOM_LEFT |
                       TextureFlags::RB_SWAPPED |
                       TextureFlags::NON_PREMULTIPLIED;
   TextureFlags required = flags & mask;
   TextureFlags present = ret->GetFlags() & mask;
 
   if (present != required) {
     printf_stderr("Present flags: 0x%x. Required: 0x%x.\n",
                   (uint32_t)present,
--- a/gfx/layers/opengl/GrallocTextureHost.cpp
+++ b/gfx/layers/opengl/GrallocTextureHost.cpp
@@ -207,18 +207,18 @@ GrallocTextureHostOGL::GetRenderState()
 {
   android::GraphicBuffer* graphicBuffer = GetGraphicBufferFromDesc(mGrallocHandle).get();
 
   if (graphicBuffer) {
     LayerRenderStateFlags flags = LayerRenderStateFlags::LAYER_RENDER_STATE_DEFAULT;
     if (mIsOpaque) {
       flags |= LayerRenderStateFlags::OPAQUE;
     }
-    if (mFlags & TextureFlags::NEEDS_Y_FLIP) {
-      flags |= LayerRenderStateFlags::Y_FLIPPED;
+    if (mFlags & TextureFlags::ORIGIN_BOTTOM_LEFT) {
+      flags |= LayerRenderStateFlags::ORIGIN_BOTTOM_LEFT;
     }
     if (mFlags & TextureFlags::RB_SWAPPED) {
       flags |= LayerRenderStateFlags::FORMAT_RB_SWAP;
     }
     return LayerRenderState(graphicBuffer,
                             gfx::ThebesIntSize(mDescriptorSize),
                             flags,
                             this);
--- a/gfx/layers/opengl/TextureClientOGL.cpp
+++ b/gfx/layers/opengl/TextureClientOGL.cpp
@@ -29,18 +29,18 @@ EGLImageTextureClient::EGLImageTextureCl
   , mSize(aSize)
   , mIsLocked(false)
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default,
              "Can't pass an `EGLImage` between processes.");
 
   AddFlags(TextureFlags::DEALLOCATE_CLIENT);
 
-  if (aImage->GetData()->mInverted) {
-    AddFlags(TextureFlags::NEEDS_Y_FLIP);
+  if (aImage->GetData()->mOriginPos == gl::OriginPos::BottomLeft) {
+    AddFlags(TextureFlags::ORIGIN_BOTTOM_LEFT);
   }
 }
 
 bool
 EGLImageTextureClient::ToSurfaceDescriptor(SurfaceDescriptor& aOutDescriptor)
 {
   MOZ_ASSERT(IsValid());
   MOZ_ASSERT(IsAllocated());
@@ -72,30 +72,30 @@ EGLImageTextureClient::Unlock()
 // SurfaceTextureClient
 
 #ifdef MOZ_WIDGET_ANDROID
 
 SurfaceTextureClient::SurfaceTextureClient(ISurfaceAllocator* aAllocator,
                                            TextureFlags aFlags,
                                            AndroidSurfaceTexture* aSurfTex,
                                            gfx::IntSize aSize,
-                                           bool aInverted)
+                                           gl::OriginPos aOriginPos)
   : TextureClient(aAllocator, aFlags)
   , mSurfTex(aSurfTex)
   , mSize(aSize)
   , mIsLocked(false)
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default,
              "Can't pass pointers between processes.");
 
   // Our data is always owned externally.
   AddFlags(TextureFlags::DEALLOCATE_CLIENT);
 
-  if (aInverted) {
-    AddFlags(TextureFlags::NEEDS_Y_FLIP);
+  if (aOriginPos == gl::OriginPos::BottomLeft) {
+    AddFlags(TextureFlags::ORIGIN_BOTTOM_LEFT);
   }
 }
 
 SurfaceTextureClient::~SurfaceTextureClient()
 {
   // Our data is always owned externally.
 }
 
--- a/gfx/layers/opengl/TextureClientOGL.h
+++ b/gfx/layers/opengl/TextureClientOGL.h
@@ -72,17 +72,17 @@ protected:
 
 class SurfaceTextureClient : public TextureClient
 {
 public:
   SurfaceTextureClient(ISurfaceAllocator* aAllocator,
                        TextureFlags aFlags,
                        gl::AndroidSurfaceTexture* aSurfTex,
                        gfx::IntSize aSize,
-                       bool aInverted);
+                       gl::OriginPos aOriginPos);
 
   ~SurfaceTextureClient();
 
   virtual bool IsAllocated() const MOZ_OVERRIDE { return true; }
 
   virtual bool HasInternalBuffer() const MOZ_OVERRIDE { return false; }
 
   virtual gfx::IntSize GetSize() const { return mSize; }
--- a/gfx/layers/opengl/TextureHostOGL.cpp
+++ b/gfx/layers/opengl/TextureHostOGL.cpp
@@ -98,18 +98,18 @@ CreateTextureHostOGL(const SurfaceDescri
 
 static gl::TextureImage::Flags
 FlagsToGLFlags(TextureFlags aFlags)
 {
   uint32_t result = TextureImage::NoFlags;
 
   if (aFlags & TextureFlags::USE_NEAREST_FILTER)
     result |= TextureImage::UseNearestFilter;
-  if (aFlags & TextureFlags::NEEDS_Y_FLIP)
-    result |= TextureImage::NeedsYFlip;
+  if (aFlags & TextureFlags::ORIGIN_BOTTOM_LEFT)
+    result |= TextureImage::OriginBottomLeft;
   if (aFlags & TextureFlags::DISALLOW_BIGIMAGE)
     result |= TextureImage::DisallowBigImage;
 
   return static_cast<gl::TextureImage::Flags>(result);
 }
 
 #if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
 bool
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -220,17 +220,17 @@ TestAPZCTreeManager::MakeAPZCInstance(ui
 
 static FrameMetrics
 TestFrameMetrics()
 {
   FrameMetrics fm;
 
   fm.SetDisplayPort(CSSRect(0, 0, 10, 10));
   fm.mCompositionBounds = ParentLayerRect(0, 0, 10, 10);
-  fm.mCriticalDisplayPort = CSSRect(0, 0, 10, 10);
+  fm.SetCriticalDisplayPort(CSSRect(0, 0, 10, 10));
   fm.SetScrollableRect(CSSRect(0, 0, 100, 100));
 
   return fm;
 }
 
 class APZCBasicTester : public ::testing::Test {
 public:
   explicit APZCBasicTester(AsyncPanZoomController::GestureBehavior aGestureBehavior = AsyncPanZoomController::DEFAULT_GESTURES)
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -424,17 +424,17 @@ class GCRuntime
 #endif
 
     void setAlwaysPreserveCode() { alwaysPreserveCode = true; }
 
     bool isIncrementalGCAllowed() { return incrementalAllowed; }
     void disallowIncrementalGC() { incrementalAllowed = false; }
 
     bool isIncrementalGCEnabled() { return mode == JSGC_MODE_INCREMENTAL && incrementalAllowed; }
-    bool isIncrementalGCInProgress() { return state() != gc::NO_INCREMENTAL && !verifyPreData; }
+    bool isIncrementalGCInProgress() { return state() != gc::NO_INCREMENTAL; }
 
     bool isGenerationalGCEnabled() { return generationalDisabled == 0; }
     void disableGenerationalGC();
     void enableGenerationalGC();
 
 #ifdef JSGC_COMPACTING
     void disableCompactingGC();
     void enableCompactingGC();
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -768,17 +768,17 @@ Statistics::endGC()
     runtime->addTelemetry(JS_TELEMETRY_GC_MARK_MS, t(phaseTimes[PHASE_MARK]));
     runtime->addTelemetry(JS_TELEMETRY_GC_SWEEP_MS, t(phaseTimes[PHASE_SWEEP]));
     runtime->addTelemetry(JS_TELEMETRY_GC_MARK_ROOTS_MS, t(phaseTimes[PHASE_MARK_ROOTS]));
     runtime->addTelemetry(JS_TELEMETRY_GC_MARK_GRAY_MS, t(phaseTimes[PHASE_SWEEP_MARK_GRAY]));
     runtime->addTelemetry(JS_TELEMETRY_GC_NON_INCREMENTAL, !!nonincrementalReason);
     runtime->addTelemetry(JS_TELEMETRY_GC_INCREMENTAL_DISABLED, !runtime->gc.isIncrementalGCAllowed());
     runtime->addTelemetry(JS_TELEMETRY_GC_SCC_SWEEP_TOTAL_MS, t(sccTotal));
     runtime->addTelemetry(JS_TELEMETRY_GC_SCC_SWEEP_MAX_PAUSE_MS, t(sccLongest));
- 
+
     double mmu50 = computeMMU(50 * PRMJ_USEC_PER_MSEC);
     runtime->addTelemetry(JS_TELEMETRY_GC_MMU_50, mmu50 * 100);
 
     if (fp)
         printStats();
 
     // Clear the timers at the end of a GC because we accumulate time in
     // between GCs for some (which come before PHASE_GC_BEGIN in the list.)
@@ -787,17 +787,17 @@ Statistics::endGC()
 }
 
 void
 Statistics::beginSlice(const ZoneGCStats &zoneStats, JSGCInvocationKind gckind,
                        JS::gcreason::Reason reason)
 {
     this->zoneStats = zoneStats;
 
-    bool first = runtime->gc.state() == gc::NO_INCREMENTAL;
+    bool first = !runtime->gc.isIncrementalGCInProgress();
     if (first)
         beginGC(gckind);
 
     SliceData data(reason, PRMJ_Now(), GetPageFaultCount());
     if (!slices.append(data))
         CrashAtUnhandlableOOM("Failed to allocate statistics slice.");
 
     runtime->addTelemetry(JS_TELEMETRY_GC_REASON, reason);
@@ -815,17 +815,17 @@ void
 Statistics::endSlice()
 {
     slices.back().end = PRMJ_Now();
     slices.back().endFaults = GetPageFaultCount();
 
     runtime->addTelemetry(JS_TELEMETRY_GC_SLICE_MS, t(slices.back().end - slices.back().start));
     runtime->addTelemetry(JS_TELEMETRY_GC_RESET, !!slices.back().resetReason);
 
-    bool last = runtime->gc.state() == gc::NO_INCREMENTAL;
+    bool last = !runtime->gc.isIncrementalGCInProgress();
     if (last)
         endGC();
 
     // Slice callbacks should only fire for the outermost level
     if (--gcDepth == 0) {
         bool wasFullGC = zoneStats.isCollectingAllZones();
         if (sliceCallback)
             (*sliceCallback)(runtime, last ? JS::GC_CYCLE_END : JS::GC_SLICE_END,
--- a/js/src/gc/Verifier.cpp
+++ b/js/src/gc/Verifier.cpp
@@ -165,17 +165,17 @@ NextNode(VerifyNode *node)
     else
         return (VerifyNode *)((char *)node + sizeof(VerifyNode) +
                              sizeof(EdgeValue)*(node->count - 1));
 }
 
 void
 gc::GCRuntime::startVerifyPreBarriers()
 {
-    if (verifyPreData || incrementalState != NO_INCREMENTAL)
+    if (verifyPreData || isIncrementalGCInProgress())
         return;
 
     /*
      * The post barrier verifier requires the storebuffer to be enabled, but the
      * pre barrier verifier disables it as part of disabling GGC.  Don't allow
      * starting the pre barrier verifier if the post barrier verifier is already
      * running.
      */
@@ -398,21 +398,18 @@ struct VerifyPostTracer : JSTracer
 /*
  * The post-barrier verifier runs the full store buffer and a fake nursery when
  * running and when it stops, walks the full heap to ensure that all the
  * important edges were inserted into the storebuffer.
  */
 void
 gc::GCRuntime::startVerifyPostBarriers()
 {
-    if (verifyPostData ||
-        incrementalState != NO_INCREMENTAL)
-    {
+    if (verifyPostData || isIncrementalGCInProgress())
         return;
-    }
 
     evictNursery();
 
     number++;
 
     VerifyPostTracer *trc = js_new<VerifyPostTracer>(rt, JSTraceCallback(nullptr));
     if (!trc)
         return;
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -102,23 +102,16 @@ void
 Zone::onTooMuchMalloc()
 {
     if (!gcMallocGCTriggered) {
         GCRuntime &gc = runtimeFromAnyThread()->gc;
         gcMallocGCTriggered = gc.triggerZoneGC(this, JS::gcreason::TOO_MUCH_MALLOC);
     }
 }
 
-bool
-Zone::isCloseToAllocTrigger(bool highFrequencyGC) const
-{
-    double factor = highFrequencyGC ? 0.85 : 0.9;
-    return usage.gcBytes() >= factor * threshold.gcTriggerBytes();
-}
-
 void
 Zone::beginSweepTypes(FreeOp *fop, bool releaseTypes)
 {
     // Periodically release observed types for all scripts. This is safe to
     // do when there are no frames for the zone on the stack.
     if (active)
         releaseTypes = false;
 
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -57,16 +57,17 @@ class ZoneHeapThreshold
   public:
     ZoneHeapThreshold()
       : gcHeapGrowthFactor_(3.0),
         gcTriggerBytes_(0)
     {}
 
     double gcHeapGrowthFactor() const { return gcHeapGrowthFactor_; }
     size_t gcTriggerBytes() const { return gcTriggerBytes_; }
+    bool isCloseToAllocTrigger(const js::gc::HeapUsage& usage, bool highFrequencyGC) const;
 
     void updateAfterGC(size_t lastBytes, JSGCInvocationKind gckind,
                        const GCSchedulingTunables &tunables, const GCSchedulingState &state);
     void updateForRemovedArena(const GCSchedulingTunables &tunables);
 
   private:
     static double computeZoneHeapGrowthFactorForHeapSize(size_t lastBytes,
                                                          const GCSchedulingTunables &tunables,
@@ -148,18 +149,16 @@ struct Zone : public JS::shadow::Zone,
         gcMallocBytes -= ptrdiff_t(nbytes);
         if (MOZ_UNLIKELY(isTooMuchMalloc()))
             onTooMuchMalloc();
     }
 
     bool isTooMuchMalloc() const { return gcMallocBytes <= 0; }
     void onTooMuchMalloc();
 
-    bool isCloseToAllocTrigger(bool highFrequencyGC) const;
-
     void *onOutOfMemory(void *p, size_t nbytes) {
         return runtimeFromMainThread()->onOutOfMemory(p, nbytes);
     }
     void reportAllocationOverflow() { js_ReportAllocationOverflow(nullptr); }
 
     void beginSweepTypes(js::FreeOp *fop, bool releaseTypes);
 
     bool hasMarkedCompartments();
--- a/js/src/jsapi-tests/testGCFinalizeCallback.cpp
+++ b/js/src/jsapi-tests/testGCFinalizeCallback.cpp
@@ -20,17 +20,17 @@ BEGIN_TEST(testGCFinalizeCallback)
     CHECK(checkSingleGroup());
     CHECK(checkFinalizeStatus());
     CHECK(checkFinalizeIsCompartmentGC(false));
 
     /* Full GC, incremental. */
     FinalizeCalls = 0;
     JS::PrepareForFullGC(rt);
     JS::IncrementalGC(rt, JS::gcreason::API, 1000000);
-    CHECK(rt->gc.state() == js::gc::NO_INCREMENTAL);
+    CHECK(!rt->gc.isIncrementalGCInProgress());
     CHECK(rt->gc.isFullGc());
     CHECK(checkMultipleGroups());
     CHECK(checkFinalizeStatus());
     CHECK(checkFinalizeIsCompartmentGC(false));
 
     JS::RootedObject global1(cx, createTestGlobal());
     JS::RootedObject global2(cx, createTestGlobal());
     JS::RootedObject global3(cx, createTestGlobal());
@@ -57,29 +57,29 @@ BEGIN_TEST(testGCFinalizeCallback)
     CHECK(checkSingleGroup());
     CHECK(checkFinalizeStatus());
     CHECK(checkFinalizeIsCompartmentGC(true));
 
     /* Compartment GC, incremental, single compartment. */
     FinalizeCalls = 0;
     JS::PrepareZoneForGC(global1->zone());
     JS::IncrementalGC(rt, JS::gcreason::API, 1000000);
-    CHECK(rt->gc.state() == js::gc::NO_INCREMENTAL);
+    CHECK(!rt->gc.isIncrementalGCInProgress());
     CHECK(!rt->gc.isFullGc());
     CHECK(checkSingleGroup());
     CHECK(checkFinalizeStatus());
     CHECK(checkFinalizeIsCompartmentGC(true));
 
     /* Compartment GC, incremental, multiple compartments. */
     FinalizeCalls = 0;
     JS::PrepareZoneForGC(global1->zone());
     JS::PrepareZoneForGC(global2->zone());
     JS::PrepareZoneForGC(global3->zone());
     JS::IncrementalGC(rt, JS::gcreason::API, 1000000);
-    CHECK(rt->gc.state() == js::gc::NO_INCREMENTAL);
+    CHECK(!rt->gc.isIncrementalGCInProgress());
     CHECK(!rt->gc.isFullGc());
     CHECK(checkMultipleGroups());
     CHECK(checkFinalizeStatus());
     CHECK(checkFinalizeIsCompartmentGC(true));
 
 #ifdef JS_GC_ZEAL
 
     /* Full GC with reset due to new compartment, becoming compartment GC. */
@@ -90,17 +90,17 @@ BEGIN_TEST(testGCFinalizeCallback)
     js::SliceBudget budget(js::WorkBudget(1));
     rt->gc.gcDebugSlice(budget);
     CHECK(rt->gc.state() == js::gc::MARK);
     CHECK(rt->gc.isFullGc());
 
     JS::RootedObject global4(cx, createTestGlobal());
     budget = js::SliceBudget(js::WorkBudget(1));
     rt->gc.gcDebugSlice(budget);
-    CHECK(rt->gc.state() == js::gc::NO_INCREMENTAL);
+    CHECK(!rt->gc.isIncrementalGCInProgress());
     CHECK(!rt->gc.isFullGc());
     CHECK(checkMultipleGroups());
     CHECK(checkFinalizeStatus());
 
     for (unsigned i = 0; i < FinalizeCalls - 1; ++i)
         CHECK(!IsCompartmentGCBuffer[i]);
     CHECK(IsCompartmentGCBuffer[FinalizeCalls - 1]);
 
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -1109,17 +1109,17 @@ JS_FRIEND_API(bool)
 JS::IsIncrementalGCEnabled(JSRuntime *rt)
 {
     return rt->gc.isIncrementalGCEnabled();
 }
 
 JS_FRIEND_API(bool)
 JS::IsIncrementalGCInProgress(JSRuntime *rt)
 {
-    return rt->gc.isIncrementalGCInProgress();
+    return rt->gc.isIncrementalGCInProgress() && !rt->gc.isVerifyPreBarriersEnabled();
 }
 
 JS_FRIEND_API(void)
 JS::DisableIncrementalGC(JSRuntime *rt)
 {
     rt->gc.disallowIncrementalGC();
 }
 
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1622,20 +1622,20 @@ bool
 GCRuntime::addRoot(T *rp, const char *name, JSGCRootType rootType)
 {
     /*
      * Sometimes Firefox will hold weak references to objects and then convert
      * them to strong references by calling AddRoot (e.g., via PreserveWrapper,
      * or ModifyBusyCount in workers). We need a read barrier to cover these
      * cases.
      */
-    if (rt->gc.incrementalState != NO_INCREMENTAL)
+    if (isIncrementalGCInProgress())
         BarrierOwner<T>::result::writeBarrierPre(*rp);
 
-    return rt->gc.rootsHash.put((void *)rp, RootInfo(name, rootType));
+    return rootsHash.put((void *)rp, RootInfo(name, rootType));
 }
 
 void
 GCRuntime::removeRoot(void *rp)
 {
     rootsHash.remove(rp);
     poke();
 }
@@ -1743,16 +1743,23 @@ GCRuntime::updateMallocCounter(JS::Zone 
 
 void
 GCRuntime::onTooMuchMalloc()
 {
     if (!mallocGCTriggered)
         mallocGCTriggered = triggerGC(JS::gcreason::TOO_MUCH_MALLOC);
 }
 
+bool
+ZoneHeapThreshold::isCloseToAllocTrigger(const js::gc::HeapUsage& usage, bool highFrequencyGC) const
+{
+    double factor = highFrequencyGC ? 0.85 : 0.9;
+    return usage.gcBytes() >= factor * gcTriggerBytes();
+}
+
 /* static */ double
 ZoneHeapThreshold::computeZoneHeapGrowthFactorForHeapSize(size_t lastBytes,
                                                           const GCSchedulingTunables &tunables,
                                                           const GCSchedulingState &state)
 {
     if (!tunables.isDynamicHeapGrowthEnabled())
         return 3.0;
 
@@ -3004,27 +3011,27 @@ RunLastDitchGC(JSContext *cx, JS::Zone *
     size_t thingSize = Arena::thingSize(thingKind);
     return zone->allocator.arenas.allocateFromFreeList(thingKind, thingSize);
 }
 
 template <AllowGC allowGC>
 /* static */ void *
 GCRuntime::refillFreeListFromMainThread(JSContext *cx, AllocKind thingKind)
 {
-    MOZ_ASSERT(!cx->runtime()->isHeapBusy(), "allocating while under GC");
-    MOZ_ASSERT_IF(allowGC, !cx->runtime()->currentThreadHasExclusiveAccess());
+    JSRuntime *rt = cx->runtime();
+    MOZ_ASSERT(!rt->isHeapBusy(), "allocating while under GC");
+    MOZ_ASSERT_IF(allowGC, !rt->currentThreadHasExclusiveAccess());
 
     Allocator *allocator = cx->allocator();
     Zone *zone = allocator->zone_;
 
     // If we have grown past our GC heap threshold while in the middle of an
     // incremental GC, we're growing faster than we're GCing, so stop the world
     // and do a full, non-incremental GC right now, if possible.
-    const bool mustCollectNow = allowGC &&
-                                cx->runtime()->gc.incrementalState != NO_INCREMENTAL &&
+    const bool mustCollectNow = allowGC && rt->gc.isIncrementalGCInProgress() &&
                                 zone->usage.gcBytes() > zone->threshold.gcTriggerBytes();
 
     bool outOfMemory = false;  // Set true if we fail to allocate.
     bool ranGC = false;  // Once we've GC'd and still cannot allocate, report.
     do {
         if (MOZ_UNLIKELY(mustCollectNow || outOfMemory)) {
             // If we are doing a fallible allocation, percolate up the OOM
             // instead of reporting it.
@@ -3042,17 +3049,17 @@ GCRuntime::refillFreeListFromMainThread(
         void *thing = allocator->arenas.allocateFromArena(zone, thingKind, maybeStartBGAlloc);
         if (MOZ_LIKELY(thing))
             return thing;
 
         // Even if allocateFromArena failed due to OOM, a background
         // finalization task may be running (freeing more memory); wait for it
         // to finish, then try to allocate again in case it freed up the memory
         // we need.
-        cx->runtime()->gc.waitBackgroundSweepEnd();
+        rt->gc.waitBackgroundSweepEnd();
 
         thing = allocator->arenas.allocateFromArena(zone, thingKind, maybeStartBGAlloc);
         if (MOZ_LIKELY(thing))
             return thing;
 
         // Retry after a last-ditch GC, unless we've already tried that.
         outOfMemory = true;
     } while (!ranGC);
@@ -3304,18 +3311,18 @@ GCRuntime::maybeGC(Zone *zone)
         return true;
     }
 #endif
 
     if (gcIfNeeded())
         return true;
 
     if (zone->usage.gcBytes() > 1024 * 1024 &&
-        zone->isCloseToAllocTrigger(schedulingState.inHighFrequencyGCMode()) &&
-        incrementalState == NO_INCREMENTAL &&
+        zone->threshold.isCloseToAllocTrigger(zone->usage, schedulingState.inHighFrequencyGCMode()) &&
+        !isIncrementalGCInProgress() &&
         !isBackgroundSweeping())
     {
         PrepareZoneForGC(zone);
         gcSlice(GC_NORMAL, JS::gcreason::MAYBEGC);
         return true;
     }
 
     return false;
@@ -3661,17 +3668,17 @@ GCHelperState::startBackgroundShrink(con
 }
 
 void
 GCHelperState::waitBackgroundSweepEnd()
 {
     AutoLockGC lock(rt);
     while (state() == SWEEPING)
         waitForBackgroundThread();
-    if (rt->gc.incrementalState == NO_INCREMENTAL)
+    if (!rt->gc.isIncrementalGCInProgress())
         rt->gc.assertBackgroundSweepingFinished();
 }
 
 void
 GCHelperState::doSweep(AutoLockGC &lock)
 {
     // The main thread may call queueZonesForBackgroundSweep() or
     // ShrinkGCBuffers() while this is running so we must check there is no more
@@ -5875,17 +5882,17 @@ GCRuntime::incrementalCollectSlice(Slice
          * Do the incremental collection type specified by zeal mode if the
          * collection was triggered by runDebugGC() and incremental GC has not
          * been cancelled by resetIncrementalGC().
          */
         zeal = zealMode;
     }
 #endif
 
-    MOZ_ASSERT_IF(incrementalState != NO_INCREMENTAL, isIncremental);
+    MOZ_ASSERT_IF(isIncrementalGCInProgress(), isIncremental);
     isIncremental = !budget.isUnlimited();
 
     if (zeal == ZealIncrementalRootsThenFinish || zeal == ZealIncrementalMarkAllThenFinish) {
         /*
          * Yields between slices occurs at predetermined points in these modes;
          * the budget is not used.
          */
         budget.makeUnlimited();
@@ -6033,21 +6040,18 @@ GCRuntime::budgetIncrementalGC(SliceBudg
 
     bool reset = false;
     for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
         if (zone->usage.gcBytes() >= zone->threshold.gcTriggerBytes()) {
             budget.makeUnlimited();
             stats.nonincremental("allocation trigger");
         }
 
-        if (incrementalState != NO_INCREMENTAL &&
-            zone->isGCScheduled() != zone->wasGCStarted())
-        {
+        if (isIncrementalGCInProgress() && zone->isGCScheduled() != zone->wasGCStarted())
             reset = true;
-        }
 
         if (zone->isTooMuchMalloc()) {
             budget.makeUnlimited();
             stats.nonincremental("malloc bytes trigger");
         }
     }
 
     if (reset)
@@ -6096,32 +6100,32 @@ GCRuntime::gcCycle(bool incremental, Sli
     AutoDisableStoreBuffer adsb(this);
 
     AutoTraceSession session(rt, MajorCollecting);
 
     majorGCRequested = false;
     interFrameGC = true;
 
     number++;
-    if (incrementalState == NO_INCREMENTAL)
+    if (!isIncrementalGCInProgress())
         majorGCNumber++;
 
     // It's ok if threads other than the main thread have suppressGC set, as
     // they are operating on zones which will not be collected from here.
     MOZ_ASSERT(!rt->mainThread.suppressGC);
 
     // Assert if this is a GC unsafe region.
     JS::AutoAssertOnGC::VerifyIsSafeToGC(rt);
 
     {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
 
         // As we are about to clear the mark bits, wait for background
         // finalization to finish. We only need to wait on the first slice.
-        if (incrementalState == NO_INCREMENTAL)
+        if (!isIncrementalGCInProgress())
             waitBackgroundSweepEnd();
 
         // We must also wait for background allocation to finish so we can
         // avoid taking the GC lock when manipulating the chunks during the GC.
         // The background alloc task can run between slices, so we must wait
         // for it at the start of every slice.
         allocTask.cancel(GCParallelTask::CancelAndWait);
     }
@@ -6138,23 +6142,23 @@ GCRuntime::gcCycle(bool incremental, Sli
 
         stats.nonincremental("requested");
         budget.makeUnlimited();
     } else {
         budgetIncrementalGC(budget);
     }
 
     /* The GC was reset, so we need a do-over. */
-    if (prevState != NO_INCREMENTAL && incrementalState == NO_INCREMENTAL)
+    if (prevState != NO_INCREMENTAL && !isIncrementalGCInProgress())
         return true;
 
     TraceMajorGCStart();
 
     /* Set the invocation kind in the first slice. */
-    if (incrementalState == NO_INCREMENTAL)
+    if (!isIncrementalGCInProgress())
         invocationKind = gckind;
 
     incrementalCollectSlice(budget, reason);
 
 #ifndef JS_MORE_DETERMINISTIC
     nextFullGCTime = PRMJ_Now() + GC_IDLE_FULL_SPAN;
 #endif
 
@@ -6210,21 +6214,21 @@ gcstats::ZoneGCStats
 GCRuntime::scanZonesBeforeGC()
 {
     gcstats::ZoneGCStats zoneStats;
     for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
         if (mode == JSGC_MODE_GLOBAL)
             zone->scheduleGC();
 
         /* This is a heuristic to avoid resets. */
-        if (incrementalState != NO_INCREMENTAL && zone->needsIncrementalBarrier())
+        if (isIncrementalGCInProgress() && zone->needsIncrementalBarrier())
             zone->scheduleGC();
 
         /* This is a heuristic to reduce the total number of collections. */
-        if (zone->isCloseToAllocTrigger(schedulingState.inHighFrequencyGCMode()))
+        if (zone->threshold.isCloseToAllocTrigger(zone->usage, schedulingState.inHighFrequencyGCMode()))
             zone->scheduleGC();
 
         zoneStats.zoneCount++;
         if (zone->isGCScheduled()) {
             zoneStats.collectedZoneCount++;
             zoneStats.collectedCompartmentCount += zone->compartments.length();
         }
     }
@@ -6269,42 +6273,42 @@ GCRuntime::collect(bool incremental, Sli
     cleanUpEverything = ShouldCleanUpEverything(reason, gckind);
 
     bool repeat = false;
     do {
         /*
          * Let the API user decide to defer a GC if it wants to (unless this
          * is the last context). Invoke the callback regardless.
          */
-        if (incrementalState == NO_INCREMENTAL) {
+        if (!isIncrementalGCInProgress()) {
             gcstats::AutoPhase ap(stats, gcstats::PHASE_GC_BEGIN);
             if (gcCallback.op)
                 gcCallback.op(rt, JSGC_BEGIN, gcCallback.data);
         }
 
         poked = false;
         bool wasReset = gcCycle(incremental, budget, gckind, reason);
 
-        if (incrementalState == NO_INCREMENTAL) {
+        if (!isIncrementalGCInProgress()) {
             gcstats::AutoPhase ap(stats, gcstats::PHASE_GC_END);
             if (gcCallback.op)
                 gcCallback.op(rt, JSGC_END, gcCallback.data);
         }
 
         /* Need to re-schedule all zones for GC. */
         if (poked && cleanUpEverything)
             JS::PrepareForFullGC(rt);
 
         /*
          * This code makes an extra effort to collect compartments that we
          * thought were dead at the start of the GC. See the large comment in
          * beginMarkPhase.
          */
         bool repeatForDeadZone = false;
-        if (incremental && incrementalState == NO_INCREMENTAL) {
+        if (incremental && !isIncrementalGCInProgress()) {
             for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
                 if (c->scheduledForDestruction) {
                     incremental = false;
                     repeatForDeadZone = true;
                     reason = JS::gcreason::COMPARTMENT_REVIVED;
                     c->zone()->scheduleGC();
                 }
             }
@@ -6314,17 +6318,17 @@ GCRuntime::collect(bool incremental, Sli
          * If we reset an existing GC, we need to start a new one. Also, we
          * repeat GCs that happen during shutdown (the gcShouldCleanUpEverything
          * case) until we can be sure that no additional garbage is created
          * (which typically happens if roots are dropped during finalizers).
          */
         repeat = (poked && cleanUpEverything) || wasReset || repeatForDeadZone;
     } while (repeat);
 
-    if (incrementalState == NO_INCREMENTAL)
+    if (!isIncrementalGCInProgress())
         EnqueuePendingParseTasksAfterGC(rt);
 }
 
 void
 GCRuntime::gc(JSGCInvocationKind gckind, JS::gcreason::Reason reason)
 {
     SliceBudget budget;
     collect(false, budget, gckind, reason);
@@ -6672,17 +6676,17 @@ GCRuntime::runDebugGC()
     {
         js::gc::State initialState = incrementalState;
         if (type == ZealIncrementalMultipleSlices) {
             /*
              * Start with a small slice limit and double it every slice. This
              * ensure that we get multiple slices, and collection runs to
              * completion.
              */
-            if (initialState == NO_INCREMENTAL)
+            if (!isIncrementalGCInProgress())
                 incrementalLimit = zealFrequency / 2;
             else
                 incrementalLimit *= 2;
             budget = SliceBudget(WorkBudget(incrementalLimit));
         } else {
             // This triggers incremental GC but is actually ignored by IncrementalMarkSlice.
             budget = SliceBudget(WorkBudget(1));
         }
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -2897,20 +2897,20 @@ ElementRestyler::ComputeRestyleResultFro
 
   if (oldContext->HasPseudoElementData() !=
         aNewContext->HasPseudoElementData()) {
     LOG_RESTYLE_CONTINUE("NS_STYLE_HAS_PSEUDO_ELEMENT_DATA differs between old"
                          " and new style contexts");
     return eRestyleResult_Continue;
   }
 
-  if (oldContext->IsDirectlyInsideRuby() !=
-        aNewContext->IsDirectlyInsideRuby()) {
-    LOG_RESTYLE_CONTINUE("NS_STYLE_IS_DIRECTLY_INSIDE_RUBY differes between old"
-                         " and new style contexts");
+  if (oldContext->IsInlineDescendantOfRuby() !=
+        aNewContext->IsInlineDescendantOfRuby()) {
+    LOG_RESTYLE_CONTINUE("NS_STYLE_IS_INLINE_DESCENDANT_OF_RUBY differes"
+                         "between old and new style contexts");
     return eRestyleResult_Continue;
   }
 
   return eRestyleResult_Stop;
 }
 
 ElementRestyler::RestyleResult
 ElementRestyler::RestyleSelf(nsIFrame* aSelf,
new file mode 100644
--- /dev/null
+++ b/layout/base/crashtests/1116104.html
@@ -0,0 +1,15 @@
+<html>
+
+<head>
+
+</head>
+
+<body>
+<style>colgroup::after { content:"after"; }</style>
+
+<table>
+<colgroup><col style="display: inline;">t</col></colgroup>
+</table>
+
+</body>
+</html>
--- a/layout/base/crashtests/crashtests.list
+++ b/layout/base/crashtests/crashtests.list
@@ -449,9 +449,10 @@ load 936988-1.html
 load 931450.html
 load 931460-1.html
 load 935765-1.html
 load 942690.html
 load 973390-1.html
 load 1001237.html
 load 1043163-1.html
 load 1061028.html
+load 1116104.html
 load 1107508-1.html
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -6065,18 +6065,21 @@ AdjustAppendParentForAfterContent(nsFram
     while (!done && parent) {
       // Ensure that all normal flow children are on the principal child list.
       parent->DrainSelfOverflowList();
 
       nsIFrame* child = parent->GetLastChild(nsIFrame::kPrincipalList);
       if (child && child->IsPseudoFrame(aContainer) &&
           !child->IsGeneratedContentFrame()) {
         // Drill down into non-generated pseudo frames of aContainer.
-        parent = nsLayoutUtils::LastContinuationWithChild(do_QueryFrame(child));
-        continue;
+        nsContainerFrame* childAsContainer = do_QueryFrame(child);
+        if (childAsContainer) {
+          parent = nsLayoutUtils::LastContinuationWithChild(childAsContainer);
+          continue;
+        }
       }
 
       for (; child; child = child->GetPrevSibling()) {
         nsIContent* c = child->GetContent();
         if (child->IsGeneratedContentFrame()) {
           nsIContent* p = c->GetParent();
           if (c->Tag() == nsGkAtoms::mozgeneratedcontentafter) {
             if (!nsContentUtils::ContentIsDescendantOf(aChild, p) &&
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -716,17 +716,17 @@ nsDisplayScrollLayer::ComputeFrameMetric
     }
     nsRect dp;
     if (nsLayoutUtils::GetDisplayPort(content, &dp)) {
       metrics.SetDisplayPort(CSSRect::FromAppUnits(dp));
       nsLayoutUtils::LogTestDataForPaint(aLayer->Manager(), scrollId, "displayport",
           metrics.GetDisplayPort());
     }
     if (nsLayoutUtils::GetCriticalDisplayPort(content, &dp)) {
-      metrics.mCriticalDisplayPort = CSSRect::FromAppUnits(dp);
+      metrics.SetCriticalDisplayPort(CSSRect::FromAppUnits(dp));
     }
     DisplayPortMarginsPropertyData* marginsData =
         static_cast<DisplayPortMarginsPropertyData*>(content->GetProperty(nsGkAtoms::DisplayPortMargins));
     if (marginsData) {
       metrics.SetDisplayPortMargins(marginsData->mMargins);
     }
   }
 
--- a/layout/generic/nsBRFrame.cpp
+++ b/layout/generic/nsBRFrame.cpp
@@ -92,17 +92,17 @@ BRFrame::Reflow(nsPresContext* aPresCont
                            // mode by nsLineLayout::VerticalAlignFrames .
                            // However, it's not always 0.  See below.
   finalSize.ISize(wm) = 0;
   aMetrics.SetBlockStartAscent(0);
 
   // Only when the BR is operating in a line-layout situation will it
   // behave like a BR. BR is suppressed when it is inside ruby frames.
   nsLineLayout* ll = aReflowState.mLineLayout;
-  if (ll && !StyleContext()->IsDirectlyInsideRuby()) {
+  if (ll && !StyleContext()->IsInlineDescendantOfRuby()) {
     // Note that the compatibility mode check excludes AlmostStandards
     // mode, since this is the inline box model.  See bug 161691.
     if ( ll->LineIsEmpty() ||
          aPresContext->CompatibilityMode() == eCompatibility_FullStandards ) {
       // The line is logically empty; any whitespace is trimmed away.
       //
       // If this frame is going to terminate the line we know
       // that nothing else will go on the line. Therefore, in this
--- a/layout/generic/nsLineLayout.cpp
+++ b/layout/generic/nsLineLayout.cpp
@@ -437,17 +437,17 @@ nsLineLayout::BeginSpan(nsIFrame* aFrame
   psd->mReflowState = aSpanReflowState;
   psd->mIStart = aIStart;
   psd->mICoord = aIStart;
   psd->mIEnd = aIEnd;
   psd->mBaseline = aBaseline;
 
   nsIFrame* frame = aSpanReflowState->frame;
   psd->mNoWrap = !frame->StyleText()->WhiteSpaceCanWrap(frame) ||
-                 frame->StyleContext()->IsDirectlyInsideRuby();
+                 frame->StyleContext()->IsInlineDescendantOfRuby();
   psd->mWritingMode = aSpanReflowState->GetWritingMode();
 
   // Switch to new span
   mCurrentSpan = psd;
   mSpanDepth++;
 }
 
 nscoord
@@ -2849,23 +2849,23 @@ nsLineLayout::TextAlignLine(nsLineBox* a
 
   int32_t additionalGaps = 0;
   if (!isSVG && (mHasRuby || (doTextAlign &&
                               textAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY))) {
     JustificationComputationState computeState;
     ComputeFrameJustification(psd, computeState);
     if (mHasRuby && computeState.mFirstParticipant) {
       PerFrameData* firstFrame = computeState.mFirstParticipant;
-      if (firstFrame->mFrame->StyleContext()->IsDirectlyInsideRuby()) {
+      if (firstFrame->mFrame->StyleContext()->IsInlineDescendantOfRuby()) {
         MOZ_ASSERT(!firstFrame->mJustificationAssignment.mGapsAtStart);
         firstFrame->mJustificationAssignment.mGapsAtStart = 1;
         additionalGaps++;
       }
       PerFrameData* lastFrame = computeState.mLastParticipant;
-      if (lastFrame->mFrame->StyleContext()->IsDirectlyInsideRuby()) {
+      if (lastFrame->mFrame->StyleContext()->IsInlineDescendantOfRuby()) {
         MOZ_ASSERT(!lastFrame->mJustificationAssignment.mGapsAtEnd);
         lastFrame->mJustificationAssignment.mGapsAtEnd = 1;
         additionalGaps++;
       }
     }
   }
 
   if (!isSVG && doTextAlign) {
--- a/layout/generic/nsRubyBaseContainerFrame.cpp
+++ b/layout/generic/nsRubyBaseContainerFrame.cpp
@@ -339,17 +339,17 @@ nsRubyBaseContainerFrame::Reflow(nsPresC
   WritingMode frameWM = aReflowState.GetWritingMode();
   LogicalMargin borderPadding = aReflowState.ComputedLogicalBorderPadding();
   nscoord startEdge = borderPadding.IStart(frameWM);
   nscoord endEdge = aReflowState.AvailableISize() - borderPadding.IEnd(frameWM);
   aReflowState.mLineLayout->BeginSpan(this, &aReflowState,
                                       startEdge, endEdge, &mBaseline);
 
   nsIFrame* parent = GetParent();
-  bool inNestedRuby = parent->StyleContext()->IsDirectlyInsideRuby();
+  bool inNestedRuby = parent->StyleContext()->IsInlineDescendantOfRuby();
   // Allow line break between ruby bases when white-space allows,
   // we are not inside a nested ruby, and there is no span.
   bool allowLineBreak = !inNestedRuby && StyleText()->WhiteSpaceCanWrap(this);
   bool allowInitialLineBreak = allowLineBreak;
   if (!GetPrevInFlow()) {
     allowInitialLineBreak = !inNestedRuby &&
       parent->StyleText()->WhiteSpaceCanWrap(parent);
   }
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -1891,17 +1891,17 @@ BuildTextRunsScanner::BuildTextRunForFra
     textStyle = f->StyleText();
     if (NS_STYLE_TEXT_TRANSFORM_NONE != textStyle->mTextTransform) {
       anyTextTransformStyle = true;
     }
     textFlags |= GetSpacingFlags(LetterSpacing(f));
     textFlags |= GetSpacingFlags(WordSpacing(f));
     nsTextFrameUtils::CompressionMode compression =
       CSSWhitespaceToCompressionMode[textStyle->mWhiteSpace];
-    if ((enabledJustification || f->StyleContext()->IsDirectlyInsideRuby()) &&
+    if ((enabledJustification || f->StyleContext()->IsInlineDescendantOfRuby()) &&
         !textStyle->WhiteSpaceIsSignificant() && !isSVG) {
       textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING;
     }
     fontStyle = f->StyleFont();
     nsIFrame* parent = mLineContainer->GetParent();
     if (NS_MATHML_MATHVARIANT_NONE != fontStyle->mMathVariant) {
       if (NS_MATHML_MATHVARIANT_NORMAL != fontStyle->mMathVariant) {
         anyMathMLStyling = true;
@@ -2756,17 +2756,17 @@ static int32_t FindChar(const nsTextFrag
         return (static_cast<const char*>(p) - str) + aOffset;
     }
   }
   return -1;
 }
 
 static bool IsChineseOrJapanese(nsIFrame* aFrame)
 {
-  if (aFrame->StyleContext()->IsDirectlyInsideRuby()) {
+  if (aFrame->StyleContext()->IsInlineDescendantOfRuby()) {
     // Always treat ruby as CJ language so that those characters can
     // be expanded properly even when surrounded by other language.
     return true;
   }
 
   nsIAtom* language = aFrame->StyleFont()->mLanguage;
   if (!language) {
     return false;
@@ -8236,17 +8236,17 @@ nsTextFrame::ReflowText(nsLineLayout& aL
   gfxFloat trimmedWidth = 0;
   gfxFloat availWidth = aAvailableWidth;
   bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() ||
                                    (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML);
   int32_t unusedOffset;  
   gfxBreakPriority breakPriority;
   aLineLayout.GetLastOptionalBreakPosition(&unusedOffset, &breakPriority);
   gfxTextRun::SuppressBreak suppressBreak = gfxTextRun::eNoSuppressBreak;
-  if (StyleContext()->IsDirectlyInsideRuby()) {
+  if (StyleContext()->IsInlineDescendantOfRuby()) {
     suppressBreak = gfxTextRun::eSuppressAllBreaks;
   } else if (!aLineLayout.LineIsBreakable()) {
     suppressBreak = gfxTextRun::eSuppressInitialBreak;
   }
   uint32_t transformedCharsFit =
     mTextRun->BreakAndMeasureText(transformedOffset, transformedLength,
                                   (GetStateBits() & TEXT_START_OF_LINE) != 0,
                                   availWidth,
@@ -8505,17 +8505,17 @@ nsTextFrame::ReflowText(nsLineLayout& aL
   } else if (cachedNewlineOffset) {
     mContent->DeleteProperty(nsGkAtoms::newline);
   }
 
   // Compute space and letter counts for justification, if required
   if (!textStyle->WhiteSpaceIsSignificant() &&
       (lineContainer->StyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
        lineContainer->StyleText()->mTextAlignLast == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
-       StyleContext()->IsDirectlyInsideRuby()) &&
+       StyleContext()->IsInlineDescendantOfRuby()) &&
       !lineContainer->IsSVGText()) {
     AddStateBits(TEXT_JUSTIFICATION_ENABLED);
     provider.ComputeJustification(offset, charsFit);
     aLineLayout.SetJustificationInfo(provider.GetJustificationInfo());
   }
 
   SetLength(contentLength, &aLineLayout, ALLOW_FRAME_CREATION_AND_DESTRUCTION);
 
--- a/layout/style/nsStyleContext.cpp
+++ b/layout/style/nsStyleContext.cpp
@@ -558,19 +558,19 @@ nsStyleContext::ApplyStyleFixups(bool aS
           mutable_display->mDisplay = displayVal;
         }
       }
     }
 
     // The display change should only occur for "in-flow" children
     if (!disp->IsOutOfFlowStyle() &&
         ((containerDisp->mDisplay == NS_STYLE_DISPLAY_INLINE &&
-          containerContext->IsDirectlyInsideRuby()) ||
+          containerContext->IsInlineDescendantOfRuby()) ||
          containerDisp->IsRubyDisplayType())) {
-      mBits |= NS_STYLE_IS_DIRECTLY_INSIDE_RUBY;
+      mBits |= NS_STYLE_IS_INLINE_DESCENDANT_OF_RUBY;
       uint8_t displayVal = disp->mDisplay;
       nsRuleNode::EnsureInlineDisplay(displayVal);
       if (displayVal != disp->mDisplay) {
         nsStyleDisplay *mutable_display =
           static_cast<nsStyleDisplay*>(GetUniqueStyleData(eStyleStruct_Display));
         mutable_display->mDisplay = displayVal;
       }
     }
--- a/layout/style/nsStyleContext.h
+++ b/layout/style/nsStyleContext.h
@@ -128,18 +128,18 @@ public:
   // Does this style context or any of its ancestors have text
   // decoration lines?
   bool HasTextDecorationLines() const
     { return !!(mBits & NS_STYLE_HAS_TEXT_DECORATION_LINES); }
 
   // Whether this style context or any of its inline-level ancestors
   // is directly contained by a ruby box? It is used to inlinize
   // block-level descendants and suppress line breaks inside ruby.
-  bool IsDirectlyInsideRuby() const
-    { return !!(mBits & NS_STYLE_IS_DIRECTLY_INSIDE_RUBY); }
+  bool IsInlineDescendantOfRuby() const
+    { return !!(mBits & NS_STYLE_IS_INLINE_DESCENDANT_OF_RUBY); }
 
   // Does this style context represent the style for a pseudo-element or
   // inherit data from such a style context?  Whether this returns true
   // is equivalent to whether it or any of its ancestors returns
   // non-null for GetPseudo.
   bool HasPseudoElementData() const
     { return !!(mBits & NS_STYLE_HAS_PSEUDO_ELEMENT_DATA); }
 
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -51,18 +51,18 @@ class imgIContainer;
 #define NS_STYLE_IS_STYLE_IF_VISITED       0x008000000
 // See nsStyleContext::UsesGrandancestorStyle
 #define NS_STYLE_USES_GRANDANCESTOR_STYLE  0x010000000
 // See nsStyleContext::IsShared
 #define NS_STYLE_IS_SHARED                 0x020000000
 // See nsStyleContext::AssertStructsNotUsedElsewhere
 // (This bit is currently only used in #ifdef DEBUG code.)
 #define NS_STYLE_IS_GOING_AWAY             0x040000000
-// See nsStyleContext::IsDirectlyInsideRuby
-#define NS_STYLE_IS_DIRECTLY_INSIDE_RUBY   0x080000000
+// See nsStyleContext::IsInlineDescendantOfRuby
+#define NS_STYLE_IS_INLINE_DESCENDANT_OF_RUBY 0x080000000
 // See nsStyleContext::GetPseudoEnum
 #define NS_STYLE_CONTEXT_TYPE_SHIFT        32
 
 // Additional bits for nsRuleNode's mDependentBits:
 #define NS_RULE_NODE_GC_MARK                0x02000000
 #define NS_RULE_NODE_USED_DIRECTLY          0x04000000
 #define NS_RULE_NODE_IS_IMPORTANT           0x08000000
 #define NS_RULE_NODE_LEVEL_MASK             0xf0000000
--- a/mobile/android/base/DownloadsIntegration.java
+++ b/mobile/android/base/DownloadsIntegration.java
@@ -7,26 +7,29 @@ package org.mozilla.gecko;
 
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
 
 import java.io.File;
+import java.lang.IllegalArgumentException;
 import java.util.ArrayList;
 import java.util.List;
 
 import android.app.DownloadManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.media.MediaScannerConnection;
 import android.media.MediaScannerConnection.MediaScannerConnectionClient;
 import android.net.Uri;
 import android.text.TextUtils;
+import android.util.Log;
 
 public class DownloadsIntegration implements NativeEventListener
 {
     private static final String LOGTAG = "GeckoDownloadsIntegration";
 
     @SuppressWarnings("serial")
     private static final List<String> UNKNOWN_MIME_TYPES = new ArrayList<String>(3) {{
         add("unknown/unknown"); // This will be used as a default mime type for unknown files
@@ -83,16 +86,33 @@ public class DownloadsIntegration implem
     public void handleMessage(final String event, final NativeJSObject message,
                               final EventCallback callback) {
         if (DOWNLOAD_REMOVE.equals(event)) {
             final Download d = Download.fromJSON(message);
             removeDownload(d);
         }
     }
 
+    private static boolean useSystemDownloadManager() {
+        if (!AppConstants.ANDROID_DOWNLOADS_INTEGRATION) {
+            return false;
+        }
+
+        int state = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        try {
+            state = GeckoAppShell.getContext().getPackageManager().getApplicationEnabledSetting("com.android.providers.downloads");
+        } catch (IllegalArgumentException e) {
+            // Download Manager package does not exist
+            return false;
+        }
+
+        return (PackageManager.COMPONENT_ENABLED_STATE_ENABLED == state ||
+                PackageManager.COMPONENT_ENABLED_STATE_DEFAULT == state);
+    }
+
     @WrapElementForJNI
     public static void scanMedia(final String aFile, String aMimeType) {
         String mimeType = aMimeType;
         if (UNKNOWN_MIME_TYPES.contains(mimeType)) {
             // If this is a generic undefined mimetype, erase it so that we can try to determine
             // one from the file extension below.
             mimeType = "";
         }
@@ -110,17 +130,17 @@ public class DownloadsIntegration implem
         if (TextUtils.isEmpty(mimeType)) {
             if (TextUtils.isEmpty(aMimeType)) {
                 mimeType = UNKNOWN_MIME_TYPES.get(0);
             } else {
                 mimeType = aMimeType;
             }
         }
 
-        if (AppConstants.ANDROID_DOWNLOADS_INTEGRATION) {
+        if (useSystemDownloadManager()) {
             final File f = new File(aFile);
             final DownloadManager dm = (DownloadManager) GeckoAppShell.getContext().getSystemService(Context.DOWNLOAD_SERVICE);
             dm.addCompletedDownload(f.getName(),
                     f.getName(),
                     true, // Media scanner should scan this
                     mimeType,
                     f.getAbsolutePath(),
                     Math.max(1, f.length()), // Some versions of Android require downloads to be at least length 1
@@ -128,17 +148,17 @@ public class DownloadsIntegration implem
         } else {
             final Context context = GeckoAppShell.getContext();
             final GeckoMediaScannerClient client = new GeckoMediaScannerClient(context, aFile, mimeType);
             client.connect();
         }
     }
 
     public static void removeDownload(final Download download) {
-        if (!AppConstants.ANDROID_DOWNLOADS_INTEGRATION) {
+        if (!useSystemDownloadManager()) {
             return;
         }
 
         final DownloadManager dm = (DownloadManager) GeckoAppShell.getContext().getSystemService(Context.DOWNLOAD_SERVICE);
 
         Cursor c = null;
         try {
             c = dm.query((new DownloadManager.Query()).setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL));
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -1581,19 +1581,19 @@ public abstract class GeckoApp
         if (SmsManager.isEnabled()) {
             SmsManager.getInstance().start();
         }
 
         mContactService = new ContactService(EventDispatcher.getInstance(), this);
 
         mPromptService = new PromptService(this);
 
-        mTextSelection = new TextSelection((TextSelectionHandle) findViewById(R.id.start_handle),
-                                           (TextSelectionHandle) findViewById(R.id.middle_handle),
-                                           (TextSelectionHandle) findViewById(R.id.end_handle),
+        mTextSelection = new TextSelection((TextSelectionHandle) findViewById(R.id.anchor_handle),
+                                           (TextSelectionHandle) findViewById(R.id.caret_handle),
+                                           (TextSelectionHandle) findViewById(R.id.focus_handle),
                                            EventDispatcher.getInstance(),
                                            this);
 
         PrefsHelper.getPref("app.update.autodownload", new PrefsHelper.PrefHandlerBase() {
             @Override public void prefValue(String pref, String value) {
                 UpdateServiceHelper.registerForUpdates(GeckoApp.this, value);
             }
         });
--- a/mobile/android/base/TextSelection.java
+++ b/mobile/android/base/TextSelection.java
@@ -31,20 +31,20 @@ import java.util.Timer;
 import java.util.TimerTask;
 
 import android.util.Log;
 import android.view.View;
 
 class TextSelection extends Layer implements GeckoEventListener {
     private static final String LOGTAG = "GeckoTextSelection";
 
-    private final TextSelectionHandle mStartHandle;
-    private final TextSelectionHandle mMiddleHandle;
-    private final TextSelectionHandle mEndHandle;
-    private final EventDispatcher mEventDispatcher;
+    private final TextSelectionHandle anchorHandle;
+    private final TextSelectionHandle caretHandle;
+    private final TextSelectionHandle focusHandle;
+    private final EventDispatcher eventDispatcher;
 
     private final DrawListener mDrawListener;
     private boolean mDraggingHandles;
 
     private float mViewLeft;
     private float mViewTop;
     private float mViewZoom;
 
@@ -63,37 +63,37 @@ class TextSelection extends Layer implem
                 public void run() {
                     endActionMode();
                 }
             });
         }
     };
     private ActionModeTimerTask mActionModeTimerTask;
 
-    TextSelection(TextSelectionHandle startHandle,
-                  TextSelectionHandle middleHandle,
-                  TextSelectionHandle endHandle,
+    TextSelection(TextSelectionHandle anchorHandle,
+                  TextSelectionHandle caretHandle,
+                  TextSelectionHandle focusHandle,
                   EventDispatcher eventDispatcher,
                   GeckoApp activity) {
-        mStartHandle = startHandle;
-        mMiddleHandle = middleHandle;
-        mEndHandle = endHandle;
-        mEventDispatcher = eventDispatcher;
+        this.anchorHandle = anchorHandle;
+        this.caretHandle = caretHandle;
+        this.focusHandle = focusHandle;
+        this.eventDispatcher = eventDispatcher;
 
         mDrawListener = new DrawListener() {
             @Override
             public void drawFinished() {
                 if (!mDraggingHandles) {
                     GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("TextSelection:LayerReflow", ""));
                 }
             }
         };
 
         // Only register listeners if we have valid start/middle/end handles
-        if (mStartHandle == null || mMiddleHandle == null || mEndHandle == null) {
+        if (anchorHandle == null || caretHandle == null || focusHandle == null) {
             Log.e(LOGTAG, "Failed to initialize text selection because at least one handle is null");
         } else {
             EventDispatcher.getInstance().registerGeckoThreadListener(this,
                 "TextSelection:ShowHandles",
                 "TextSelection:HideHandles",
                 "TextSelection:PositionHandles",
                 "TextSelection:Update",
                 "TextSelection:DraggingHandle");
@@ -105,22 +105,26 @@ class TextSelection extends Layer implem
             "TextSelection:ShowHandles",
             "TextSelection:HideHandles",
             "TextSelection:PositionHandles",
             "TextSelection:Update",
             "TextSelection:DraggingHandle");
     }
 
     private TextSelectionHandle getHandle(String name) {
-        if (name.equals("START")) {
-            return mStartHandle;
-        } else if (name.equals("MIDDLE")) {
-            return mMiddleHandle;
-        } else {
-            return mEndHandle;
+        switch (TextSelectionHandle.HandleType.valueOf(name)) {
+            case ANCHOR:
+                return anchorHandle;
+            case CARET:
+                return caretHandle;
+            case FOCUS:
+                return focusHandle;
+
+            default:
+                throw new IllegalArgumentException("TextSelectionHandle is invalid type.");
         }
     }
 
     @Override
     public void handleMessage(final String event, final JSONObject message) {
         if ("TextSelection:DraggingHandle".equals(event)) {
             mDraggingHandles = message.optBoolean("dragging", false);
             return;
@@ -160,19 +164,19 @@ class TextSelection extends Layer implem
                         if (layerView != null) {
                             layerView.removeDrawListener(mDrawListener);
                             layerView.removeLayer(TextSelection.this);
                         }
 
                         mActionModeTimerTask = new ActionModeTimerTask();
                         mActionModeTimer.schedule(mActionModeTimerTask, 250);
 
-                        mStartHandle.setVisibility(View.GONE);
-                        mMiddleHandle.setVisibility(View.GONE);
-                        mEndHandle.setVisibility(View.GONE);
+                        anchorHandle.setVisibility(View.GONE);
+                        caretHandle.setVisibility(View.GONE);
+                        focusHandle.setVisibility(View.GONE);
                     } else if (event.equals("TextSelection:PositionHandles")) {
                         final boolean rtl = message.getBoolean("rtl");
                         final JSONArray positions = message.getJSONArray("positions");
                         for (int i=0; i < positions.length(); i++) {
                             JSONObject position = positions.getJSONObject(i);
                             int left = position.getInt("left");
                             int top = position.getInt("top");
 
@@ -195,26 +199,26 @@ class TextSelection extends Layer implem
         }
         mCurrentItems = itemsString;
 
         if (mCallback != null) {
             mCallback.updateItems(items);
             return;
         }
 
-        final Context context = mStartHandle.getContext();
+        final Context context = anchorHandle.getContext();
         if (context instanceof ActionModeCompat.Presenter) {
             final ActionModeCompat.Presenter presenter = (ActionModeCompat.Presenter) context;
             mCallback = new TextSelectionActionModeCallback(items);
             presenter.startActionModeCompat(mCallback);
         }
     }
 
     private void endActionMode() {
-        Context context = mStartHandle.getContext();
+        Context context = anchorHandle.getContext();
         if (context instanceof ActionModeCompat.Presenter) {
             final ActionModeCompat.Presenter presenter = (ActionModeCompat.Presenter) context;
             presenter.endActionModeCompat();
         }
         mCurrentItems = null;
     }
 
     @Override
@@ -233,19 +237,19 @@ class TextSelection extends Layer implem
         }
         mViewLeft = viewLeft;
         mViewTop = viewTop;
         mViewZoom = viewZoom;
 
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
-                mStartHandle.repositionWithViewport(viewLeft, viewTop, viewZoom);
-                mMiddleHandle.repositionWithViewport(viewLeft, viewTop, viewZoom);
-                mEndHandle.repositionWithViewport(viewLeft, viewTop, viewZoom);
+                anchorHandle.repositionWithViewport(viewLeft, viewTop, viewZoom);
+                caretHandle.repositionWithViewport(viewLeft, viewTop, viewZoom);
+                focusHandle.repositionWithViewport(viewLeft, viewTop, viewZoom);
             }
         });
     }
 
     private class TextSelectionActionModeCallback implements Callback {
         private JSONArray mItems;
         private ActionModeCompat mActionMode;
     
@@ -271,17 +275,17 @@ class TextSelection extends Layer implem
             int length = mItems.length();
             for (int i = 0; i < length; i++) {
                 try {
                     final JSONObject obj = mItems.getJSONObject(i);
                     final GeckoMenuItem menuitem = (GeckoMenuItem) menu.add(0, i, 0, obj.optString("label"));
                     final int actionEnum = obj.optBoolean("showAsAction") ? GeckoMenuItem.SHOW_AS_ACTION_ALWAYS : GeckoMenuItem.SHOW_AS_ACTION_NEVER;
                     menuitem.setShowAsAction(actionEnum, R.attr.menuItemActionModeStyle);
 
-                    BitmapUtils.getDrawable(mStartHandle.getContext(), obj.optString("icon"), new BitmapLoader() {
+                    BitmapUtils.getDrawable(anchorHandle.getContext(), obj.optString("icon"), new BitmapLoader() {
                         @Override
                         public void onBitmapFound(Drawable d) {
                             if (d != null) {
                                 menuitem.setIcon(d);
                             }
                         }
                     });
                 } catch(Exception ex) {
--- a/mobile/android/base/TextSelectionHandle.java
+++ b/mobile/android/base/TextSelectionHandle.java
@@ -14,20 +14,40 @@ import android.content.res.TypedArray;
 import android.graphics.PointF;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.RelativeLayout;
 
+/**
+ * Text selection handles enable a user to change position of selected text in
+ * Gecko's DOM structure.
+ *
+ * A text "Selection" or nsISelection object, has start and end positions,
+ * referred to as Anchor and Focus objects.
+ *
+ * If the Anchor and Focus objects are at the same point, it represents a text
+ * selection Caret, commonly diplayed as a blinking, vertical |.
+ *
+ * Anchor and Focus objects each represent a DOM node, and character offset
+ * from the start of the node. The Anchor always refers to the start of the
+ * Selection, and the Focus refers to its end.
+ *
+ * In LTR languages such as English, the Anchor is to the left of the Focus.
+ * In RTL languages such as Hebrew, the Anchor is to the right of the Focus.
+ *
+ * For multi-line Selections, in both LTR and RTL languages, the Anchor starts
+ * above the Focus.
+ */
 class TextSelectionHandle extends ImageView implements View.OnTouchListener {
     private static final String LOGTAG = "GeckoTextSelectionHandle";
 
-    private enum HandleType { START, MIDDLE, END }; 
+    public enum HandleType { ANCHOR, CARET, FOCUS };
 
     private final HandleType mHandleType;
     private final int mWidth;
     private final int mHeight;
     private final int mShadow;
 
     private float mLeft;
     private float mTop;
@@ -46,21 +66,21 @@ class TextSelectionHandle extends ImageV
     public TextSelectionHandle(Context context, AttributeSet attrs) {
         super(context, attrs);
         setOnTouchListener(this);
 
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TextSelectionHandle);
         int handleType = a.getInt(R.styleable.TextSelectionHandle_handleType, 0x01);
 
         if (handleType == 0x01)
-            mHandleType = HandleType.START;
+            mHandleType = HandleType.ANCHOR;
         else if (handleType == 0x02)
-            mHandleType = HandleType.MIDDLE;
+            mHandleType = HandleType.CARET;
         else
-            mHandleType = HandleType.END;
+            mHandleType = HandleType.FOCUS;
 
         mGeckoPoint = new PointF(0.0f, 0.0f);
 
         mWidth = getResources().getDimensionPixelSize(R.dimen.text_selection_handle_width);
         mHeight = getResources().getDimensionPixelSize(R.dimen.text_selection_handle_height);
         mShadow = getResources().getDimensionPixelSize(R.dimen.text_selection_handle_shadow);
     }
 
@@ -127,17 +147,17 @@ class TextSelectionHandle extends ImageV
             Log.e(LOGTAG, "Error building JSON arguments for TextSelection:Move");
         }
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("TextSelection:Move", args.toString()));
 
         // If we're positioning a cursor, don't move the handle here. Gecko
         // will tell us the position of the caret, so we set the handle
         // position then. This allows us to lock the handle to wherever the
         // caret appears.
-        if (mHandleType != HandleType.MIDDLE) {
+        if (mHandleType != HandleType.CARET) {
             setLayoutPosition();
         }
     }
 
     void positionFromGecko(int left, int top, boolean rtl) {
         LayerView layerView = GeckoAppShell.getLayerView();
         if (layerView == null) {
             Log.e(LOGTAG, "Can't position handle because layerView is null");
@@ -161,19 +181,19 @@ class TextSelectionHandle extends ImageV
 
         mLeft = viewPoint.x - adjustLeftForHandle();
         mTop = viewPoint.y;
 
         setLayoutPosition();
     }
 
     private float adjustLeftForHandle() {
-        if (mHandleType == HandleType.START) {
+        if (mHandleType == HandleType.ANCHOR) {
             return mIsRTL ? mShadow : mWidth - mShadow;
-        } else if (mHandleType == HandleType.MIDDLE) {
+        } else if (mHandleType == HandleType.CARET) {
             return mWidth / 2;
         } else {
             return mIsRTL ? mWidth - mShadow : mShadow;
         }
     }
 
     private void setLayoutPosition() {
         if (mLayoutParams == null) {
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -524,22 +524,16 @@ just addresses the organization to follo
 <!ENTITY updater_downloading_title2 "Downloading &brandShortName;">
 <!ENTITY updater_downloading_title_failed2 "Download failed">
 <!ENTITY updater_downloading_select2 "Touch to apply update once downloaded">
 <!ENTITY updater_downloading_retry2 "Touch to retry">
 
 <!ENTITY updater_apply_title2 "Update available for &brandShortName;">
 <!ENTITY updater_apply_select2 "Touch to update">
 
-<!-- New tablet UI -->
-
-<!-- LOCALIZATION NOTE (new_tablet_restart): Notification displayed after the user toggles the new tablet UI in the settings screen.-->
-<!ENTITY new_tablet_restart "Restart the browser for the changes to take effect">
-<!ENTITY new_tablet_pref "Enable new tablet UI">
-
 <!-- Guest mode -->
 <!ENTITY new_guest_session "New Guest Session">
 <!ENTITY exit_guest_session "Exit Guest Session">
 <!ENTITY guest_session_dialog_continue "Continue">
 <!ENTITY guest_session_dialog_cancel "Cancel">
 <!ENTITY new_guest_session_title "&brandShortName; will now restart">
 <!ENTITY new_guest_session_text "The person using it will not be able to see any of your personal browsing data (like saved passwords, history or bookmarks).\n\nWhen your guest is done, their browsing data will be deleted and your session will be restored.">
 <!ENTITY guest_browsing_notification_title "Guest browsing is enabled">
--- a/mobile/android/base/menu/GeckoMenu.java
+++ b/mobile/android/base/menu/GeckoMenu.java
@@ -512,34 +512,26 @@ public class GeckoMenu extends ListView
             final View actionView;
             if (item.getActionEnum() == GeckoMenuItem.SHOW_AS_ACTION_ALWAYS) {
                 actionView = mPrimaryActionItems.get(item);
             } else {
                 actionView = mSecondaryActionItems.get(item);
             }
 
             if (actionView != null) {
-                // The update could be coming from the background thread.
-                // Post a runnable on the UI thread of the view for it to update.
-                final GeckoMenuItem menuItem = item;
-                actionView.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (menuItem.isVisible()) {
-                            actionView.setVisibility(View.VISIBLE);
-                            if (actionView instanceof MenuItemActionBar) {
-                                ((MenuItemActionBar) actionView).initialize(menuItem);
-                            } else {
-                                ((MenuItemActionView) actionView).initialize(menuItem);
-                            }
-                        } else {
-                            actionView.setVisibility(View.GONE);
-                        }
+                if (item.isVisible()) {
+                    actionView.setVisibility(View.VISIBLE);
+                    if (actionView instanceof MenuItemActionBar) {
+                        ((MenuItemActionBar) actionView).initialize(item);
+                    } else {
+                        ((MenuItemActionView) actionView).initialize(item);
                     }
-                });
+                } else {
+                    actionView.setVisibility(View.GONE);
+                }
             }
         } else {
             mAdapter.notifyDataSetChanged();
         }
     }
 
     @Override
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
--- a/mobile/android/base/menu/GeckoMenuItem.java
+++ b/mobile/android/base/menu/GeckoMenuItem.java
@@ -5,16 +5,17 @@
 package org.mozilla.gecko.menu;
 
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.widget.GeckoActionProvider;
 
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
 import android.view.ActionProvider;
 import android.view.ContextMenu;
 import android.view.MenuItem;
 import android.view.SubMenu;
 import android.view.View;
 
 public class GeckoMenuItem implements MenuItem {
     public static final int SHOW_AS_ACTION_NEVER = 0;
@@ -239,46 +240,56 @@ public class GeckoMenuItem implements Me
 
     @Override
     public MenuItem setAlphabeticShortcut(char alphaChar) {
         return this;
     }
 
     @Override
     public MenuItem setCheckable(boolean checkable) {
-        mCheckable = checkable;
-        mMenu.onItemChanged(this);
+        if (mCheckable != checkable) {
+            mCheckable = checkable;
+            mMenu.onItemChanged(this);
+        }
         return this;
     }
 
     @Override
     public MenuItem setChecked(boolean checked) {
-        mChecked = checked;
-        mMenu.onItemChanged(this);
+        if (mChecked != checked) {
+            mChecked = checked;
+            mMenu.onItemChanged(this);
+        }
         return this;
     }
 
     @Override
     public MenuItem setEnabled(boolean enabled) {
-        mEnabled = enabled;
-        mMenu.onItemChanged(this);
+        if (mEnabled != enabled) {
+            mEnabled = enabled;
+            mMenu.onItemChanged(this);
+        }
         return this;
     }
 
     @Override
     public MenuItem setIcon(Drawable icon) {
-        mIcon = icon;
-        mMenu.onItemChanged(this);
+        if (mIcon != icon) {
+            mIcon = icon;
+            mMenu.onItemChanged(this);
+        }
         return this;
     }
 
     @Override
     public MenuItem setIcon(int iconRes) {
-        mIconRes = iconRes;
-        mMenu.onItemChanged(this);
+        if (mIconRes != iconRes) {
+            mIconRes = iconRes;
+            mMenu.onItemChanged(this);
+        }
         return this;
     }
 
     @Override
     public MenuItem setIntent(Intent intent) {
         return this;
     }
 
@@ -349,38 +360,41 @@ public class GeckoMenuItem implements Me
 
     public MenuItem setSubMenu(GeckoSubMenu subMenu) {
         mSubMenu = subMenu;
         return this;
     }
 
     @Override
     public MenuItem setTitle(CharSequence title) {
-        mTitle = title;
-        mMenu.onItemChanged(this);
+        if (!TextUtils.equals(mTitle, title)) {
+            mTitle = title;
+            mMenu.onItemChanged(this);
+        }
         return this;
     }
 
     @Override
     public MenuItem setTitle(int title) {
-        mTitle = mMenu.getResources().getString(title);
-        mMenu.onItemChanged(this);
-        return this;
+        CharSequence newTitle = mMenu.getResources().getString(title);
+        return setTitle(newTitle);
     }
 
     @Override
     public MenuItem setTitleCondensed(CharSequence title) {
         mTitleCondensed = title;
         return this;
     }
 
     @Override
     public MenuItem setVisible(boolean visible) {
-        mVisible = visible;
-        mMenu.onItemChanged(this);
+        if (mVisible != visible) {
+            mVisible = visible;
+            mMenu.onItemChanged(this);
+        }
         return this;
     }
 
     public boolean invoke() {
         if (mMenuItemClickListener != null)
             return mMenuItemClickListener.onMenuItemClick(this);
         else
             return false;
rename from mobile/android/base/resources/drawable/handle_start_level.xml
rename to mobile/android/base/resources/drawable/handle_anchor_level.xml
rename from mobile/android/base/resources/drawable/handle_end_level.xml
rename to mobile/android/base/resources/drawable/handle_focus_level.xml
--- a/mobile/android/base/resources/layout/text_selection_handles.xml
+++ b/mobile/android/base/resources/layout/text_selection_handles.xml
@@ -1,29 +1,29 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:gecko="http://schemas.android.com/apk/res-auto">
 
-   <org.mozilla.gecko.TextSelectionHandle android:id="@+id/start_handle"
+   <org.mozilla.gecko.TextSelectionHandle android:id="@+id/anchor_handle"
                                           android:layout_width="@dimen/text_selection_handle_width"
                                           android:layout_height="@dimen/text_selection_handle_height"
-                                          android:src="@drawable/handle_start_level"
+                                          android:src="@drawable/handle_anchor_level"
                                           android:visibility="gone"
                                           gecko:handleType="start"/>
 
-   <org.mozilla.gecko.TextSelectionHandle android:id="@+id/middle_handle"
+   <org.mozilla.gecko.TextSelectionHandle android:id="@+id/caret_handle"
                                           android:layout_width="@dimen/text_selection_handle_width"
                                           android:layout_height="@dimen/text_selection_handle_height"
                                           android:src="@drawable/handle_middle"
                                           android:visibility="gone"
                                           gecko:handleType="middle"/>
 
-   <org.mozilla.gecko.TextSelectionHandle android:id="@+id/end_handle"
+   <org.mozilla.gecko.TextSelectionHandle android:id="@+id/focus_handle"
                                           android:layout_width="@dimen/text_selection_handle_width"
                                           android:layout_height="@dimen/text_selection_handle_height"
-                                          android:src="@drawable/handle_end_level"
+                                          android:src="@drawable/handle_focus_level"
                                           android:visibility="gone"
                                           gecko:handleType="end"/>
 </merge>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -442,20 +442,16 @@
   <string name="updater_downloading_title">&updater_downloading_title2;</string>
   <string name="updater_downloading_title_failed">&updater_downloading_title_failed2;</string>
   <string name="updater_downloading_select">&updater_downloading_select2;</string>
   <string name="updater_downloading_retry">&updater_downloading_retry2;</string>
 
   <string name="updater_apply_title">&updater_apply_title2;</string>
   <string name="updater_apply_select">&updater_apply_select2;</string>
 
-  <!-- New tablet UI -->
-  <string name="new_tablet_restart">&new_tablet_restart;</string>
-  <string name="new_tablet_pref">&new_tablet_pref;</string>
-
   <!-- Search suggestions opt-in -->
   <string name="suggestions_prompt">&suggestions_prompt3;</string>
 
   <string name="suggestion_for_engine">&suggestion_for_engine;</string>
 
   <!-- Set Image Notifications -->
   <string name="set_image_fail">&set_image_fail;</string>
   <string name="set_image_path_fail">&set_image_path_fail;</string>
--- a/mobile/android/base/tests/components/ToolbarComponent.java
+++ b/mobile/android/base/tests/components/ToolbarComponent.java
@@ -78,16 +78,17 @@ public class ToolbarComponent extends Ba
         fAssertFalse("The edit text is not selected", isUrlEditTextSelected());
         return this;
     }
 
     /**
      * Returns the root View for the browser toolbar.
      */
     private View getToolbarView() {
+        mSolo.waitForView(R.id.browser_toolbar);
         return mSolo.getView(R.id.browser_toolbar);
     }
 
     private EditText getUrlEditText() {
         return (EditText) getToolbarView().findViewById(R.id.url_edit_text);
     }
 
     private View getUrlDisplayLayout() {
--- a/mobile/android/base/tests/helpers/WaitHelper.java
+++ b/mobile/android/base/tests/helpers/WaitHelper.java
@@ -20,17 +20,19 @@ import com.jayway.android.robotium.solo.
 import com.jayway.android.robotium.solo.Solo;
 
 /**
  * Provides functionality related to waiting on certain events to happen.
  */
 public final class WaitHelper {
     // TODO: Make public for when Solo.waitForCondition is used directly (i.e. do not want
     // assertion from waitFor)?
-    private static final int DEFAULT_MAX_WAIT_MS = 5000;
+    // DEFAULT_MAX_WAIT_MS of 5000 was intermittently insufficient during
+    // initialization on Android 2.3 emulator -- bug 1114655
+    private static final int DEFAULT_MAX_WAIT_MS = 15000;
     private static final int PAGE_LOAD_WAIT_MS = 10000;
     private static final int CHANGE_WAIT_MS = 15000;
 
     // TODO: via lucasr - Add ThrobberVisibilityChangeVerifier?
     private static final ChangeVerifier[] PAGE_LOAD_VERIFIERS = new ChangeVerifier[] {
         new ToolbarTitleTextChangeVerifier()
     };
 
--- a/mobile/android/base/tests/roboextender/testSelectionHandler.html
+++ b/mobile/android/base/tests/roboextender/testSelectionHandler.html
@@ -202,17 +202,20 @@ function testCloseSelection() {
 
   }).then(function() {
     sh.startSelection(inputNode);
     sh.observe(null, "Tab:Selected", {});
     return ok(!sh.isSelectionActive(), "Tab:Selected should close active selection");
 
   }).then(function() {
     sh.startSelection(inputNode);
-    sh.handleEvent({ type: "pagehide" });
+    sh.handleEvent({ type: "pagehide", originalTarget: {} });
+    return ok(sh.isSelectionActive(), "unrelated pagehide should not close active selection");
+  }).then(function() {
+    sh.handleEvent({ type: "pagehide", originalTarget: document });
     return ok(!sh.isSelectionActive(), "pagehide should close active selection");
 
   }).then(function() {
     sh.startSelection(inputNode);
     sh.handleEvent({ type: "blur" });
     return ok(!sh.isSelectionActive(), "blur should close active selection");
   });
 }
--- a/mobile/android/chrome/content/SelectionHandler.js
+++ b/mobile/android/chrome/content/SelectionHandler.js
@@ -3,19 +3,19 @@
  * 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";
 
 // Define elements that bound phone number containers.
 const PHONE_NUMBER_CONTAINERS = "td,div";
 
 var SelectionHandler = {
-  HANDLE_TYPE_START: "START",
-  HANDLE_TYPE_MIDDLE: "MIDDLE",
-  HANDLE_TYPE_END: "END",
+  HANDLE_TYPE_ANCHOR: "ANCHOR",
+  HANDLE_TYPE_CARET: "CARET",
+  HANDLE_TYPE_FOCUS: "FOCUS",
 
   TYPE_NONE: 0,
   TYPE_CURSOR: 1,
   TYPE_SELECTION: 2,
 
   SELECT_ALL: 0,
   SELECT_AT_POINT: 1,
 
@@ -128,17 +128,17 @@ var SelectionHandler = {
           this._updateCacheForSelection();
         }
         break;
       }
       case "TextSelection:Move": {
         let data = JSON.parse(aData);
         if (this._activeType == this.TYPE_SELECTION) {
           this._startDraggingHandles();
-          this._moveSelection(data.handleType == this.HANDLE_TYPE_START, data.x, data.y);
+          this._moveSelection(data.handleType == this.HANDLE_TYPE_ANCHOR, data.x, data.y);
 
         } else if (this._activeType == this.TYPE_CURSOR) {
           this._startDraggingHandles();
 
           // Ignore IMM composition notifications when caret movement starts
           this._ignoreCompositionChanges = true;
           this._moveCaret(data.x, data.y);
 
@@ -147,17 +147,17 @@ var SelectionHandler = {
         }
         break;
       }
       case "TextSelection:Position": {
         if (this._activeType == this.TYPE_SELECTION) {
           this._startDraggingHandles();
 
           // Check to see if the handles should be reversed.
-          let isStartHandle = JSON.parse(aData).handleType == this.HANDLE_TYPE_START;
+          let isStartHandle = JSON.parse(aData).handleType == this.HANDLE_TYPE_ANCHOR;
           try {
             let selectionReversed = this._updateCacheForSelection(isStartHandle);
             if (selectionReversed) {
               // Reverse the anchor and focus to correspond to the new start and end handles.
               let selection = this._getSelection();
               let anchorNode = selection.anchorNode;
               let anchorOffset = selection.anchorOffset;
               selection.collapse(selection.focusNode, selection.focusOffset);
@@ -217,17 +217,25 @@ var SelectionHandler = {
 
   handleEvent: function sh_handleEvent(aEvent) {
     switch (aEvent.type) {
       case "scroll":
         // Maintain position when top-level document is scrolled
         this._positionHandlesOnChange();
         break;
 
-      case "pagehide":
+      case "pagehide": {
+        // We only care about events on the selected tab.
+        let tab = BrowserApp.getTabForWindow(aEvent.originalTarget.defaultView);
+        if (tab == BrowserApp.selectedTab) {
+          this._closeSelection();
+        }
+        break;
+      }
+
       case "blur":
         this._closeSelection();
         break;
 
       // Update caret position on keyboard activity
       case "keyup":
         // Not generated by Swiftkeyboard
       case "compositionupdate":
@@ -314,17 +322,17 @@ var SelectionHandler = {
       return false;
     }
 
     // Add a listener to end the selection if it's removed programatically
     selection.QueryInterface(Ci.nsISelectionPrivate).addSelectionListener(this);
     this._activeType = this.TYPE_SELECTION;
 
     // Initialize the cache
-    this._cache = { start: {}, end: {}};
+    this._cache = { anchorPt: {}, focusPt: {}};
     this._updateCacheForSelection();
 
     let scroll = this._getScrollPos();
     // Figure out the distance between the selection and the click
     let positions = this._getHandlePositions(scroll);
 
     if (aOptions.mode == this.SELECT_AT_POINT && !this._selectionNearClick(scroll.X + aOptions.x,
                                                                       scroll.Y + aOptions.y,
@@ -332,17 +340,17 @@ var SelectionHandler = {
         this._closeSelection();
         return false;
     }
 
     // Determine position and show handles, open actionbar
     this._positionHandles(positions);
     Messaging.sendRequest({
       type: "TextSelection:ShowHandles",
-      handles: [this.HANDLE_TYPE_START, this.HANDLE_TYPE_END]
+      handles: [this.HANDLE_TYPE_ANCHOR, this.HANDLE_TYPE_FOCUS]
     });
     this._updateMenu();
     return true;
   },
 
   /*
    * Called to perform a selection operation, given a target element, selection method, starting point etc.
    */
@@ -711,17 +719,17 @@ var SelectionHandler = {
     BrowserApp.deck.addEventListener("compositionend", this, false);
 
     this._activeType = this.TYPE_CURSOR;
 
     // Determine position and show caret, open actionbar
     this._positionHandles();
     Messaging.sendRequest({
       type: "TextSelection:ShowHandles",
-      handles: [this.HANDLE_TYPE_MIDDLE]
+      handles: [this.HANDLE_TYPE_CARET]
     });
     this._updateMenu();
 
     return true;
   },
 
   // Target initialization for both TYPE_CURSOR and TYPE_SELECTION
   _initTargetInfo: function sh_initTargetInfo(aElement, aSelectionType) {
@@ -838,40 +846,40 @@ var SelectionHandler = {
     // Constrain text selection within editable elements.
     let targetIsEditable = this._targetElement instanceof Ci.nsIDOMNSEditableElement;
     if (targetIsEditable && (caretPos.offsetNode != this._targetElement)) {
       return;
     }
 
     // Update the cache as the handle is dragged (keep the cache in client coordinates).
     if (aIsStartHandle) {
-      this._cache.start.x = aX;
-      this._cache.start.y = aY;
+      this._cache.anchorPt.x = aX;
+      this._cache.anchorPt.y = aY;
     } else {
-      this._cache.end.x = aX;
-      this._cache.end.y = aY;
+      this._cache.focusPt.x = aX;
+      this._cache.focusPt.y = aY;
     }
 
     let selection = this._getSelection();
 
     // The handles work the same on both LTR and RTL pages, but the anchor/focus nodes
     // are reversed, so we need to reverse the logic to extend the selection.
     if ((aIsStartHandle && !this._isRTL) || (!aIsStartHandle && this._isRTL)) {
       if (targetIsEditable) {
-        let anchorX = this._isRTL ? this._cache.start.x : this._cache.end.x;
+        let anchorX = this._isRTL ? this._cache.anchorPt.x : this._cache.focusPt.x;
         this._moveSelectionInEditable(anchorX, aX, caretPos);
       } else {
         let focusNode = selection.focusNode;
         let focusOffset = selection.focusOffset;
         selection.collapse(caretPos.offsetNode, caretPos.offset);
         selection.extend(focusNode, focusOffset);
       }
     } else {
       if (targetIsEditable) {
-        let anchorX = this._isRTL ? this._cache.end.x : this._cache.start.x;
+        let anchorX = this._isRTL ? this._cache.focusPt.x : this._cache.anchorPt.x;
         this._moveSelectionInEditable(anchorX, aX, caretPos);
       } else {
         selection.extend(caretPos.offsetNode, caretPos.offset);
       }
     }
   },
 
   _moveCaret: function sh_moveCaret(aX, aY) {
@@ -1047,24 +1055,24 @@ var SelectionHandler = {
       // nsISelection object exists, but there's nothing actually selected
       throw "Failed to update cache for invalid selection";
     }
 
     let start = { x: this._isRTL ? rects[0].right : rects[0].left, y: rects[0].bottom };
     let end = { x: this._isRTL ? rects[rects.length - 1].left : rects[rects.length - 1].right, y: rects[rects.length - 1].bottom };
 
     let selectionReversed = false;
-    if (this._cache.start) {
+    if (this._cache.anchorPt) {
       // If the end moved past the old end, but we're dragging the start handle, then that handle should become the end handle (and vice versa)
-      selectionReversed = (aIsStartHandle && (end.y > this._cache.end.y || (end.y == this._cache.end.y && end.x > this._cache.end.x))) ||
-                          (!aIsStartHandle && (start.y < this._cache.start.y || (start.y == this._cache.start.y && start.x < this._cache.start.x)));
+      selectionReversed = (aIsStartHandle && (end.y > this._cache.focusPt.y || (end.y == this._cache.focusPt.y && end.x > this._cache.focusPt.x))) ||
+                          (!aIsStartHandle && (start.y < this._cache.anchorPt.y || (start.y == this._cache.anchorPt.y && start.x < this._cache.anchorPt.x)));
     }
 
-    this._cache.start = start;
-    this._cache.end = end;
+    this._cache.anchorPt = start;
+    this._cache.focusPt = end;
 
     return selectionReversed;
   },
 
   _getHandlePositions: function sh_getHandlePositions(scroll) {
     // the checkHidden function tests to see if the given point is hidden inside an
     // iframe/subdocument. this is so that if we select some text inside an iframe and
     // scroll the iframe so the selection is out of view, we hide the handles rather
@@ -1084,38 +1092,38 @@ var SelectionHandler = {
       // The left and top properties returned are relative to the client area
       // of the window, so we don't need to account for a sub-frame offset.
       let cursor = this._domWinUtils.sendQueryContentEvent(this._domWinUtils.QUERY_CARET_RECT, this._targetElement.selectionEnd, 0, 0, 0,
                                                            this._domWinUtils.QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK);
       // the return value from sendQueryContentEvent is in LayoutDevice pixels and we want CSS pixels, so
       // divide by the pixel ratio
       let x = cursor.left / window.devicePixelRatio;
       let y = (cursor.top + cursor.height) / window.devicePixelRatio;
-      return [{ handle: this.HANDLE_TYPE_MIDDLE,
+      return [{ handle: this.HANDLE_TYPE_CARET,
                 left: x + scroll.X,
                 top: y + scroll.Y,
                 hidden: checkHidden(x, y) }];
     } else {
-      let sx = this._cache.start.x;
-      let sy = this._cache.start.y;
-      let ex = this._cache.end.x;
-      let ey = this._cache.end.y;
+      let anchorX = this._cache.anchorPt.x;
+      let anchorY = this._cache.anchorPt.y;
+      let focusX = this._cache.focusPt.x;
+      let focusY = this._cache.focusPt.y;
 
       // Translate coordinates to account for selections in sub-frames. We can't cache
       // this because the top-level page may have scrolled since selection started.
       let offset = this._getViewOffset();
 
-      return  [{ handle: this.HANDLE_TYPE_START,
-                 left: sx + offset.x + scroll.X,
-                 top: sy + offset.y + scroll.Y,
-                 hidden: checkHidden(sx, sy) },
-               { handle: this.HANDLE_TYPE_END,
-                 left: ex + offset.x + scroll.X,
-                 top: ey + offset.y + scroll.Y,
-                 hidden: checkHidden(ex, ey) }];
+      return  [{ handle: this.HANDLE_TYPE_ANCHOR,
+                 left: anchorX + offset.x + scroll.X,
+                 top: anchorY + offset.y + scroll.Y,
+                 hidden: checkHidden(anchorX, anchorY) },
+               { handle: this.HANDLE_TYPE_FOCUS,
+                 left: focusX + offset.x + scroll.X,
+                 top: focusY + offset.y + scroll.Y,
+                 hidden: checkHidden(focusX, focusY) }];
     }
   },
 
   // Position handles, but avoid superfluous re-positioning (helps during
   // "TextSelection:LayerReflow", "scroll" of top-level document, etc).
   _positionHandlesOnChange: function() {
     // Helper function to compare position messages
     let samePositions = function(aPrev, aCurr) {
--- a/testing/web-platform/meta/media-source/mediasource-sourcebuffer-mode.html.ini
+++ b/testing/web-platform/meta/media-source/mediasource-sourcebuffer-mode.html.ini
@@ -1,5 +1,7 @@
 [mediasource-sourcebuffer-mode.html]
   type: testharness
   [Test setting SourceBuffer.mode and SourceBuffer.timestampOffset while parsing media segment.]
     expected: FAIL
+  [Test setting SourceBuffer.mode]
+    expected: FAIL # Not supported yet - see bug 1116353
 
--- a/toolkit/components/places/Bookmarks.jsm
+++ b/toolkit/components/places/Bookmarks.jsm
@@ -649,24 +649,49 @@ let Bookmarks = Object.freeze({
    *        Ordered array of the children's GUIDs.  If this list contains
    *        non-existing entries they will be ignored.  If the list is
    *        incomplete, missing entries will be appended.
    *
    * @return {Promise} resolved when reordering is complete.
    * @rejects if an error happens while reordering.
    * @throws if the arguments are invalid.
    */
-  // TODO must implement these methods yet:
-  // void setItemIndex(in long long aItemId, in long aNewIndex);
   reorder(parentGuid, orderedChildrenGuids) {
-    throw new Error("Not yet implemented");
+    let info = { guid: parentGuid };
+    info = validateBookmarkObject(info, { guid: { required: true } });
+
+    if (!Array.isArray(orderedChildrenGuids) || !orderedChildrenGuids.length)
+      throw new Error("Must provide a sorted array of children GUIDs.");
+    try {
+      orderedChildrenGuids.forEach(VALIDATORS.guid);
+    } catch (ex) {
+      throw new Error("Invalid GUID found in the sorted children array.");
+    }
+
+    return Task.spawn(function* () {
+      let parent = yield fetchBookmark(info);
+      if (!parent || parent.type != this.TYPE_FOLDER)
+        throw new Error("No folder found for the provided GUID.");
+
+      let sortedChildren = yield reorderChildren(parent, orderedChildrenGuids);
+
+      let observers = PlacesUtils.bookmarks.getObservers();
+      // Note that child.index is the old index.
+      for (let i = 0; i < sortedChildren.length; ++i) {
+        let child = sortedChildren[i];
+        notify(observers, "onItemMoved", [ child._id, child._parentId,
+                                           child.index, child._parentId,
+                                           i, child.type,
+                                           child.guid, child.parentGuid,
+                                           child.parentGuid ]);
+      }
+    }.bind(this));
   }
 });
 
-
 ////////////////////////////////////////////////////////////////////////////////
 // Globals.
 
 /**
  * Sends a bookmarks notification through the given observers.
  *
  * @param observers
  *        array of nsINavBookmarkObserver objects.
@@ -944,16 +969,36 @@ function* fetchBookmarksByKeyword(info) 
      LEFT JOIN moz_places h ON h.id = b.fk
      WHERE keyword = :keyword
      ORDER BY b.lastModified DESC
     `, { keyword: info.keyword });
 
   return rows.length ? rowsToItemsArray(rows) : null;
 }
 
+function* fetchBookmarksByParent(info) {
+  let db = yield DBConnPromised;
+
+  let rows = yield db.executeCached(
+    `SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
+            b.dateAdded, b.lastModified, b.type, b.title, h.url AS url,
+            keyword, b.id AS _id, b.parent AS _parentId,
+            (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
+            p.parent AS _grandParentId
+     FROM moz_bookmarks b
+     LEFT JOIN moz_bookmarks p ON p.id = b.parent
+     LEFT JOIN moz_keywords k ON k.id = b.keyword_id
+     LEFT JOIN moz_places h ON h.id = b.fk
+     WHERE p.guid = :parentGuid
+     ORDER BY b.position ASC
+    `, { parentGuid: info.parentGuid });
+
+  return rowsToItemsArray(rows);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Remove implementation.
 
 function* removeBookmark(item) {
   let db = yield DBConnPromised;
 
   let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
 
@@ -992,16 +1037,87 @@ function* removeBookmark(item) {
     // ...though we don't wait for the calculation.
     updateFrecency(db, [item.url]).then(null, Cu.reportError);
   }
 
   return item;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+// Reorder implementation.
+
+function* reorderChildren(parent, orderedChildrenGuids) {
+  let db = yield DBConnPromised;
+
+  return db.executeTransaction(function* () {
+    // Select all of the direct children for the given parent.
+    let children = yield fetchBookmarksByParent({ parentGuid: parent.guid });
+    if (!children.length)
+      return;
+
+    // Reorder the children array according to the specified order, provided
+    // GUIDs come first, others are appended in somehow random order.
+    children.sort((a, b) => {
+      let i = orderedChildrenGuids.indexOf(a.guid);
+      let j = orderedChildrenGuids.indexOf(b.guid);
+      // This works provided fetchBookmarksByParent returns sorted children.
+      return (i == -1 && j == -1) ? 0 :
+               (i != -1 && j != -1 && i < j) || (i != -1 && j == -1) ? -1 : 1;
+     });
+
+    // Update the bookmarks position now.  If any unknown guid have been
+    // inserted meanwhile, its position will be set to -position, and we'll
+    // handle it later.
+    // To do the update in a single step, we build a VALUES (guid, position)
+    // table.  We then use count() in the sorting table to avoid skipping values
+    // when no more existing GUIDs have been provided.
+    let valuesTable = children.map((child, i) => `("${child.guid}", ${i})`)
+                              .join();
+    yield db.execute(
+      `WITH sorting(g, p) AS (
+         VALUES ${valuesTable}
+       )
+       UPDATE moz_bookmarks SET position = (
+         SELECT CASE count(a.g) WHEN 0 THEN -position
+                                ELSE count(a.g) - 1
+                END
+         FROM sorting a
+         JOIN sorting b ON b.p <= a.p
+         WHERE a.g = guid
+           AND parent = :parentId
+      )`, { parentId: parent._id});
+
+    // Update position of items that could have been inserted in the meanwhile.
+    // Since this can happen rarely and it's only done for schema coherence
+    // resonds, we won't notify about these changes.
+    yield db.executeCached(
+      `CREATE TEMP TRIGGER moz_bookmarks_reorder_trigger
+         AFTER UPDATE OF position ON moz_bookmarks
+         WHEN NEW.position = -1
+       BEGIN
+         UPDATE moz_bookmarks
+         SET position = (SELECT MAX(position) FROM moz_bookmarks
+                         WHERE parent = NEW.parent) +
+                        (SELECT count(*) FROM moz_bookmarks
+                         WHERE parent = NEW.parent
+                           AND position BETWEEN OLD.position AND -1)
+         WHERE guid = NEW.guid;
+       END
+      `);
+
+    yield db.executeCached(
+      `UPDATE moz_bookmarks SET position = -1 WHERE position < 0`);
+
+    yield db.executeCached(`DROP TRIGGER moz_bookmarks_reorder_trigger`);
+
+    return children;
+  }.bind(this));
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // Helpers.
 
 /**
  * Merges objects into a new object, included non-enumerable properties.
  *
  * @param sources
  *        source objects to merge.
  * @return a new object including all properties from the source objects.
--- a/toolkit/components/places/tests/bookmarks/test_bookmarks_notifications.js
+++ b/toolkit/components/places/tests/bookmarks/test_bookmarks_notifications.js
@@ -409,16 +409,68 @@ add_task(function* eraseEverything_notif
                     { name: "onItemRemoved",
                      arguments: [ toolbarBmId, toolbarBmParentId,
                                   toolbarBm.index, toolbarBm.type,
                                   toolbarBm.url, toolbarBm.guid,
                                   toolbarBm.parentGuid ] }
                  ]);
 });
 
+add_task(function* reorder_notification() {
+  let bookmarks = [
+    { type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+      url: "http://example1.com/",
+      parentGuid: PlacesUtils.bookmarks.unfiledGuid
+    },
+    { type: PlacesUtils.bookmarks.TYPE_FOLDER,
+      parentGuid: PlacesUtils.bookmarks.unfiledGuid
+    },
+    { type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+      parentGuid: PlacesUtils.bookmarks.unfiledGuid
+    },
+    { type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+      url: "http://example2.com/",
+      parentGuid: PlacesUtils.bookmarks.unfiledGuid
+    },
+    { type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+      url: "http://example3.com/",
+      parentGuid: PlacesUtils.bookmarks.unfiledGuid
+    },
+  ];
+  let sorted = [];
+  for (let bm of bookmarks){
+    sorted.push(yield PlacesUtils.bookmarks.insert(bm));
+  }
+
+  // Randomly reorder the array.
+  sorted.sort(() => 0.5 - Math.random());
+
+  let observer = expectNotifications();
+  yield PlacesUtils.bookmarks.reorder(PlacesUtils.bookmarks.unfiledGuid,
+                                      sorted.map(bm => bm.guid));
+
+  let expectedNotifications = [];
+  for (let i = 0; i < sorted.length; ++i) {
+    let child = sorted[i];
+    let childId = yield PlacesUtils.promiseItemId(child.guid);
+    expectedNotifications.push({ name: "onItemMoved",
+                                 arguments: [ childId,
+                                              PlacesUtils.unfiledBookmarksFolderId,
+                                              child.index,
+                                              PlacesUtils.unfiledBookmarksFolderId,
+                                              i,
+                                              child.type,
+                                              child.guid,
+                                              child.parentGuid,
+                                              child.parentGuid
+                                            ] });
+  }
+  observer.check(expectedNotifications);
+});
+
 function expectNotifications() {
   let notifications = [];
   let observer = new Proxy(NavBookmarkObserver, {
     get(target, name) {
       if (name == "check") {
         PlacesUtils.bookmarks.removeObserver(observer);
         return expectedNotifications =>
           Assert.deepEqual(notifications, expectedNotifications);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_bookmarks_reorder.js
@@ -0,0 +1,110 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* invalid_input_throws() {
+  Assert.throws(() => PlacesUtils.bookmarks.reorder(),
+                /Invalid value for property 'guid'/);
+  Assert.throws(() => PlacesUtils.bookmarks.reorder(null),
+                /Invalid value for property 'guid'/);
+
+  Assert.throws(() => PlacesUtils.bookmarks.reorder("test"),
+                /Invalid value for property 'guid'/);
+  Assert.throws(() => PlacesUtils.bookmarks.reorder(123),
+                /Invalid value for property 'guid'/);
+
+  Assert.throws(() => PlacesUtils.bookmarks.reorder({ guid: "test" }),
+                /Invalid value for property 'guid'/);
+
+  Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012"),
+                /Must provide a sorted array of children GUIDs./);
+  Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", {}),
+                /Must provide a sorted array of children GUIDs./);
+  Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", null),
+                /Must provide a sorted array of children GUIDs./);
+  Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", []),
+                /Must provide a sorted array of children GUIDs./);
+
+  Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", [ null ]),
+                /Invalid GUID found in the sorted children array/);
+  Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", [ "" ]),
+                /Invalid GUID found in the sorted children array/);
+  Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", [ {} ]),
+                /Invalid GUID found in the sorted children array/);
+  Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", [ "012345678901" , null ]),
+                /Invalid GUID found in the sorted children array/);
+});
+
+add_task(function* reorder_nonexistent_guid() {
+  yield Assert.rejects(PlacesUtils.bookmarks.reorder("123456789012", [ "012345678901" ]),
+                       /No folder found for the provided GUID/,
+                       "Should throw for nonexisting guid");
+});
+
+add_task(function* reorder() {
+  let bookmarks = [
+    { type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+      url: "http://example1.com/",
+      parentGuid: PlacesUtils.bookmarks.unfiledGuid
+    },
+    { type: PlacesUtils.bookmarks.TYPE_FOLDER,
+      parentGuid: PlacesUtils.bookmarks.unfiledGuid
+    },
+    { type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+      parentGuid: PlacesUtils.bookmarks.unfiledGuid
+    },
+    { type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+      url: "http://example2.com/",
+      parentGuid: PlacesUtils.bookmarks.unfiledGuid
+    },
+    { type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+      url: "http://example3.com/",
+      parentGuid: PlacesUtils.bookmarks.unfiledGuid
+    }
+  ];
+  let sorted = [for (bm of bookmarks) yield PlacesUtils.bookmarks.insert(bm)]
+
+  // Check the initial append sorting.
+  Assert.ok(sorted.every((bm, i) => bm.index == i),
+            "Initial bookmarks sorting is correct");
+
+  // Apply random sorting and run multiple tests.
+  for (let t = 0; t < 4; t++) {
+    sorted.sort(() => 0.5 - Math.random());
+    let sortedGuids = sorted.map(child => child.guid);
+    dump("Expected order: " + sortedGuids.join() + "\n");
+    // Add a nonexisting guid to the array, to ensure nothing will break.
+    sortedGuids.push("123456789012");
+    yield PlacesUtils.bookmarks.reorder(PlacesUtils.bookmarks.unfiledGuid,
+                                        sortedGuids);
+    for (let i = 0; i < sorted.length; ++i) {
+      let item = yield PlacesUtils.bookmarks.fetch(sorted[i].guid);
+      Assert.equal(item.index, i);
+    }
+  }
+
+  do_print("Test partial sorting");
+  // Try a partial sorting by passing only 2 entries.
+  // The unspecified entries should retain the original order.
+  sorted = [ sorted[1], sorted[0] ].concat(sorted.slice(2));
+  let sortedGuids = [ sorted[0].guid, sorted[1].guid ];
+  dump("Expected order: " + [b.guid for (b of sorted)].join() + "\n");
+  yield PlacesUtils.bookmarks.reorder(PlacesUtils.bookmarks.unfiledGuid,
+                                      sortedGuids);
+  for (let i = 0; i < sorted.length; ++i) {
+    let item = yield PlacesUtils.bookmarks.fetch(sorted[i].guid);
+    Assert.equal(item.index, i);
+  }
+
+  // Use triangular numbers to detect skipped position.
+  let db = yield PlacesUtils.promiseDBConnection();
+  let rows = yield db.execute(
+    `SELECT parent
+     FROM moz_bookmarks
+     GROUP BY parent
+     HAVING (SUM(DISTINCT position + 1) - (count(*) * (count(*) + 1) / 2)) <> 0`);
+  Assert.equal(rows.length, 0, "All the bookmarks should have consistent positions");
+});
+
+function run_test() {
+  run_next_test();
+}
--- a/toolkit/components/places/tests/bookmarks/xpcshell.ini
+++ b/toolkit/components/places/tests/bookmarks/xpcshell.ini
@@ -29,16 +29,17 @@ skip-if = toolkit == 'android' || toolki
 [test_async_observers.js]
 [test_bmindex.js]
 [test_bookmarks.js]
 [test_bookmarks_eraseEverything.js]
 [test_bookmarks_fetch.js]
 [test_bookmarks_insert.js]
 [test_bookmarks_notifications.js]
 [test_bookmarks_remove.js]
+[test_bookmarks_reorder.js]
 [test_bookmarks_update.js]
 [test_changeBookmarkURI.js]
 [test_getBookmarkedURIFor.js]
 [test_keywords.js]
 [test_nsINavBookmarkObserver.js]
 [test_protectRoots.js]
 [test_removeItem.js]
 [test_savedsearches.js]
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -229,82 +229,92 @@ CombinedStacks::SizeOfExcludingThis() co
 class HangReports {
 public:
   /**
    * This struct encapsulates information for an individual ChromeHang annotation.
    * mHangIndex is the index of the corresponding ChromeHang.
    */
   struct AnnotationInfo {
     AnnotationInfo(uint32_t aHangIndex,
-                   HangAnnotations* aAnnotations)
+                   UniquePtr<HangAnnotations> aAnnotations)
       : mHangIndex(aHangIndex)
-      , mAnnotations(aAnnotations)
+      , mAnnotations(Move(aAnnotations))
     {}
-    AnnotationInfo(const AnnotationInfo& aOther)
+    AnnotationInfo(AnnotationInfo&& aOther)
       : mHangIndex(aOther.mHangIndex)
-      , mAnnotations(aOther.mAnnotations)
+      , mAnnotations(Move(aOther.mAnnotations))
     {}
     ~AnnotationInfo() {}
+    AnnotationInfo& operator=(AnnotationInfo&& aOther)
+    {
+      mHangIndex = aOther.mHangIndex;
+      mAnnotations = Move(aOther.mAnnotations);
+      return *this;
+    }
     uint32_t mHangIndex;
-    mutable nsAutoPtr<HangAnnotations> mAnnotations;
+    UniquePtr<HangAnnotations> mAnnotations;
+
+  private:
+    // Force move constructor
+    AnnotationInfo(const AnnotationInfo& aOther) MOZ_DELETE;
+    void operator=(const AnnotationInfo& aOther) MOZ_DELETE;
   };
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
   void AddHang(const Telemetry::ProcessedStack& aStack, uint32_t aDuration,
                int32_t aSystemUptime, int32_t aFirefoxUptime,
-               HangAnnotations* aAnnotations);
+               UniquePtr<HangAnnotations> aAnnotations);
   uint32_t GetDuration(unsigned aIndex) const;
   int32_t GetSystemUptime(unsigned aIndex) const;
   int32_t GetFirefoxUptime(unsigned aIndex) const;
-  const std::vector<AnnotationInfo>& GetAnnotationInfo() const;
+  const nsTArray<AnnotationInfo>& GetAnnotationInfo() const;
   const CombinedStacks& GetStacks() const;
 private:
   /**
    * This struct encapsulates the data for an individual ChromeHang, excluding
    * annotations.
    */
   struct HangInfo {
     // Hang duration (in seconds)
     uint32_t mDuration;
     // System uptime (in minutes) at the time of the hang
     int32_t mSystemUptime;
     // Firefox uptime (in minutes) at the time of the hang
     int32_t mFirefoxUptime;
   };
   std::vector<HangInfo> mHangInfo;
-  std::vector<AnnotationInfo> mAnnotationInfo;
+  nsTArray<AnnotationInfo> mAnnotationInfo;
   CombinedStacks mStacks;
 };
 
 void
 HangReports::AddHang(const Telemetry::ProcessedStack& aStack,
                      uint32_t aDuration,
                      int32_t aSystemUptime,
                      int32_t aFirefoxUptime,
-                     HangAnnotations* aAnnotations) {
+                     UniquePtr<HangAnnotations> aAnnotations) {
   HangInfo info = { aDuration, aSystemUptime, aFirefoxUptime };
   mHangInfo.push_back(info);
   if (aAnnotations) {
     AnnotationInfo ainfo(static_cast<uint32_t>(mHangInfo.size() - 1),
-                         aAnnotations);
-    mAnnotationInfo.push_back(ainfo);
+                         Move(aAnnotations));
+    mAnnotationInfo.AppendElement(Move(ainfo));
   }
   mStacks.AddStack(aStack);
 }
 
 size_t
 HangReports::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
   size_t n = 0;
   n += mStacks.SizeOfExcludingThis();
   // This is a crude approximation. See comment on
   // CombinedStacks::SizeOfExcludingThis.
   n += mHangInfo.capacity() * sizeof(HangInfo);
-  n += mAnnotationInfo.capacity() * sizeof(AnnotationInfo);
-  for (std::vector<AnnotationInfo>::const_iterator i = mAnnotationInfo.begin(),
-       e = mAnnotationInfo.end(); i != e; ++i) {
-    n += i->mAnnotations->SizeOfIncludingThis(aMallocSizeOf);
+  n += mAnnotationInfo.Capacity() * sizeof(AnnotationInfo);
+  for (int32_t i = 0, l = mAnnotationInfo.Length(); i < l; ++i) {
+    n += mAnnotationInfo[i].mAnnotations->SizeOfIncludingThis(aMallocSizeOf);
   }
   return n;
 }
 
 const CombinedStacks&
 HangReports::GetStacks() const {
   return mStacks;
 }
@@ -319,17 +329,17 @@ HangReports::GetSystemUptime(unsigned aI
   return mHangInfo[aIndex].mSystemUptime;
 }
 
 int32_t
 HangReports::GetFirefoxUptime(unsigned aIndex) const {
   return mHangInfo[aIndex].mFirefoxUptime;
 }
 
-const std::vector<HangReports::AnnotationInfo>&
+const nsTArray<HangReports::AnnotationInfo>&
 HangReports::GetAnnotationInfo() const {
   return mAnnotationInfo;
 }
 
 /**
  * IOInterposeObserver recording statistics of main-thread I/O during execution,
  * aimed at consumption by TelemetryImpl
  */
@@ -811,17 +821,17 @@ public:
   static void ShutdownTelemetry();
   static void RecordSlowStatement(const nsACString &sql, const nsACString &dbName,
                                   uint32_t delay);
 #if defined(MOZ_ENABLE_PROFILER_SPS)
   static void RecordChromeHang(uint32_t aDuration,
                                Telemetry::ProcessedStack &aStack,
                                int32_t aSystemUptime,
                                int32_t aFirefoxUptime,
-                               HangAnnotations* aAnnotations);
+                               UniquePtr<HangAnnotations> aAnnotations);
 #endif
   static void RecordThreadHangStats(Telemetry::ThreadHangStats& aStats);
   static nsresult GetHistogramEnumId(const char *name, Telemetry::ID *id);
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
   struct Stat {
     uint32_t hitCount;
     uint32_t totalTime;
   };
@@ -2400,55 +2410,54 @@ TelemetryImpl::GetChromeHangs(JSContext 
       return NS_ERROR_FAILURE;
     }
     if (!JS_SetElement(cx, systemUptimeArray, i, mHangReports.GetSystemUptime(i))) {
       return NS_ERROR_FAILURE;
     }
     if (!JS_SetElement(cx, firefoxUptimeArray, i, mHangReports.GetFirefoxUptime(i))) {
       return NS_ERROR_FAILURE;
     }
-    const std::vector<HangReports::AnnotationInfo>& annotationInfo =
+    const nsTArray<HangReports::AnnotationInfo>& annotationInfo =
                                                 mHangReports.GetAnnotationInfo();
-    uint32_t annotationsArrayIndex = 0;
-    for (std::vector<HangReports::AnnotationInfo>::const_iterator
-         ai = annotationInfo.begin(), e = annotationInfo.end(); ai != e;
-         ++ai, ++annotationsArrayIndex) {
+    for (uint32_t iterIndex = 0, arrayLen = annotationInfo.Length();
+         iterIndex < arrayLen; ++iterIndex) {
       JS::Rooted<JSObject*> keyValueArray(cx, JS_NewArrayObject(cx, 0));
       if (!keyValueArray) {
         return NS_ERROR_FAILURE;
       }
       JS::RootedValue indexValue(cx);
-      indexValue.setNumber(ai->mHangIndex);
+      indexValue.setNumber(annotationInfo[iterIndex].mHangIndex);
       if (!JS_SetElement(cx, keyValueArray, 0, indexValue)) {
         return NS_ERROR_FAILURE;
       }
       JS::Rooted<JSObject*> jsAnnotation(cx, JS_NewObject(cx, nullptr,
                                                           JS::NullPtr(),
                                                           JS::NullPtr()));
       if (!jsAnnotation) {
         return NS_ERROR_FAILURE;
       }
       nsAutoPtr<HangAnnotations::Enumerator> annotationsEnum;
-      if (!ai->mAnnotations->GetEnumerator(annotationsEnum.StartAssignment())) {
+      if (!annotationInfo[iterIndex].mAnnotations->GetEnumerator(
+            annotationsEnum.StartAssignment())) {
         return NS_ERROR_FAILURE;
       }
       nsAutoString  key;
       nsAutoString  value;
       while (annotationsEnum->Next(key, value)) {
         JS::RootedValue jsValue(cx);
         jsValue.setString(JS_NewUCStringCopyN(cx, value.get(), value.Length()));
         if (!JS_DefineUCProperty(cx, jsAnnotation, key.get(), key.Length(),
                                  jsValue, JSPROP_ENUMERATE)) {
           return NS_ERROR_FAILURE;
         }
       }
       if (!JS_SetElement(cx, keyValueArray, 1, jsAnnotation)) {
         return NS_ERROR_FAILURE;
       }
-      if (!JS_SetElement(cx, annotationsArray, annotationsArrayIndex,
+      if (!JS_SetElement(cx, annotationsArray, iterIndex,
                          keyValueArray)) {
         return NS_ERROR_FAILURE;
       }
     }
   }
 
   return NS_OK;
 }
@@ -3195,26 +3204,26 @@ TelemetryImpl::RecordSlowStatement(const
 }
 
 #if defined(MOZ_ENABLE_PROFILER_SPS)
 void
 TelemetryImpl::RecordChromeHang(uint32_t aDuration,
                                 Telemetry::ProcessedStack &aStack,
                                 int32_t aSystemUptime,
                                 int32_t aFirefoxUptime,
-                                HangAnnotations* aAnnotations)
+                                UniquePtr<HangAnnotations> aAnnotations)
 {
   if (!sTelemetry || !sTelemetry->mCanRecord)
     return;
 
   MutexAutoLock hangReportMutex(sTelemetry->mHangReportsMutex);
 
   sTelemetry->mHangReports.AddHang(aStack, aDuration,
                                    aSystemUptime, aFirefoxUptime,
-                                   aAnnotations);
+                                   Move(aAnnotations));
 }
 #endif
 
 void
 TelemetryImpl::RecordThreadHangStats(Telemetry::ThreadHangStats& aStats)
 {
   if (!sTelemetry || !sTelemetry->mCanRecord)
     return;
@@ -3468,20 +3477,21 @@ void Init()
   MOZ_ASSERT(telemetryService);
 }
 
 #if defined(MOZ_ENABLE_PROFILER_SPS)
 void RecordChromeHang(uint32_t duration,
                       ProcessedStack &aStack,
                       int32_t aSystemUptime,
                       int32_t aFirefoxUptime,
-                      HangAnnotations* aAnnotations)
+                      UniquePtr<HangAnnotations> aAnnotations)
 {
   TelemetryImpl::RecordChromeHang(duration, aStack,
-                                  aSystemUptime, aFirefoxUptime, aAnnotations);
+                                  aSystemUptime, aFirefoxUptime,
+                                  Move(aAnnotations));
 }
 #endif
 
 void RecordThreadHangStats(ThreadHangStats& aStats)
 {
   TelemetryImpl::RecordThreadHangStats(aStats);
 }
 
--- a/toolkit/components/telemetry/Telemetry.h
+++ b/toolkit/components/telemetry/Telemetry.h
@@ -233,17 +233,18 @@ class ProcessedStack;
  * @param aFirefoxUptime - Firefox uptime at the time of the hang, in minutes
  * @param aAnnotations - Any annotations to be added to the report
  */
 #if defined(MOZ_ENABLE_PROFILER_SPS)
 void RecordChromeHang(uint32_t aDuration,
                       ProcessedStack &aStack,
                       int32_t aSystemUptime,
                       int32_t aFirefoxUptime,
-                      mozilla::HangMonitor::HangAnnotations* aAnnotations = nullptr);
+                      mozilla::UniquePtr<mozilla::HangMonitor::HangAnnotations>
+                              aAnnotations);
 #endif
 
 class ThreadHangStats;
 
 /**
  * Move a ThreadHangStats to Telemetry storage. Normally Telemetry queries
  * for active ThreadHangStats through BackgroundHangMonitor, but once a
  * thread exits, the thread's copy of ThreadHangStats needs to be moved to
--- a/widget/gonk/HwcComposer2D.cpp
+++ b/widget/gonk/HwcComposer2D.cpp
@@ -421,23 +421,26 @@ HwcComposer2D::PrepareLayerList(Layer* a
 
     // Buffer rotation is not to be confused with the angled rotation done by a transform matrix
     // It's a fancy PaintedLayer feature used for scrolling
     if (state.BufferRotated()) {
         LOGD("%s Layer has a rotated buffer", aLayer->Name());
         return false;
     }
 
+    const bool needsYFlip = state.OriginBottomLeft() ? true
+                                                     : false;
+
     hwc_rect_t sourceCrop, displayFrame;
     if(!HwcUtils::PrepareLayerRects(visibleRect,
                           layerTransform,
                           layerBufferTransform,
                           clip,
                           bufferRect,
-                          state.YFlipped(),
+                          needsYFlip,
                           &(sourceCrop),
                           &(displayFrame)))
     {
         return true;
     }
 
     // OK!  We can compose this layer with hwc.
     int current = mList ? mList->numHwLayers : 0;
@@ -604,17 +607,20 @@ HwcComposer2D::PrepareLayerList(Layer* a
                 //
                 // |  1   0  |
                 // |  0   1  |
                 //
                 hwcLayer.transform = 0;
             }
         }
 
-        if (state.YFlipped()) {
+        const bool needsYFlip = state.OriginBottomLeft() ? true
+                                                         : false;
+
+        if (needsYFlip) {
            // Invert vertical reflection flag if it was already set
            hwcLayer.transform ^= HWC_TRANSFORM_FLIP_V;
         }
         hwc_region_t region;
         if (visibleRegion.GetNumRects() > 1) {
             mVisibleRegions.push_back(HwcUtils::RectVector());
             HwcUtils::RectVector* visibleRects = &(mVisibleRegions.back());
             if(!HwcUtils::PrepareVisibleRegion(visibleRegion,
--- a/xpcom/threads/HangMonitor.cpp
+++ b/xpcom/threads/HangMonitor.cpp
@@ -3,24 +3,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/HangMonitor.h"
 
 #include <set>
 
+#include "mozilla/Atomics.h"
 #include "mozilla/BackgroundHangMonitor.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/ProcessedStack.h"
 #include "mozilla/Telemetry.h"
-#include "mozilla/ProcessedStack.h"
-#include "mozilla/Atomics.h"
 #include "mozilla/StaticPtr.h"
-#include "nsAutoPtr.h"
+#include "mozilla/UniquePtr.h"
 #include "nsReadableUtils.h"
 #include "nsStackWalk.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 
 #ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #endif
@@ -358,17 +358,17 @@ ThreadMain(void*)
   // run twice to trigger hang protection.
   PRIntervalTime lastTimestamp = 0;
   int waitCount = 0;
 
 #ifdef REPORT_CHROME_HANGS
   Telemetry::ProcessedStack stack;
   int32_t systemUptime = -1;
   int32_t firefoxUptime = -1;
-  nsAutoPtr<ChromeHangAnnotations> annotations = new ChromeHangAnnotations();
+  auto annotations = MakeUnique<ChromeHangAnnotations>();
 #endif
 
   while (true) {
     if (gShutdown) {
       return; // Exit the thread
     }
 
     // avoid rereading the volatile value in this loop
@@ -405,20 +405,19 @@ ThreadMain(void*)
         }
       }
 #endif
     } else {
 #ifdef REPORT_CHROME_HANGS
       if (waitCount >= 2) {
         uint32_t hangDuration = PR_IntervalToSeconds(now - lastTimestamp);
         Telemetry::RecordChromeHang(hangDuration, stack, systemUptime,
-                                    firefoxUptime, annotations->IsEmpty() ?
-                                    nullptr : annotations.forget());
+                                    firefoxUptime, Move(annotations));
         stack.Clear();
-        annotations = new ChromeHangAnnotations();
+        annotations = MakeUnique<ChromeHangAnnotations>();
       }
 #endif
       lastTimestamp = timestamp;
       waitCount = 0;
     }
 
     PRIntervalTime timeout;
     if (gTimeout <= 0) {